Skip to content

fix(composer): size the editable area off its content, not free space - #779

Open
evepupil wants to merge 1 commit into
xintaofei:mainfrom
evepupil:fix/746-mobile-composer-layout
Open

evepupil wants to merge 1 commit into
xintaofei:mainfrom
evepupil:fix/746-mobile-composer-layout

Conversation

@evepupil

Copy link
Copy Markdown

Fixes #746

根因

消息输入框是一个竖向 flex 盒子:编辑区在上、操作按钮行贴在下面。整个盒子的高度只由外层传进来的 min-height 决定(普通会话 min-h-24,新建会话的欢迎框 min-h-30)。

问题出在编辑区用 flex-1 去拿自己那份高度。flex-1 等于「起始高度为 0,再靠父容器分下来的剩余空间长大」,同时还写了 min-h-0 把下限也放开到 0。也就是说,编辑区的高度全部依赖父容器分配剩余空间。

如果浏览器把「只有 min-height、没有确定高度」的竖向 flex 盒子当作主轴尺寸不确定,它就不会分配任何剩余空间。起始高度是 0,编辑区最后就真的是 0px:

  • 添加按钮、Agent 图标、停止按钮所在的那一行被顶到盒子最上面;
  • 占位提示文字被 0 高度的滚动区裁掉,所以一个字都看不到;
  • 按钮行以下全是外壳,不是可编辑元素,点下去自然没有光标、打不出字。

这正是 #746 截图里的样子。Chromium 会把百分比起始高度 0% 在尺寸不确定时当作 content 处理,等于替这段写法兜了底,所以桌面端一直看不出问题。

顺带修掉第二个问题:短草稿下方那片空白本来就不是 contenteditable 本体——可编辑区域里只有 65% 是它(欢迎框 40%,自动化任务的提示框 24%)。桌面靠外壳上的 mousedown 兜底把焦点塞回编辑器,触摸屏没有对应的可靠事件,于是「点了没反应」。

改动

src/components/chat/composer/rich-composer.tsxsrc/components/chat/message-input.tsx

  1. 编辑区改用内容起始高度(grow)代替零起始高度(flex-1)。这样不管浏览器分不分配剩余空间,编辑区至少和它装的文字一样高,绝不会塌成 0;min-h-0 保留,输入框到达最大高度时照样能收缩并滚动。
  2. 把滚动区变成竖向 flex 容器,让 contenteditable 在里面长满。短草稿下方的空白也就是编辑器本体了,手指点在那里直接原生获得焦点、弹出键盘,不再依赖 mousedown 兜底。

没有动任何绝对定位,也没有改按钮、发送、停止、附件、Agent 选择这些交互。

测试

npx eslint .                                  # 通过
pnpm build                                    # 通过(静态导出)
npx vitest run src/components/chat/composer/rich-composer.test.tsx \
               src/components/chat/message-input.test.tsx          # 109 通过
pnpm test                                     # 见下方说明

新增回归测试(jsdom 没有排版引擎,所以锁的是盒模型声明本身,也正是被改坏的那一条):

  • rich-composer.test.tsx — 可编辑区必须用内容起始高度、不能回退成 flex-1;contenteditable 必须能在滚动区里长满。
  • message-input.test.tsx — 编辑区不能用零起始高度;操作按钮行必须是固定高度、且排在编辑区之后。

pnpm test 全量跑:6621/6625 通过。挂掉的是 forge-page.test.tsxtask-settings-dialog.test.tsx 里几条 5 秒超时的用例,和本次改动无关——这两个文件都不引用输入框相关代码,单独跑 78/78 全过(其中一条本身就要 4.8 秒),连续两次全量跑挂的用例集合还不一样(4 条 → 1 条),是本机并发下的抖动。

真实排版验证

用构建产物的真实 CSS(pnpm build 出来的 chunk)加上组件真实渲染出的 DOM,在 Chromium(headless Edge)360px 宽下逐像素量过,并且用 CSS 模拟了「不分配 min-height 剩余空间」的引擎行为做对照。

改动前后,正常引擎下每种状态的盒子高度、可编辑区位置和高度、按钮行偏移完全一致

状态 盒子高 可编辑区 (top/高) 按钮行 top
空输入框 96 1 / 54 55
带图片附件 130.8 49 / 40.8 89.8
欢迎框(tall) 120 1 / 78 79
内容溢出滚动 240 1 / 198 199

三个用到 RichComposer 的地方(会话输入框、自动化编辑器、任务抽屉输入框)几何尺寸都没变。唯一变化的是 contenteditable 在可编辑区里的占比:65% / 40% / 24% → 全部 100%。

模拟故障引擎下:

  • 改动前 —— 盒子 96px,可编辑区只剩 16px 内边距,按钮行 top=1(顶到最上面),可点击占比 0%,下面 55px 死区。和截图完全吻合。
  • 改动后 —— 按钮行回到编辑区下方,可编辑区正常,可点击占比 100%,溢出和带附件两种状态与正常引擎逐像素一致。

手工验证范围

请在真机上确认这几点(我没有真机,见下方未验证说明):

  1. 手机浏览器打开一个 Claude Code 会话,输入框里应该看到占位提示文字,添加按钮和 Agent 图标在左下角、发送/停止按钮在右下角。
  2. 点输入框中间的空白处,光标出现、键盘弹出,能正常打字。
  3. 生成过程中点停止按钮,能中断。
  4. 输入多行文字直到输入框长到最高,检查能正常滚动、按钮行依然贴在底部。
  5. 贴一张图片进输入框,缩略图条出现后布局仍然正常。
  6. 新建会话的欢迎输入框(较高的那个)重复第 1、2 步。

未验证部分

  • 没有在真机或真实 WebKit 上跑过。 本机只有 Chromium。故障引擎那一列是用 CSS 规则模拟「不分配 min-height 剩余空间」的行为得到的,它和 手机 Web 版 Claude Code 会话消息输入框布局错位,无法输入 #746 截图的几何完全对得上(96px 盒子、按钮行贴顶、55px 死区),但「具体是哪个浏览器、哪个版本触发」这一点我无法证实——issue 里也没有提供机型、系统和浏览器版本。
  • 软键盘弹出时的 visualViewport 表现没有测过;本次没有改动这方面的逻辑。
  • 桌面端我是按量出来的尺寸判定无变化,没有人工肉眼回归。

🤖 Generated with Claude Code

The message composer is a flex column — editor on top, action row pinned
under it — whose height comes only from the `min-height` its host passes
(`min-h-24`, or `min-h-30` for the welcome box). The editor claimed its
share of that box with `flex-1`: a flex basis of ZERO plus a `min-h-0`
floor, so its whole height was free space the container had to hand down.

An engine that reads a min-height-only flex column as main-size-indefinite
distributes no free space at all, and off a zero basis the editor then
lands at 0px. The add / agent / stop controls ride up to the top of the
box, the placeholder is clipped away, and everything below the controls is
chrome no tap can focus — the mobile-web report in xintaofei#746. Chromium rescues
the same markup by resolving the percentage basis as `content`, which is
why desktop never showed it.

Give the editor a content basis (`grow`) instead. It is then never shorter
than the text it holds, whatever the engine does, while `min-h-0` still
lets it shrink and scroll once the composer reaches its max height.

The blank space under a short draft was also not the contenteditable —
only 65% of the editable area was, 40% in the taller welcome box and 24%
in the automation editor's prompt box — so focusing it went through the
chrome's mousedown fallback, which touch has no reliable equivalent for.
Make the scroll area a column and let the editable node grow inside it, so
a tap anywhere in the editor lands on it natively.

Measured against the built CSS in Chromium at 360px: the composer's box
height, editable area and action-row offset are unchanged in every state
(empty, image attached, welcome, overflowing) and in all three RichComposer
hosts. Only the contenteditable's share of the editable area moves, to
100%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

手机 Web 版 Claude Code 会话消息输入框布局错位,无法输入

1 participant