Skip to content

fix(paste): skip trailing space after CJK text in append smart spacing - #1823

Merged
Chadpiha merged 1 commit into
mainfrom
fix/cjk-append-smart-spacing
Aug 25, 2026
Merged

fix(paste): skip trailing space after CJK text in append smart spacing#1823
Chadpiha merged 1 commit into
mainfrom
fix/cjk-append-smart-spacing

Conversation

@Chadpiha

Copy link
Copy Markdown
Collaborator

Summary

Append-mode smart spacing — the only path used in production (the paste hot path in ipcHandlers.js; prepend is intentionally skipped there) — unconditionally appended an ASCII space after every dictation. CJK output pasted as 你好 and consecutive dictations accumulated 你好 世界 gaps, violating East Asian typography.

applyAppend now skips the trailing space when the text ends with Han, kana, or CJK punctuation (CJK Symbols and Punctuation, Fullwidth/Halfwidth Forms, Vertical Forms, Compatibility Forms). Script properties (\p{Script=Han} etc.) cover astral-plane ideographs too.

Hangul deliberately keeps the trailing space: modern Korean separates words with ASCII spaces, so suppressing it would break Korean dictation the same way this bug breaks Chinese. A test pins that choice.

Context: #1788 attempted this fix but only touched applyPrepend, which is dead code in production — it was closed unmerged.

Changes

  • src/helpers/smartSpacing.js: new ENDS_WITH_CJK regex; applyAppend returns the text unchanged when it matches
  • test/helpers/smartSpacing.test.js: 6 new cases — ideographs, kana, full-width punctuation, halfwidth/vertical forms, mixed-script last-char rule, Hangul keeps its space

Testing

  • node --test test/helpers/smartSpacing.test.js: 26 pass / 0 fail (Node 24)
  • prettier --check clean on both files

Follow-up (not in this PR)

applyPrepend still has no CJK awareness. It is unused in production today, but needs the mirror-image treatment if prepend mode is ever revived.

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.
@Chadpiha
Chadpiha merged commit f26b059 into main Aug 25, 2026
9 checks passed
github-actions Bot pushed a commit to PlaybookMediaLLC/openwhispr that referenced this pull request Aug 26, 2026
# [1.11.0](v1.10.0...v1.11.0) (2026-08-26)

### Bug Fixes

* **ai:** retry HTTP 408 request timeouts in the API retry strategy ([OpenWhispr#1734](https://github.com/PlaybookMediaLLC/openwhispr/issues/1734)) ([d3661cb](d3661cb))
* **auth:** Unify onboarding and reauthentication display ([OpenWhispr#1763](https://github.com/PlaybookMediaLLC/openwhispr/issues/1763)) ([3cc197f](3cc197f))
* **calendar:** fail OAuth loopback immediately on state mismatch ([OpenWhispr#1753](https://github.com/PlaybookMediaLLC/openwhispr/issues/1753)) ([8b77a59](8b77a59))
* **calendar:** group date-only upcoming events on the local calendar day ([2ef397b](2ef397b))
* **calendar:** resolve Microsoft series masters through their calendar ([OpenWhispr#1835](https://github.com/PlaybookMediaLLC/openwhispr/issues/1835)) ([a0bbf4f](a0bbf4f))
* **chat:** persist conversation migration links ([OpenWhispr#1772](https://github.com/PlaybookMediaLLC/openwhispr/issues/1772)) ([15b5b3f](15b5b3f))
* click should be the delibrate action to select model for STT models ([90cfe03](90cfe03))
* **cli:** enforce request body byte limit ([OpenWhispr#1777](https://github.com/PlaybookMediaLLC/openwhispr/issues/1777)) ([996fe79](996fe79))
* **cli:** preserve UTF-8 request bodies ([OpenWhispr#1386](https://github.com/PlaybookMediaLLC/openwhispr/issues/1386)) ([ef627c9](ef627c9))
* **diarization:** diarize the mic track for in-person meetings — fixes [OpenWhispr#1627](https://github.com/PlaybookMediaLLC/openwhispr/issues/1627) ([0a10184](0a10184))
* **diarization:** skip orphan speaker embeddings in softened mic mode ([2ffbe35](2ffbe35))
* **dictation:** initialize onboarding event ref safely ([OpenWhispr#1756](https://github.com/PlaybookMediaLLC/openwhispr/issues/1756)) ([c5d515b](c5d515b))
* dismiss dictation preview when no audio detected ([OpenWhispr#1667](https://github.com/PlaybookMediaLLC/openwhispr/issues/1667)) ([7208bdf](7208bdf))
* **history:** keep the wide two-column layout regardless of calendar connection ([d45d1d2](d45d1d2))
* **i18n:** guard localStorage deref against partial window stubs ([OpenWhispr#1842](https://github.com/PlaybookMediaLLC/openwhispr/issues/1842)) ([0686a26](0686a26))
* **linux:** map KDE punctuation hotkeys to Qt key codes ([OpenWhispr#1752](https://github.com/PlaybookMediaLLC/openwhispr/issues/1752)) ([523acab](523acab))
* **linux:** Preserve focus for Sway overlays ([OpenWhispr#1718](https://github.com/PlaybookMediaLLC/openwhispr/issues/1718)) ([063b5b6](063b5b6))
* **linux:** push-to-talk on Wayland (Hyprland, KDE, GNOME 48+) for dictation hotkey ([OpenWhispr#1738](https://github.com/PlaybookMediaLLC/openwhispr/issues/1738)) ([b5c5fb4](b5c5fb4))
* **local-llm:** Phase 0 correctness + hygiene fixes for local model params and canaries ([OpenWhispr#1714](https://github.com/PlaybookMediaLLC/openwhispr/issues/1714)) ([41cf1d4](41cf1d4)), closes [OpenWhispr#1176](https://github.com/PlaybookMediaLLC/openwhispr/issues/1176) [OpenWhispr#1021](https://github.com/PlaybookMediaLLC/openwhispr/issues/1021) [OpenWhispr#1611](https://github.com/PlaybookMediaLLC/openwhispr/issues/1611)
* **logging:** Suppress packaged Windows console output ([OpenWhispr#1719](https://github.com/PlaybookMediaLLC/openwhispr/issues/1719)) ([30dedcf](30dedcf))
* make model selection an explicit click and only bootstrap if no model is selected/downloaed ([b9b5335](b9b5335))
* **meeting:** handle nullish segments and non-object patches in speakerAssignmentPolicy ([OpenWhispr#1798](https://github.com/PlaybookMediaLLC/openwhispr/issues/1798)) ([c519e1f](c519e1f)), closes [OpenWhispr#1797](https://github.com/PlaybookMediaLLC/openwhispr/issues/1797)
* Mode-switch validity guard ([58d9af0](58d9af0))
* **models:** Honor redirected cache roots ([OpenWhispr#1721](https://github.com/PlaybookMediaLLC/openwhispr/issues/1721)) ([da39195](da39195)), closes [OpenWhispr#1279](https://github.com/PlaybookMediaLLC/openwhispr/issues/1279)
* **network:** treat the full IPv6 link-local range as private HTTP ([OpenWhispr#1733](https://github.com/PlaybookMediaLLC/openwhispr/issues/1733)) ([1fe91f2](1fe91f2))
* **notes:** abort local transcription and diarization when an upload is cancelled — fixes [OpenWhispr#1401](https://github.com/PlaybookMediaLLC/openwhispr/issues/1401) ([7bbc04c](7bbc04c))
* **notes:** format future timestamps as dates ([OpenWhispr#1768](https://github.com/PlaybookMediaLLC/openwhispr/issues/1768)) ([5d5f08c](5d5f08c))
* **notes:** keep markdown mirror paths inside base ([OpenWhispr#1773](https://github.com/PlaybookMediaLLC/openwhispr/issues/1773)) ([2893be4](2893be4))
* **notes:** keep note content visible above the floating chat panel ([OpenWhispr#1769](https://github.com/PlaybookMediaLLC/openwhispr/issues/1769)) ([fa11ddc](fa11ddc))
* **notes:** reject cancelled uploads before ffmpeg conversion and server boot ([53ec075](53ec075))
* **parakeet:** Clarify unsupported-macOS guidance and dedupe version compare ([cd4dedc](cd4dedc)), closes [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862) [OpenWhispr#764](https://github.com/PlaybookMediaLLC/openwhispr/issues/764) [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862)
* **parakeet:** Gate incompatible macOS runtime ([0c271fe](0c271fe)), closes [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862)
* **parakeet:** Localize disabled provider tab labels ([514866c](514866c)), closes [OpenWhispr#862](https://github.com/PlaybookMediaLLC/openwhispr/issues/862)
* **paste:** skip trailing space after CJK text in append smart spacing ([OpenWhispr#1823](https://github.com/PlaybookMediaLLC/openwhispr/issues/1823)) ([f26b059](f26b059))
* retire dead cloud models and make the canary suites trustworthy ([OpenWhispr#1722](https://github.com/PlaybookMediaLLC/openwhispr/issues/1722)) ([9b3f57a](9b3f57a))
* **settings:** Clamp the LLM request timeout input on blur ([7032088](7032088))
* **settings:** close stale-state gaps in explicit model selection ([82fd37a](82fd37a))
* **settings:** enforce auto-end and request timeout defaults ([OpenWhispr#1755](https://github.com/PlaybookMediaLLC/openwhispr/issues/1755)) ([77c6661](77c6661))
* **settings:** harden browse-only provider tabs and drop dead code ([983bf58](983bf58))
* **settings:** keep the browsed whisper-list validation off foreign local models ([204e83b](204e83b))
* **snippets:** do not crash snippet expansion when the list is nullish ([773fed0](773fed0))
* **stores:** Survive imports under partial browser window stubs ([OpenWhispr#1841](https://github.com/PlaybookMediaLLC/openwhispr/issues/1841)) ([b934b88](b934b88))
* **uploads:** anchor upload transcript timestamps to the meeting path's epoch-ms base ([df826f0](df826f0))
* **uploads:** normalize provider names and clamp verbose segment timestamps ([OpenWhispr#1800](https://github.com/PlaybookMediaLLC/openwhispr/issues/1800)) ([d9499f5](d9499f5)), closes [OpenWhispr#1799](https://github.com/PlaybookMediaLLC/openwhispr/issues/1799)
* **utils:** handle nullish and invalid inputs safely in date formatting ([OpenWhispr#1783](https://github.com/PlaybookMediaLLC/openwhispr/issues/1783)) ([OpenWhispr#1784](https://github.com/PlaybookMediaLLC/openwhispr/issues/1784)) ([858e824](858e824))
* validate transcription hosts exactly ([8aced79](8aced79))
* **whisper:** accept any casing for the GPU opt-out flag ([28bf6f4](28bf6f4))
* **whisper:** engage downloaded GPU packs without the env flag ([d4c207a](d4c207a)), closes [OpenWhispr#1340](https://github.com/PlaybookMediaLLC/openwhispr/issues/1340)
* **whisper:** raise decoder anti-hallucination thresholds on local transcription — fixes [OpenWhispr#1458](https://github.com/PlaybookMediaLLC/openwhispr/issues/1458) ([b7fdf80](b7fdf80))
* **windows:** block untrusted file navigation ([OpenWhispr#1776](https://github.com/PlaybookMediaLLC/openwhispr/issues/1776)) ([45c20b8](45c20b8))
* **windows:** restore the captured target window before pasting — fixes [OpenWhispr#859](https://github.com/PlaybookMediaLLC/openwhispr/issues/859) ([de9e335](de9e335)), closes [OpenWhispr#668](https://github.com/PlaybookMediaLLC/openwhispr/issues/668)

### Features

* **billing:** self-serve Enterprise upgrade + admin console entry ([OpenWhispr#1623](https://github.com/PlaybookMediaLLC/openwhispr/issues/1623)) ([e039861](e039861))
* **calendar:** add provider-neutral availability tool ([OpenWhispr#1821](https://github.com/PlaybookMediaLLC/openwhispr/issues/1821)) ([b96f4a2](b96f4a2))
* **calendar:** redesign Coming up sidebar with join-and-take-notes and empty states ([a5e59ee](a5e59ee))
* complete Oppulence Voice cloud integration ([fed389c](fed389c))
* **hyprland:** support both Lua and legacy configs ([OpenWhispr#1664](https://github.com/PlaybookMediaLLC/openwhispr/issues/1664)) ([47da21d](47da21d))
* **llm:** Apply the configurable request timeout to the Tinfoil provider ([f240775](f240775)), closes [OpenWhispr#1479](https://github.com/PlaybookMediaLLC/openwhispr/issues/1479)
* **meetings:** Auto-end forgotten meeting recordings ([OpenWhispr#1494](https://github.com/PlaybookMediaLLC/openwhispr/issues/1494)) ([bc306d2](bc306d2))
* **models:** add Gemini 3.5 and 3.1 Flash Lite models ([OpenWhispr#1702](https://github.com/PlaybookMediaLLC/openwhispr/issues/1702)) ([90f79b6](90f79b6))
* **notes:** real speaker identity in note generation and [@mention](https://github.com/mention) owner tagging ([672cc90](672cc90))
* **onboarding:** rebuild guided setup experience ([OpenWhispr#1670](https://github.com/PlaybookMediaLLC/openwhispr/issues/1670)) ([9382171](9382171)), closes [#141414](https://github.com/PlaybookMediaLLC/openwhispr/issues/141414)
* **policy:** Enforce org-required local model downloads ([OpenWhispr#1836](https://github.com/PlaybookMediaLLC/openwhispr/issues/1836)) ([409bc28](409bc28))
* **policy:** Mirror the server-only memoryEnabled org-policy field ([OpenWhispr#1717](https://github.com/PlaybookMediaLLC/openwhispr/issues/1717)) ([91c1195](91c1195))
* **sidebar:** redesign Upgrade to Pro banner to new card style ([973cbf5](973cbf5))
* **uploads:** persist segment timestamps from BYOK cloud transcriptions for SRT export — fixes [OpenWhispr#1095](https://github.com/PlaybookMediaLLC/openwhispr/issues/1095) ([aada021](aada021))

### Performance Improvements

* **windows:** skip the paste settle when the target is already foreground ([0266639](0266639))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant