diff --git a/doc/plans/2026-10-02-pi-production-readiness.md b/doc/plans/2026-10-02-pi-production-readiness.md index d46a67b8521..9335bb13326 100644 --- a/doc/plans/2026-10-02-pi-production-readiness.md +++ b/doc/plans/2026-10-02-pi-production-readiness.md @@ -15,6 +15,17 @@ failures remain retained. The Mac file-edit correction also passes. ### Latest boundary fixes — 2026-10-05 +The explicit version-12 Mac memory attempt at `867fa4711` still times out +at the unchanged native-session limit; cleanup passes. Its valid fenced JSON +arrives in a duplicated description: the Markdown paste capture inserts the +parsed content, then Lexical inserts the same plain text. The editor now stops +that handled paste before it reaches the inner editor. A regression fails on +the old propagation, and all 51 editor tests pass after the fix, including an +ordinary-text paste control. The token gates pass. This is a new shipping input; +the previous image and live result remain bound to `867fa4711`, and neither +is qualification of the corrected editor. No paid retry is justified until a +zero-model browser check verifies one submitted prompt with intact JSON bytes. + Normal public installs of runtime `b012b3aebe` admit profile 15 on Mac ARM, Mac Intel (through Rosetta), and native Linux. The native Linux image is `sha256:3279d92405a59b4654cb6af5de27bfacffee73a2c57311bf5f2d5ff9272b9b67`. diff --git a/ui/src/components/MarkdownEditor.test.tsx b/ui/src/components/MarkdownEditor.test.tsx index 20bd75cd321..6ac13e44137 100644 --- a/ui/src/components/MarkdownEditor.test.tsx +++ b/ui/src/components/MarkdownEditor.test.tsx @@ -912,6 +912,35 @@ describe("MarkdownEditor", () => { }); }); + it("handles Markdown paste once before the inner editor receives it", async () => { + const root = createRoot(container); + await act(async () => { + root.render( {}} />); + }); + await flush(); + const editable = container.querySelector('[data-testid="mdx-editor"]')!; + const innerPaste = vi.fn(); + editable.addEventListener("paste", innerPaste); + const pasted = '```json\n{"content":"nonce\\n"}\n```'; + const event = new Event("paste", { bubbles: true, cancelable: true }); + Object.defineProperty(event, "clipboardData", { + value: { types: ["text/plain"], getData: () => pasted }, + }); + await act(async () => { editable.dispatchEvent(event); }); + expect(event.defaultPrevented).toBe(true); + expect(mdxEditorMockState.insertedMarkdownValues).toEqual([pasted]); + expect(innerPaste).not.toHaveBeenCalled(); + + const plain = new Event("paste", { bubbles: true, cancelable: true }); + Object.defineProperty(plain, "clipboardData", { + value: { types: ["text/plain"], getData: () => "ordinary text" }, + }); + await act(async () => { editable.dispatchEvent(plain); }); + expect(plain.defaultPrevented).toBe(false); + expect(innerPaste).toHaveBeenCalledOnce(); + await act(async () => { root.unmount(); }); + }); + it("escapes angle brackets in pasted markdown", async () => { const root = createRoot(container); diff --git a/ui/src/components/MarkdownEditor.tsx b/ui/src/components/MarkdownEditor.tsx index 0fdee647e5b..37844f212b3 100644 --- a/ui/src/components/MarkdownEditor.tsx +++ b/ui/src/components/MarkdownEditor.tsx @@ -1281,6 +1281,9 @@ export const MarkdownEditor = forwardRef if (!looksLikeMarkdownPaste(rawText)) return; event.preventDefault(); + // Lexical also handles paste on the editable element. Once Markdown is + // inserted here, prevent that handler from inserting the plain text again. + event.stopPropagation(); ref.current.insertMarkdown(escapeUnsupportedAngleBrackets(normalizeMarkdown(rawText))); }, []);