diff --git a/CHANGELOG.md b/CHANGELOG.md index 2daa9d37..77a9ca5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ 合同没跑完但模型写了字时,那段回答会先按句子过一遍合规门:保证性结论整句拿掉,其余留下,并注明这次没跑完星盘计算。不会只丢一句说明、上面空空的。同一轮重试过的旧稿不会再拼进这次回答。Skill 版本不变。 +## 2026-09-18 — 聊天列表画出符号,思考条贴紧正文 + +回答里的清单重新画出圆点和序号,不再只是往右缩进的孤行。口语段落里的冒号不会再被改成列表。思考条和正文之间的空隙收成一档。Skill 版本不变。 + ## 2026-09-18 — 申报时段咨询重新能回答 只知道一段出生范围时,问事业 / 运势会在几十毫秒内失败,提示「未完成必要的方法与计算步骤」,从八月下旬起一直如此。根因是这条路线的助手装了方法校验、却没把方法写进系统提示,模型一次都没被叫到。现在方法已经装上;没有跑完计算但模型写过字时,会把那段留下并注明「没跑完星盘计算」,不再整轮空白。Skill 版本不变。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 043bf6a4..164bf380 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -12664,6 +12664,54 @@ - 复发自:无 - 修复版本:`5b6abc23` +## BUG-962 | 聊天正文列表没有项目符号 + +- 状态:resolved +- 首次发现:2026-09-18 +- 最近更新:2026-09-18 +- 影响面:`.message-markdown ul.markdown-list` / `ol.markdown-list`(聊天正文,不含个人报告 Markdown) +- 用户现象:回答里的清单只是往右缩进的孤行,看不出是列表;正文段落靠左、列表块缩进,页面出现两级左边界。 +- 触发条件:助手回答含 bullet / 有序列表,或 `promoteDefinitionLists` 提升出的列表。Tailwind v4 preflight 把 `ul, ol` 的 `list-style` 清成 `none`。 +- 根因:`.markdown-list` 设了 `display: grid`、`gap`、`padding-inline-start`,没有恢复 `list-style`。 +- 修复:`ul.markdown-list` 用 `list-style-type: disc`,`ol.markdown-list` 用 `decimal`;`::marker` 取 `--color-ink-secondary`。保持 `display: grid`。不改 `personal-report-markdown-view`。 +- 验证:`frontend/tests/chat-stream-layout.test.ts` 源码断言该选择器块含 `list-style-type`(ul=disc / ol=decimal)。 +- 防复发:`.markdown-list` 规则块必须含 `list-style`;不得再只设缩进不设符号。 +- 相关记录:BUG-356、BUG-963、BUG-964 +- 复发自:无 +- 修复版本:待发布 + +## BUG-963 | 四标题口语散文被自动提升成列表 + +- 状态:resolved +- 首次发现:2026-09-18 +- 最近更新:2026-09-18 +- 影响面:`promoteDefinitionLists`(`frontend/src/lib/chat-definition-lists.ts`) +- 用户现象:申报时段回答里三段带冒号的散文被改写成三条列表项,和文末真正的行动清单同一种样式。 +- 触发条件:BUG-943 之后本命/窗口正文是四标题口语体,段落经常写成「你这半年的事业是 X:……」「推的是火星:……」。连续 ≥2 段被旧判据当成并列定义项。 +- 根因:`DEFINITION_LINE = /^(.{2,80}?)[::](.+)$/` 的 term 允许含句号,body 只要求 `length >= 4`。一段一百多字的多句散文照样算「解释」。 +- 修复:收紧,不删除。连续 ≥2 段且同时满足:term 不含句末标点 `。!?;!?;` 与换行;body 是单句(不含句末标点,或仅以一个句末标点收尾);body ≤ 40 字。仍跳过 fence 与已有列表。BUG-356 的并列短项(如「情绪与精力契合。」)继续提升。 +- 验证:`frontend/tests/chat-definition-lists.test.ts` 既有 BUG-356 fixture 仍绿;新增申报时段口语 fixture 保持三段原文,不产出 `- **term**:`。 +- 防复发:不得放宽 term 句末标点或 body 长度上限来「多提升一些」;散文冒号不是列表。 +- 相关记录:BUG-356、BUG-943、BUG-962 +- 复发自:BUG-356(并列短项提升判据过宽,口语体冒号段被误伤) +- 修复版本:待发布 + +## BUG-964 | 思考条与正文之间空 56px + +- 状态:resolved +- 首次发现:2026-09-18 +- 最近更新:2026-09-18 +- 影响面:`.consultation-thinking-report`、`.message-stage-and-answer`、会话作用域 `.message-markdown h2` +- 用户现象:思维链和正文中间间隔好大。harness 实测思考条底到正文首标题 56px。 +- 触发条件:会话里助手回答以 `h2` 开头(四标题口语体的「先回答你的问题」)。 +- 根因:56px = `.consultation-thinking-report { gap: 24px }` + 首个 `h2` 的 `margin-top: 32px`。`.message-markdown > *:first-child { margin-top: 0 }` 被更高特指度的 `.conversation:not(.is-empty):not(.is-rectification) .message-markdown h2 { margin: var(--space-8) 0 var(--space-3) }` 盖掉。另一条路径 `.message-stage-and-answer` 的 gap 只有 8px。 +- 修复:会话作用域补 `.message-markdown > *:first-child { margin-top: 0 }`(无 `!important`)。两条路径的 gap 都改读 `--consult-think-answer-gap`(`var(--space-3)` = 12px)。会话 `.markdown-list` 的 gap 从 `--space-4`(16px)降到 `--space-2`(8px)。 +- 验证:`frontend/tests/chat-stream-layout.test.ts` 锁会话 first-child 清零、两处 gap 同一 token、列表符号恢复。headless Chrome:`gapPx=12`,`h2MarginTop=0px`,`listStyle=disc`。截图 `docs/testing/chat-markdown-list-20260918-after.png`。 +- 防复发:思考条与正文间距不得再各写各的;会话作用域不得让首元素 `h2` 再带 `--space-8` 的 margin-top。 +- 相关记录:BUG-962、BUG-356 +- 复发自:无 +- 修复版本:待发布 + ## BUG-968 | 账户弹窗打开后整个弹窗点不动,退出登录做不了 diff --git a/docs/tasks/PROGRESS-chat-markdown-list-20260918.md b/docs/tasks/PROGRESS-chat-markdown-list-20260918.md new file mode 100644 index 00000000..9e622bf8 --- /dev/null +++ b/docs/tasks/PROGRESS-chat-markdown-list-20260918.md @@ -0,0 +1,63 @@ +# PROGRESS · 回答正文的列表渲染(2026-09-18) + +- 分支:`codex/chat-markdown-list-20260918`,worktree `.worktrees/chat-markdown-list-20260918` +- 基线:`origin/staging` @ `1061514f`(任务书写 `ea0280c1`,本 worktree 从当时 origin/staging 拉出) +- BUG:962 / 963 / 964 +- Skill 版本:不变 + +## 1. 做了什么 + +| BUG | 落点 | +| --- | --- | +| 962 列表无符号 | `globals.css`:`.message-markdown ul.markdown-list { list-style-type: disc }`,`ol` 用 `decimal`;`::marker` 用 `--color-ink-secondary`。保持 `display: grid`。不动个人报告 Markdown。 | +| 963 散文被提升 | `chat-definition-lists.ts`:收紧 `isDefinitionLine`,不删函数。term 不得含 `。!?;!?;` / 换行;body 必须单句且 ≤ 40 字;仍要连续 ≥2 段,仍跳过 fence 与已有列表。 | +| 964 思考条空 56px | 新增 `--consult-think-answer-gap: var(--space-3)`(12px),`.consultation-thinking-report` 与 `.message-stage-and-answer` 都读它。会话作用域补 `.message-markdown > *:first-child { margin-top: 0 }`,盖过 h2 的 `--space-8`。会话 `.markdown-list` gap 从 `--space-4`(16px)降到 `--space-2`(8px)。 | + +BUG-356 防复发仍在:既有 fixture「月亮行运三分本命太阳……:情绪与精力契合。」等短项继续提升成 `- **term**:body`。那些 body 最长约 10 字,远低于 40。 + +## 2. 间距口径(headless Chrome 实测) + +改前(任务书 harness):思考条底 → 正文首标题 **56px** = 报告 gap 24 + h2 margin-top 32,`list-style: none`。 + +改后(本机 Chrome headless,`renderChatMarkdown` + 会话作用域 CSS + Tailwind preflight 的 `ul, ol { list-style: none }`): + +``` +{"gapPx":12,"h2MarginTop":"0px","reportGap":"12px","listStyle":"disc","pCount":3,"liCount":2,"firstChild":"H2"} +``` + +| 项 | 改前 | 改后 | +| --- | --- | --- | +| `.consultation-thinking-report` gap | `--space-6`(24px) | `--consult-think-answer-gap`(12px) | +| `.message-stage-and-answer` gap | `--space-2`(8px) | 同一 token(12px) | +| 会话首元素 `h2` margin-top | `--space-8`(32px,特指度压过清零) | 0px | +| 思考条底 → 正文首标题 | 56px | **12px**(≤ 16px) | +| 会话 `.markdown-list` gap | `--space-4`(16px) | `--space-2`(8px) | +| 列表 `list-style-type` | `none` | `disc` | +| 三段口语冒号段 | 三条 `
  • ` | 三个 `

    `;只有文末行动是列表 | + +截图:`docs/testing/chat-markdown-list-20260918-after.png`。步骤仍写在 `docs/testing/chat-markdown-list-20260918.md`。 + +## 3. 被改的既有断言(原值 / 新值 / 原因) + +| 文件 · 测试 | 原值 | 新值 | 原因 | +| --- | --- | --- | --- | +| `tests/chat-stream-layout.test.ts` | `.message-stage-and-answer { gap: var(--space-2) }` | `gap: var(--consult-think-answer-gap)`,并新增 thinking-report 同 token、first-child 清零、ul/ol `list-style-type` | BUG-964 / 962 | +| `tests/rectification-agentic-entry.test.ts` | 同上 `--space-2` | 同上 `--consult-think-answer-gap` | 同一 token,校正面也走这条堆叠 | + +`chat-definition-lists.test.ts` 既有「consecutive label-colon paragraphs become a markdown list」**一字未改**且须保持绿。 + +## 4. 测试 + +| 项 | 结果 | +| --- | --- | +| `tsc --noEmit` | 0 错 | +| `npm run lint` | 0 error / 120 warning(未新增;未顺手修) | +| `tsx --test tests/chat-definition-lists.test.ts` | 3/3 | +| `tsx --test tests/chat-stream-layout.test.ts` | 6/6 | +| `tsx --test tests/chat-markdown-split.test.ts` | 5/5 | +| `tsx --test tests/markdown-code-block-contract.test.ts` | 5/5 | +| `tsx --test tests/rectification-agentic-entry.test.ts` | 43/43 | +| `npm test` 全量 | tests 3458 / pass 3380 / fail 78。多出来的红是本机 Windows 环境:`G:\G:\…` 路径加倍、symlink EPERM、无 Docker 的 database/backup。与本轮 CSS/lib 无关。任务书 Linux 基线是 3501 / 3455 / 31。 | +| `next build` | 未跑。本轮只动 CSS 与一个 lib,首屏 gzip 预期接近 0;staging 验收时再核 `/` 仍 Static。 | + +Chrome headless 改后:`gapPx=12`(≤16)、`listStyle=disc`、三段口语仍是 `

    `。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 3a5cb4f1..5c92a715 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -140,8 +140,8 @@ | `TASK-consult-pass4-streaming-20260918.md` | `PROGRESS-consult-pass4-streaming-20260918.md` | 验收 review 三轮:Pass 4 一 hold 正文就整段蹦出、逐字流式消失(BUG-950 产品拍板按句放行);无出生分钟模式整段被一句拒绝顶掉、一般知识句一起丢(951 改按句丢弃);该模式下日期不留痕(952);校正流 token 级 thinking 是死链,按 P2 删除并把测试翻转成否定合同(953)。基线 `1e553976` | 待验收 | `cd4775ae` | | `TASK-window-consult-contract-20260918.md` | `PROGRESS-window-consult-contract-20260918.md` | **P0 线上**:申报时段模式**自 2026-08-21 起每轮秒败**(69ms、0 token、模型从未被调用)。服务端日志实证根因:窗口 Agent attach 了 `jyotishSkillBinding()`,但 `windowJyotishInstructions` 从来不含方法块 marker,输入处理器直接 abort(BUG-954)。abort 与「模型没调工具」同码,是它藏四周的原因(955);另含合同未绿不得丢正文(956)、计算不该由模型触发(957,须等 954 上线后另轮)、窗口指令应期冲突(958)。基线 `9cdcf96b` | 已验收通过(Claude:tsc 0 / lint 0 error / npm test 3501 条 31 红同基线 / `/` Static / gzip 无变化;实跑确认窗口 Agent 指令已含 marker);review 另出 BUG-959~961 见下一行 | `5b6abc23` | | `TASK-contract-degraded-pass4-20260918.md` | `PROGRESS-contract-degraded-pass4-20260918.md` | 验收 review:BUG-956 新增的降级交付路径绕过 Pass 4,保证句原样送达(BUG-959);`uncontractedText` 跨 attempt 不清零,同一轮正文说两遍(960);降级后还空跑一轮 compose(961)。基线 `877128ce` | 待验收 | `e07d5190` | -| `TASK-window-consult-contract-20260918.md` §5 | `PROGRESS-window-consult-precompute-20260918.md` | BUG-957:申报时段计算改为服务端预跑并走同请求缓存。产品确认 BUG-954 已在 staging 验证,本轮单独做。不得 `toolChoice: "required"`,本命不预跑 | 待验收 | `59788ac6` | -| `TASK-chat-markdown-list-20260918.md` | — | 真机排版反馈:聊天正文列表**没有项目符号**(Tailwind v4 preflight 清了 `list-style`,`.markdown-list` 没恢复,BUG-962);`promoteDefinitionLists` 把四标题口语体的散文误判成并列项,三段正文被改写成列表(BUG-963,判据太松,收紧而非删除——BUG-356 的场景要留);思考条与正文之间实测 56px=报告 grid gap 24 + 首标题 margin-top 32,后者是被特指度压掉的「首元素清零」规则(BUG-964)。基线 `ea0280c1` | 待领取 | — | +| `TASK-window-consult-contract-20260918.md` §5 | `PROGRESS-window-consult-precompute-20260918.md` | BUG-957:申报时段计算改为服务端预跑并走同请求缓存。产品确认 BUG-954 已在 staging 验证,本轮单独做。不得 `toolChoice: "required"`,本命不预跑 | 待验收 | `efd40d43` | +| `TASK-chat-markdown-list-20260918.md` | `PROGRESS-chat-markdown-list-20260918.md` | 真机排版反馈:聊天正文列表**没有项目符号**(Tailwind v4 preflight 清了 `list-style`,`.markdown-list` 没恢复,BUG-962);`promoteDefinitionLists` 把四标题口语体的散文误判成并列项,三段正文被改写成列表(BUG-963,判据太松,收紧而非删除——BUG-356 的场景要留);思考条与正文之间实测 56px=报告 grid gap 24 + 首标题 margin-top 32,后者是被特指度压掉的「首元素清零」规则(BUG-964)。基线 `ea0280c1` | 待验收 | `8b102d52` | | `TASK-secondary-page-entry-20260918.md` | — | 真机反馈:星盘 / 星历 / 报告进入时抖一下——三页都是「矮的等待文案块 → 高的正文块」一次推挤,且 `use-chart-page` 无缓存所以每次进入都抖(BUG-966)。产品已拍板方案一:统一外壳 + 缓存 + 预取消灭中间态,**不加 spinner**,红线不动。另含 BUG-967:标签页跨过部署后客户端导航静默失效(BUG-965 已复现确认,刷新即恢复),要按 `NEXT_PUBLIC_GIT_COMMIT` 与 `/api/health` 比对自愈。基线 `41902067` | 待领取 | — | | `TASK-account-dialog-inert-20260918.md` | — | 真机:账户弹窗打开后整个弹窗点不动、退出登录做不了,刷新依旧——`e4e73f56` 把 `SidebarInset` 搬进 layout 后 `inert={modalOpen}` 罩住了没有 portal 的 `AccountDialogOverlay`(BUG-968,P0,代码级确认)。另含 BUG-969:校正「换一件事问」后无下文,服务端已出下一题且快照重算完整,客户端停在开场状态,GET 路由与客户端两端静默,本单只做可观测 + 不静默 + 题干进正文。基线 `1061514f` | 待领取 | — | | `TASK-first-paint-dead-screen-fallback-20260917.md` | — | 真机:首页永远停在「正在载入账户」,兜底全在没跑起来的 bundle 里(BUG-936 investigating)。根 layout 加与 bundle 无关的内联兜底 + 去掉本仓正则后行断言 | 待领取 | — | diff --git a/docs/testing/chat-markdown-list-20260918-after.png b/docs/testing/chat-markdown-list-20260918-after.png new file mode 100644 index 00000000..356300f0 Binary files /dev/null and b/docs/testing/chat-markdown-list-20260918-after.png differ diff --git a/docs/testing/chat-markdown-list-20260918.md b/docs/testing/chat-markdown-list-20260918.md new file mode 100644 index 00000000..a2b2c8b3 --- /dev/null +++ b/docs/testing/chat-markdown-list-20260918.md @@ -0,0 +1,23 @@ +# 真人验收 · 聊天列表符号、散文不提升、思考条间距(2026-09-18) + +本机有 Chrome,无登录态。headless 已用 `renderChatMarkdown` + 会话 CSS 量过改后数字(见下)。staging 真机走查仍欠。 + +## 产品可见 + +1. 打开一段带清单的助手回答(文末「这周可以做的一件事」那种短行动,或模型自己写的 `-` 列表):每条前面有圆点,符号颜色是次级文字色,不是纯黑。 +2. 打开一段申报时段 / 四标题口语体回答(「先回答你的问题」起头,正文里有「你这半年的事业是……:……」「推的是火星:……」):这三段仍是段落,不是三条列表。文末真正的行动清单才是列表。 +3. 同一条回答里,思考条底边到正文第一个标题之间不要空出一块「像缺了一行」的空白。目测大约一个小间距(12px 量级),不要再是将近两行的空隙。 +4. 个人报告页的 Markdown 列表样子不变。 + +## 量思考条到正文的 gap(headless Chrome,本轮未跑) + +改前 harness 数字(任务书已测):`gapPx=56`,其中报告 grid gap 24px + 首个 `h2` margin-top 32px,`listStyle=none`。 + +改后实测(本机 headless Chrome):`gapPx=12`,`h2MarginTop=0px`,`reportGap=12px`,`listStyle=disc`,三段口语仍是 `

    `。截图 `docs/testing/chat-markdown-list-20260918-after.png`。 + +步骤: + +1. 用仓库自己的渲染管线跑 `renderChatMarkdown(answer)`(`frontend/src/components/chat-markdown-view.tsx`),`answer` 以 `## 先回答你的问题` 开头。 +2. 套 `next build` 产物里的 CSS chunk,容器 class 同时带 `conversation`(不要 `is-empty` / `is-rectification`)、`consultation-thinking-report`、`message-markdown`。思考条用一个固定高度的占位块。 +3. headless Chrome 里对思考条底与首个 `h2` 顶做 `getBoundingClientRect`,记录 `gapPx`、`h2MarginTop`、`reportGap`、列表的 `listStyleType`。 +4. 截 `.message-markdown`(改前改后各一张)。数字写进本轮进度记录,不要口算。 diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 29e77a8d..c2f9632a 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -186,7 +186,7 @@ One documented exception: the thinking text inside a timeline step (`.consultati ## 4. Spacing & Layout -The base unit is 4px. Tokens are `--space-1: 4px`, `--space-2: 8px`, `--space-3: 12px`, `--space-4: 16px`, `--space-5: 20px`, `--space-6: 24px`, `--space-8: 32px`, `--space-10: 40px`, `--space-12: 48px`, `--space-16: 64px`, and `--space-24: 96px`. +The base unit is 4px. Tokens are `--space-1: 4px`, `--space-2: 8px`, `--space-3: 12px`, `--space-4: 16px`, `--space-5: 20px`, `--space-6: 24px`, `--space-8: 32px`, `--space-10: 40px`, `--space-12: 48px`, `--space-16: 64px`, and `--space-24: 96px`. `--consult-think-answer-gap` is `var(--space-3)` (12px): it is the only gap between the thinking bar and the answer body. Both `.consultation-thinking-report` and `.message-stage-and-answer` use it; do not write a second value on either path. Radii have two visual steps. Controls use 8px (`--radius-md`; `--radius-xs` and `--radius-sm` alias that value). Cards and sheets use 12px (`--radius-lg`; `--radius-xl` aliases it). Circles stay `50%`; pills stay `999px`. @@ -755,6 +755,14 @@ Admin 的 antd `` 是独立设计系统,不在此表。 - **窄屏表格:** 三列表(技法审计)继续卡片化。四列及以上(Yoga、统一参数)在 `.conversation` 里横向滚动,右缘用 canvas 渐隐提示还可滑;`.conversation` 自身 `overflow-x: hidden`,页面不得出现横向滚动条。个人报告表格不走这套规则。 - **表单输入 16px:** 全局 `input` / `select` 用 `--type-body-md`(16px)。iOS Safari 对小于 16px 的字段聚焦时会整页放大且不缩回。`.otp-input` 仍是 20px,`.composer textarea` 仍是 16px。 +### Chat body lists + +Three product rules for assistant markdown in `.message-markdown` (BUG-962 / 963 / 964). Personal-report markdown (`personal-report-markdown-view`) is a different surface and is not restyled here. + +- **Lists have markers.** `.message-markdown ul.markdown-list` is `list-style-type: disc`; `ol.markdown-list` is `decimal`. `::marker` uses `--color-ink-secondary`, not ink. Tailwind v4 preflight zeros `ul, ol { list-style }`; this surface must restore it. Keep `display: grid` — markers render with grid. +- **Prose is not auto-promoted to lists.** `promoteDefinitionLists` still lifts consecutive short `term:body` blocks (BUG-356: parallel transit / advice items). A block promotes only when every one of these holds, and still only for ≥2 consecutive blocks: the term has no sentence-end punctuation (`。!?;!?;`) and no newlines; the body is a single sentence (no sentence-end, or exactly one at the end); the body is ≤ 40 characters; fences and existing lists are skipped. Spoken four-heading prose with a colon inside a long paragraph stays paragraphs. +- **Thinking-bar-to-body spacing is one token.** `--consult-think-answer-gap` (`var(--space-3)` = 12px) is the gap on both `.consultation-thinking-report` and `.message-stage-and-answer`. In conversation scope, `.message-markdown > *:first-child { margin-top: 0 }` so an opening `h2` does not add `--space-8` on top of that gap. Conversation `.markdown-list` item gap is `--space-2` (8px). + ## 14. 断点 宽度切点只允许出现在 `globals.css` 顶部注释的白名单里。壳层切点以 `sidebarViewportForWidth` 为准:小于 768 移动,小于 1024 平板,否则桌面。组件级栅格(会员方案卡、校正候选)用 640/641。校正独立问题块(`.rectification-message-question.is-standalone`)与助手正文共用 `--assistant-content-inset`。报告目录在 860 塌成抽屉,因为那是第三栏的内容宽度,不是设备宽度。 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 2ee41cff..62ac86b5 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -163,6 +163,10 @@ --space-12: 48px; --space-16: 64px; --space-24: 96px; + /* Gap between the thinking bar and the answer body. Both stacked paths + (timeline report and stage-and-answer) must use this token — do not + write a second value on either (BUG-964). */ + --consult-think-answer-gap: var(--space-3); --composer-reserve: 124px; --ease-out: cubic-bezier(.22, 1, .36, 1); /* The personal report's paper surface. Declared here so the dark blocks can @@ -556,7 +560,7 @@ button:disabled { cursor: default; opacity: .45; } .agent-thinking-panel + .message-answer { margin-top: var(--space-2); } .message-stage-and-answer { display: grid; - gap: var(--space-2); + gap: var(--consult-think-answer-gap); } .message-stage-and-answer .agent-thinking-panel, .message-stage-and-answer .agent-activity-status { @@ -1196,7 +1200,7 @@ button:disabled { cursor: default; opacity: .45; } } .consultation-thinking-report { display: grid; - gap: var(--space-6); + gap: var(--consult-think-answer-gap); } .consultation-report-analysis { min-width: 0; @@ -1340,6 +1344,15 @@ button:disabled { cursor: default; opacity: .45; } margin: var(--space-3) 0 var(--space-5); padding-inline-start: 1.35em; } +.message-markdown ul.markdown-list { + list-style-type: disc; +} +.message-markdown ol.markdown-list { + list-style-type: decimal; +} +.message-markdown .markdown-list ::marker { + color: var(--color-ink-secondary); +} .message-markdown .markdown-list > li { padding-block: 0; padding-inline-start: 6px; @@ -2343,7 +2356,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class } .conversation:not(.is-empty):not(.is-rectification) .message-markdown .markdown-list { - gap: var(--space-4); + gap: var(--space-2); } .conversation:not(.is-empty):not(.is-rectification) .markdown-table { @@ -2355,6 +2368,10 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class margin: var(--space-8) 0 var(--space-3); } +.conversation:not(.is-empty):not(.is-rectification) .message-markdown > *:first-child { + margin-top: 0; +} + .conversation:not(.is-empty):not(.is-rectification) .message-markdown h2 { font-size: var(--type-title-lg); } diff --git a/frontend/src/lib/chat-definition-lists.ts b/frontend/src/lib/chat-definition-lists.ts index 0d14ee33..57a5d718 100644 --- a/frontend/src/lib/chat-definition-lists.ts +++ b/frontend/src/lib/chat-definition-lists.ts @@ -1,17 +1,26 @@ const FENCE = /(```[\s\S]*?```)/; const DEFINITION_LINE = /^(.{2,80}?)[::](.+)$/u; +const SENTENCE_END = /[。!?;!?;]/u; +const BODY_MAX_CHARS = 40; function isSkippableLine(line: string): boolean { return /^(#{1,6}\s|>\s|[-*+]\s|\d+[.)]\s|\|)/.test(line) || line.startsWith("```"); } +function isSingleSentence(text: string): boolean { + return !SENTENCE_END.test(text.replace(/[。!?;!?;]$/u, "")); +} + function isDefinitionLine(block: string): boolean { const trimmed = block.trim(); if (!trimmed || trimmed.includes("\n") || isSkippableLine(trimmed)) return false; const match = DEFINITION_LINE.exec(trimmed); if (!match) return false; + const term = match[1]?.trim() ?? ""; const body = match[2]?.trim() ?? ""; - return body.length >= 4; + if (body.length < 4 || body.length > BODY_MAX_CHARS) return false; + if (SENTENCE_END.test(term)) return false; + return isSingleSentence(body); } function toListItem(block: string): string { diff --git a/frontend/tests/chat-definition-lists.test.ts b/frontend/tests/chat-definition-lists.test.ts index f9263bee..b4f2a17a 100644 --- a/frontend/tests/chat-definition-lists.test.ts +++ b/frontend/tests/chat-definition-lists.test.ts @@ -29,3 +29,16 @@ test("code fences and existing lists are left alone", () => { ].join("\n"); assert.equal(promoteDefinitionLists(source), source); }); + +test("spoken four-heading prose with a colon stays paragraphs", () => { + const prose = [ + "你这半年的事业是「外刚内不承」:表面硬气是真的硬——你确实扛得住场面,底下却承不住。别人看见的是硬气,盘上撑着的是另一层。", + "", + "推的是火星:事情由你自己起动、自己顶上去。别人帮不上忙,你也没法把责任分出去,更别指望场面自己圆。", + "", + "所以别去应火星的「硬顶」,也别去应土星弱位的「拖」。去扮演太阳的「署名」:把事做成一件能签字的作品,而不是一场硬顶。", + ].join("\n"); + const result = promoteDefinitionLists(prose); + assert.equal(result, prose); + assert.equal(result.includes("- **"), false); +}); diff --git a/frontend/tests/chat-stream-layout.test.ts b/frontend/tests/chat-stream-layout.test.ts index 348ad975..ba92814c 100644 --- a/frontend/tests/chat-stream-layout.test.ts +++ b/frontend/tests/chat-stream-layout.test.ts @@ -80,7 +80,24 @@ test("shows honest agent activity states before and during streamed text", () => assert.doesNotMatch(messageRowSource, /vargaSentence=\{showThinkingPanel \? null : vargaSentence\}/); assert.match(messageRowSource, / \*:first-child \{[\s\S]*margin-top:\s*0/, + ); + assert.match(globalStyles, /\.message-markdown ul\.markdown-list \{[\s\S]*list-style-type:\s*disc/); + assert.match(globalStyles, /\.message-markdown ol\.markdown-list \{[\s\S]*list-style-type:\s*decimal/); + assert.match(globalStyles, /\.markdown-list[^{]*\{[^}]*list-style/); + assert.match( + globalStyles, + /\.conversation:not\(\.is-empty\):not\(\.is-rectification\) \.message-markdown \.markdown-list \{[\s\S]*gap:\s*var\(--space-2\)/, + ); + assert.match(globalStyles, /\.markdown-list ::marker \{[\s\S]*color:\s*var\(--color-ink-secondary\)/); assert.match(globalStyles, /\.agent-activity-status \+ \.message-answer/); // Former lock: `