From fdc06a1ec7ae2b1098ebf9cb696c03c50600f0ce Mon Sep 17 00:00:00 2001 From: Chadpiha Date: Mon, 24 Aug 2026 18:48:59 -0700 Subject: [PATCH] fix(paste): skip trailing space after CJK text in append smart spacing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Append-mode smart spacing unconditionally added an ASCII space after every dictation, so CJK output pasted as "你好 " and consecutive dictations accumulated "你好 世界" gaps. Skip the space when the text ends with Han, kana, or CJK punctuation (Symbols and Punctuation, Fullwidth/Halfwidth Forms, Vertical Forms, Compatibility Forms). Hangul deliberately keeps the trailing space: Korean separates words with ASCII spaces, so suppressing it would break Korean dictation. Append mode is the only smart-spacing path used in production (prepend is intentionally skipped in the paste hot path), which is why the applyPrepend-only attempt in #1788 never fixed the bug. --- src/helpers/smartSpacing.js | 9 +++++++++ test/helpers/smartSpacing.test.js | 33 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/helpers/smartSpacing.js b/src/helpers/smartSpacing.js index 3e1dfbeb9d..7fe8b1b917 100644 --- a/src/helpers/smartSpacing.js +++ b/src/helpers/smartSpacing.js @@ -21,8 +21,17 @@ function applyPrepend(text, precedingChar) { return " " + text; } +// Unspaced scripts (Han, kana) and CJK punctuation (Symbols and Punctuation, +// Fullwidth/Halfwidth Forms, Vertical Forms, Compatibility Forms): a trailing +// ASCII space after "你好" or "です。" violates East Asian typography and +// accumulates as "你好 世界" gaps across consecutive dictations. Hangul is +// excluded on purpose — Korean separates words with spaces. +const ENDS_WITH_CJK = + /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\u3000-\u303f\uff00-\uff65\ufe10-\ufe1f\ufe30-\ufe4f]$/u; + function applyAppend(text) { if (/\s$/.test(text)) return text; + if (ENDS_WITH_CJK.test(text)) return text; return text + " "; } diff --git a/test/helpers/smartSpacing.test.js b/test/helpers/smartSpacing.test.js index 2bb7330edc..844a7d5857 100644 --- a/test/helpers/smartSpacing.test.js +++ b/test/helpers/smartSpacing.test.js @@ -98,6 +98,39 @@ test("append: handles empty transcript", () => { assert.equal(append(""), ""); }); +test("append: no trailing space after CJK ideographs", () => { + assert.equal(append("你好"), "你好"); + assert.equal(append("日本語"), "日本語"); +}); + +test("append: no trailing space after kana", () => { + assert.equal(append("こんにちは"), "こんにちは"); + assert.equal(append("カタカナ"), "カタカナ"); +}); + +test("append: no trailing space after full-width punctuation", () => { + assert.equal(append("你好。"), "你好。"); + assert.equal(append("すごい!"), "すごい!"); + assert.equal(append("何?"), "何?"); + assert.equal(append("はい、"), "はい、"); + assert.equal(append("「引用」"), "「引用」"); + assert.equal(append("(括弧)"), "(括弧)"); +}); + +test("append: no trailing space after halfwidth CJK punctuation and vertical forms", () => { + assert.equal(append("テスト。"), "テスト。"); + assert.equal(append("你好︒"), "你好︒"); +}); + +test("append: last character decides for mixed-script text", () => { + assert.equal(append("hello 你好"), "hello 你好"); + assert.equal(append("你好 hello"), "你好 hello "); +}); + +test("append: keeps trailing space after Hangul (Korean uses word spacing)", () => { + assert.equal(append("안녕하세요"), "안녕하세요 "); +}); + test("returns text unchanged for unknown mode", () => { assert.equal(applySmartSpacing({ text: "hello", mode: "noop" }), "hello"); });