Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/images/message-timestamps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 消息时间:真实页面对照

前后图片均来自 Windows 上的 Chromium,使用生产构建和同一个本地 Go 服务、同一条已保存聊天。测试服务通过实际文件工具读取 README.md,返回离线验证回复;没有修改页面 DOM 来模拟消息或时间。

- 基线:`10311148d5f581d68e4603e9a435c257015cc81d`。
- 桌面视口:1280 × 900,见 [before.png](before.png) / [after.png](after.png)。
- 窄视口:600 × 900,见 [before-narrow.png](before-narrow.png) / [after-narrow.png](after-narrow.png)。
- 用户消息原始时间为 `2026-10-21T17:47:40.211Z`;助手消息为 `2026-10-21T17:47:40.402Z`。本机 UTC+8,界面均显示 `01:47`。
- 实际页面的 `title` 与读屏标签包含 `2026年10月22日 GMT+8 01:47:40`,`time` 元素保留原始 ISO 时间。
- 已检查两个视口中时间与复制、引用、分支等操作的排列。600 px 的正文横向滚动条在基线中也存在。

15 项 narrative / narrative-streaming 聚焦测试及生产构建通过;测试覆盖缺失、无效时间和正在生成的回复。此次没有原生桌面打包验证。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/message-timestamps/after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/message-timestamps/before-narrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/message-timestamps/before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions docs/images/message-tooltips/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 消息操作提示的窄屏边界

用户消息位于聊天右侧,操作提示原来从按钮左边向右展开。即使提示处于 `visibility: hidden`,其宽度仍扩大聊天滚动区域;鼠标悬停或键盘聚焦时,提示末尾被裁掉。

本次仅将 `.v2-user-actions .v2-assistant-action-tooltip` 改为 `left: auto; right: 0`,让用户消息提示向左展开。助手消息提示保留原有对齐方式。

## 真实页面对照

2026-10-22,Windows Edge。修改前使用 #439 / `ef6ae6b1` 的现成生产 bundle,修改后使用本工作树的生产构建。两套页面连接同一份 SQLite 中已保存的对话,使用生产 API;对话由 `cmd/workbenchjourney` 离线模型提供者实际读取 README 后生成。未替换 DOM、注入修复样式或拦截接口制造结果。

600px 中文,鼠标悬停“引用消息”:

| 修改前 | 修改后 |
| --- | --- |
| ![中文提示修改前](before-zh-CN-600-hover.png) | ![中文提示修改后](after-zh-CN-600-hover.png) |

390px 英文,使用 Tab 聚焦“Quote message”:

| 修改前 | 修改后 |
| --- | --- |
| ![英文提示修改前](before-en-US-390-focus.png) | ![英文提示修改后](after-en-US-390-focus.png) |

## 尺寸与滚动检查

以下各组都检查了初始隐藏状态,以及四个用户操作按钮逐一悬停、逐一键盘聚焦。修改后所有消息提示均位于聊天容器左右边界内。

| 语言 / 视口宽度 | 聊天 clientWidth | 修改前 scrollWidth | 修改后 scrollWidth |
| --- | ---: | ---: | ---: |
| 中文 / 600px | 600 | 621 | 600 |
| 中文 / 390px | 390 | 411 | 390 |
| English / 600px | 600 | 704 | 600 |
| English / 390px | 390 | 494 | 390 |

[完整 DOM 尺寸记录](measurements.csv)包含每次测量的提示边界、可见状态和透明度。基线生产 bundle 摘要为 `ecb366bd9e5d692d8dedc260669f3ac456b31113ca1565b50b400add6f97752b`,修改后为 `ddca30555373a91473dd60bb522246d39fc0b39c5f3a023bafe0a216bfbdd945`。

另从真实输入框发送并保存了带长行的 JavaScript 代码块。代码区域的 `scrollWidth` 为 2570px,在 600px / 390px 视口下自身宽度分别为 401px / 237px。横向滚轮将代码自身的 `scrollLeft` 分别推进至 1200px / 2000px;聊天容器保持 `scrollLeft=0`,且 `scrollWidth=clientWidth`。完整代码原文保留。[滚动测量](code-scroll.json)

![390px 代码块内部横向滚动](after-code-scroll-390.png)

`npm run build` 通过。以上为生产网页验证,未作为原生打包窗口验证。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions docs/images/message-tooltips/code-scroll.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"width": 600,
"before": {
"clientWidth": 401,
"scrollWidth": 2570,
"scrollLeft": 400,
"overflowX": "auto"
},
"after": {
"codeLeft": 1200,
"chatLeft": 0,
"chatWidth": 600,
"chatScrollWidth": 600,
"fullCodePreserved": true
}
},
{
"width": 390,
"before": {
"clientWidth": 237,
"scrollWidth": 2570,
"scrollLeft": 1200,
"overflowX": "auto"
},
"after": {
"codeLeft": 2000,
"chatLeft": 0,
"chatWidth": 390,
"chatScrollWidth": 390,
"fullCodePreserved": true
}
}
]
73 changes: 73 additions & 0 deletions docs/images/message-tooltips/measurements.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"phase","locale","viewportWidth","state","buttonIndex","clientWidth","scrollWidth","minTooltipLeft","maxTooltipRight","measuredTooltip","tooltipLeft","tooltipRight","visibility","opacity"
"before","zh-CN","600","idle",,"600","621","20","620.515625","引用选中文字或整条消息","481.515625","620.515625","hidden","0"
"before","zh-CN","600","hover","0","600","621","20","620.515625","复制消息","417.515625","479.515625","visible","1"
"before","zh-CN","600","hover","1","600","621","20","620.515625","回填文字到输入框","449.515625","555.515625","visible","1"
"before","zh-CN","600","hover","2","600","621","20","620.515625","引用选中文字或整条消息","481.515625","620.515625","visible","1"
"before","zh-CN","600","hover","3","600","621","20","620.515625","分支到新聊天","513.515625","597.515625","visible","1"
"before","zh-CN","600","focus","0","600","621","20","620.515625","复制消息","417.515625","479.515625","visible","1"
"before","zh-CN","600","focus","1","600","621","20","620.515625","回填文字到输入框","449.515625","555.515625","visible","1"
"before","zh-CN","600","focus","2","600","621","20","620.515625","引用选中文字或整条消息","481.515625","620.515625","visible","1"
"before","zh-CN","600","focus","3","600","621","20","620.515625","分支到新聊天","513.515625","597.515625","visible","1"
"before","zh-CN","390","idle",,"390","411","20","410.515625","引用选中文字或整条消息","271.515625","410.515625","hidden","0"
"before","zh-CN","390","hover","0","390","411","20","410.515625","复制消息","207.515625","269.515625","visible","1"
"before","zh-CN","390","hover","1","390","411","20","410.515625","回填文字到输入框","239.515625","345.515625","visible","1"
"before","zh-CN","390","hover","2","390","411","20","410.515625","引用选中文字或整条消息","271.515625","410.515625","visible","1"
"before","zh-CN","390","hover","3","390","411","20","410.515625","分支到新聊天","303.515625","387.515625","visible","1"
"before","zh-CN","390","focus","0","390","411","20","410.515625","复制消息","207.515625","269.515625","visible","1"
"before","zh-CN","390","focus","1","390","411","20","410.515625","回填文字到输入框","239.515625","345.515625","visible","1"
"before","zh-CN","390","focus","2","390","411","20","410.515625","引用选中文字或整条消息","271.515625","410.515625","visible","1"
"before","zh-CN","390","focus","3","390","411","20","410.515625","分支到新聊天","303.515625","387.515625","visible","1"
"before","en-US","600","idle",,"600","704","20","703.578125","Quote selected text or the whole message","481.515625","703.578125","hidden","0"
"before","en-US","600","hover","0","600","704","20","703.578125","Copy message","417.515625","505.96875","visible","1"
"before","en-US","600","hover","1","600","704","20","703.578125","Reuse text in composer","449.515625","580.515625","visible","1"
"before","en-US","600","hover","2","600","704","20","703.578125","Quote selected text or the whole message","481.515625","703.578125","visible","1"
"before","en-US","600","hover","3","600","704","20","703.578125","Branch into new chat","513.515625","633.4375","visible","1"
"before","en-US","600","focus","0","600","704","20","703.578125","Copy message","417.515625","505.96875","visible","1"
"before","en-US","600","focus","1","600","704","20","703.578125","Reuse text in composer","449.515625","580.515625","visible","1"
"before","en-US","600","focus","2","600","704","20","703.578125","Quote selected text or the whole message","481.515625","703.578125","visible","1"
"before","en-US","600","focus","3","600","704","20","703.578125","Branch into new chat","513.515625","633.4375","visible","1"
"before","en-US","390","idle",,"390","494","20","493.578125","Quote selected text or the whole message","271.515625","493.578125","hidden","0"
"before","en-US","390","hover","0","390","494","20","493.578125","Copy message","207.515625","295.96875","visible","1"
"before","en-US","390","hover","1","390","494","20","493.578125","Reuse text in composer","239.515625","370.515625","visible","1"
"before","en-US","390","hover","2","390","494","20","493.578125","Quote selected text or the whole message","271.515625","493.578125","visible","1"
"before","en-US","390","hover","3","390","494","20","493.578125","Branch into new chat","303.515625","423.4375","visible","1"
"before","en-US","390","focus","0","390","494","20","493.578125","Copy message","207.515625","295.96875","visible","1"
"before","en-US","390","focus","1","390","494","20","493.578125","Reuse text in composer","239.515625","370.515625","visible","1"
"before","en-US","390","focus","2","390","494","20","493.578125","Quote selected text or the whole message","271.515625","493.578125","visible","1"
"before","en-US","390","focus","3","390","494","20","493.578125","Branch into new chat","303.515625","423.4375","visible","1"
"after","zh-CN","600","idle",,"600","600","20","541.515625","引用选中文字或整条消息","370.515625","509.515625","hidden","0"
"after","zh-CN","600","hover","0","600","600","20","541.515625","复制消息","383.515625","445.515625","visible","1"
"after","zh-CN","600","hover","1","600","600","20","541.515625","回填文字到输入框","371.515625","477.515625","visible","1"
"after","zh-CN","600","hover","2","600","600","20","541.515625","引用选中文字或整条消息","370.515625","509.515625","visible","1"
"after","zh-CN","600","hover","3","600","600","20","541.515625","分支到新聊天","457.515625","541.515625","visible","1"
"after","zh-CN","600","focus","0","600","600","20","541.515625","复制消息","383.515625","445.515625","visible","1"
"after","zh-CN","600","focus","1","600","600","20","541.515625","回填文字到输入框","371.515625","477.515625","visible","1"
"after","zh-CN","600","focus","2","600","600","20","541.515625","引用选中文字或整条消息","370.515625","509.515625","visible","1"
"after","zh-CN","600","focus","3","600","600","20","541.515625","分支到新聊天","457.515625","541.515625","visible","1"
"after","zh-CN","390","idle",,"390","390","20","331.515625","引用选中文字或整条消息","160.515625","299.515625","hidden","0"
"after","zh-CN","390","hover","0","390","390","20","331.515625","复制消息","173.515625","235.515625","visible","1"
"after","zh-CN","390","hover","1","390","390","20","331.515625","回填文字到输入框","161.515625","267.515625","visible","1"
"after","zh-CN","390","hover","2","390","390","20","331.515625","引用选中文字或整条消息","160.515625","299.515625","visible","1"
"after","zh-CN","390","hover","3","390","390","20","331.515625","分支到新聊天","247.515625","331.515625","visible","1"
"after","zh-CN","390","focus","0","390","390","20","331.515625","复制消息","173.515625","235.515625","visible","1"
"after","zh-CN","390","focus","1","390","390","20","331.515625","回填文字到输入框","161.515625","267.515625","visible","1"
"after","zh-CN","390","focus","2","390","390","20","331.515625","引用选中文字或整条消息","160.515625","299.515625","visible","1"
"after","zh-CN","390","focus","3","390","390","20","331.515625","分支到新聊天","247.515625","331.515625","visible","1"
"after","en-US","600","idle",,"600","600","20","541.515625","Quote selected text or the whole message","287.453125","509.515625","hidden","0"
"after","en-US","600","hover","0","600","600","20","541.515625","Copy message","357.0625","445.515625","visible","1"
"after","en-US","600","hover","1","600","600","20","541.515625","Reuse text in composer","346.515625","477.515625","visible","1"
"after","en-US","600","hover","2","600","600","20","541.515625","Quote selected text or the whole message","287.453125","509.515625","visible","1"
"after","en-US","600","hover","3","600","600","20","541.515625","Branch into new chat","421.59375","541.515625","visible","1"
"after","en-US","600","focus","0","600","600","20","541.515625","Copy message","357.0625","445.515625","visible","1"
"after","en-US","600","focus","1","600","600","20","541.515625","Reuse text in composer","346.515625","477.515625","visible","1"
"after","en-US","600","focus","2","600","600","20","541.515625","Quote selected text or the whole message","287.453125","509.515625","visible","1"
"after","en-US","600","focus","3","600","600","20","541.515625","Branch into new chat","421.59375","541.515625","visible","1"
"after","en-US","390","idle",,"390","390","20","331.515625","Quote selected text or the whole message","77.453125","299.515625","hidden","0"
"after","en-US","390","hover","0","390","390","20","331.515625","Copy message","147.0625","235.515625","visible","1"
"after","en-US","390","hover","1","390","390","20","331.515625","Reuse text in composer","136.515625","267.515625","visible","1"
"after","en-US","390","hover","2","390","390","20","331.515625","Quote selected text or the whole message","77.453125","299.515625","visible","1"
"after","en-US","390","hover","3","390","390","20","331.515625","Branch into new chat","211.59375","331.515625","visible","1"
"after","en-US","390","focus","0","390","390","20","331.515625","Copy message","147.0625","235.515625","visible","1"
"after","en-US","390","focus","1","390","390","20","331.515625","Reuse text in composer","136.515625","267.515625","visible","1"
"after","en-US","390","focus","2","390","390","20","331.515625","Quote selected text or the whole message","77.453125","299.515625","visible","1"
"after","en-US","390","focus","3","390","390","20","331.515625","Branch into new chat","211.59375","331.515625","visible","1"
3 changes: 3 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ Ctrl+Shift+V(macOS 为 Cmd+Shift+V)按纯文本粘贴,保留 Markdown 标
围栏块中;块顶部显示“文本”和复制按钮,复制内容不包含围栏。代码围栏显示语言名称。
普通段落保留整条回复的复制入口。

已保存的用户消息和助手回复在操作图标旁显示本地时间。悬停时间可查看完整日期、秒和时区;
屏幕阅读器可读取同样的信息。仍在发送或生成的消息、没有有效原始时间的历史记录不显示时间。

聊天正文使用 14 px 字号、1.625 倍行高和正常字距,代码块保持 13 px。
已完成的段落采用智能换行,标题均衡换行,长标识符在空间不足时折行。
实时回复的新片段以 150 ms 的轻微模糊淡入显示,不额外等待或延迟输出;历史消息和
Expand Down
21 changes: 21 additions & 0 deletions web/src/v2/components/narrative.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ const markdown = vi.hoisted(() => vi.fn(({ children }: { children: string }) =>
vi.mock("react-markdown", () => ({ default: markdown }));
afterEach(() => { cleanup(); markdown.mockClear(); });

it("shows stored message times with a full local date and timezone, without replacing missing timestamps", () => {
const createdAt = "2026-10-21T09:16:43Z";
const entries: NarrativeEntry[] = [
{ id: "user", kind: "user", text: "Request", createdAt, provisional: false },
{ id: "reply", kind: "assistant", text: "Reply", createdAt, provisional: false },
{ id: "missing", kind: "assistant", text: "Older reply", createdAt: "", provisional: false },
{ id: "invalid", kind: "assistant", text: "Invalid time", createdAt: "invalid", provisional: false },
{ id: "live", kind: "assistant", text: "Streaming", createdAt, provisional: true },
];
const { container } = render(<V2Narrative client={{} as APIClient} entries={entries} threadID="thread" />);
const times = container.querySelectorAll("time");
expect(times).toHaveLength(2);
for (const time of times) {
expect(time).toHaveAttribute("datetime", "2026-10-21T09:16:43.000Z");
expect(time.textContent).toMatch(/^\d{2}:\d{2}$/u);
expect(time.title).toContain("2026");
expect(time.title).toContain("43");
expect(time).toHaveAccessibleName("Message time: " + time.title);
}
});

it("copies and reuses processed user text, quotes only the selection, and branches from that input", async () => {
const user = userEvent.setup();
const writeText = vi.spyOn(navigator.clipboard, "writeText").mockResolvedValue(undefined);
Expand Down
Loading
Loading