Skip to content

feat(ui-web): drag the composer height up to half the window - #1150

Open
LivXue wants to merge 1 commit into
mainfrom
feat/composer-resizable-height
Open

LivXue wants to merge 1 commit into
mainfrom
feat/composer-resizable-height

Conversation

@LivXue

@LivXue LivXue commented Oct 30, 2026

Copy link
Copy Markdown
Member

Summary

Make the chat composer's height adjustable: a seam along the top edge of the card can be dragged (or moved with ArrowUp/ArrowDown) to set the writing field between its resting height and half the window.

  • The floor is the field's existing resting height, read back from the min-height page.css already gives it (52px, two lines), so the composer can never be made shorter than it opens today.
  • The ceiling is half the viewport, computed from window.innerHeight at the moment it is needed and re-clamped on every fit, so a resized window moves the cap instead of leaving a stale pixel value behind.
  • A dragged height overrides the content-driven fit for the rest of the page's lifetime (it is not persisted across a reload, which was not asked for). Typing, sending and draft switching leave it untouched.
  • Each pointer move writes the field's height and calls the existing dockLift() in the same handler, so --lift, the transcript reserve and the sheets stacked above the card follow the pointer rather than a frame behind it. The sheets ride the dock's normal flow, so they move with the card automatically.
  • The automatic fit now shares the same half-window ceiling, replacing the separate min(286px, 36vh) cap. The handle is a focusable separator with the same 10/40px keyboard step as the pane grips, and its label is a new catalogue key.
  • i18n/messages.json gained gui.composer.resize; the TUI copy (ui-tui/src/i18n/messages.generated.ts) was regenerated with its own script. The region and boot DOM goldens gained the one new node, and nothing else moved.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

  • npx vitest run src/features/composer src/chrome/Dock.test.tsx src/chrome/behaviour/panes.test.ts src/state/globalListeners.test.ts src/test/regions.test.ts --maxWorkers=2 - 14 files, 298 tests passed.

  • npx vitest run src/chrome src/test src/state/lang --maxWorkers=2 - 15 files, 217 tests passed.

  • npx vitest run --no-file-parallelism scripts/gates - 54 files, 257 tests passed.

  • npx tsc --noEmit -p tsconfig.json - no errors.

  • npx eslint src/features/composer src/chrome/Dock.tsx src/chrome/Dock.test.tsx - no issues.

  • python ui-web/build.py - page assembled; both boot DOM snapshots match.

  • node ui-tui/scripts/gen-i18n.mjs --check - generated catalogue is up to date.

  • Mutation checks on the new test file: dropping the in-move dockLift() fails the lift test; replacing the half-window ceiling with a constant fails the four cap tests; deleting the manual-height branch fails the two persistence tests. Each restored to green afterwards.

  • Repo gates: scripts/check_commit_messages.py origin/main...HEAD, scripts/check_source_language.py origin/main...HEAD, scripts/check_large_files.py origin/main...HEAD, and commitlint --from origin/main --to HEAD all pass.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

No docs page describes the composer height, and no screenshot was required for this change.

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

User-visible changes: the composer now has a drag seam, and the automatic fit may grow to half the window before scrolling instead of stopping at 36vh. Nothing is persisted, so rollback is reverting the squash commit; the next page load returns to the old behavior.

Related Issues

N/A

The composer card gets a seam along its top edge. Dragging it writes the field's height on every pointer move and republishes --lift in the same handler, so the transcript reserve and the sheets stacked above the card travel with the pointer. A dragged height overrides the content fit and is kept for the page's lifetime; the floor is the field's resting min-height, the ceiling is half the window, recomputed on every pass so a resize re-clamps it.

The automatic fit now shares that ceiling instead of a separate 36vh cap. The handle takes keyboard steps like the pane grips, and its label is a new catalogue key whose TUI copy is regenerated.
@LivXue
LivXue requested review from 0xKT and gloryfromca October 30, 2026 08:10

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

Reviewed the full github/main...HEAD diff and surrounding composer, dock-lift, resize-grip, lifecycle, and generated-i18n code. I also checked the change against AGENTS.md, the Web UI context vocabulary and architecture, callers, relevant history, backward behavior, and the tests for weakening; I found no concrete regression.

Verification:

  • npm test: 262 files, 4518 tests passed
  • focused composer/dock tests: 3 files, 49 tests passed
  • npm run type-check: passed
  • npm run lint: 0 errors; 4 pre-existing warnings in untouched files
  • npm run build and uv run python ui-web/build.py: passed; both boot snapshots match
  • page, CSS, and class-namespace checks: passed
  • source-language and large-file checks for github/main..HEAD: passed

@LivXue

LivXue commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility review — PR #1150 @ 3d2bf793bdc5a59c58f4b59fedcc96dbb92d9b8e

Scope: the full diff at this head against base 11ca10d8 (11 files, +325/−11): a drag handle on the composer card's top edge (composerGrip in chrome/Dock.tsx), resizeField/gripDrag in features/composer/store.ts, the mount hookup, styles/page.css, an i18n string, boot goldens and tests. Reviewed on native Windows.

Result: no Windows-specific defect found by reading. The feature is pointer-event and layout logic — window.innerHeight, getComputedStyle(...).minHeight, pointer capture, touch-action: none, keyboard arrows on a separator role — and behaves the same in Chromium on Windows; it adds no path, filesystem, subprocess, signal, or encoding surface. The bounds are recomputed on every fit and drag, so a window that shrinks re-clamps a manually dragged height, and the floor reads the stylesheet's min-height back instead of repeating it.

Verification: no executable check was possible for the UI — the shared D:/projects/Raven/ui-web/node_modules is empty (0 entries), so vitest (including the new store.grip.test.ts and the boot/region goldens) cannot run. The diff contains no Python.

Coverage gaps: by the same token, the goldens and the generated i18n catalogue were not regenerated or diffed here; the review of this PR rests on code reading.

@0xKT

0xKT commented Oct 30, 2026

Copy link
Copy Markdown
Member

Not a blocker (acceptance on 3d2bf793, merged onto the current main): one behaviour worth a deliberate yes before merge.

Severity of this one finding, not a verdict on the pull request. Any blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.

Once the grip has been dragged even once, the composer stops growing with its content for the rest of the page's life. manualHeight in ui-web/src/features/composer/store.ts is set by resizeField and cleared only in _resetForTests; fitField takes the manual branch whenever it is non-null. So a reader who drags the field up and then back down to the resting height is left with a two-line box that scrolls inside instead of growing as they type, across sends and session switches, until a reload. If that is the intent, fine; if not, clearing manualHeight when a drag ends at the floor (or on a double-click of the grip) would hand the field back to its content.

Checked on the merged tree: type-check and lint clean, vitest 263 files / 4529 tests passed, gen-i18n --check up to date, and nine mutants of the resize logic (ceiling, both clamps, drag direction, Shift step, pointer filter, listener removal, the manual branch, the test reset) are all killed by store.grip.test.ts.

This branch has not been deployed

No deployments
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.

3 participants