COORD: 44.21.90
OFFSET: +12.5Β°
SYS.READY
BUFFER: 99%
FOCAL_PT
BACK TO DEVLOG
POCKET

NBSP Spacing Bug β€” Claude Code Uses Non-Breaking Spaces

User reported missing spaces in Claude Code's terminal output:

2026-01-16 // RAW LEARNING CAPTURE
PROJECTPOCKET

Starting Point

User reported missing spaces in Claude Code's terminal output:

  • >Try should be > Try
  • |🌲nogit should 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 matches

PUA 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.plist with font entries
  • Changed Theme.swift cascade from MesloLGS-NF-Regular to MesloLGSNerdFontMono-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)" | xxd

Output:

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:

  1. NBSP gets width -1 (non-printable)
  2. Cursor doesn't advance
  3. 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' build

Artifacts

  • Commit b49a74c: Added Mono Nerd Fonts and Claude Code test samples
  • Local clone: ~/Code/SwiftTerm for 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:) function
  • Pocket/Pocket.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved β€” SPM pins
  • Pocket/Pocket/Core/SessionRecording.swift β€” Added sampleClaudeCode with NBSP for testing

Takeaways

  1. SPM pins commits, not branches. Even with branch = main, you stay on the pinned revision until you explicitly update.

  2. Capture actual bytes. xxd on terminal output reveals encoding issues invisible to the eye. NBSP and regular space look identical but are different characters.

  3. tmux capture-pane is invaluable for debugging terminal output:

    tmux capture-pane -t session -p | xxd | head -50
  4. Check upstream before forking. We almost forked SwiftTerm for a fix that landed 6 hours earlier.

  5. The fix was one character: <= β†’ <. Hours of debugging for a single character change β€” classic.

LOG.ENTRY_END
ref:pocket
RAW