Просмотр исходного кода

Merge pull request #3145 from deepseek-harness/ihsiangzhang/secondary-font-tier

feat(ui): unify the flow-row secondary font tier and scale tables
ihsiang 1 месяц назад
Родитель
Сommit
bc1f515b04
24 измененных файлов с 128 добавлено и 95 удалено
  1. 2 2
      .agents/notes/implemented/feature/2026-08-18-settings-font-size-control.i18n.yaml
  2. 0 1
      .agents/notes/implemented/feature/2026-08-18-settings-font-size-control.md
  3. 0 1
      .agents/notes/implemented/feature/2026-08-18-settings-font-size-control.zh.md
  4. 17 0
      apps/web/tests/settings-chrome.e2e.ts
  5. 6 6
      packages/client/ui-chat/src/client/chat/ChatView.module.css
  6. 2 2
      packages/client/ui-chat/src/client/chat/ContextInjectionRow.module.css
  7. 1 1
      packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css
  8. 11 11
      packages/client/ui-chat/src/client/chat/MessageItem.module.css
  9. 7 6
      packages/client/ui-chat/src/client/chat/ReasoningRow.module.css
  10. 5 4
      packages/client/ui-chat/src/client/chat/StatsLine.module.css
  11. 2 2
      packages/client/ui-chat/src/client/chat/TurnUsageDisclosure.module.css
  12. 7 7
      packages/client/ui-chat/tests/chat-font-axis-styles.client.spec.ts
  13. 1 1
      packages/client/ui-message-feedback/src/client/MessageFeedbackActions.module.css
  14. 7 5
      packages/client/ui-primitives/src/DisclosureRow.module.css
  15. 2 2
      packages/client/ui-primitives/tests/disclosure-row-styles.client.spec.ts
  16. 2 2
      packages/client/ui-theme/README.i18n.yaml
  17. 2 2
      packages/client/ui-theme/README.md
  18. 2 2
      packages/client/ui-theme/README.zh.md
  19. 14 8
      packages/client/ui-theme/src/styles/gradient-shadow-text.css
  20. 8 6
      packages/client/ui-tool/src/client/tool/components/ToolRow.module.css
  21. 2 2
      packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css
  22. 4 3
      packages/client/ui-tool/tests/tool-row-styles.client.spec.ts
  23. 16 13
      packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css
  24. 8 6
      packages/client/ui-workflow-run/tests/panel-styles.client.spec.ts

+ 2 - 2
.agents/notes/implemented/feature/2026-08-18-settings-font-size-control.i18n.yaml

@@ -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: 34ee7f28f58a0a5a5dc8cd39bc4cecf5e081359f
-2026-08-18-settings-font-size-control.zh.md: 2c2ea714d50ca1f934d78ede6175935338488331
+2026-08-18-settings-font-size-control.md: 778893a2c0943f1d3af8905a27efb526071e073b
+2026-08-18-settings-font-size-control.zh.md: 71ff3b556622e7091891f8e18f5eefcbda2b0851

Разница между файлами не показана из-за своего большого размера
+ 0 - 1
.agents/notes/implemented/feature/2026-08-18-settings-font-size-control.md


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
.agents/notes/implemented/feature/2026-08-18-settings-font-size-control.zh.md


+ 17 - 0
apps/web/tests/settings-chrome.e2e.ts

@@ -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()

+ 6 - 6
packages/client/ui-chat/src/client/chat/ChatView.module.css

@@ -101,8 +101,8 @@
 .turnStatusClock {
   margin-left: 8px;
   font: var(--dsw-font-xs-13);
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   font-weight: 400;
   font-variant-numeric: tabular-nums;
   color: var(--dsw-alias-label-caption);
@@ -125,14 +125,14 @@
 
 .hint {
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(18px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
 }
 
 .openError {
   color: var(--dsw-alias-state-error-primary);
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(18px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
 }
 
 .older {

+ 2 - 2
packages/client/ui-chat/src/client/chat/ContextInjectionRow.module.css

@@ -29,7 +29,7 @@
   min-width: 0;
   overflow: hidden;
   color: var(--dsw-alias-label-tertiary);
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -41,7 +41,7 @@
   min-width: 0;
   overflow: hidden;
   color: var(--dsw-alias-label-tertiary);
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;

+ 1 - 1
packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css

@@ -55,7 +55,7 @@
   overflow: hidden;
   flex: 1 1 auto;
   color: var(--dsw-alias-label-tertiary);
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;

+ 11 - 11
packages/client/ui-chat/src/client/chat/MessageItem.module.css

@@ -42,8 +42,8 @@
 
 .referenceSummary {
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(18px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
 }
 .contextRow {
   padding: 2px 0;
@@ -120,7 +120,7 @@
 
 .compactionTitle {
   flex: none;
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-primary-dimmed);
 }
@@ -139,7 +139,7 @@
   min-width: 0;
   overflow: hidden;
   color: var(--dsw-alias-label-tertiary);
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -149,14 +149,14 @@
   /* Indent under the title, which starts at leading (16 + delta) + gap 6. */
   padding: 4px 0 4px calc(22px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-tertiary);
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
 }
 
 .retryRow {
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
 }
 
 .retrySummary {
@@ -227,8 +227,8 @@
   margin-top: 3px;
   padding-left: 14px;
   overflow-wrap: anywhere;
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(18px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
 }
 
 .retryDetailLabel {
@@ -241,8 +241,8 @@
   gap: 8px;
   align-items: start;
   padding: 2px 0;
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
 }
 
 .turnErrorDot {

+ 7 - 6
packages/client/ui-chat/src/client/chat/ReasoningRow.module.css

@@ -50,15 +50,16 @@
   background: var(--dsw-alias-label-caption);
 }
 
-/* Think text sits one 2px step under the body size and follows the Settings
-   font-size preference through the same px delta, keeping the step. */
+/* Think text reads the secondary tier — one step under the body size
+   (setting −1 at ≤14, setting −2 above), shared with every flow-row title
+   and summary. */
 .summary {
   min-width: 0;
   overflow: hidden;
   flex: 1 1 auto;
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;
 }
@@ -71,8 +72,8 @@
   /* Indent under the title, which starts at leading (16 + delta) + gap 6. */
   padding: 4px 0 4px calc(22px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   white-space: pre-wrap;
   word-break: break-word;
 }

+ 5 - 4
packages/client/ui-chat/src/client/chat/StatsLine.module.css

@@ -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
@@ -11,8 +12,8 @@
   margin: 0 auto;
   box-sizing: border-box;
   padding: 4px calc(var(--dsh-composer-side-clearance) + 16px) 0px;
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   color: var(--dsw-alias-label-tertiary);
   white-space: nowrap;
   overflow: hidden;

+ 2 - 2
packages/client/ui-chat/src/client/chat/TurnUsageDisclosure.module.css

@@ -29,9 +29,9 @@
   min-width: 0;
   overflow: hidden;
   color: var(--dsw-alias-label-tertiary);
-  font-size: 14px;
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   font-variant-numeric: tabular-nums;
-  line-height: 24px;
+  line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;
 }

+ 7 - 7
packages/client/ui-chat/tests/chat-font-axis-styles.client.spec.ts

@@ -19,25 +19,25 @@ function declarationsFrom(source: string, selector: string): string[] {
 }
 
 describe('chat flow font-size axis', () => {
-  it('think text keeps its 2px step under the body size and moves with the delta', () => {
+  it('think text reads the secondary tier (one step under the body size)', () => {
     const css = read('ReasoningRow.module.css')
     for (const selector of ['.summary', '.thinkBody']) {
       expect(declarationsFrom(css, selector)).toEqual(expect.arrayContaining([
-        'font-size: calc(12px + var(--dsh-content-font-delta, 0px))',
-        'line-height: calc(20px + var(--dsh-content-font-delta, 0px))',
+        'font-size: var(--dsh-content-font-size-secondary, 13px)',
+        'line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px))',
       ]))
     }
   })
 
-  it('command and context summaries ride the axis at the body size', () => {
+  it('command and context summaries read the secondary tier on the shared row line', () => {
     expect(declarationsFrom(read('GenericCommandCard.module.css'), '.summary')).toEqual(expect.arrayContaining([
-      'font-size: var(--dsh-content-font-size, 14px)',
+      'font-size: var(--dsh-content-font-size-secondary, 13px)',
       'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
     ]))
     const context = read('ContextInjectionRow.module.css')
     for (const selector of ['.source', '.summary']) {
       expect(declarationsFrom(context, selector)).toEqual(expect.arrayContaining([
-        'font-size: var(--dsh-content-font-size, 14px)',
+        'font-size: var(--dsh-content-font-size-secondary, 13px)',
         'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
       ]))
     }
@@ -60,7 +60,7 @@ describe('chat flow font-size axis', () => {
     const css = read('MessageItem.module.css')
     for (const selector of ['.compactionTitle', '.compactionSummary', '.compactionBody']) {
       expect(declarationsFrom(css, selector)).toEqual(expect.arrayContaining([
-        'font-size: var(--dsh-content-font-size, 14px)',
+        'font-size: var(--dsh-content-font-size-secondary, 13px)',
         'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
       ]))
     }

+ 1 - 1
packages/client/ui-message-feedback/src/client/MessageFeedbackActions.module.css

@@ -46,7 +46,7 @@
   border-radius: 14px;
   background: transparent;
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(28px + var(--dsh-content-font-delta, 0px));
   white-space: nowrap;
   text-overflow: ellipsis;

+ 7 - 5
packages/client/ui-primitives/src/DisclosureRow.module.css

@@ -1,8 +1,10 @@
-/* Shared disclosure header: [16px leading] gap 6 [title 14/24] at the default
+/* Shared disclosure header: [16px leading] gap 6 [title 13/24] at the default
    size. The Settings font-size preference moves the row through the
-   body-published axis: title size follows --dsh-content-font-size, and the row
-   height, leading box, and glyph edge shift by the same px delta so the icon
-   keeps its optical share of the line. */
+   body-published axis: title size follows the secondary tier
+   (--dsh-content-font-size-secondary: one step under the body — setting −1 at
+   ≤14, setting −2 above), and the row height, leading box, and glyph edge
+   shift by the body px delta so the icon keeps its optical share of the
+   line. */
 
 .root {
   display: flex;
@@ -76,7 +78,7 @@ button.leading {
 
 .title {
   flex: none;
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-secondary);
 }

+ 2 - 2
packages/client/ui-primitives/tests/disclosure-row-styles.client.spec.ts

@@ -19,9 +19,9 @@ function declarations(selector: string): string[] {
 }
 
 describe('DisclosureRow.module.css font-size axis', () => {
-  it('sizes the title from the content font axis with the shared px delta', () => {
+  it('sizes the title from the secondary content tier on the shared row line', () => {
     expect(declarations('.title')).toEqual(expect.arrayContaining([
-      'font-size: var(--dsh-content-font-size, 14px)',
+      'font-size: var(--dsh-content-font-size-secondary, 13px)',
       'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
     ]))
   })

+ 2 - 2
packages/client/ui-theme/README.i18n.yaml

@@ -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: 12ea48aa2f321deec82ecea14c42aad7362523d6
-README.zh.md: a720916f5c9eab005a056415455e5017c0d08f0d
+README.md: 32ec60edb39b30afdb9b54804e62302cdc58e531
+README.zh.md: ab17910ab9cec4e1ff519af0922ee27034a6fa7c

+ 2 - 2
packages/client/ui-theme/README.md

@@ -29,7 +29,7 @@ Users switch the color scheme and content font size from two rows in Settings (G
 
 ### Appearance and font size
 
-The plugin registers Appearance preference cubes and a font-size stepper in the General section. The stepper accepts integer values from 12 through 17 px and defaults to 14 px. It changes conversation headings and base text by the same increment, including the user bubble and composer draft; tables, small text, and code keep fixed sizes. Each accepted change writes through the Host settings API. Rapid changes serialize in gesture order with namespace revisions, and a rejected latest write reloads the durable values. Non-loopback pages keep both choices process-local.
+The plugin registers Appearance preference cubes and a font-size stepper in the General section. The stepper accepts integer values from 12 through 17 px and defaults to 14 px. It changes conversation headings and base text by the same increment, including the user bubble and composer draft; flow-row titles, summaries, and tables follow one step under the body size, while small text and code keep fixed sizes. Each accepted change writes through the Host settings API. Rapid changes serialize in gesture order with namespace revisions, and a rejected latest write reloads the durable values. Non-loopback pages keep both choices process-local.
 
 ### Registering a theme
 
@@ -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. Dense table, small, and code variants stay fixed. The user bubble and composer draft 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
 

+ 2 - 2
packages/client/ui-theme/README.zh.md

@@ -29,7 +29,7 @@ kind: "package-reference"
 
 ### 外观与字号
 
-插件在「通用」分区注册外观偏好方块与字号步进器。步进器接受 12 至 17 px 的整数,默认值为 14 px。它以相同增量调整会话标题与基础文本,包括用户气泡与 composer 草稿;表格、小号文本和代码保持固定字号。每次通过的变更都经 Host settings API 写入。连续快速变更按操作顺序携带命名空间 revision 串行写入,最新写入被拒时重新加载持久值。非 loopback 页面把两个选择都保留在进程内。
+插件在「通用」分区注册外观偏好方块与字号步进器。步进器接受 12 至 17 px 的整数,默认值为 14 px。它以相同增量调整会话标题与基础文本,包括用户气泡与 composer 草稿;流内行的标题、摘要与表格跟随比正文低一档的字号,小号文本和代码保持固定字号。每次通过的变更都经 Host settings API 写入。连续快速变更按操作顺序携带命名空间 revision 串行写入,最新写入被拒时重新加载持久值。非 loopback 页面把两个选择都保留在进程内。
 
 ### 注册主题
 
@@ -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 标题与基础文本阶梯。紧凑的表格、小号文本与代码变体保持固定字号。用户气泡与 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 草稿直接读取正文档变量对,流内行的标题及摘要读取低一档变量对。
 
 ### 滚动条重新绑定
 

+ 14 - 8
packages/client/ui-theme/src/styles/gradient-shadow-text.css

@@ -22,10 +22,16 @@ body[data-ds-dark-theme] {
    重新从 Figma 导出时需要保留这一缩放。 */
 /* 正文字号调节:--dsh-content-font-size 由主题 bootstrap 与 ThemePresenter
    写在 body 上(整数 px,12–17,默认 14)。标题与正文变体按同一像素增量
-   (--dsh-content-font-delta)随动,保持层级;表格、small、code 属于密集
-   次级文本,保持固定。 */
+   (--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)。式中
+   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)));
+  --dsh-content-font-delta-secondary: calc(var(--dsh-content-font-size-secondary) - 13px);
 
   --dsw-font-markdown-h1: 700 calc(21px + var(--dsh-content-font-delta)) / calc(30px + var(--dsh-content-font-delta)) var(--dsw-font-family);
   --dsw-font-markdown-h1-font-family: var(--dsw-font-family);
@@ -83,18 +89,18 @@ body {
   --dsw-font-markdown-base-strong-italic-font-size: var(--dsh-content-font-size, 14px);
   --dsw-font-markdown-base-strong-italic-font-style: italic;
 
-  --dsw-font-markdown-table: 13px/22px var(--dsw-font-family);
+  --dsw-font-markdown-table: var(--dsh-content-font-size-secondary, 13px)/calc(22px + var(--dsh-content-font-delta-secondary, 0px)) var(--dsw-font-family);
   --dsw-font-markdown-table-font-family: var(--dsw-font-family);
   --dsw-font-markdown-table-font-weight: 400;
-  --dsw-font-markdown-table-line-height: 22px;
-  --dsw-font-markdown-table-font-size: 13px;
+  --dsw-font-markdown-table-line-height: calc(22px + var(--dsh-content-font-delta-secondary, 0px));
+  --dsw-font-markdown-table-font-size: var(--dsh-content-font-size-secondary, 13px);
   --dsw-font-markdown-table-font-style: normal;
 
-  --dsw-font-markdown-table-head: 500 13px/22px var(--dsw-font-family);
+  --dsw-font-markdown-table-head: 500 var(--dsh-content-font-size-secondary, 13px)/calc(22px + var(--dsh-content-font-delta-secondary, 0px)) var(--dsw-font-family);
   --dsw-font-markdown-table-head-font-family: var(--dsw-font-family);
   --dsw-font-markdown-table-head-font-weight: 500;
-  --dsw-font-markdown-table-head-line-height: 22px;
-  --dsw-font-markdown-table-head-font-size: 13px;
+  --dsw-font-markdown-table-head-line-height: calc(22px + var(--dsh-content-font-delta-secondary, 0px));
+  --dsw-font-markdown-table-head-font-size: var(--dsh-content-font-size-secondary, 13px);
   --dsw-font-markdown-table-head-font-style: normal;
 
   --dsw-font-markdown-small: 12px/20px var(--dsw-font-family);

+ 8 - 6
packages/client/ui-tool/src/client/tool/components/ToolRow.module.css

@@ -1,8 +1,10 @@
 /* Tool summary row (figma 122:9479): 24px single line at the default size —
-   [16 leading] gap6 [title 14/24] gap8 [2x2 dot] gap8 [summary FILL truncate].
+   [16 leading] gap6 [title 13/24] gap8 [2x2 dot] gap8 [summary FILL truncate].
    The Settings font-size preference rides the body-published axis: the shared
-   DisclosureRow chrome scales the row height, title, and leading glyph; the
-   summary texts here follow the same size and px delta. */
+   DisclosureRow chrome scales the row height and leading glyph by the body px
+   delta; the title and the summary texts here read the secondary tier
+   (--dsh-content-font-size-secondary: one step under the body — setting −1 at
+   ≤14, setting −2 above), matching think text. */
 
 .root {
   display: flex;
@@ -82,7 +84,7 @@
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-tertiary);
 }
@@ -96,7 +98,7 @@
   flex: none;
   margin-left: 4px;
   white-space: nowrap;
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-tertiary);
 }
@@ -114,7 +116,7 @@
   background: none;
   font: inherit;
   text-align: left;
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-secondary);
   text-decoration: underline;

+ 2 - 2
packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css

@@ -170,7 +170,7 @@
 
 .title {
   flex: none;
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-secondary);
 }
@@ -190,7 +190,7 @@
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   color: var(--dsw-alias-label-tertiary);
 }

+ 4 - 3
packages/client/ui-tool/tests/tool-row-styles.client.spec.ts

@@ -43,12 +43,13 @@ describe('ToolRow.module.css summary line', () => {
     expect(declarations('.summarySuffix')).not.toEqual(expect.arrayContaining(['text-overflow: ellipsis']))
   })
 
-  it('sizes the summary texts from the content font axis', () => {
+  it('sizes the summary texts from the secondary content tier', () => {
     // The Settings font-size preference must reach tool-call rows, not only
-    // the narration body: summary, suffix, and file link ride the axis.
+    // the narration body: summary, suffix, and file link read the secondary
+    // tier (one step under the body), matching think text.
     for (const selector of ['.summary', '.summarySuffix', '.fileLink']) {
       expect(declarations(selector)).toEqual(expect.arrayContaining([
-        'font-size: var(--dsh-content-font-size, 14px)',
+        'font-size: var(--dsh-content-font-size-secondary, 13px)',
         'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
       ]))
     }

+ 16 - 13
packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css

@@ -37,7 +37,7 @@
   flex: none;
   max-width: 42%;
   color: var(--dsw-alias-label-secondary);
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   font-weight: 510;
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   text-overflow: ellipsis;
@@ -49,8 +49,8 @@
   flex: 1;
   min-width: 0;
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(12px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(18px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;
 }
@@ -102,7 +102,7 @@
   min-width: 0;
   max-width: 42%;
   color: var(--dsw-alias-label-secondary);
-  font-size: var(--dsh-content-font-size, 14px);
+  font-size: var(--dsh-content-font-size-secondary, 13px);
   line-height: calc(24px + var(--dsh-content-font-delta, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -113,8 +113,8 @@
   flex: 1;
   min-width: 0;
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   text-overflow: ellipsis;
   white-space: nowrap;
 }
@@ -124,10 +124,10 @@
   flex: none;
   /* Column width scales with the text (~132/13 ≈ 10px per font-size px) so
      larger sizes keep the same status text visible instead of truncating. */
-  width: calc(132px + var(--dsh-content-font-delta, 0px) * 10);
+  width: calc(132px + var(--dsh-content-font-delta-secondary, 0px) * 10);
   color: var(--dsw-alias-label-secondary);
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   text-align: right;
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -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;
@@ -234,8 +237,8 @@
   overflow: hidden;
   width: 64px;
   color: var(--dsw-alias-label-secondary);
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   text-align: right;
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -243,8 +246,8 @@
 
 .empty {
   color: var(--dsw-alias-label-tertiary);
-  font-size: calc(13px + var(--dsh-content-font-delta, 0px));
-  line-height: calc(20px + var(--dsh-content-font-delta, 0px));
+  font-size: var(--dsh-content-font-size-secondary, 13px);
+  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
   padding: 0;
 }
 

+ 8 - 6
packages/client/ui-workflow-run/tests/panel-styles.client.spec.ts

@@ -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'
@@ -31,18 +33,18 @@ describe('WorkflowRunPanel.module.css font-size axis', () => {
     ]))
   })
 
-  it('member status and the empty placeholder keep their 1px step under the body', () => {
+  it('member status and the empty placeholder read the secondary tier', () => {
     for (const selector of ['.memberStatus', '.empty']) {
       expect(declarations(selector)).toEqual(expect.arrayContaining([
-        'font-size: calc(13px + var(--dsh-content-font-delta, 0px))',
-        'line-height: calc(20px + var(--dsh-content-font-delta, 0px))',
+        'font-size: var(--dsh-content-font-size-secondary, 13px)',
+        'line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px))',
       ]))
     }
   })
 
   it('the phase status column widens with the text so larger sizes do not truncate', () => {
     expect(declarations('.phaseStatus')).toEqual(expect.arrayContaining([
-      'width: calc(132px + var(--dsh-content-font-delta, 0px) * 10)',
+      'width: calc(132px + var(--dsh-content-font-delta-secondary, 0px) * 10)',
     ]))
   })
 })

Некоторые файлы не были показаны из-за большого количества измененных файлов