Skip to content

Resizable composer, remembered per device - #11

Merged
devswha merged 3 commits into
devswha:mainfrom
Yoonwoo-Ha:feat/composer-resize
Sep 24, 2026
Merged

devswha merged 3 commits into
devswha:mainfrom
Yoonwoo-Ha:feat/composer-resize

Conversation

@Yoonwoo-Ha

@Yoonwoo-Ha Yoonwoo-Ha commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Split out of #5, with its review notes addressed.

A grip on the composer's top edge sets the box's height:

  • Drag it, or use ↑/↓ (Shift for bigger steps).
  • Double-click or double-tap it, press Home, or drag it down to the floor to go back to the automatic height.
  • The height is saved per device, when the pointer is released. It is capped at half the visible viewport (--app-height) or 480px.
  • When a raised keyboard or a rotation shrinks the viewport, CSS clamps the box, and the saved height comes back once there's room again.

Details:

  • The floor is the box's automatic height (the textarea's CSS min-height, 42px on desktop, 48px on touch). The grip's aria-valuemin, -valuenow and -valuemax agree, and valuemax follows the viewport limit.
  • A press becomes a resize only after 3px of movement (mouse) or 10px (touch), so a tap or a resting finger sets nothing.
  • The hit area rises from the border into the status line's gap and never reaches into the box. A tap at the box's top reaches the text.
  • The chat view keeps a reader who is at the end of the conversation at the end when the view shrinks, instantly and regardless of scroll-behavior. Without this, growing the composer hid the last message.
  • Fixes a problem that already exists on main: the empty box is refitted when its placeholder changes. The composer mounts before the socket connects, so on a phone it used to keep the height of the two-line "Reconnecting…" placeholder until the user typed.
  • A small alignment tweak: the text sits centred in a slightly taller box.

Headless Chrome checks, desktop and touch emulation:

  • a hit test at the box's top edge
  • mouse and touch slack
  • drag, then reload
  • nothing saved mid-drag
  • keyboard steps
  • the viewport limit
  • double-click, double-tap and drag-to-floor resets
  • --app-height clamping and restoring a saved height
  • the transcript stays at its end while the composer grows
  • no horizontal overflow on a phone

bun test passes.

🤖 Generated with Claude Code

@devswha devswha left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for splitting this out. A few things before merge:

  1. Min height is above the automatic height. The automatic height is 42px on desktop and 48px on touch, below COMPOSER_HEIGHT_MIN (56). So aria-valuenow falls outside aria-valuemin, and ArrowDown or a downward drag in automatic mode makes the box grow to 56. Please set the minimum to the automatic minimum.
  2. An accidental touch locks in a height. The touch grip reaches about 12px into the box and calls preventDefault on pointerdown. A tap near the top of the box doesn't focus the textarea, and 1px of finger movement switches to manual 56px, which is then saved for good. Please add a movement threshold before switching to manual mode, and keep the hit area off the textarea. On touch the only way back is double-tap, and iOS may not deliver dblclick there.
  3. The keyboard clamp uses 50dvh. On older iOS Safari, dvh doesn't shrink with the keyboard (see lib/viewport.ts), so a saved 400px composer can fill the visible area. calc(var(--app-height) / 2) follows the visual viewport.

Minor: localStorage is written on every pointermove (write on pointerup instead), and aria-valuemax stays at 480 even when the viewport limit is lower. Please also rebase onto main: #5 and #14 are merged, and #14 touches Composer.css.

A grip on the box's top edge sets its height: drag it, use the arrow keys
(Shift for larger steps), or double-click / Home to return to the automatic
height. The height is kept per device in localStorage, limited to half the
visible viewport and 480px; a raised keyboard or rotation clamps it through
CSS and the saved height returns when there is room again. The text also
sits centred in the slightly taller box.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 42c442e)
- The floor is the box's automatic height (the textarea's CSS min-height,
  42px on desktop, 48px on touch), not a fixed 56px. aria-valuemin,
  -valuenow and -valuemax now agree, and valuemax follows the viewport
  limit. ArrowDown or a drag to the floor returns the automatic height
  instead of growing the box.
- A press on the grip becomes a resize only after 3px (mouse) or 10px
  (touch) of movement, so a tap or a resting finger sets nothing.
- The hit area rises from the border into the status line's gap and never
  reaches into the box. A tap at the box's top reaches the text, and the
  attachment strip's remove buttons stay reachable.
- Two quick taps on the grip reset it, detected from pointer events: iOS
  may not send dblclick.
- The keyboard clamp uses half of --app-height (the visual viewport), not
  50dvh, which older iOS Safari does not shrink for the keyboard.
- The height is saved when the pointer is released, not on every move.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The composer mounts before the socket connects, so the first auto-size
measures the long "Reconnecting… message held here, never queued"
placeholder. On a phone that wraps to two lines, and the box kept that
height after connecting until the user typed (72px instead of 48px; 58px
instead of 34px on main). Re-running the auto-size when the placeholder
changes fits the empty box to the placeholder it actually shows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Yoonwoo-Ha

Copy link
Copy Markdown
Contributor Author

Thanks. Reworked in 21046e2 and rebased onto main:

  1. Floor. The floor is now the box's automatic height, read from the textarea's CSS min-height (42px on desktop, 48px on touch). aria-valuemin, -valuenow and -valuemax agree, and valuemax follows the viewport limit (updated on visualViewport resize). ArrowDown, or dragging down to the floor, returns the box to automatic height, so it can't grow.
  2. Accidental touches. A press becomes a resize only after 3px of movement (mouse) or 10px (touch). The hit area now rises from the border into the status line's gap and never reaches into the box: a tap at the box's top focuses the text, and the attachment strip's remove buttons stay reachable. Double-tap reset is detected from pointer taps, not dblclick. Dragging down to the floor is a second way back.
  3. Clamp. Now min(calc(var(--app-height, 100dvh) / 2), 480px).

Minor. The height is saved on pointerup (and on each key step), no longer on every move.

Also in this PR (0c05b5e). I found a problem that already exists on main. The composer mounts before the socket connects, so on a phone it auto-sizes to the two-line "Reconnecting… message held here, never queued" placeholder and keeps that height after connecting (58px instead of 34px on main). The auto-size now reruns when the placeholder changes.

Checked in Chromium, desktop and touch emulation:

  • a hit test at the box's top edge
  • mouse and touch slack
  • drag, then reload
  • nothing written mid-drag
  • keyboard steps
  • the viewport limit
  • double-click and double-tap reset
  • drag-to-floor reset
  • --app-height: 400px, which clamps a 422px box to 200px, and the box returning to 422px after

bun test passes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants