kimi-code/HANDOVER-kimi-web-table-width.md
qer b905dd4910
feat(web): redesign web UI and add design system (#1258)
* feat: redesign web ui & add design system

* feat(web): add motion to redesigned UI and add changesets

Animate toast enter/leave, dialog open, and workspace-list and tool-row expand/collapse instead of snapping, and add the changesets covering the web redesign.

* fix(web): remove undo message exit animation

* fix(web): route agent tools to AgentTool and focus dialog on open

- toolRegistry matched the raw 'agent' name, but normalizeToolName folds
  agent/subagent into 'task', so agent calls fell through to GenericTool
  and lost the inline Open button for the subagent detail panel.
- Dialog's focus watcher only fired on change; callers that mount with
  open already true (Login, Settings, ...) never moved focus into the
  modal. Run it immediately so initial focus and restore-on-close work.

* feat(web): add logo long-press design-system easter egg

Hold the sidebar logo for 3 seconds to open a dialog showing the design system page. Also trim and rebalance the redesign changesets.

* fix(web): silence Sidebar v-show warning by making it single-root

Nest the design-system Teleport inside the sidebar <aside> so the component has a single element root. App applies v-show to Sidebar, which needs an element root to attach to; the fragment root logged a "non-element root node" warning on every reactive update and the collapse did not take effect.

* fix(web): thinking toggle, tool-group i18n, agent detail button

- Show the default-thinking switch as on whenever thinking is effectively enabled (enabled !== false), matching the core resolver.

- Route the grouped tool-call header and status through vue-i18n.

- Hide the subagent "Open detail" button when no matching task exists (e.g. a completed foreground subagent after a refresh).

* fix(web): use strict equality in agent detail button guard

oxlint eqeqeq flagged the loose != null check; resolveAgentTaskId returns string | undefined, so compare with !== undefined.

* chore(web): remove stray design mockups and screenshots

Remove the design exploration mockups, screenshots, prompts, and notes that were accidentally committed under apps/kimi-web/design. Keep design-system.html, which the sidebar logo easter egg still references.

* fix(web): keep sessions on continuation failure, treat absent thinking as on

- Keep sessions already loaded from earlier pages when a continuation page fetch fails, instead of replacing the workspace with an empty page.

- Treat an absent thinking config as enabled in the settings toggle, matching the core resolver (thinking is on unless explicitly disabled).

* feat(web): open design-system easter egg on 10 logo clicks

Replace the 3-second long-press trigger with 10 consecutive clicks on the Kimi mark; the count resets after a short idle. The long-press was unreliable because pointerleave cancelled the timer on any drift.

* fix(web): treat cancelled swarm members as finished

phaseForTask now lets a terminal status (completed/failed/cancelled) override a stale subagentPhase, so a cancelled swarm member no longer stays live and suppresses the finished AgentSwarm card.

* feat(web): use 1s long-press for design-system easter egg

Switch the logo easter egg back to a long-press, shortened to 1 second, and make it reliable this time: use pointer capture plus touch-action:none so a slight drift no longer cancels the hold.

* fix(web): use plain Spinner for activity notices

ActivityNotice renders for non-chat loading states (e.g. compaction), so it must use Spinner per the design-system rule that reserves MoonSpinner for the chat first-response state.

* docs(web): add a11y guidance to design system

* docs(web): drop stale design README link

* fix(web): restore model search focus and define panel header weight

- Bind the model picker's search Input to searchRef so useDialogFocus moves focus into it on open instead of the dialog's close button.

- Use the defined --weight-semibold token for panel header titles (--weight-bold is not declared, so the shorthand was invalid).

* fix(web): let any open dialog own Escape over the side panel

Track open design-system Dialog instances in a shared count and include it in App.vue's anyOverlayOpen, so a dialog whose open state lives outside App.vue (such as the sidebar session search) captures Escape before the background side panel closes.

* fix(web): base dock-work flag on filtered dock task lists

Foreground subagents are excluded from the dock task lists, so a session whose only task is a foreground subagent no longer renders an empty workbar above the composer.

* fix(web): create subagent task before forwarding text deltas

A client that subscribes from a snapshot after subagent.spawned already fired never received the lifecycle taskCreated; the reducer only applies taskProgress to existing tasks, so assistant text deltas were dropped and the live subagent detail stayed blank. Emit taskCreated (via patchSubagent) before the text progress, mirroring the tool-progress path.

* fix(web): keep plan, swarm, and goal mode toggles per session

Plan, swarm, and goal modes were stored as global scalars on the web client and a single localStorage key each, so they leaked across sessions. Bind them to the active session via per-session maps, persist per session, and apply server status/events to the originating session so background sessions keep independent state.

* fix(web): keep subagent detail reachable for synthesized tasks

When the web client subscribes after a subagent already spawned, the synthesized subagent task has no parentToolCallId, so the Agent tool's Open-detail button was hidden and the panel would not open. Fall back to the single unmapped subagent task when resolving the detail target in both the button visibility and the panel open paths.

* fix(web): keep session kebab menu from being clipped

Teleport the SessionRow kebab menu to body and anchor it with fixed positioning so the collapsing group-sessions list's overflow:hidden no longer clips the dropdown.

* fix(web): apply staged modes to the created session by id

When starting the first prompt, apply the staged plan/swarm/goal modes to the just-created session's per-session maps by id instead of via the activeSessionId-based setters, so a session switch during the selectSession await can't drop the modes for this session or pollute another.

* refactor(web): unify confirmation dialogs into a single modal

Replace the inconsistent confirmation patterns (native confirm(),
two-step menu arming, hand-rolled inline strips, bare buttons) with one
modal ConfirmDialog driven by a global useConfirmDialog() composable,
and consolidate the duplicated confirm/cancel i18n keys.

* feat(web): inline message queue with separate stop button

- Send button always sends/enqueues; interrupt moves to a separate red Stop
  button shown only while running, so the two can no longer be confused.
- Queued prompts now render inline at the tail of the transcript (after the
  running turn) instead of behind the dock panel: click to edit, remove, drag
  the grip to reorder, with image thumbnails and a "next up" marker.
- Remove the dock queue panel and the QueuePane component; Steer stays on
  Ctrl/Cmd+S.

* chore: add changeset for web queue UX

* feat(web): prompt reliability, sidebar menu, and composer/markdown polish

- Fix spurious errors when question/approval/task actions were already complete
- Add loading feedback to question and approval prompts; block double-clicks
- Make the question "Other" option selectable by row click and let Enter advance/submit
- Consolidate workspace section actions into an overflow menu
- Tighten markdown prose line-height and block spacing
- Recall input history only when the caret is at text start
2026-07-01 20:47:12 +08:00

8.7 KiB
Raw Blame History

接手文档kimi-web 聊天表格宽度

当前状态(未提交,用户先用着)表格在阅读列≈760px单元格换行显示—— 不横向滚动、不溢出、不裁切,但没有比正文更宽。这是目前效果最干净、可以先用的版本。

仍未达成的目标:让宽表格像 Manus 那样突破到比正文更宽的列再换行。两次突破尝试 (容器单位 cqw、固定 px @container 断点)都没做出满意效果,已回退/停用,见 §4、§6。

只改了 1 个文件apps/kimi-web/src/components/chat/Markdown.vue(表格样式块)。 typecheck / check:style 通过。

1. 背景与目标

apps/kimi-web 聊天里 markstream 渲染的 markdown 表格,默认被钉在阅读列宽内 width:100% + table-layout:fixed),宽表格要么被挤成很窄的列、要么表格内横向滚动。

用户想要的最终效果(参照 Manus 截图):

  1. 表格比正文阅读列更宽(横向铺开),但
  2. 单元格换行,所以表格永不横向滚动(表格自身和对话面板都不滚),且
  3. 正文 / 代码 / diff / 工具块 / thinking 等其它块仍限在 760px 阅读列内。

第 1 点(撑得比正文宽)目前没做到——见 §6。当前上线的是"列内换行"的降级版。

2. 当前实现(列内换行版,正在用)

Markdown.vue <style scoped> 表格块:

.md :deep(.table-node) {              /* <table>:列宽按内容、可换行 */
  width: auto !important;
  max-width: 100% !important;
  table-layout: auto !important;
}
/* 表格在阅读列内换行;装不下(不可断的长内容)才滚动兜底 */
.md :deep(.table-node-wrapper) {
  width: max-content;
  max-width: 100% !important;         /* 100% = 阅读列宽,封顶 → 换行 */
  overflow-x: auto !important;
}
.md :deep(.table-node th),
.md :deep(.table-node td) {
  text-align: left;
  vertical-align: top;
}

要点:

  • 换行靠的是"去掉旧代码的 white-space:nowrap"。markstream 单元格默认就是 white-space:normal(见 node_modules/markstream-vue/dist/index.px.css),旧代码特意加 nowrap !important 才不换行;现在不设 white-space就回到 markstream 默认的换行。
  • max-content + max-width:100%:列宽按内容撑,但整体不超过阅读列 → 到列宽就换行。
  • .a-msg 保持 HEAD 的 width:94%.content-wrap 保持 max-width:760px,都没动。

3. DOM / CSS 结构与关键约束

.con (container-type:inline-size; position:relative)            ← 不裁剪;@container 的查询根
 └ .chat-layout
    └ .panes.chat-scroll { overflow-y:auto }                    ← 决定性裁剪点
       └ .content-wrap { max-width:760px; margin:auto }         ← 居中阅读列(桌面恒 align-center
          └ .chat (flex column; padding:16px 14px)
             └ .a-msg (width:94%; align-self:flex-start)        ← 左对齐
                ├ .msg → Markdown(.md) → .markdown-renderer
                │     └ .node-slot   { width:100% }             ← markstream不裁剪
                │       └ .node-content { width:100% }          ← markstream不裁剪
                │         └ .table-node-wrapper                 ← 表格(突破点,若要撑宽)
                │           └ table.table-node
                └ .think / .tool-group / .tool / .a-msg-ft

两条硬约束(踩过的坑):

  1. .panes 是裁剪点。 它有 overflow-y:auto,按 CSS 规则另一方向的 visible 会被强制算成 auto,所以 .panes 实际两个方向都裁剪。表格若比面板宽,.panes 就会横向滚动 macOS 悬浮滚动条静止时看不见,容易误以为是"被裁掉")。后代无法靠 CSS 逃出它 position:fixed 除外,但脱流、不随消息滚动,不可用)。
  2. .node-content / .node-slot 不裁剪(只有 width:100%,无 overflow所以更宽的表格 wrapper 能正常"顶出"它们向右显示——如果 wrapper 宽度真的生效的话(见 §4 两次失败)。

markstream 的 .table-node-wrapper 原始样式: { position:relative; max-width:100%; overflow-x:auto; ... },所以覆盖它的 max-width / overflow-x 需要 !important

4. 撑宽尝试为什么失败(避免重复试错)

目标是"表格突破 760 列、显示得比正文宽、再换行"。试过两种给 wrapper 更大宽度的写法,都不行:

  1. 容器单位 cqwmax-width: calc((100cqw + 760px)/2 - 48px)cqw(容器宽度单位)在本仓库别处从没用过,实测没生效——max-width 没卡住, width:max-content 就一路撑到不换行的完整宽度、溢出面板(.panes 横滚)。 (注意:更早还踩过 (100cqw+760)/2760 不带单位 → 长度+纯数字 非法 → 整条声明被丢弃, 但即便修正带单位,cqw 依旧没做出效果。)
  2. 固定 px @container (min-width) 断点@container (min-width:960px){ ... max-width:800px } 等按面板宽分档给固定上限。容器查询本身在本仓库可靠Sidebar 等在用),语法也过了 lint 但实际视觉效果用户仍不满意(表格没有明显变宽 / 或档位不合适),已撤掉。

结论:单纯改 .table-node-wrappermax-width 这条路,没能稳定做出"比正文宽且换行填满"的效果。 下次重做建议换思路(见 §6

5. 涉及文件

文件 关键位置
apps/kimi-web/src/components/chat/Markdown.vue <style scoped>.table-node / .table-node-wrapper / 单元格块(本任务唯一改动
apps/kimi-web/src/components/chat/ConversationPane.vue .con { container-type:inline-size; --read-max:760px }.content-wrap.panes(裁剪源)——未改
apps/kimi-web/src/components/chat/ChatPane.vue .a-msg { width:94% }——未改(本任务)

6. 若要重做"撑得比正文宽"

难点:让表格 wrapper 拿到一个"比 760 列宽、但不超过面板"的宽度,且这个宽度要真的生效并触发换行。 几个可试方向(按推荐度):

方向 说明 风险
A. 撑开 .a-msg + 让表格 max-content .a-msgwidth:fit-content; min-width:100%,其余直接子元素限 760px表格 max-content 顶宽。曾实现过,问题是超面板时 .panes 横滚、且正文与宽表格同屏排版怪。 中:需解决"超面板横滚"和排版
B. JS 量面板宽写进 CSS 变量 ResizeObserver.con 宽度,写 --pane-w,表格 max-width: calc((var(--pane-w)+760px)/2 - …)。绕开 cqw 不生效的问题。 中:引入 JS但最可控
C. Teleport 到 .panes 宽表格用 Vue Teleport 渲到滚动容器外、JS 定位覆盖到消息处。 高:定位+滚动同步脆;且表格是 markstream 第三方 DOM流式重渲染会打架
D. 页面级滚动重构 去掉 .panes 内滚动,整页纵向滚。 高:要重写滚动到底/加载更多/新消息提示等

重做第一步:先在浏览器量清楚——用户常见宽表格的自然宽度、以及侧栏展开/收起时 .con(面板)实际多宽。若表格自然宽度基本 ≤ 面板宽,方向 A/B 就够;否则必然要在 "表格内滚动 / 面板滚动 / 换行压缩"三者里选一个让步§3 硬约束决定纯 CSS 无法三者全免)。

7. 验证

export PATH="$HOME/.nvm/versions/node/v24.16.0/bin:$PATH"
pnpm --filter kimi-web typecheck    # 通过
pnpm --filter kimi-web check:style  # baseline本文件无新增告警

浏览器肉眼(本环境不能渲染):

  • 让 agent 输出一个多列宽表格。
  • 当前版本预期:表格在阅读列内换行显示,不横向滚动、不溢出;但不会比正文宽
  • macOS 是悬浮滚动条,判断"有没有横滚"别只看静止截图,拖一下或缩小窗口确认。

8. 与本任务无关的未提交改动(不要误改 / 误回退)

git diff 里还有这些非本任务的改动:

  • ConversationPane.vue:平滑滚动守卫(smoothScrollUntil / handleEditMessage / scrollToBottom 调整)。
  • ChatPane.vue.u-copyjustify-content:center + .u-copy svgtranslateY(1.3px)
  • apps/kimi-web/src/i18n/…lib/desktopFlag.tscomponents/chat/ConversationToc.vue 等仓库里早已存在的杂项。

9. 收尾

表格改动只在 Markdown.vue 一个文件。若就用当前"列内换行"版收尾,按 gen-changesets 技能 生成 changesetscope: web描述markdown 表格单元格换行、避免被挤成窄列)即可。