From eb16ef29ce0038c2a688df3b1a06e7cae9d84835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20H=C3=BClscher?= <25116822+eweren@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:32:03 +0200 Subject: [PATCH 01/13] docs(settings): specify key format cursor fix --- .../2026-07-10-key-format-cursor-design.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-10-key-format-cursor-design.md diff --git a/docs/superpowers/specs/2026-07-10-key-format-cursor-design.md b/docs/superpowers/specs/2026-07-10-key-format-cursor-design.md new file mode 100644 index 00000000..71d63272 --- /dev/null +++ b/docs/superpowers/specs/2026-07-10-key-format-cursor-design.md @@ -0,0 +1,26 @@ +# Key format cursor fix + +## Problem + +The key-format editor starts completion on every `keydown`, before CodeMirror +has applied the typed character. Its custom completion transaction also bypasses +CodeMirror's built-in completion lifecycle. After inserting a placeholder, the +next separator or placeholder can therefore use a stale completion range and +move the cursor unexpectedly. + +## Design + +- Keep click-to-open completion. +- Remove the editor wrapper's `onKeyDown` completion trigger. CodeMirror already + activates completion for normal typing. +- Represent each placeholder's `apply` value as the inserted string so CodeMirror + owns the document change, selection mapping, and completion closing. +- Do not change placeholder names, formatting, preview generation, or persistence. + +## Verification + +Add a Cypress regression that opens the key-format editor, inserts a placeholder, +types `.` at the cursor, inserts another placeholder, and verifies the separator +remains between the two placeholders. Run the focused test first to prove the +current implementation fails, then run it again after the fix together with the +TypeScript and build checks. From a48923aa20dd27aac38a606918f737c5a39061f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20H=C3=BClscher?= <25116822+eweren@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:37:30 +0200 Subject: [PATCH 02/13] chore(git): ignore local worktrees --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b17c95e9..b3fdc1a3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ cypress/videos cypress/screenshots .vscode +.worktrees From 8c6b09a039b54a1c40faade1203da5c591998037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20H=C3=BClscher?= <25116822+eweren@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:47:49 +0200 Subject: [PATCH 03/13] fix(settings): preserve key format cursor position --- cypress/e2e/keyFormatEditor.cy.ts | 36 +++++ .../plans/2026-07-10-key-format-cursor.md | 132 ++++++++++++++++++ src/ui/views/Settings/StringsEditor.tsx | 20 +-- 3 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 cypress/e2e/keyFormatEditor.cy.ts create mode 100644 docs/superpowers/plans/2026-07-10-key-format-cursor.md diff --git a/cypress/e2e/keyFormatEditor.cy.ts b/cypress/e2e/keyFormatEditor.cy.ts new file mode 100644 index 00000000..3e1afebf --- /dev/null +++ b/cypress/e2e/keyFormatEditor.cy.ts @@ -0,0 +1,36 @@ +import { DEFAULT_CREDENTIALS } from "@/web/urlConfig"; +import { visitWithState } from "../common/tools"; + +describe("Key format editor", () => { + it("keeps separators between inserted placeholders", () => { + visitWithState({ + config: { + apiUrl: DEFAULT_CREDENTIALS.apiUrl, + apiKey: DEFAULT_CREDENTIALS.apiKey, + language: "en", + namespace: "", + documentInfo: true, + pageInfo: true, + prefillKeyFormat: true, + keyFormat: "", + }, + }); + + cy.iframe().findDcy("index_settings_button").click(); + cy.iframe().findDcy("settings_expandable_strings").click(); + cy.iframe().findDcy("global-editor").click(); + cy.iframe().find(".cm-content").as("editorContent"); + cy.iframe().find(".cm-tooltip-autocomplete").should("be.visible"); + cy.wait(150); + + cy.get("@editorContent").type("{enter}").should("have.text", "artboard"); + cy.iframe().find(".cm-tooltip-autocomplete").should("not.exist"); + cy.get("@editorContent").type(".component"); + cy.iframe().find(".cm-tooltip-autocomplete").should("be.visible"); + cy.wait(150); + cy.get("@editorContent") + .type("{enter}") + .should("have.text", "artboard.component"); + cy.iframe().find(".cm-tooltip-autocomplete").should("not.exist"); + }); +}); diff --git a/docs/superpowers/plans/2026-07-10-key-format-cursor.md b/docs/superpowers/plans/2026-07-10-key-format-cursor.md new file mode 100644 index 00000000..b77bb123 --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-key-format-cursor.md @@ -0,0 +1,132 @@ +# Key Format Cursor Fix Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Keep separators and the cursor at the intended position when users combine multiple placeholders in the key-format editor. + +**Architecture:** Let CodeMirror own completion insertion, selection mapping, and completion closing. Open completion from a CodeMirror content click handler instead of restarting it from every bubbled click and keydown event. + +**Tech Stack:** TypeScript, Preact, CodeMirror 6, Cypress, Bun + +## Global Constraints + +- Keep click-to-open completion. +- Do not change placeholder names, formatting, preview generation, or persistence. +- Add no dependencies or new abstractions. +- Use Bun for project commands. + +--- + +### Task 1: Preserve the cursor between key-format placeholders + +**Files:** + +- Create: `cypress/e2e/keyFormatEditor.cy.ts` +- Modify: `src/ui/views/Settings/StringsEditor.tsx:93-108,163-193,227-242` + +**Interfaces:** + +- Consumes: CodeMirror `Completion.apply`, `EditorView.domEventHandlers`, and the existing `StringsEditor` props. +- Produces: unchanged `StringsEditor` props and unchanged key-format strings through `onChange`. + +- [x] **Step 1: Write the failing Cypress regression** + +```typescript +import { DEFAULT_CREDENTIALS } from "@/web/urlConfig"; +import { visitWithState } from "../common/tools"; + +describe("Key format editor", () => { + it("keeps separators between inserted placeholders", () => { + visitWithState({ + config: { + apiUrl: DEFAULT_CREDENTIALS.apiUrl, + apiKey: DEFAULT_CREDENTIALS.apiKey, + language: "en", + namespace: "", + documentInfo: true, + pageInfo: true, + prefillKeyFormat: true, + keyFormat: "", + }, + }); + + cy.iframe().findDcy("index_settings_button").click(); + cy.iframe().findDcy("settings_expandable_strings").click(); + cy.iframe().findDcy("global-editor").click(); + cy.iframe().find(".cm-content").as("editorContent"); + cy.iframe().find(".cm-tooltip-autocomplete").should("be.visible"); + cy.wait(150); + + cy.get("@editorContent").type("{enter}").should("have.text", "artboard"); + cy.iframe().find(".cm-tooltip-autocomplete").should("not.exist"); + cy.get("@editorContent").type(".component"); + cy.iframe().find(".cm-tooltip-autocomplete").should("be.visible"); + cy.wait(150); + cy.get("@editorContent") + .type("{enter}") + .should("have.text", "artboard.component"); + cy.iframe().find(".cm-tooltip-autocomplete").should("not.exist"); + }); +}); +``` + +- [x] **Step 2: Run the focused test and verify RED** + +Run: + +```bash +bunx cypress run --spec cypress/e2e/keyFormatEditor.cy.ts +``` + +Expected: FAIL because `.cm-tooltip-autocomplete` still exists after accepting the first placeholder, demonstrating that completion was restarted by the wrapper event. + +- [x] **Step 3: Delegate insertion and click handling to CodeMirror** + +Replace `createCompletionOption` with CodeMirror's string `apply` contract: + +```typescript +function createCompletionOption( + label: string, + detail: string, + insertText: string, +) { + return { + label, + detail, + type: "keyword" as const, + apply: insertText, + }; +} +``` + +Add a content-scoped click handler to the editor extensions: + +```typescript +EditorView.domEventHandlers({ + click(_event, view) { + startCompletion(view); + return false; + }, +}), +``` + +Remove the Preact `onClick` and `onKeyDown` props from the editor wrapper. This prevents completion-option clicks and ordinary key presses from starting a stale explicit completion. + +- [x] **Step 4: Run focused and static verification** + +Run: + +```bash +bunx cypress run --spec cypress/e2e/keyFormatEditor.cy.ts +bun run tsc +bun run build +``` + +Expected: the Cypress spec passes, TypeScript reports no errors, and the plugin build completes successfully. + +- [x] **Step 5: Commit the bug fix** + +```bash +git add cypress/e2e/keyFormatEditor.cy.ts src/ui/views/Settings/StringsEditor.tsx docs/superpowers/plans/2026-07-10-key-format-cursor.md +git commit -m "fix(settings): preserve key format cursor position" +``` diff --git a/src/ui/views/Settings/StringsEditor.tsx b/src/ui/views/Settings/StringsEditor.tsx index becc3610..e3c3761a 100644 --- a/src/ui/views/Settings/StringsEditor.tsx +++ b/src/ui/views/Settings/StringsEditor.tsx @@ -99,13 +99,7 @@ export const StringsEditor = ({ label, detail, type: "keyword" as const, - apply(view: EditorView, _: any, from: number, to: number) { - const insert = insertText; - view.dispatch({ - changes: { from, to, insert }, - selection: { anchor: from + insert.length }, - }); - }, + apply: insertText, }; } @@ -174,6 +168,12 @@ export const StringsEditor = ({ callbacksRef.current?.onChange?.(v.state.doc.toString()); } }), + EditorView.domEventHandlers({ + click(_event, view) { + startCompletion(view); + return false; + }, + }), placeholders.current.of( PlaceholderPlugin({ examplePluralNum: 1, @@ -227,12 +227,6 @@ export const StringsEditor = ({ return (