Summary
Multiple bug reports describe the same defect: when a user starts typing a word and accepts the inline suggestion with Tab, Cotabby inserts the completion with an extra space in the middle of the word (e.g. typing after and accepting afternoon yields after noon). The ghost text renders correctly, but the inserted text does not match what was shown.
What occurred
The model's first token is the boundary signal: it emits a leading space when it intends a new word, and emits none when it intends to continue the current word. Real generations in llm-io.jsonl confirm the model gets this right consistently (e.g. prompt ending ...its hrd to kn produces raw w wht to say..., correctly continuing kn → knw). The overlay also renders this correctly, so the ghost text reads afternoon.
The defect is at accept time. SuggestionSessionReconciler.insertionChunk Rule 2 (Cotabby/Support/SuggestionSessionReconciler.swift:451) unconditionally inserts a space whenever the live preceding character is a word character and the accepted chunk's first character is a word character:
guard let firstChunkChar = chunk.first, firstChunkChar.isAcceptanceWordCharacter,
let lastPrecedingChar = precedingText.last, lastPrecedingChar.isAcceptanceWordCharacter
else { return chunk }
return " " + chunk
Its own doc comment admits the gap: "we don't try to distinguish a fresh new word from a partial-word completion." This single accept-time heuristic overrules the model's correct boundary decision and breaks the WYSIWYG invariant (ghost shows afternoon, Tab inserts after noon).
Cause
Cotabby was originally designed to only trigger after the user typed a space, so every completion was treated as a fresh word and accept-time space injection was safe. Users have come to rely on mid-word completions, but the accept-time space-injection rule was never updated. The model's leading-space signal is correct and is preserved through normalization; it is only at the final insertion step that the boundary information is discarded.
Fix
PR #622 makes insertion render-faithful: the model's leading-space signal is treated as part of the first chunk and accept-time space injection is removed, so Tab inserts exactly the ghost text. New-word boundaries (e.g. I went to the | store) remain correct because the model emits the leading space itself, which is now preserved end-to-end.
Fixed by #622.
Related issues
Bug reports:
Feature requests describing the same underlying defect:
Note
Some of the issues linked above (#559, #557, #553, #491, #395) were accidentally closed by me in a triage sweep with a comment claiming they were addressed in 0.5.0-beta. They are not yet fixed in any released build. Those issues have been reopened and the misleading comments removed.
Summary
Multiple bug reports describe the same defect: when a user starts typing a word and accepts the inline suggestion with Tab, Cotabby inserts the completion with an extra space in the middle of the word (e.g. typing
afterand acceptingafternoonyieldsafter noon). The ghost text renders correctly, but the inserted text does not match what was shown.What occurred
The model's first token is the boundary signal: it emits a leading space when it intends a new word, and emits none when it intends to continue the current word. Real generations in
llm-io.jsonlconfirm the model gets this right consistently (e.g. prompt ending...its hrd to knproduces raww wht to say..., correctly continuingkn→knw). The overlay also renders this correctly, so the ghost text readsafternoon.The defect is at accept time.
SuggestionSessionReconciler.insertionChunkRule 2 (Cotabby/Support/SuggestionSessionReconciler.swift:451) unconditionally inserts a space whenever the live preceding character is a word character and the accepted chunk's first character is a word character:Its own doc comment admits the gap: "we don't try to distinguish a fresh new word from a partial-word completion." This single accept-time heuristic overrules the model's correct boundary decision and breaks the WYSIWYG invariant (ghost shows
afternoon, Tab insertsafter noon).Cause
Cotabby was originally designed to only trigger after the user typed a space, so every completion was treated as a fresh word and accept-time space injection was safe. Users have come to rely on mid-word completions, but the accept-time space-injection rule was never updated. The model's leading-space signal is correct and is preserved through normalization; it is only at the final insertion step that the boundary information is discarded.
Fix
PR #622 makes insertion render-faithful: the model's leading-space signal is treated as part of the first chunk and accept-time space injection is removed, so Tab inserts exactly the ghost text. New-word boundaries (e.g.
I went to the | store) remain correct because the model emits the leading space itself, which is now preserved end-to-end.Fixed by #622.
Related issues
Bug reports:
Feature requests describing the same underlying defect:
Note
Some of the issues linked above (#559, #557, #553, #491, #395) were accidentally closed by me in a triage sweep with a comment claiming they were addressed in 0.5.0-beta. They are not yet fixed in any released build. Those issues have been reopened and the misleading comments removed.