- 01.Starting Point
- 02.Initial Hypothesis: Nerd Font Glyph Width
- 03.Tried Mono Fonts
- 04.The Breakthrough: Capturing Actual Bytes
- 05.Found the Bug
- 06.The Plot Twist: Upstream Fixed It 6 Hours Ago
- 07.Resolution
- 08.Artifacts
- 09.Key Files
- 10.Takeaways
NBSP Spacing Bug β Claude Code Uses Non-Breaking Spaces
User reported missing spaces in Claude Code's terminal output:
Starting Point
User reported missing spaces in Claude Code's terminal output:
>Tryshould be> Try|π²nogitshould be| π² nogit
Other terminals (Termius) rendered correctly. Suspected SwiftTerm character width issue.
Initial Hypothesis: Nerd Font Glyph Width
Explored SwiftTerm's columnWidth(rune:) function in Sources/SwiftTerm/Utilities.swift. Found hardcoded width tables for East Asian Wide characters and emoji.
Key insight from SwiftTerm comments (lines 119-122):
"That said, this is probably the wrong approach, as it might be possible that the various emoji could have different sizes depending on the font"
Checked if Nerd Font glyphs (Private Use Area U+E000-U+F8FF) were in width tables:
grep -i "276F\|F1BB\|1F332" Sources/SwiftTerm/
# No matchesPUA characters have ea=A (Ambiguous width) per Unicode, defaulting to width 1.
Tried Mono Fonts
Downloaded Nerd Font Mono variants (icons scaled to 1 cell instead of 2):
curl -sL "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.3.0/Meslo.zip" -o Meslo.zip
unzip -o Meslo.zip "MesloLGSNerdFontMono-*"Added to project:
Pocket/Pocket/Resources/Fonts/MesloLGSNerdFontMono-*.ttf- Updated
Info.plistwith font entries - Changed
Theme.swiftcascade fromMesloLGS-NF-RegulartoMesloLGSNerdFontMono-Regular
Result: Nerd Font glyphs showed as replacement characters (font not loading properly), and spacing still wrong.
The Breakthrough: Capturing Actual Bytes
User suggested using tmux to capture Claude Code's actual output:
tmux new-session -d -s test_claude
tmux send-keys -t test_claude 'cd /tmp && claude' Enter
sleep 4
tmux capture-pane -t test_claude -p | grep -E "(Ctx|>.*Try)" | xxdOutput:
00000000: 3ec2 a054 7279 2022 686f 7720 646f 6573 >..Try "how does
...
00000030: a07c c2a0 f096 a0b0 c2a0 6e6f c2a0 6769 .|........no..gi
Key discovery: c2 a0 = UTF-8 for U+00A0 = Non-Breaking Space (NBSP)!
Claude Code uses NBSP, not regular space (U+0020).
Found the Bug
Checked SwiftTerm's columnWidth function:
// Sources/SwiftTerm/Utilities.swift line 335-337
// more non-printable characters
if irune <= 0xA0 {
return -1
}U+00A0 = 0xA0 = 160, which satisfies <= 0xA0, returning -1 (non-printable).
This means:
- NBSP gets width -1 (non-printable)
- Cursor doesn't advance
- Next character overwrites where space should be
The fix: Change <= to <:
if irune < 0xA0 { // NBSP (0xA0) is now excluded, gets width 1
return -1
}The Plot Twist: Upstream Fixed It 6 Hours Ago
Patched local DerivedData checkout, confirmed fix worked. Then checked upstream:
curl -sL "https://raw.githubusercontent.com/migueldeicaza/SwiftTerm/main/Sources/SwiftTerm/Utilities.swift" | grep -A3 "C1 control"// C1 control characters (0x7F-0x9F) return -1
// Note: 0xA0 (NO-BREAK SPACE) is excluded - it should have width 1
if irune < 0xA0 {
Upstream main already had the fix! Checked our pinned version:
# Package.resolved showed:
"revision" : "a48c5da034efdc9c36e1517d01506ff6105ac098"
# That revision has OLD code:
git show a48c5da:Sources/SwiftTerm/Utilities.swift | grep -A2 "more non-printable"
# if irune <= 0xA0 {Checked GitHub β fix was committed 6 hours ago in 992504e:
Fix NO-BREAK SPACE (U+00A0) column width calculation (#448)
We independently discovered and diagnosed the exact same bug.
Resolution
Updated Package.resolved to use the fixed commit:
{
"identity" : "swiftterm",
"state" : {
"branch" : "main",
"revision" : "992504e1323e72393e5f233fe4b3ba4cedf98153"
}
}Cleaned DerivedData and rebuilt:
rm -rf ~/Library/Developer/Xcode/DerivedData/Pocket-*/SourcePackages/checkouts/SwiftTerm
xcodebuild -scheme Pocket -destination 'platform=iOS Simulator,name=iPhone 17 Pro' buildArtifacts
- Commit
b49a74c: Added Mono Nerd Fonts and Claude Code test samples - Local clone:
~/Code/SwiftTermfor future debugging - Private mirror:
github.com/digitalpine/SwiftTerm(not needed but kept) - CLAUDE.md: Added SwiftTerm debugging section
Key Files
Sources/SwiftTerm/Utilities.swiftβcolumnWidth(rune:)functionPocket/Pocket.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedβ SPM pinsPocket/Pocket/Core/SessionRecording.swiftβ AddedsampleClaudeCodewith NBSP for testing
Takeaways
-
SPM pins commits, not branches. Even with
branch = main, you stay on the pinned revision until you explicitly update. -
Capture actual bytes.
xxdon terminal output reveals encoding issues invisible to the eye. NBSP and regular space look identical but are different characters. -
tmux capture-pane is invaluable for debugging terminal output:
tmux capture-pane -t session -p | xxd | head -50 -
Check upstream before forking. We almost forked SwiftTerm for a fix that landed 6 hours earlier.
-
The fix was one character:
<=β<. Hours of debugging for a single character change β classic.