Summary
The composer text field horizontally scrolls when a line is longer than the field. It should wrap and grow the input vertically (up to the existing chrome cap), then scroll inside the field vertically — never a horizontal gutter.
This is already the locked contract (docs/harness-limits.md Wrap / grow; plans #457 / #579; repo no-h-scroll policy #344 / #457 / #579). The flags are set (multiline + break_lines) but a long unwrapped line still pan the caret sideways instead of wrapping.
Observed
- Focus the canvas composer (
Message the model…).
- Type (or paste) a long single line with no newlines — longer than the field width.
Actual: the line stays one row; the field scrolls horizontally with the caret. The chrome stays idle height (~44 px). Hard to read; violates the no-h-scroll policy.
Expected: the line wraps at the field width. The composer chrome grows up with wrapped lines (idle ~44 px → cap 124 px). Past the cap, the entry scrolls vertically inside. No horizontal gutter past the trailing ▶/■ icons.
Typing Enter (newline) already grows the field. The miss is soft wrap of a long line, not explicit newlines.
Likely seam
Composer textEntry in native/harness/src/ui.zig:
var te = dvui.textEntry(@src(), .{
.text = .{ .buffer = state.prompt_buf[0..] },
.placeholder = "Message the model…",
.multiline = true,
.break_lines = true,
}, .{
.expand = .horizontal,
.gravity_y = 0.5,
.min_size_content = .{ .w = 120, .h = metrics.TOUCH_H - 2 * metrics.COMPOSER_TE_PAD },
.max_size_content = .{ .w = dvui.max_float_safe, .h = metrics.COMPOSER_INPUT_MAX_H - 2 * metrics.COMPOSER_TE_PAD },
// …
});
break_lines = true is already on.
max_size_content.w = dvui.max_float_safe was set so a 0 width + 2×COMPOSER_TE_PAD bake would not cap the layout at 10 px. A wrap-on-width TextEntry needs a finite content width (the allocated row minus the icon column). max_float_safe lets the inner layout believe it can grow infinitely wide, so wrap never fires and the widget h-scrolls.
- Dynamic hug (
composer_last_h) samples wrapped height after te.deinit(). If wrap never happens, hug stays idle.
Queue-row editor (native/harness/src/ui/queue_band.zig) is single-line (multiline = false) by design — out of scope unless the same no-h-scroll policy should apply there too (ellipsis, not grow).
Goal
- Long lines wrap at the composer field width.
- Chrome grows vertically with wrapped content up to
COMPOSER_MAX_CHROME_H (124 px), then vertical internal scroll (COMPOSER_INPUT_MAX_H = 120 px).
- No horizontal scroll / gutter on the composer (no-h-scroll policy).
- Idle single short line still hugs ~44 px. Enter-to-newline + Ctrl/Cmd+Enter send unchanged.
Non-goals
- Changing
COMPOSER_INPUT_MAX_H / COMPOSER_MAX_CHROME_H.
- Dual DOM chat input.
- Making the queue-row editor multi-line (unless a follow-up).
- Transcript wrap (already a different path).
Related
Suggested next: type a long URL / sentence with no newline; the caret should wrap onto a second line and the chrome should grow, not slide sideways.
Summary
The composer text field horizontally scrolls when a line is longer than the field. It should wrap and grow the input vertically (up to the existing chrome cap), then scroll inside the field vertically — never a horizontal gutter.
This is already the locked contract (
docs/harness-limits.mdWrap / grow; plans #457 / #579; repo no-h-scroll policy #344 / #457 / #579). The flags are set (multiline+break_lines) but a long unwrapped line still pan the caret sideways instead of wrapping.Observed
Message the model…).Actual: the line stays one row; the field scrolls horizontally with the caret. The chrome stays idle height (~44 px). Hard to read; violates the no-h-scroll policy.
Expected: the line wraps at the field width. The composer chrome grows up with wrapped lines (idle ~44 px → cap 124 px). Past the cap, the entry scrolls vertically inside. No horizontal gutter past the trailing ▶/■ icons.
Typing Enter (newline) already grows the field. The miss is soft wrap of a long line, not explicit newlines.
Likely seam
Composer
textEntryinnative/harness/src/ui.zig:break_lines = trueis already on.max_size_content.w = dvui.max_float_safewas set so a0width + 2×COMPOSER_TE_PADbake would not cap the layout at 10 px. A wrap-on-width TextEntry needs a finite content width (the allocated row minus the icon column).max_float_safelets the inner layout believe it can grow infinitely wide, so wrap never fires and the widget h-scrolls.composer_last_h) samples wrapped height afterte.deinit(). If wrap never happens, hug stays idle.Queue-row editor (
native/harness/src/ui/queue_band.zig) is single-line (multiline = false) by design — out of scope unless the same no-h-scroll policy should apply there too (ellipsis, not grow).Goal
COMPOSER_MAX_CHROME_H(124 px), then vertical internal scroll (COMPOSER_INPUT_MAX_H= 120 px).Non-goals
COMPOSER_INPUT_MAX_H/COMPOSER_MAX_CHROME_H.Related
native/harness/src/ui.zig— composertextEntry(multiline/break_lines/max_size_content.w)native/harness/src/ui/metrics.zig—COMPOSER_INPUT_MAX_H,COMPOSER_IDLE_CHROME_H,COMPOSER_MAX_CHROME_Hdocs/harness-limits.md— Layout / composer chrome; Wrap / grow (“Never a horizontal gutter”)Suggested next: type a long URL / sentence with no newline; the caret should wrap onto a second line and the chrome should grow, not slide sideways.