What happens
Reading Claude output on my phone, most lines run off the right edge and I have to pan
sideways to read them. Turning on View → WrapText helps, but the wrapping it produces
is ragged, because by then the lines have already been broken at the desktop's width.
Why
Two separate things stack up.
1. Wrap defaults to off.
DEFAULTS in web/src/hooks/use-display-prefs.ts is { wrap: false, ... }, and
AnsiOutput's wrap prop defaults to false. With wrap off, preClass() renders
whitespace-pre overflow-x-auto, so the <pre> becomes a horizontal panner. The comment
says this is deliberate ("preserves column alignment like desktop Herdr"), which is the
right call for TUI tables. It is the wrong default for reading agent prose on a phone,
which is most of what the mirror shows.
2. The bridge asks for the pre-wrapped snapshot.
bridge/server.ts:458 reads:
const read = await herdr.readPane(paneId, "recent", lines, "ansi");
source: "recent" returns the terminal grid, so every line is already hard-broken at the
desktop pane's column count. herdr spawns those panes at the desktop terminal's size; in
my session that is 190 columns (spawning pane terminal ... rows=63 cols=190 in
herdr-server.log). A phone in portrait shows roughly 45 to 50 columns at the default
12px font.
Measured on one of my live panes, 200 lines, ANSI stripped:
source |
lines |
longest line |
lines over 60 cols |
recent (used today) |
199 |
187 chars |
84 of 199 |
recent-unwrapped |
176 |
721 chars |
79 of 176 |
So ~42% of lines need panning, and the 187-char maximum is exactly the desktop pty width.
The consequence is that turning wrap on does not really fix it: the browser re-wraps lines
that were already wrapped at 190 columns, so breaks land mid-sentence at arbitrary
points and Claude's own indentation and continuation structure is lost. You get correct
text in a shape that reads worse than either endpoint.
Suggested direction
Both of these are small, and the second is the one that actually matters:
-
Default wrap to true. The pref is already persisted and already has a UI
toggle, so anyone who wants column-faithful panning keeps it one tap away. v3 is
already in the STORAGE_KEY, so a bump to v4 would reset existing devices onto the
new default if that is wanted.
-
When wrap is on, read source: "recent-unwrapped" so the browser wraps logical
lines to the phone's actual width instead of re-wrapping desktop-width fragments.
ReadSource in bridge/herdr-client.ts:114 already includes it; nothing calls it. This
would need to be plumbed from the client pref through to the read, since the bridge
currently picks the source with no knowledge of the viewer.
Tradeoff worth flagging, since I have not tested it: recent-unwrapped gives up the
terminal grid, so it will probably break box-drawing tables, and it may well break the
Claude block grammars in web/src/lib/harness/claude/ that key off the visible layout
(prompt-select detection, chrome stripping, the status strip). Tying the source to the
existing wrap toggle keeps recent as the default path for exactly that reason, and
bridge/server.ts:780 deliberately couples the binding read to readPane()'s source, so
that coupling needs to hold too. If any of that makes it a non-starter, then just the
wrap: true default plus smarter break points would still be a real improvement.
Environment
- collie 0.17.0, installed as a herdr plugin
- herdr 0.7.x, panes spawned at 190x63
- iOS, installed PWA, over Tailscale
- Claude Code in the mirrored pane
Related
What happens
Reading Claude output on my phone, most lines run off the right edge and I have to pan
sideways to read them. Turning on View → WrapText helps, but the wrapping it produces
is ragged, because by then the lines have already been broken at the desktop's width.
Why
Two separate things stack up.
1. Wrap defaults to off.
DEFAULTSinweb/src/hooks/use-display-prefs.tsis{ wrap: false, ... }, andAnsiOutput'swrapprop defaults tofalse. With wrap off,preClass()renderswhitespace-pre overflow-x-auto, so the<pre>becomes a horizontal panner. The commentsays this is deliberate ("preserves column alignment like desktop Herdr"), which is the
right call for TUI tables. It is the wrong default for reading agent prose on a phone,
which is most of what the mirror shows.
2. The bridge asks for the pre-wrapped snapshot.
bridge/server.ts:458reads:source: "recent"returns the terminal grid, so every line is already hard-broken at thedesktop pane's column count. herdr spawns those panes at the desktop terminal's size; in
my session that is 190 columns (
spawning pane terminal ... rows=63 cols=190inherdr-server.log). A phone in portrait shows roughly 45 to 50 columns at the default12px font.
Measured on one of my live panes, 200 lines, ANSI stripped:
sourcerecent(used today)recent-unwrappedSo ~42% of lines need panning, and the 187-char maximum is exactly the desktop pty width.
The consequence is that turning wrap on does not really fix it: the browser re-wraps lines
that were already wrapped at 190 columns, so breaks land mid-sentence at arbitrary
points and Claude's own indentation and continuation structure is lost. You get correct
text in a shape that reads worse than either endpoint.
Suggested direction
Both of these are small, and the second is the one that actually matters:
Default
wraptotrue. The pref is already persisted and already has a UItoggle, so anyone who wants column-faithful panning keeps it one tap away.
v3isalready in the
STORAGE_KEY, so a bump tov4would reset existing devices onto thenew default if that is wanted.
When wrap is on, read
source: "recent-unwrapped"so the browser wraps logicallines to the phone's actual width instead of re-wrapping desktop-width fragments.
ReadSourceinbridge/herdr-client.ts:114already includes it; nothing calls it. Thiswould need to be plumbed from the client pref through to the read, since the bridge
currently picks the source with no knowledge of the viewer.
Tradeoff worth flagging, since I have not tested it:
recent-unwrappedgives up theterminal grid, so it will probably break box-drawing tables, and it may well break the
Claude block grammars in
web/src/lib/harness/claude/that key off the visible layout(prompt-select detection, chrome stripping, the status strip). Tying the source to the
existing wrap toggle keeps
recentas the default path for exactly that reason, andbridge/server.ts:780deliberately couples the binding read toreadPane()'s source, sothat coupling needs to hold too. If any of that makes it a non-starter, then just the
wrap: truedefault plus smarter break points would still be a real improvement.Environment
Related