fix(web): refine file diff review colors

This commit is contained in:
sjw1231 committed 2026-09-22 20:23:45 +08:00
1 parent 37e4dc2dee
commit fd784e4429
9 files changed
+42 -20

No files matched your search

+16 -6
View File
@@ -351,7 +351,8 @@ describe('web e2e: a git workspace turn ends with its changed files', () => {
if (number === undefined || text === null) throw new Error('added diff line is incomplete')
const left = line.getBoundingClientRect().left
return {
shadow: getComputedStyle(line).boxShadow,
shadow: getComputedStyle(number).boxShadow,
markerColour: getComputedStyle(number).color,
textColour: getComputedStyle(text).color,
numberLeft: number.getBoundingClientRect().left - left,
textLeft: text.getBoundingClientRect().left - left,
@@ -367,7 +368,8 @@ describe('web e2e: a git workspace turn ends with its changed files', () => {
textLeft: text.getBoundingClientRect().left - left,
}
})
expect(addedRule.shadow).toContain(addedRule.textColour)
expect(addedRule.shadow).toContain(addedRule.markerColour)
expect(addedRule.textColour).not.toBe(addedRule.markerColour)
expect(addedRule.numberLeft).toBeCloseTo(rightContextRule.numberLeft, 1)
expect(addedRule.textLeft).toBeCloseTo(rightContextRule.textLeft, 1)
// The ignored file has no snapshot; its comparison comes from the copies captured around the write call.
@@ -402,7 +404,8 @@ describe('web e2e: a git workspace turn ends with its changed files', () => {
if (number === undefined || text === null) throw new Error('deleted diff line is incomplete')
const left = line.getBoundingClientRect().left
return {
shadow: getComputedStyle(line).boxShadow,
shadow: getComputedStyle(number).boxShadow,
markerColour: getComputedStyle(number).color,
textColour: getComputedStyle(text).color,
numberLeft: number.getBoundingClientRect().left - left,
textLeft: text.getBoundingClientRect().left - left,
@@ -418,7 +421,8 @@ describe('web e2e: a git workspace turn ends with its changed files', () => {
textLeft: text.getBoundingClientRect().left - left,
}
})
expect(deletedRule.shadow).toContain(deletedRule.textColour)
expect(deletedRule.shadow).toContain(deletedRule.markerColour)
expect(deletedRule.textColour).not.toBe(deletedRule.markerColour)
expect(deletedRule.numberLeft).toBeCloseTo(leftContextRule.numberLeft, 1)
expect(deletedRule.textLeft).toBeCloseTo(leftContextRule.textLeft, 1)
await compareTool.click()
@@ -472,9 +476,15 @@ describe('web e2e: a git workspace turn ends with its changed files', () => {
const wrappedAdditionRule = await wrappedAddition.evaluate((cell) => {
const text = cell.querySelector<HTMLElement>('[data-diff-code]')
if (text === null) throw new Error('wrapped addition has no text')
return { shadow: getComputedStyle(cell).boxShadow, textColour: getComputedStyle(text).color }
const number = cell.firstElementChild!
return {
shadow: getComputedStyle(number).boxShadow,
markerColour: getComputedStyle(number).color,
textColour: getComputedStyle(text).color,
}
})
expect(wrappedAdditionRule.shadow).toContain(wrappedAdditionRule.textColour)
expect(wrappedAdditionRule.shadow).toContain(wrappedAdditionRule.markerColour)
expect(wrappedAdditionRule.textColour).not.toBe(wrappedAdditionRule.markerColour)
// No desktop, so the tools offer the sidebar file but no native open.
expect(await review.locator('[data-review-tool="open-file"]').count()).toBe(1)
expect(await review.locator('[data-review-tool="open-native"]').count()).toBe(0)
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-deliverables/README.md
README.md: 7611e613073457c7843cc2a969e2f295fc204bbf
README.zh.md: 15162229dc4adc5c0143cd141481887a5c996be7
README.md: c52db38df0c800f52de4eebb3a9692eabef6c9df
README.zh.md: 785c2215746e36ead16fe08b1a21b04a58ef418c
+1 -1
View File
@@ -42,7 +42,7 @@ The card renders the summary the Host serves for the turn's latest `workspace/ch
### The review tab
A row opens the turn's `changes-review` tab, addressed by the viewed Session and the announcing event's sequence and titled by the turn, on that row's file; another row of the same card reveals the same tab on its file. The header's file selector lists every recorded file with its counts and switches the comparison. A new tab starts in the side-by-side view without wrapping. Its controls switch between unified and side-by-side comparison, toggle line wrapping, open the whole current file in the Sidebar, and, with a Host desktop available, open it in the default application with the same pending and retryable-error states as the cards; the view and wrap choices are kept per tab. The tab reads the summary and each comparison once through the authenticated routes. A text comparison lists its hunks with the old and new line numbers of every line, and highlights each rendered hunk side independently with the code preview's filename grammar and Shiki token colours. Additions and deletions use aligned success or error markers and background tints. The tab also shows a note when the file was created or deleted in the turn, when both sides hold the same lines, when the Host's line comparison timed out and every line shows as replaced, or when the tab stopped drawing at 5,000 lines. A binary or oversized file, a comparison the Host no longer serves, and a failed read each show one line instead; a failed read offers Retry. The comparison is the turn's snapshot of the file, not its current content.
A row opens the turn's `changes-review` tab, addressed by the viewed Session and the announcing event's sequence and titled by the turn, on that row's file; another row of the same card reveals the same tab on its file. The header's file selector lists every recorded file with its counts and switches the comparison. A new tab starts in the side-by-side view without wrapping. Its controls switch between unified and side-by-side comparison, toggle line wrapping, open the whole current file in the Sidebar, and, with a Host desktop available, open it in the default application with the same pending and retryable-error states as the cards; the view and wrap choices are kept per tab. The tab reads the summary and each comparison once through the authenticated routes. A text comparison lists its hunks with the old and new line numbers of every line, and highlights each rendered hunk side independently with the code preview's filename grammar and Shiki token colours. Additions and deletions use separate code and line-number backgrounds in each theme, with coloured line numbers, signs, and aligned edge markers; plain code text keeps the primary text colour. The tab also shows a note when the file was created or deleted in the turn, when both sides hold the same lines, when the Host's line comparison timed out and every line shows as replaced, or when the tab stopped drawing at 5,000 lines. A binary or oversized file, a comparison the Host no longer serves, and a failed read each show one line instead; a failed read offers Retry. The comparison is the turn's snapshot of the file, not its current content.
The selected-file title uses the shared `ui-primitives` [`PathLabel`](../ui-primitives/README.md#component-catalog): complete and left-aligned when it fits, otherwise clipped at the left with a fade that preserves trailing characters and the extension. Directories use subdued text, the filename uses primary text, and hovering reveals the complete displayed path. Resizing the pane or selecting another file updates the fade; the dropdown arrow, line counts, and toolbar actions retain their space.
+1 -1
View File
@@ -42,7 +42,7 @@ Web 的 `standard`、`ptc` 与 `cordis` preset 提供 `present`,用于声明
### review tab
行在该行的文件上打开本轮的 `changes-review` tab,其地址由当前查看的 Session 和宣告事件的序号组成,以轮号作标题;同一张卡片的另一行会在其文件上显示同一个 tab。头部的文件选择器列出所有记录的文件及其行数,用于切换对比。新 tab 默认使用不换行的左右对比。其控件用于切换单栏或左右对比、切换自动换行、在 Sidebar 中打开当前完整文件,并在 Host 有桌面时用默认应用打开文件;原生打开的等待与可重试错误状态与卡片一致,视图和换行选择按 tab 保留。tab 通过经过认证的路由各读取一次摘要和每个对比。文本对比逐个列出 hunk,每一行带旧侧和新侧的行号,并按代码预览的文件名 grammar 与 Shiki token 色分别高亮每个已渲染 hunk 的两侧。新增与删除使用对齐的成功色或错误色标记和背景色。文件是本轮新建或删除、两侧内容相同、Host 的逐行对比超时而按整文件替换显示,或 tab 在 5000 行处停止绘制时,tab 还会给出一行说明。二进制或过大的文件、Host 已不再提供的对比,以及读取失败各显示一行提示;读取失败时提供重试。对比是本轮对该文件的快照,不是它当前的内容。
行在该行的文件上打开本轮的 `changes-review` tab,其地址由当前查看的 Session 和宣告事件的序号组成,以轮号作标题;同一张卡片的另一行会在其文件上显示同一个 tab。头部的文件选择器列出所有记录的文件及其行数,用于切换对比。新 tab 默认使用不换行的左右对比。其控件用于切换单栏或左右对比、切换自动换行、在 Sidebar 中打开当前完整文件,并在 Host 有桌面时用默认应用打开文件;原生打开的等待与可重试错误状态与卡片一致,视图和换行选择按 tab 保留。tab 通过经过认证的路由各读取一次摘要和每个对比。文本对比逐个列出 hunk,每一行带旧侧和新侧的行号,并按代码预览的文件名 grammar 与 Shiki token 色分别高亮每个已渲染 hunk 的两侧。新增与删除在各主题下使用独立的代码区与行号区底色,并为行号、增删符号和对齐的左侧色条着色;普通代码文字保留主文本色。文件是本轮新建或删除、两侧内容相同、Host 的逐行对比超时而按整文件替换显示,或 tab 在 5000 行处停止绘制时,tab 还会给出一行说明。二进制或过大的文件、Host 已不再提供的对比,以及读取失败各显示一行提示;读取失败时提供重试。对比是本轮对该文件的快照,不是它当前的内容。
当前文件标题使用 `ui-primitives` 的共享组件 [`PathLabel`](../ui-primitives/README.zh.md#component-catalog):空间足够时完整靠左显示,否则从左侧裁剪并渐隐,保留尾部字符和扩展名。目录使用弱化颜色,文件名使用主色,悬停显示完整路径。调整面板宽度或切换文件时更新渐隐;下拉箭头、行数和工具栏操作保留各自的空间。
@@ -1,6 +1,5 @@
/** Shared comparison layout, header, states, and numbered diff lines for previews and review tabs. */
.root { --diff-add-fill: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent); --diff-del-fill: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent); --diff-empty-fill: color-mix(in srgb, var(--dsw-alias-interactive-bg-hover) 50%, transparent); display: flex; flex-direction: column; box-sizing: border-box; width: 100%; min-height: 0; color: var(--dsw-alias-label-primary); }
:global(body[data-ds-dark-theme]) .root { --diff-add-fill: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); --diff-del-fill: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); }
.root { --diff-empty-fill: color-mix(in srgb, var(--dsw-alias-interactive-bg-hover) 50%, transparent); display: flex; flex-direction: column; box-sizing: border-box; width: 100%; min-height: 0; color: var(--dsw-alias-label-primary); }
.header { display: flex; flex: 0 0 auto; gap: 6px; align-items: center; box-sizing: border-box; height: 38px; padding: 0 6px 0 8px; border-bottom: 0.5px solid var(--dsw-alias-border-l3); }
.status { display: flex; gap: 12px; align-items: center; margin: 0; padding: 16px; font-size: 13px; color: var(--dsw-alias-label-secondary); }
.body { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; overflow: auto; padding: 8px 0 16px; font: var(--dsw-font-markdown-code-block); }
@@ -21,9 +20,10 @@
.text { padding-right: 16px; }
.body[data-review-wrap] .line, .body[data-review-wrap] .splitLine { white-space: pre-wrap; }
.body[data-review-wrap] .text { overflow-wrap: anywhere; }
.add { background: var(--diff-add-fill); box-shadow: inset 3px 0 0 var(--dsw-alias-state-success-primary); }
.add .sign, .add .text { color: var(--dsw-alias-state-success-primary); }
.del { background: var(--diff-del-fill); box-shadow: inset 3px 0 0 var(--dsw-alias-state-error-primary); }
.del .sign, .del .text { color: var(--dsw-alias-state-error-primary); }
.add { --diff-gutter-fill: var(--dsw-alias-file-diff-added-gutter); --diff-marker: var(--dsw-alias-file-diff-added-marker); background: var(--dsw-alias-file-diff-added-bg); }
.del { --diff-gutter-fill: var(--dsw-alias-file-diff-deleted-gutter); --diff-marker: var(--dsw-alias-file-diff-deleted-marker); background: var(--dsw-alias-file-diff-deleted-bg); }
.add .number, .del .number { background: var(--diff-gutter-fill); color: var(--diff-marker); }
.add .number:first-child, .del .number:first-child { box-shadow: inset 3px 0 0 var(--diff-marker); }
.add .sign, .del .sign { color: var(--diff-marker); }
.context .text { color: var(--dsw-alias-label-secondary); }
.empty { background: var(--diff-empty-fill); }
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-theme/README.md
README.md: f8db4c3c353aa9b6854c05288316babd198475d7
README.zh.md: 135878213908ea11093d267c361cf2f80b4d768a
README.md: 7248b7230afa28f817bec7c0eb26fd9512fff405
README.zh.md: 6dc1db064dd9a6754f6c6475dde18ccf1a17841e
+1 -1
View File
@@ -51,7 +51,7 @@ The service owns theme and font-size state and publishes snapshots. The ui-layou
### Stylesheets
`src/styles/` holds six sheets imported in order by ui-theme's dynamic client entry: `base.css`, `corner-shape.css`, `design-platform.css`, `scrollbar.css`, `gradient-shadow-text.css`, and `shiki.css`. The client bundle compiles and injects them as plugin-owned global styles, so unload and HMR remove them with ui-theme. `scrollbar.css` consumes the `--dsw-alias-scrollbar-*` tokens and must follow `design-platform.css`, which declares them. Status marks use their own semantic state tokens. `design-platform.css` also owns the code-diff fill aliases and their static alpha palette entries; `shiki.css` owns syntax colors.
`src/styles/` holds six sheets imported in order by ui-theme's dynamic client entry: `base.css`, `corner-shape.css`, `design-platform.css`, `scrollbar.css`, `gradient-shadow-text.css`, and `shiki.css`. The client bundle compiles and injects them as plugin-owned global styles, so unload and HMR remove them with ui-theme. `scrollbar.css` consumes the `--dsw-alias-scrollbar-*` tokens and must follow `design-platform.css`, which declares them. Status marks use their own semantic state tokens. `design-platform.css` also owns the code-diff fill aliases and their static alpha palette entries, plus the `--dsw-alias-file-diff-*` code, gutter, and marker palette for file comparisons; `shiki.css` owns syntax colors.
`brand-font.css` exports the local Montserrat Light, Regular and Medium faces (normal style, weights 300, 400 and 500), with `montserrat-light.woff2`, `montserrat-regular.woff2`, `montserrat-medium.woff2` and its SIL Open Font License in `lib/styles/`. Desktop bundles the same stylesheet, font and license for offline welcome brand text; ordinary UI keeps its system font stack.
+1 -1
View File
@@ -51,7 +51,7 @@ kind: "package-reference"
### 样式表
`src/styles/` 下有六张样式表,由 ui-theme 的动态客户端 entry 依次导入:`base.css`、`corner-shape.css`、`design-platform.css`、`scrollbar.css`、`gradient-shadow-text.css` 与 `shiki.css`。客户端 bundle 将其编译并注入为插件持有的全局样式,因此卸载与 HMR(热模块替换)会随 ui-theme 一同移除。`scrollbar.css` 消费 `--dsw-alias-scrollbar-*` token,必须排在声明这些 token 的 `design-platform.css` 之后。状态标记使用各自的语义状态 token。`design-platform.css` 还负责代码差异底色的别名及其静态透明度色阶;`shiki.css` 负责语法颜色。
`src/styles/` 下有六张样式表,由 ui-theme 的动态客户端 entry 依次导入:`base.css`、`corner-shape.css`、`design-platform.css`、`scrollbar.css`、`gradient-shadow-text.css` 与 `shiki.css`。客户端 bundle 将其编译并注入为插件持有的全局样式,因此卸载与 HMR(热模块替换)会随 ui-theme 一同移除。`scrollbar.css` 消费 `--dsw-alias-scrollbar-*` token,必须排在声明这些 token 的 `design-platform.css` 之后。状态标记使用各自的语义状态 token。`design-platform.css` 还负责代码差异底色的别名及其静态透明度色阶,以及文件对比所用的 `--dsw-alias-file-diff-*` 代码区、行号区和标记配色;`shiki.css` 负责语法颜色。
`brand-font.css` 导出本地 Montserrat Light、Regular 和 Medium 字体(正体、字重 300、400 和 500),`lib/styles/` 同时提供 `montserrat-light.woff2`、`montserrat-regular.woff2`、`montserrat-medium.woff2` 及其 SIL Open Font License。Desktop 将同一份样式表、字体和许可证打包,用于欢迎页品牌文字的离线显示;普通界面保留系统字体栈。
@@ -236,6 +236,12 @@ body {
--dsw-alias-state-business-tertiary: var(--dsw-static-deepseek-100);
--dsw-alias-code-diff-added: var(--dsw-static-green-500-a08);
--dsw-alias-code-diff-deleted: var(--dsw-static-red-600-a08);
--dsw-alias-file-diff-added-bg: rgb(230, 244, 231);
--dsw-alias-file-diff-added-gutter: rgb(237, 247, 237);
--dsw-alias-file-diff-added-marker: rgb(1, 162, 65);
--dsw-alias-file-diff-deleted-bg: rgb(252, 230, 226);
--dsw-alias-file-diff-deleted-gutter: rgb(253, 236, 233);
--dsw-alias-file-diff-deleted-marker: rgb(186, 39, 35);
--dsw-alias-state-error-primary: var(--dsw-static-red-600);
--dsw-alias-state-error-secondary: var(--dsw-static-red-400);
--dsw-alias-state-idle-primary: var(--dsw-static-neutral-300);
@@ -336,6 +342,12 @@ body[data-ds-dark-theme] {
--dsw-alias-state-business-tertiary: var(--dsw-static-deepseek-800);
--dsw-alias-code-diff-added: var(--dsw-static-green-500-a12);
--dsw-alias-code-diff-deleted: var(--dsw-static-red-400-a12);
--dsw-alias-file-diff-added-bg: rgb(31, 49, 36);
--dsw-alias-file-diff-added-gutter: rgb(19, 32, 22);
--dsw-alias-file-diff-added-marker: rgb(65, 201, 119);
--dsw-alias-file-diff-deleted-bg: rgb(60, 31, 27);
--dsw-alias-file-diff-deleted-gutter: rgb(40, 19, 14);
--dsw-alias-file-diff-deleted-marker: rgb(250, 66, 62);
--dsw-alias-state-error-primary: var(--dsw-static-red-400);
--dsw-alias-state-error-secondary: var(--dsw-static-red-400);
--dsw-alias-state-idle-primary: var(--dsw-static-neutral-600);