fix(review): correct the secondary-tier floor claim and pin engine-resolved sizes

The Agent Note (both languages), the PR prose, and the commit message
claimed a 13px floor for the table variants; the formula has none —
max(13px, setting − 2px) selects the −1 branch at low settings rather
than clamping the result, so the tier bottoms out at 11px at the 12px
setting, matching think text. Rewrite the claim, say so in the axis
comment, and split the README sentence that lumped body-pair and
secondary-pair consumers together.

Assert the engine-resolved secondary size in the settings-chrome e2e
(13px at the default, 13px at the 15px boundary, 14px at 16px,
unchanged across reload), sync the StatsLine and workflow-panel spec
headers with the tier they now pin, and note why memberLabel stays at
the body size.
This commit is contained in:
yx.zhang 2026-08-26 17:52:57 +08:00
parent a77e23a975
commit 9e33469913
11 changed files with 38 additions and 14 deletions

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 .agents/notes/implemented/feature/2026-08-18-settings-font-size-control.md
2026-08-18-settings-font-size-control.md: 9e8b933653359bd45abf34e45fec5497289435ec
2026-08-18-settings-font-size-control.zh.md: cff574bd5009db6d4d855384c10d6f9a034ea343
2026-08-18-settings-font-size-control.md: 778893a2c0943f1d3af8905a27efb526071e073b
2026-08-18-settings-font-size-control.zh.md: 71ff3b556622e7091891f8e18f5eefcbda2b0851

View file

@ -24,7 +24,7 @@ The conversation's body text size was fixed (14px after the 0.875 markdown-ladde
**Scaling via a multiplier (`em`/percentage) instead of a px delta.** Rejected: multiplying spreads the 12–17px range disproportionately across the ladder (21px h1 would swing ~18–25.5px) and produces fractional line heights; the fixed px shift keeps every step integer and the hierarchy's px gaps intact.
**Scaling every font token (small, code).** Rejected: those variants are dense by design; at −2 the small ladder would hit 10px and code 9px, below legibility. The table variants instead join the secondary tier, whose 13px floor keeps them legible at every setting.
**Scaling every font token (small, code).** Rejected: those variants are dense by design; at −2 the small ladder would hit 10px and code 9px, below legibility. The table variants instead join the secondary tier, bottoming out at 11px at the 12px setting — the same size think text reaches there.
## Consequences

View file

@ -24,7 +24,7 @@
**用倍率(`em`/百分比)而非像素增量缩放。**否决:乘法会让 12–17px 的范围在阶梯上不成比例地放大(21px 的 h1 会摆动到约 18–25.5px),并产生小数行高;固定像素平移让每一档都是整数,层级间的像素差保持不变。
**缩放全部字体 token(code、small)。**否决:这些变体按设计就是密集文本;−2 档时 small 阶梯会降到 10px、code 降到 9px,低于可读下限。表格变体改为并入低一档层级,其 13px 下限在任何设置下都保持可读。
**缩放全部字体 token(code、small)。**否决:这些变体按设计就是密集文本;−2 档时 small 阶梯会降到 10px、code 降到 9px,低于可读下限。表格变体改为并入低一档层级,在 12px 设置下触底 11px——与 think 文本在该设置下相同。
## 后果

View file

@ -351,7 +351,19 @@ describe('web e2e: settings modal and General preferences', () => {
const readFontSize = async (target: Page = page): Promise<string> => await target.evaluate(
() => document.body.style.getPropertyValue('--dsh-content-font-size'),
)
// The secondary tier resolved by the real engine: a probe element's
// font-size forces min/max/calc evaluation, which the CSS-text specs
// cannot exercise. Setting −1 at ≤14, setting −2 above.
const readSecondaryFontSize = async (): Promise<string> => await page.evaluate(() => {
const probe = document.createElement('div')
probe.style.fontSize = 'var(--dsh-content-font-size-secondary, 13px)'
document.body.appendChild(probe)
const size = getComputedStyle(probe).fontSize
probe.remove()
return size
})
expect(await readFontSize()).toBe('14px')
expect(await readSecondaryFontSize()).toBe('13px')
await page.getByRole('button', { name: '设置', exact: true }).click()
const dialog = page.getByRole('dialog', { name: '设置' })
await dialog.waitFor({ timeout: 10_000 })
@ -360,9 +372,13 @@ describe('web e2e: settings modal and General preferences', () => {
const increase = dialog.getByRole('button', { name: '增大字号' })
await increase.click()
await dialog.getByText('15', { exact: true }).waitFor({ timeout: 5_000 })
// 15 is the piecewise boundary: the secondary tier holds at 13px (−2)
// where the ≤14 branch would have given 14px (−1).
await expect.poll(readSecondaryFontSize, { timeout: 5_000 }).toBe('13px')
await increase.click()
await dialog.getByText('16', { exact: true }).waitFor({ timeout: 5_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe('16px')
await expect.poll(readSecondaryFontSize, { timeout: 5_000 }).toBe('14px')
await expect.poll(async () => readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8'), { timeout: 5_000 })
.toMatch(/ui-theme:\n(?:\s+\w+: .*\n)*?\s+fontSize: 16/)
await page.keyboard.press('Escape')
@ -375,6 +391,7 @@ describe('web e2e: settings modal and General preferences', () => {
acknowledgeReloadConnectionLoss(tripwire, warningStart)
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe('16px')
expect(await readSecondaryFontSize()).toBe('14px')
// Restore the default for the specs that follow (and the dialog golden).
await page.getByRole('button', { name: '设置', exact: true }).click()

View file

@ -1,5 +1,6 @@
/* Session stats row: 12/20 tertiary text under the flow, aligned to the
shared message column axis (--dsh-chat-content-width). */
/* Session stats row: 13/20 tertiary text (the secondary content tier) under
the flow, aligned to the shared message column axis
(--dsh-chat-content-width). */
.root {
/* Block, not flex: text-overflow only elides a block's inline content, so

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: 49ea5147b12f382f37fa550455035f948dec4fe8
README.zh.md: 427eb4be339f6ad414fc84b7824445edaf934c54
README.md: 32ec60edb39b30afdb9b54804e62302cdc58e531
README.zh.md: ab17910ab9cec4e1ff519af0922ee27034a6fa7c

View file

@ -53,7 +53,7 @@ The service owns theme and font-size state and publishes snapshots. The ui-layou
`src/styles/` holds five sheets imported in order by ui-theme's dynamic client entry: `base.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` is the sole consumer of the `--dsw-alias-scrollbar-*` tokens and must follow `design-platform.css`, which declares them.
`gradient-shadow-text.css` derives `--dsh-content-font-delta` from `--dsh-content-font-size` and shifts the Markdown heading and base-text ladder by that increment. It also derives the secondary tier `--dsh-content-font-size-secondary` (setting −1 at ≤14, setting −2 above; 13px at the default) with its own `--dsh-content-font-delta-secondary` for the table variants and the flow rows one step under the body. Dense small and code variants stay fixed. The user bubble, the composer draft, and flow-row titles and summaries consume the same variables outside that ladder.
`gradient-shadow-text.css` derives `--dsh-content-font-delta` from `--dsh-content-font-size` and shifts the Markdown heading and base-text ladder by that increment. It also derives the secondary tier `--dsh-content-font-size-secondary` (setting −1 at ≤14, setting −2 above; 13px at the default) with its own `--dsh-content-font-delta-secondary` for the table variants and the flow rows one step under the body. Dense small and code variants stay fixed. Outside the ladder, the user bubble and composer draft read the body pair directly, and flow-row titles and summaries read the secondary pair.
### Scrollbar rebinding

View file

@ -53,7 +53,7 @@ kind: "package-reference"
`src/styles/` 下有五张样式表,由 ui-theme 的动态客户端 entry 依次导入:`base.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` 之后。
`gradient-shadow-text.css` 从 `--dsh-content-font-size` 派生 `--dsh-content-font-delta`,并以该增量移动 Markdown 标题与基础文本阶梯。它同时派生低一档变量 `--dsh-content-font-size-secondary`(设置 ≤14 时为设置值 −1,>14 时为设置值 −2;默认设置下为 13px)及配套的 `--dsh-content-font-delta-secondary`,供表格变体与比正文低一档的流内行使用。紧凑的小号文本与代码变体保持固定字号。用户气泡、composer 草稿与流内行的标题及摘要在该阶梯之外消费相同变量。
`gradient-shadow-text.css` 从 `--dsh-content-font-size` 派生 `--dsh-content-font-delta`,并以该增量移动 Markdown 标题与基础文本阶梯。它同时派生低一档变量 `--dsh-content-font-size-secondary`(设置 ≤14 时为设置值 −1,>14 时为设置值 −2;默认设置下为 13px)及配套的 `--dsh-content-font-delta-secondary`,供表格变体与比正文低一档的流内行使用。紧凑的小号文本与代码变体保持固定字号。阶梯之外,用户气泡与 composer 草稿直接读取正文档变量对,流内行的标题及摘要读取低一档变量对。
### 滚动条重新绑定

View file

@ -25,8 +25,9 @@ body[data-ds-dark-theme] {
(--dsh-content-font-delta)随动,保持层级。低一档文本(think、工具/bash
行的标题与摘要、markdown 表格、流内次级说明等)统一读
--dsh-content-font-size-secondary:设置 ≤14 时为设置值 −1,>14 时为
设置值 −2(12→11、13→12、14→13、15→13、16→14、17→15);small、code
属于密集次级文本,保持固定。 */
设置值 −2(12→11、13→12、14→13、15→13、16→14、17→15)。式中
max(13px,·) 的作用是让低设置取 −1 而非 −2,不是结果下限——12 设置下
层级触底 11px。small、code 属于密集次级文本,保持固定。 */
body {
--dsh-content-font-delta: calc(var(--dsh-content-font-size, 14px) - 14px);
--dsh-content-font-size-secondary: min(calc(var(--dsh-content-font-size, 14px) - 1px), max(13px, calc(var(--dsh-content-font-size, 14px) - 2px)));

View file

@ -223,6 +223,9 @@
flex: 1;
min-width: 0;
color: var(--dsw-alias-label-secondary);
/* Body size, not the secondary tier: the member label is the panel's
content (an agent's task), while the run/phase headers, counts, and
statuses around it are chrome one step down. */
font-size: var(--dsh-content-font-size, 14px);
line-height: calc(24px + var(--dsh-content-font-delta, 0px));
text-overflow: ellipsis;

View file

@ -1,8 +1,10 @@
/**
* WorkflowRunPanel's font-size-axis adoption as CSS text. jsdom has no
* layout, so these read the declarations that make the run/phase headers and
* the expanded member rows follow the Settings font-size preference through
* --dsh-content-font-size / --dsh-content-font-delta.
* the expanded member rows follow the Settings font-size preference: member
* labels at the body size (--dsh-content-font-size / --dsh-content-font-delta),
* the chrome around them on the secondary tier
* (--dsh-content-font-size-secondary / --dsh-content-font-delta-secondary).
*/
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'