浏览代码

Merge pull request #3411 from deepseek-harness/feat/3287-smooth-corners

feat(web): superellipse corners and hairline elevation strokes
ihsiang 1 月之前
父节点
当前提交
4bc0b000f5
共有 96 个文件被更改,包括 867 次插入 和 292 次删除
  1. 2 2
      .agents/notes/implemented/bug-fix/2026-08-13-feedback-note-editor-popover.i18n.yaml
  2. 1 1
      .agents/notes/implemented/bug-fix/2026-08-13-feedback-note-editor-popover.md
  3. 1 1
      .agents/notes/implemented/bug-fix/2026-08-13-feedback-note-editor-popover.zh.md
  4. 6 0
      .agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.i18n.yaml
  5. 42 0
      .agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.md
  6. 42 0
      .agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.zh.md
  7. 6 0
      .agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.i18n.yaml
  8. 34 0
      .agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.md
  9. 34 0
      .agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.zh.md
  10. 2 2
      docs/web-styling.i18n.yaml
  11. 3 0
      docs/web-styling.md
  12. 3 0
      docs/web-styling.zh.md
  13. 1 1
      packages/client/locale/src/client/LanguageRow.module.css
  14. 17 13
      packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css
  15. 1 0
      packages/client/ui-approval/src/client/ApprovalPanel.module.css
  16. 6 3
      packages/client/ui-attachment/src/AttachmentRail.module.css
  17. 2 1
      packages/client/ui-attachment/src/ImageLightbox.module.css
  18. 2 2
      packages/client/ui-attachment/src/MessageImage.module.css
  19. 4 2
      packages/client/ui-chat/src/client/chat/ChatView.module.css
  20. 1 1
      packages/client/ui-chat/src/client/chat/ContextBody.module.css
  21. 1 1
      packages/client/ui-chat/src/client/chat/GenericCommandCard.module.css
  22. 2 2
      packages/client/ui-chat/src/client/chat/TurnNavigator.module.css
  23. 1 1
      packages/client/ui-chat/src/client/chat/TurnProcessNodeView.module.css
  24. 5 4
      packages/client/ui-chat/src/client/chat/TurnUsagePanel.module.css
  25. 3 2
      packages/client/ui-chat/src/client/details/DetailsPanel.module.css
  26. 1 1
      packages/client/ui-chat/src/client/settings/TranscriptViewRow.module.css
  27. 7 6
      packages/client/ui-commands/src/client/PopupSelectView.module.css
  28. 4 3
      packages/client/ui-conversation/src/client/queue/QueueDock.module.css
  29. 1 1
      packages/client/ui-conversation/src/client/settings/EnterBehaviorRow.module.css
  30. 6 3
      packages/client/ui-conversation/src/client/skeleton/ContextMeter.module.css
  31. 2 2
      packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css
  32. 2 2
      packages/client/ui-conversation/src/client/skeleton/HeroShell.module.css
  33. 10 6
      packages/client/ui-conversation/src/client/skeleton/InputBar.module.css
  34. 1 1
      packages/client/ui-conversation/src/client/skeleton/TodoPanel.module.css
  35. 4 4
      packages/client/ui-directory-picker-browse/src/client/DirectoryBrowser.module.css
  36. 3 2
      packages/client/ui-goal/src/client/GoalBar.module.css
  37. 5 4
      packages/client/ui-input-trigger/src/client/MenuView.module.css
  38. 4 3
      packages/client/ui-jobs/src/client/JobListAction.module.css
  39. 3 3
      packages/client/ui-layout/src/client/AppFrame.module.css
  40. 4 3
      packages/client/ui-message-feedback/src/client/MessageFeedbackActions.module.css
  41. 4 3
      packages/client/ui-model-selection/src/client/ModelSelect.module.css
  42. 1 1
      packages/client/ui-permission-presets/src/client/PermissionRow.module.css
  43. 1 0
      packages/client/ui-plan/src/client/PlanModeControl.module.css
  44. 1 1
      packages/client/ui-primitives/src/Button.module.css
  45. 1 1
      packages/client/ui-primitives/src/Input.module.css
  46. 7 6
      packages/client/ui-primitives/src/Menu.module.css
  47. 3 3
      packages/client/ui-primitives/src/Modal.module.css
  48. 2 0
      packages/client/ui-primitives/src/StateDot.module.css
  49. 1 1
      packages/client/ui-primitives/src/TerminalBlock.module.css
  50. 1 1
      packages/client/ui-primitives/src/markdown/JsonBlock.module.css
  51. 3 3
      packages/client/ui-primitives/src/markdown/MarkdownText.module.css
  52. 5 3
      packages/client/ui-schedule/src/client/ScheduleCatalogAction.module.css
  53. 5 4
      packages/client/ui-settings-general/src/client/SettingsRoot.module.css
  54. 12 11
      packages/client/ui-settings-models/src/client/ModelsSection.module.css
  55. 1 1
      packages/client/ui-settings-models/tests/styles.client.spec.ts
  56. 11 9
      packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css
  57. 5 4
      packages/client/ui-settings-plugins/src/client/PluginCard.module.css
  58. 1 1
      packages/client/ui-settings-plugins/src/client/PluginsSettingsSection.module.css
  59. 3 2
      packages/client/ui-settings-plugins/src/client/SubagentModelSelectionCard.module.css
  60. 4 2
      packages/client/ui-settings-plugins/src/client/fields.module.css
  61. 2 1
      packages/client/ui-sidebar/src/client/SidebarRoot.module.css
  62. 4 3
      packages/client/ui-skill/src/client/SkillRow.module.css
  63. 5 4
      packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css
  64. 1 1
      packages/client/ui-subagent/src/client/SubagentReadOnlyComposer.module.css
  65. 2 2
      packages/client/ui-theme/README.i18n.yaml
  66. 4 2
      packages/client/ui-theme/README.md
  67. 4 2
      packages/client/ui-theme/README.zh.md
  68. 3 3
      packages/client/ui-theme/src/client/AppearanceRow.module.css
  69. 1 1
      packages/client/ui-theme/src/client/FontSizeRow.module.css
  70. 2 0
      packages/client/ui-theme/src/client/styles.ts
  71. 26 0
      packages/client/ui-theme/src/styles/corner-shape.css
  72. 23 0
      packages/client/ui-theme/src/styles/gradient-shadow-text.css
  73. 1 0
      packages/client/ui-theme/tests/client-styles.client.spec.ts
  74. 91 0
      packages/client/ui-theme/tests/corner-shape-styles.client.spec.ts
  75. 182 0
      packages/client/ui-theme/tests/elevation-styles.client.spec.ts
  76. 2 84
      packages/client/ui-theme/tests/scrollbar-styles.client.spec.ts
  77. 91 0
      packages/client/ui-theme/tests/stylesheet-scan.ts
  78. 1 1
      packages/client/ui-tool/src/client/tool/ToolCallTree.module.css
  79. 1 1
      packages/client/ui-tool/src/client/tool/components/AskQuestionCard.module.css
  80. 5 4
      packages/client/ui-tool/src/client/tool/components/ToolRow.module.css
  81. 5 4
      packages/client/ui-tool/src/client/tool/toolviews/bash-sample.module.css
  82. 1 1
      packages/client/ui-trajectory/src/client/TrajectoryCell.module.css
  83. 13 10
      packages/client/ui-trajectory/src/client/TrajectoryTable.module.css
  84. 3 3
      packages/client/ui-trajectory/src/client/TrajectoryTimeline.module.css
  85. 3 2
      packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css
  86. 1 0
      packages/client/ui-user-questions/src/client/PlanReviewPanel.module.css
  87. 6 4
      packages/client/ui-user-questions/src/client/QuestionComposer.module.css
  88. 1 0
      packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css
  89. 1 1
      packages/client/ui-workspace/src/client/rows/Rows.module.css
  90. 6 2
      packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css
  91. 1 0
      packages/client/web/src/boot-page.module.css
  92. 6 5
      packages/experimental/client-ui-agent-team/src/client/TeamAction.module.css
  93. 4 3
      packages/extensions/ui-cordis/src/client/CordisDefineRow.module.css
  94. 9 5
      packages/extensions/ui-cordis/src/client/CordisPanel.module.css
  95. 4 2
      packages/extensions/ui-cordis/src/client/CordisRunRow.module.css
  96. 1 1
      packages/session-query/session-log-export/src/client/HeaderAction.module.css

+ 2 - 2
.agents/notes/implemented/bug-fix/2026-08-13-feedback-note-editor-popover.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/bug-fix/2026-08-13-feedback-note-editor-popover.md
-2026-08-13-feedback-note-editor-popover.md: 42c08e39cfb054db689503e23306c5049a97b6cb
-2026-08-13-feedback-note-editor-popover.zh.md: 553029f8a42dae42a38e909d716b41e2c6dd252e
+2026-08-13-feedback-note-editor-popover.md: 8f51f090cc24292ad02d96fefb1f45bc05df08c9
+2026-08-13-feedback-note-editor-popover.zh.md: e64e95ac6599be2d7c343b4432415f0e99c6ef9b

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-08-13-feedback-note-editor-popover.md

@@ -18,7 +18,7 @@ The note editor does not enter the row's flex layout at all. It is a popover: a
 
 **The action strip.** The like/dislike buttons and the note trigger stay in the row, unchanged. The trigger is a plain button (`aria-haspopup="dialog"`, `aria-expanded` while open) that shows "Add a note" before a note exists and the note text afterward.
 
-**The popover.** While open, the panel contains the textarea plus Save and Cancel, and any note-save failure, as `role="dialog"` with a title distinct from the textarea's own label so both are addressable by name. It opens beneath the trigger (4px gap), clamps to 12px from the viewport edges, auto-focuses the textarea, and closes on Escape or an outside pointer-down. Closing returns focus to the trigger only when the panel was really open, never on the initial mount (a freshly rendered rated message must not pull focus into its action row). A rating action during an open editor closes the panel. The four undefined tokens are replaced with the ones the theme actually defines, matching the primitives' precedent: `border-l2` and `bg-layer-1` for the input, `button-primary-fill` with `label-primary-foreground` plus a `button-primary-hover` state for Save; the panel surface reuses the Menu card recipe (`--dsw-specific-menu`, `--dsw-shadow-lv3`, inverted hairline `--dsw-alias-border-inverted`, `border-radius: 12px`).
+**The popover.** While open, the panel contains the textarea plus Save and Cancel, and any note-save failure, as `role="dialog"` with a title distinct from the textarea's own label so both are addressable by name. It opens beneath the trigger (4px gap), clamps to 12px from the viewport edges, auto-focuses the textarea, and closes on Escape or an outside pointer-down. Closing returns focus to the trigger only when the panel was really open, never on the initial mount (a freshly rendered rated message must not pull focus into its action row). A rating action during an open editor closes the panel. The four undefined tokens are replaced with the ones the theme actually defines, matching the primitives' precedent: `border-l2` and `bg-layer-1` for the input, `button-primary-fill` with `label-primary-foreground` plus a `button-primary-hover` state for Save; the panel surface reuses the Menu card surface recipe (`--dsw-specific-menu`, the `--dsw-elevation-prominent` shadow with the `--dsw-alias-border-l1` stroke rebind and `border: 0`) at `border-radius: 12px`.
 
 **Failure surfaces split by where the human is looking.** A rating or list-load failure shows beside the buttons in the row, legible whether or not the popover is open. A note-save failure shows inside the popover, next to Save/Cancel, and the panel stays open so the draft survives to be corrected.
 

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-08-13-feedback-note-editor-popover.zh.md

@@ -18,7 +18,7 @@ Status: implemented
 
 **操作条。** 点赞/点踩按钮与备注触发按钮保持原样留在行内。触发按钮是普通 `button`(`aria-haspopup="dialog"`,打开时 `aria-expanded`),在没有备注时显示「补充说明」,已有备注时显示备注文本。
 
-**浮层。** 打开时,面板内含 textarea、Save 与 Cancel,以及任何备注保存失败提示,作为 `role="dialog"`,其标题与 textarea 自身的标签不同,以便两者都能按名称寻址。它在触发按钮下方打开(4px 间距),钳制到距视口边缘 12px,自动聚焦 textarea,并在 Escape 或外部 pointer-down 时关闭。关闭时仅当面板确实曾经打开才把焦点还给触发按钮,绝不会在初始挂载时(新渲染出的一条已评分消息不得把焦点拉进其操作条)。编辑器打开时进行评分操作会关闭面板。四个未定义 token 换成主题确实定义的那些,与 primitives 的既有做法一致:输入框用 `border-l2` 与 `bg-layer-1`,Save 用 `button-primary-fill` 配 `label-primary-foreground` 并加 `button-primary-hover` 状态;面板表面复用 Menu 卡片的配方(`--dsw-specific-menu`、`--dsw-shadow-lv3`、反色发丝线 `--dsw-alias-border-inverted`、`border-radius: 12px`)。
+**浮层。** 打开时,面板内含 textarea、Save 与 Cancel,以及任何备注保存失败提示,作为 `role="dialog"`,其标题与 textarea 自身的标签不同,以便两者都能按名称寻址。它在触发按钮下方打开(4px 间距),钳制到距视口边缘 12px,自动聚焦 textarea,并在 Escape 或外部 pointer-down 时关闭。关闭时仅当面板确实曾经打开才把焦点还给触发按钮,绝不会在初始挂载时(新渲染出的一条已评分消息不得把焦点拉进其操作条)。编辑器打开时进行评分操作会关闭面板。四个未定义 token 换成主题确实定义的那些,与 primitives 的既有做法一致:输入框用 `border-l2` 与 `bg-layer-1`,Save 用 `button-primary-fill` 配 `label-primary-foreground` 并加 `button-primary-hover` 状态;面板表面复用 Menu 卡片的表面配方(`--dsw-specific-menu`、`--dsw-elevation-prominent` 投影配 `--dsw-alias-border-l1` 描边重绑与 `border: 0`),圆角取 `border-radius: 12px`。
 
 **失败提示按人的视线所落之处拆分。** 评分或列表加载失败显示在按钮旁的图标行里,无论浮层是否打开都清晰可读。备注保存失败显示在浮层内、Save/Cancel 旁,且面板保持打开,以便草稿留存待修正。
 

+ 6 - 0
.agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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-09-01-web-elevation-stroke-shadows.md
+2026-09-01-web-elevation-stroke-shadows.md: 2bc3105203b63c87aaf39e3d68780650b163dcca
+2026-09-01-web-elevation-stroke-shadows.zh.md: bda3ee27af03d20937253fa23968afd0cd7d39c7

+ 42 - 0
.agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.md

@@ -0,0 +1,42 @@
+# Agent Note: Web elevation — hairline stroke drawn in shadow
+
+Status: implemented
+
+English | [中文](2026-09-01-web-elevation-stroke-shadows.zh.md)
+
+## Problem
+
+Elevated web-client surfaces — menus, popovers, modals, panels, floating buttons, the composer — each paired a real `border: 1px solid <neutral token>` with a `--dsw-shadow-lv2`/`lv3` shadow. The border consumes layout (1px per side, and it is the UA-default replacement on `<button>` elements), the light theme drew most floats with no stroke at all (`--dsw-alias-border-inverted` is transparent in light) while `lv3` faked one with a blurred 1px ring, and the composer wore a broad soft `lv2` patch that read as a smudge rather than a lifted surface. Current desktop chat UIs instead draw elevation as one `box-shadow` list: a 0.5px hairline stroke plus two faint soft layers, with `border: 0` on the surface.
+
+## Decision
+
+`gradient-shadow-text.css` (the ui-theme shadow owner) defines the elevation tokens beside the `--dsw-shadow-lv*` scale:
+
+- `--dsw-elevation-stroke-color` — the hairline color, defaulting to `--dsw-alias-border-l4` (black 16% light, white 20% dark); components rebind it per surface or state: every menu-fill surface (`--dsw-specific-menu` background) rebinds the lightest `--dsw-alias-border-l1` and the composer rebinds `--dsw-alias-border-l2`, both quieter than panels and buttons. The default is declared on `body` alone while the derived tokens below are re-declared on `body, body *`: a custom property computes with `var()` already substituted, so body-only derived tokens would bake in body's color and make every rebind a no-op (the same per-element re-substitution scrollbar.css states for `--dsh-scrollbar-thumb`).
+- `--dsw-elevation-stroke: 0 0 0 0.5px var(--dsw-elevation-stroke-color)` — the stroke alone, used standalone by inline cards that want only an outline (the plugin-inventory card).
+- `--dsw-elevation-panel` / `--dsw-elevation-prominent` — the stroke plus two faint soft layers (3px directional + 16/20px glow at 2–5% black), panel for small floating widgets and cards, prominent for floats, and soft — larger blur at lower alpha — for the composer.
+
+Converted surfaces set `border: 0` and one elevation shadow: every `--dsw-shadow-lv3` float (Menu, Modal, popup selects, model select, usage/context popovers, feedback actions, schedule/job popovers, subagent lineage, settings panel, cordis panel, experimental team panel) takes prominent, and the lv2 surfaces (scroll-to-bottom button, turn-preview card, attachment-rail arrows, question composer, trajectory tooltip) take panel. The composer card takes the soft tier with the l2 stroke rebind, and its workspace-trigger state sets the stroke color `transparent` instead of the former `border-color: transparent`. Dark theme needs no shadow overrides: the soft layers are near-invisible there and the stroke carries the separation.
+
+`packages/client/ui-theme/tests/elevation-styles.client.spec.ts` pins the token composition and scans every stylesheet under `packages/`: a rule pairing an lv/elevation `box-shadow` with a `--dsw-alias-border-*` border fails, and every `solid` border on a neutral `--dsw-alias-border-*` token must be `0.5px` wide. Deliberate keeps: Toast and HoverCard (inverted fills where a theme-following stroke is meaningless), ImageLightbox (bare image), and the warn-bordered approval/plan panels, whose state-colored borders stay real borders and pass the scan.
+
+Flat widgets keep real borders at hairline width: every neutral-token `1px solid` border — buttons (the shared outline variant, add/retry/inspect buttons), inputs, inline cards, code blocks, and the settings row separators — is `0.5px solid`, with full-box strokes deepened to `--dsw-alias-border-l4` (buttons one step lighter at `--dsw-alias-border-l3`) while row separators keep `--dsw-alias-border-l2`; state logic (`border-color` swaps on focus/hover) is unchanged. Separators drawn as filled boxes take the same weight: the 1px-tall or 1px-wide lines with a border-token background (menu separators, the conversation header seam, markdown `hr`, the tool IO dividers, the trajectory rail, the directory-browser divider) are 0.5px, and the context-injection divider that read the never-defined `--dsw-alias-line-secondary` (so it never rendered) now draws `0.5px solid var(--dsw-alias-border-l2)`. Chromium paints sub-device-pixel borders as one device pixel, so 1x displays render exactly the former line and 2x displays get the hairline. Dashed affordances stay 1px (a 0.5px dash pattern degrades), and the two border-drawn spinner rings keep their track width through the spec's explicit allowlist.
+
+## Alternatives considered
+
+**Keeping 1px real borders and only softening the shadows.** Leaves the layout-consuming border, the light-theme stroke gap on `border-inverted` floats, and the double outline wherever both existed; the stroke-in-shadow form is what produces the crisp hairline edge.
+
+**A 1px stroke instead of 0.5px.** At 2x displays 0.5px renders one physical pixel, and on 1x it blends lighter, which is the intended hairline. 1px reads as the old border.
+
+**Drawing flat-widget hairlines as box-shadow strokes too.** Buttons and inputs swap `border-color` on hover and focus and several pair a box-shadow focus ring; moving their stroke into `box-shadow` would collide with those rings (one property) and rewrite every state rule, while `0.5px solid` keeps the whole state logic and changes only the weight.
+
+**Suppressing the composer trigger stroke with `box-shadow: none`.** Also drops the soft layers the trigger state keeps today; rebinding `--dsw-elevation-stroke-color: transparent` removes exactly the stroke.
+
+**Converting Toast/HoverCard too.** Their fills are inverted relative to the theme, so the theme-following stroke color is invisible-or-wrong on them; they keep `lv3` until an inverted-surface stroke token exists.
+
+## Consequences
+
+- Every converted surface gains a hairline outline in the light theme (most floats previously had none) and loses 1px of border from its box; the visual size change is at most 2px on small buttons and imperceptible on panels.
+- The neutral-border-plus-shadow pairing is now rejected by the ui-theme elevation spec, so a new elevated surface must choose the elevation tokens; the rule lives in [docs/web-styling.md](../../../../docs/web-styling.md).
+- The composer's broad `lv2` patch becomes stroke + tight glow; its dark stroke keeps the figma one-notch-weaker value through the rebind.
+- `--dsw-shadow-lv1`/`lv1-blur` currently have no consumer and `lv2`/`lv3` remain only on the deliberate keeps; the scale stays for inverted and bespoke surfaces.

+ 42 - 0
.agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.zh.md

@@ -0,0 +1,42 @@
+# Agent Note: Web elevation — hairline stroke drawn in shadow
+
+Status: implemented
+
+[English](2026-09-01-web-elevation-stroke-shadows.md) | 中文
+
+## Problem
+
+Web 客户端的高层级表面——菜单、浮层、对话框、面板、悬浮按钮、输入框——原先都把真 `border: 1px solid <中性 token>` 与 `--dsw-shadow-lv2`/`lv3` 投影配对。border 占布局(每侧 1px,且在 `<button>` 上是对 UA 默认边框的替换);浅色主题下多数浮层实际没有描边(`--dsw-alias-border-inverted` 在浅色下是透明的),靠 `lv3` 里模糊的 1px 环冒充;输入框则披着一大片柔和的 `lv2` 投影,读起来更像污渍而非悬浮表面。当前桌面聊天 UI 改用单个 `box-shadow` 列表绘制 elevation:0.5px 发丝描边加两层极淡柔光,表面本身 `border: 0`。
+
+## Decision
+
+`gradient-shadow-text.css`(ui-theme 的阴影归属地)在 `--dsw-shadow-lv*` 阶旁定义 elevation token:
+
+- `--dsw-elevation-stroke-color`——发丝描边颜色,默认 `--dsw-alias-border-l4`(浅色黑 16%、深色白 20%);组件可按表面或状态重绑:所有菜单面(`--dsw-specific-menu` 背景)重绑最浅的 `--dsw-alias-border-l1`、输入框重绑 `--dsw-alias-border-l2`,两者都比面板与按钮安静。默认色只声明在 `body` 上,而下述派生 token 在 `body, body *` 上逐元素重声明:自定义属性的计算值已替换完 `var()`,派生 token 若只在 body 声明会把 body 的颜色固化进去,让所有重绑失效(与 scrollbar.css 对 `--dsh-scrollbar-thumb` 声明的逐元素重替换是同一契约)。
+- `--dsw-elevation-stroke: 0 0 0 0.5px var(--dsw-elevation-stroke-color)`——单独的描边,供只要轮廓的行内卡片独立使用(插件清单卡片)。
+- `--dsw-elevation-panel` / `--dsw-elevation-prominent`——描边加两层极淡柔光(3px 方向光 + 16/20px 辉光,黑 2–5%),panel 用于小型悬浮部件与卡片,prominent 用于浮层,soft——更大模糊、更低透明度——用于输入框。
+
+被转换的表面设 `border: 0` 加一个 elevation 投影:所有 `--dsw-shadow-lv3` 浮层(Menu、Modal、弹出选择、模型选择、用量/上下文浮层、反馈操作条、日程/任务浮层、子代理谱系、设置面板、cordis 面板、实验性 team 面板)取 prominent;lv2 表面(回到底部按钮、回合预览卡、附件栏箭头、问题 composer、轨迹 tooltip)取 panel。输入框卡片取 soft 档并重绑 l2 描边,其 workspace-trigger 态把描边色设为 `transparent`,替代原先的 `border-color: transparent`。深色主题无需投影覆盖:柔光在深色下几乎不可见,分离由描边承担。
+
+`packages/client/ui-theme/tests/elevation-styles.client.spec.ts` 钉住 token 组成并扫描 `packages/` 下全部样式表:lv/elevation `box-shadow` 与 `--dsw-alias-border-*` border 配对的规则即失败;中性 `--dsw-alias-border-*` token 上的每个 `solid` border 必须为 `0.5px` 宽。有意保留:Toast 与 HoverCard(反色填充,跟随主题的描边色在其上无意义)、ImageLightbox(裸图片)、warn 描边的审批/计划面板——状态色 border 保持真 border,扫描放行。
+
+平面部件保留真 border 但取发丝线宽度:所有中性 token 的 `1px solid` border——按钮(共享 outline 变体、添加/重试/inspect 按钮)、输入框、行内卡片、代码块与设置行分割线——改为 `0.5px solid`,整框描边加深为 `--dsw-alias-border-l4`(按钮浅一档取 `--dsw-alias-border-l3`),行分割线保持 `--dsw-alias-border-l2`;状态逻辑(focus/hover 换 `border-color`)不变。以填充盒绘制的分隔线取同一粗细:高或宽为 1px、背景用 border token 的线(菜单分隔、对话标题栏接缝、markdown `hr`、工具 IO 分隔、轨迹竖轨、目录浏览器分隔)改为 0.5px;上下文注入分隔线原先读取从未定义的 `--dsw-alias-line-secondary`(因此从未渲染),现在绘制 `0.5px solid var(--dsw-alias-border-l2)`。Chromium 把亚设备像素 border 绘制为一个设备像素,因此 1x 屏与原先渲染完全一致,2x 屏得到发丝线。dashed 记号保持 1px(0.5px 虚线图案会退化),两个用 border 画的 spinner 圆环经 spec 显式豁免保留轨道宽度。
+
+## Alternatives considered
+
+**保留 1px 真 border、只调柔投影。** 留下占布局的 border、浅色主题 `border-inverted` 浮层的描边缺口,以及两者并存处的双轮廓;描边入投影正是产生锐利发丝边缘的形式。
+
+**用 1px 而非 0.5px 描边。** 0.5px 在 2x 屏渲染为一物理像素,1x 屏混合得更浅,正是发丝线意图。1px 读起来就是原来的 border。
+
+**平面部件的发丝线也用 box-shadow 描边画。** 按钮与输入框在 hover/focus 时切换 `border-color`,多处还配 box-shadow 焦点环;把描边挪进 `box-shadow`(单一属性)会与焦点环冲突并重写全部状态规则,而 `0.5px solid` 保留整套状态逻辑,只改粗细。
+
+**composer trigger 态用 `box-shadow: none` 抑制描边。** 会连带丢掉该状态今天保留的柔光;重绑 `--dsw-elevation-stroke-color: transparent` 恰好只去掉描边。
+
+**Toast/HoverCard 一并转换。** 其填充相对主题反色,跟随主题的描边色在其上不可见或错误;在出现反色表面描边 token 之前保留 `lv3`。
+
+## Consequences
+
+- 每个被转换表面在浅色主题下获得发丝轮廓(多数浮层此前没有),盒子少了 1px border;视觉尺寸变化在小按钮上至多 2px,面板上不可察觉。
+- 中性 border 加投影的配对现被 ui-theme elevation spec 拒绝,新的高层级表面必须选用 elevation token;规则记录于 [docs/web-styling.md](../../../../docs/web-styling.zh.md)。
+- 输入框的大片 `lv2` 变为描边加收紧的辉光;深色描边经重绑保留 figma 低一档取值。
+- `--dsw-shadow-lv1`/`lv1-blur` 目前无消费方,`lv2`/`lv3` 只剩有意保留者;该阶为反色与定制表面继续存在。

+ 6 - 0
.agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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-09-01-web-superellipse-corner-smoothing.md
+2026-09-01-web-superellipse-corner-smoothing.md: 7088438a8068343434a27495b771515d4a5158ee
+2026-09-01-web-superellipse-corner-smoothing.zh.md: 26aa64fda9f557069cfc3040422c8e1b7e34676e

+ 34 - 0
.agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.md

@@ -0,0 +1,34 @@
+# Agent Note: Web superellipse corner smoothing
+
+Status: implemented
+
+English | [中文](2026-09-01-web-superellipse-corner-smoothing.zh.md)
+
+## Problem
+
+Every rounded surface in the web client — cards, composer, buttons, popovers — draws its corners as plain circular arcs, which read as visibly harder than the smooth (squircle-like) corners current desktop chat UIs ship. That smoothness comes from CSS `corner-shape: superellipse(1.5)` applied behind an `@supports` guard, not from larger radii or masking tricks; utility-class implementations attach it to every rounded-corner class except full-round. This client has no utility classes: `border-radius` values are px literals spread across CSS Modules in every client package, so there is no single class list to attach the property to, and full-round shapes (`border-radius: 50%` circles, 999px pills) must keep circular arcs — a superellipse deforms a circle into a squircle, so a border-drawn spinner would visibly wobble, and it squares off capsule ends.
+
+## Decision
+
+`packages/client/ui-theme/src/styles/corner-shape.css` is a global sheet mounted by ui-theme's client entry (after `base.css`). Inside `@supports (corner-shape: superellipse(1.5))` it defines `--dsw-corner-shape: superellipse(1.5)` on `:root` and applies `corner-shape: var(--dsw-corner-shape)` through `*, *::before, *::after` — `corner-shape` does not inherit, so the universal selector is the mechanism that reaches every rounded surface without a utility-class system. Engines without `corner-shape` keep circular corners because both declarations live inside the guard. `superellipse(1.5)` sits between `round` (`superellipse(1)`) and `squircle` (`superellipse(2)`), matching the smoothing current desktop chat UIs ship.
+
+Full-round shapes opt back out at their declaration: every `border-radius` of `50%`, `100%`, or a pill radius (≥ 99px) pairs `corner-shape: round` in the same rule of its owning component sheet. The pairing is enforced by `packages/client/ui-theme/tests/corner-shape-styles.client.spec.ts`, which scans every stylesheet under `packages/` (the shared scan helpers live in `tests/stylesheet-scan.ts`, extracted from the scrollbar spec); the same spec pins the guard and the universal application in `corner-shape.css`. Component-local radius indirections (`--dsl-*-radius`) all hold values far below the pill threshold, so the lexical scan covers current usage.
+
+Token-based implementations pair the shape change with a 1.25× radius scale; this client has no radius tokens (px literals per component), so radii are unchanged and only the corner curvature moves.
+
+## Alternatives considered
+
+**A radius token system first, then per-token application.** Faithful, but converting ~130 px-literal radii across every client package into tokens is a large refactor serving no other current need; the universal selector reaches the same surfaces with one rule.
+
+**Applying superellipse to full-round shapes too (no opt-outs).** Fewer declarations, but spinners built from `border-radius: 50%` borders wobble when the rotating shape is not a circle, and capsule ends square off.
+
+**Subtree opt-out via `--dsw-corner-shape: round` instead of per-declaration `corner-shape: round`.** The custom property inherits, so a pill's rounded descendants would silently lose smoothing; the explicit per-rule declaration keeps the opt-out exactly as wide as the full-round shape and is what the pairing spec can check.
+
+**Scaling radii by 1.25 alongside the curvature change.** Requires the token system above; the curvature change alone already delivers the smoothness, and radii stay as designed.
+
+## Consequences
+
+- On engines with `corner-shape` (Chromium ≥ 139 behind no flag), every rounded corner in the client curves along `superellipse(1.5)`; other engines render exactly as before, with no fallback code.
+- New full-round shapes must pair `corner-shape: round` or the ui-theme corner-shape spec fails; the rule is stated in [docs/web-styling.md](../../../../docs/web-styling.md) and costs one extra declaration per circle or pill.
+- The universal selector adds one non-inherited property to every element; the declaration is a constant and profiling concerns are theoretical at current tree sizes.
+- A rounded box whose radius equals half its height (an implicit pill under 99px) still receives the superellipse; the scan cannot see computed geometry, and such ends read as intentional smoothing rather than distortion.

+ 34 - 0
.agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.zh.md

@@ -0,0 +1,34 @@
+# Agent Note: Web superellipse corner smoothing
+
+Status: implemented
+
+[English](2026-09-01-web-superellipse-corner-smoothing.md) | 中文
+
+## Problem
+
+Web 客户端里每个圆角表面——卡片、输入框、按钮、浮层——都以普通圆弧绘制圆角,观感明显硬于当前桌面聊天 UI 普遍采用的平滑(类 squircle)圆角。这种平滑感来自在 `@supports` 守卫内应用 CSS `corner-shape: superellipse(1.5)`,而非更大的半径或遮罩技巧;工具类体系的实现把它挂到除正圆之外的所有圆角工具类上。本客户端没有工具类:`border-radius` 以 px 字面量散布在各客户端包的 CSS Modules 中,没有可以统一挂载该属性的类列表;而正圆形状(`border-radius: 50%` 的圆、999px 胶囊)必须保持圆弧——超级椭圆会把圆变形为 squircle,用 border 绘制的加载圈旋转时会明显晃动,胶囊两端也会变方。
+
+## Decision
+
+`packages/client/ui-theme/src/styles/corner-shape.css` 是由 ui-theme 客户端 entry 挂载的全局样式表(位于 `base.css` 之后)。它在 `@supports (corner-shape: superellipse(1.5))` 内于 `:root` 定义 `--dsw-corner-shape: superellipse(1.5)`,并通过 `*, *::before, *::after` 应用 `corner-shape: var(--dsw-corner-shape)`——`corner-shape` 不继承,通配选择器正是在没有工具类系统的前提下触达每个圆角表面的机制。两条声明都在守卫内,因此不支持 `corner-shape` 的引擎保持普通圆弧。`superellipse(1.5)` 介于 `round`(`superellipse(1)`)与 `squircle`(`superellipse(2)`)之间,与当前桌面聊天 UI 的平滑度一致。
+
+正圆形状在其声明处退出:每个取值为 `50%`、`100%` 或胶囊半径(≥ 99px)的 `border-radius`,都在所属组件样式表的同一规则内配对 `corner-shape: round`。该配对由 `packages/client/ui-theme/tests/corner-shape-styles.client.spec.ts` 强制,它扫描 `packages/` 下的全部样式表(共享扫描辅助函数位于 `tests/stylesheet-scan.ts`,自 scrollbar spec 抽出);同一 spec 也钉住 `corner-shape.css` 的守卫与通配应用。组件局部半径变量(`--dsl-*-radius`)取值都远低于胶囊阈值,因此词法扫描覆盖当前用法。
+
+基于 token 的实现会在改变曲线的同时把半径 token 乘以 1.25;本客户端没有半径 token(各组件 px 字面量),故半径不变,只改变圆角曲率。
+
+## Alternatives considered
+
+**先建半径 token 系统,再按 token 应用。** 更忠实,但把所有客户端包约 130 处 px 字面量半径改造成 token 是没有其他现实需求的大重构;通配选择器用一条规则触达同样的表面。
+
+**对正圆形状也应用超级椭圆(不设豁免)。** 声明更少,但用 `border-radius: 50%` border 绘制的加载圈在形状不是圆时旋转会晃动,胶囊两端会变方。
+
+**用子树级 `--dsw-corner-shape: round` 替代逐声明 `corner-shape: round` 豁免。** 自定义属性会继承,胶囊的圆角后代会静默失去平滑;逐规则显式声明让豁免范围恰好等于正圆形状本身,也是配对 spec 能检查的形式。
+
+**在改变曲率的同时把半径乘 1.25。** 需要上述 token 系统;仅曲率变化已带来平滑感,半径维持设计值。
+
+## Consequences
+
+- 在支持 `corner-shape` 的引擎(Chromium ≥ 139,无需 flag)上,客户端每个圆角都沿 `superellipse(1.5)` 弯曲;其他引擎渲染与之前完全一致,没有回退代码。
+- 新增正圆形状必须配对 `corner-shape: round`,否则 ui-theme 的 corner-shape spec 失败;该规则记录于 [docs/web-styling.md](../../../../docs/web-styling.zh.md),每个圆或胶囊多付一条声明。
+- 通配选择器给每个元素增加一个不继承的属性;声明是常量,在当前树规模下性能顾虑属于理论层面。
+- 半径等于自身高度一半的圆角盒(低于 99px 的隐式胶囊)仍会得到超级椭圆;扫描看不到计算后几何,此类端部读作有意的平滑而非变形。

+ 2 - 2
docs/web-styling.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 docs/web-styling.md
-web-styling.md: 5296cc7f83f712532262eadda6da098ae9f63ec7
-web-styling.zh.md: a2ba074619a7e2737c7e9286d2aeaa90611ac633
+web-styling.md: dd057a3121422e4decfac06b9a3cf57e52254011
+web-styling.zh.md: 5ec0b65390b29e915fe3da633bb7c56ef92e17d6

+ 3 - 0
docs/web-styling.md

@@ -19,6 +19,9 @@ Global style sheets belong in `ui-theme/src/styles/`. Component styles live besi
 - Keep source text, terminal output, and diff lines unwrapped when their component contract requires column preservation; use the shared scrollbar styles rather than component-specific scrollbar selectors.
 - Put presentation in CSS. Inline React styles may pass component-local custom-property values but must not encode theme branches.
 - Preserve keyboard focus visibility and reduced-motion behavior when adding transitions or hover-only controls.
+- Rounded corners inherit the global superellipse smoothing from ui-theme's `corner-shape.css` on supporting engines. Pair `corner-shape: round` with every full-round `border-radius` (`50%`, `100%`, or a pill radius) so circles and capsules keep circular arcs; the ui-theme corner-shape spec enforces the pairing.
+- Elevated surfaces (menus, popovers, modals, panels, floating buttons, the composer) set `border: 0` and take `box-shadow: var(--dsw-elevation-panel)`, `var(--dsw-elevation-prominent)`, or the composer's `var(--dsw-elevation-soft)` (larger blur at lower alpha): the 0.5px hairline stroke is the first shadow layer, and `--dsw-elevation-stroke-color` rebinds or suppresses it per surface or state. Never pair a `--dsw-alias-border-*` border with an lv/elevation shadow — the ui-theme elevation spec rejects the pairing; state-colored borders (warn panels) stay real borders.
+- Flat borders and separators that use a neutral `--dsw-alias-border-*` token draw at `0.5px` — buttons, inputs, cards, row dividers, and separators drawn as filled boxes (menu separators, the conversation header seam, markdown `hr`, vertical rails) share the hairline weight, which Chromium paints as one device pixel. Dashed affordances and state-colored borders keep 1px; spinner ring tracks keep their width through the spec's explicit allowlist. The ui-theme elevation spec rejects wider neutral solid borders.
 
 ## Changing the system
 

+ 3 - 0
docs/web-styling.zh.md

@@ -19,6 +19,9 @@
 - 当组件约定要求保留列结构时,源码文本、终端输出和 diff 行不得换行;使用共享滚动条样式,不得定义组件专用滚动条选择器。
 - 呈现规则写在 CSS 中。React 内联样式可以传递组件局部自定义属性值,但不得编码主题分支。
 - 添加过渡动画或仅悬停可见的控件时,保留清晰可见的键盘焦点和减少动态效果行为。
+- 支持的引擎上,圆角继承 ui-theme `corner-shape.css` 的全局超级椭圆平滑。每个正圆 `border-radius`(`50%`、`100%` 或胶囊半径)必须配对 `corner-shape: round`,使圆形与胶囊保持圆弧;ui-theme 的 corner-shape spec 强制这一配对。
+- 高层级表面(菜单、浮层、对话框、面板、悬浮按钮、输入框)设 `border: 0` 并使用 `box-shadow: var(--dsw-elevation-panel)`、`var(--dsw-elevation-prominent)` 或输入框专用的 `var(--dsw-elevation-soft)`(更大模糊、更低透明度):0.5px 发丝描边是第一层投影,`--dsw-elevation-stroke-color` 可按表面或状态重绑或抑制描边。不得将 `--dsw-alias-border-*` border 与 lv/elevation 投影配对——ui-theme 的 elevation spec 会拒绝;状态色 border(warn 面板)保持真 border。
+- 使用中性 `--dsw-alias-border-*` token 的平面边框与分割线一律 `0.5px`——按钮、输入框、卡片、行分割线,以及以填充盒绘制的分隔线(菜单分隔、对话标题栏接缝、markdown `hr`、竖向轨道线)共用发丝线粗细,Chromium 将其绘制为一个设备像素。dashed 记号与状态色 border 保持 1px;spinner 圆环经 spec 的显式豁免保留原宽度。更宽的中性 solid border 会被 ui-theme elevation spec 拒绝。
 
 ## 变更系统
 

+ 1 - 1
packages/client/locale/src/client/LanguageRow.module.css

@@ -6,7 +6,7 @@
   align-items: center;
   gap: 8px;
   padding: 16px 0;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .rowText {

+ 17 - 13
packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css

@@ -54,23 +54,25 @@
 }
 
 .card {
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 12px;
+  border: 0.5px solid var(--dsw-alias-border-l4);
+  border-radius: 20px;
   display: flex;
   flex-direction: column;
-  background: var(--dsw-alias-bg-layer-3);
+  background: transparent;
   transition: border-color .16s, background .16s;
 }
 
 
 .card:hover:not(.cardActive) {
-  border-color: var(--dsw-alias-label-dimmed);
+  background: var(--dsw-alias-interactive-bg-hover);
 }
 
-/* The default preset reads as selected, not merely badged. */
+/* The default preset reads as selected exactly like the Appearance cubes:
+   platform fill + the bluish-400 border (static token — the bluish-400 step
+   has no alias-layer name). */
 .cardActive {
-  background: var(--dsw-alias-bg-layer-2);
-  border-color: var(--dsw-alias-label-primary);
+  background: var(--dsw-alias-bg-module-platform);
+  border-color: var(--dsw-static-neutral-bluish-400);
 }
 
 /* A broken preset reads as damaged before anything else: the card cannot be
@@ -85,6 +87,7 @@
 
 .brokenBadge {
   border-radius: 999px;
+  corner-shape: round;
   padding: 1px 8px;
   font-size: 11px;
   line-height: 17px;
@@ -198,6 +201,7 @@
 .badge,
 .inUse {
   border-radius: 999px;
+  corner-shape: round;
   padding: 1px 8px;
   font-size: 11px;
   line-height: 17px;
@@ -206,7 +210,7 @@
 }
 
 .badge {
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   color: var(--dsw-alias-label-tertiary);
 }
 
@@ -240,7 +244,7 @@
   margin-top: auto;
   font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
   font-size: 11px;
-  color: var(--dsw-alias-label-dimmed);
+  color: var(--dsw-alias-label-tertiary);
 }
 
 .cardFoot {
@@ -248,7 +252,7 @@
   justify-content: flex-end;
   gap: 2px;
   padding: 6px 10px;
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 /* Icon-only actions: the label rides `title` so the row stays quiet until
@@ -368,7 +372,7 @@
 .input {
   box-sizing: border-box;
   padding: 9px 12px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 10px;
   font: inherit;
   font-size: 13px;
@@ -401,7 +405,7 @@
   padding: 12px;
   max-height: min(52vh, 480px);
   overflow: auto;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 10px;
   background: var(--dsw-alias-bg-layer-2);
   color: var(--dsw-alias-label-secondary);
@@ -445,7 +449,7 @@
   gap: 6px;
   height: 44px;
   border: 1px dashed var(--dsw-alias-border-l3);
-  border-radius: 12px;
+  border-radius: 20px;
   font: inherit;
   font-size: 14px;
   line-height: 22px;

+ 1 - 0
packages/client/ui-approval/src/client/ApprovalPanel.module.css

@@ -32,6 +32,7 @@
   width: 8px;
   height: 8px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-state-warn-primary);
 }
 

+ 6 - 3
packages/client/ui-attachment/src/AttachmentRail.module.css

@@ -33,7 +33,7 @@
   height: 64px;
   padding: 0;
   overflow: hidden;
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  border: 0.5px solid var(--dsw-alias-border-l2-darkmode-thin);
   border-radius: 16px;
   background: var(--dsw-alias-interactive-bg-hover);
   cursor: zoom-in;
@@ -58,6 +58,7 @@
   padding: 0;
   border: none;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-button-contrast-fill);
   color: var(--dsw-alias-label-primary-inverted);
   cursor: pointer;
@@ -92,11 +93,13 @@
   width: 24px;
   height: 24px;
   padding: 0;
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  border: 0;
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l2-darkmode-thin);
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-specific-input-major);
   color: var(--dsw-alias-label-secondary);
-  box-shadow: var(--dsw-shadow-lv2);
+  box-shadow: var(--dsw-elevation-panel);
   cursor: pointer;
   transform: translateY(-50%);
 }

+ 2 - 1
packages/client/ui-attachment/src/ImageLightbox.module.css

@@ -36,8 +36,9 @@
   place-items: center;
   width: 36px;
   height: 36px;
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  border: 0.5px solid var(--dsw-alias-border-l2-darkmode-thin);
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-specific-input-major);
   color: var(--dsw-alias-label-primary);
   cursor: pointer;

+ 2 - 2
packages/client/ui-attachment/src/MessageImage.module.css

@@ -23,7 +23,7 @@
   min-height: 44px;
   padding: 0;
   overflow: hidden;
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  border: 0.5px solid var(--dsw-alias-border-l2-darkmode-thin);
   border-radius: 16px;
   background: var(--dsw-alias-interactive-bg-hover);
   cursor: zoom-in;
@@ -53,7 +53,7 @@
 .error {
   max-width: 240px;
   padding: 10px 12px;
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  border: 0.5px solid var(--dsw-alias-border-l2-darkmode-thin);
   border-radius: 10px;
   background: var(--dsw-alias-interactive-bg-hover-danger);
   cursor: pointer;

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

@@ -202,11 +202,13 @@
   height: 34px;
   margin-top: -34px;
   padding: 0;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0;
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l3);
   border-radius: 100px;
+  corner-shape: round;
   color: var(--dsw-alias-label-primary);
   background: var(--dsw-alias-button-floating-fill);
-  box-shadow: var(--dsw-shadow-lv2);
+  box-shadow: var(--dsw-elevation-panel);
   cursor: pointer;
   pointer-events: auto;
 }

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

@@ -16,7 +16,7 @@
   gap: 2px;
   margin: 8px 0 0;
   padding-top: 8px;
-  border-top: 1px solid var(--dsw-alias-line-secondary);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .field {

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

@@ -71,7 +71,7 @@
   margin: 4px 0 4px 4px;
   padding: 12px 16px;
   overflow: auto;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 12px;
   background: var(--dsw-alias-markdown-code-block);
   color: var(--dsw-alias-label-primary);

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

@@ -167,11 +167,11 @@
   max-height: var(--turn-preview-height);
   overflow: hidden;
   padding: 10px 12px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0;
   border-radius: 10px;
   color: var(--dsw-alias-label-primary);
   background: var(--dsw-alias-bg-layer-1);
-  box-shadow: var(--dsw-shadow-lv2);
+  box-shadow: var(--dsw-elevation-panel);
   pointer-events: none;
   animation: dsh-turn-preview-enter 120ms ease-out;
   transition: top 140ms cubic-bezier(0.2, 0.8, 0.2, 1);

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

@@ -7,7 +7,7 @@
   height: 33px;
   padding: 0 0 8px;
   border: none;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   background: none;
   color: var(--dsw-alias-label-secondary);
   cursor: pointer;

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

@@ -1,5 +1,5 @@
 /* Icon-row Turn-usage pill (data icon + turn total) plus its click-open
-   Turn-details dialog (menu surface: r12, inverted hairline, shadow-lv3 —
+   Turn-details dialog (menu surface: r12, elevation-prominent —
    ContextMeter's panel skin). */
 
 .root {
@@ -90,10 +90,11 @@
   min-width: min(300px, calc(100vw - 24px));
   max-width: min(440px, calc(100vw - 24px));
   padding: 16px;
-  border: 1px solid var(--dsw-alias-border-inverted);
+  border: 0;
   border-radius: 12px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
   font-size: 12px;
   line-height: 18px;
   color: var(--dsw-alias-label-secondary);
@@ -114,7 +115,7 @@
 /* Rule under each section heading, above its rows. */
 .titleRule {
   margin-bottom: 10px;
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .titleValue {

+ 3 - 2
packages/client/ui-chat/src/client/details/DetailsPanel.module.css

@@ -7,7 +7,7 @@
   flex-direction: column;
   height: 100%;
   min-width: 0;
-  border-left: 1px solid var(--dsw-alias-border-l2);
+  border-left: 0.5px solid var(--dsw-alias-border-l2);
   background: var(--dsw-alias-bg-base);
 }
 
@@ -18,7 +18,7 @@
   justify-content: space-between;
   gap: 8px;
   padding: 14px 12px 12px;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 /* figma I54:42735;43:41479: 14/20 wt500. */
@@ -40,6 +40,7 @@
   height: 28px;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-secondary);
   cursor: pointer;

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

@@ -5,7 +5,7 @@
   align-items: center;
   gap: 8px;
   padding: 16px 0;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .rowText {

+ 7 - 6
packages/client/ui-commands/src/client/PopupSelectView.module.css

@@ -1,6 +1,6 @@
 /* Official popupSelect shell card: menu-surface tokens (same family as
  * ui-primitives Menu.module.css — figma MenuDropdown r12 / hairline /
- * shadow-lv3), anchored by the conversation.input.overlay slot. */
+ * elevation-prominent), anchored by the conversation.input.overlay slot. */
 
 .card {
   /* The overlay anchor is a zero-height strip on the composer card's top
@@ -24,10 +24,11 @@
      (see ui-theme styles/scrollbar.css for the rebinding contract). */
   --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
   --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
-  border: 1px solid var(--dsw-alias-border-inverted);
-  border-radius: 12px;
+  border: 0;
+  border-radius: 20px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
   outline: none;
 }
 
@@ -84,7 +85,7 @@
 .search {
   margin: 2px 2px 4px;
   padding: 6px 8px;
-  border: 1px solid var(--dsw-alias-border-inverted);
+  border: 0.5px solid var(--dsw-alias-border-inverted);
   border-radius: 8px;
   background: transparent;
   font-size: 13px;
@@ -109,7 +110,7 @@
 
 .retry {
   padding: 2px 8px;
-  border: 1px solid var(--dsw-alias-border-inverted);
+  border: 0.5px solid var(--dsw-alias-border-inverted);
   border-radius: 6px;
   background: transparent;
   font-size: 12px;

+ 4 - 3
packages/client/ui-conversation/src/client/queue/QueueDock.module.css

@@ -39,7 +39,7 @@
 .panel::after {
   position: absolute;
   inset: 0;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   /* The input card's own top border closes the shape below. */
   border-bottom: none;
   border-radius: inherit;
@@ -131,7 +131,7 @@
 .thumb {
   width: 24px;
   height: 24px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 4px;
   background: var(--dsw-alias-bg-base);
   object-fit: cover;
@@ -157,7 +157,7 @@
   box-sizing: border-box;
   height: 28px;
   padding: 0 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 6px;
   outline: none;
   background: var(--dsw-alias-bg-base);
@@ -184,6 +184,7 @@
   padding: 0;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-tertiary);
   cursor: pointer;

+ 1 - 1
packages/client/ui-conversation/src/client/settings/EnterBehaviorRow.module.css

@@ -5,7 +5,7 @@
   align-items: center;
   gap: 8px;
   padding: 16px 0;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .rowText {

+ 6 - 3
packages/client/ui-conversation/src/client/skeleton/ContextMeter.module.css

@@ -1,5 +1,5 @@
 /* Context-occupancy ring beside the send button plus its click-open breakdown
-   panel (menu surface: r12, inverted hairline, shadow-lv3). */
+   panel (menu surface: r12, elevation-prominent). */
 
 .root {
   position: relative;
@@ -15,6 +15,7 @@
   height: 28px;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-secondary);
   cursor: pointer;
@@ -45,10 +46,11 @@
   box-sizing: border-box;
   width: 264px;
   padding: 12px;
-  border: 1px solid var(--dsw-alias-border-inverted);
+  border: 0;
   border-radius: 12px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
   font-size: 12px;
   line-height: 20px;
   color: var(--dsw-alias-label-secondary);
@@ -89,6 +91,7 @@
   margin: 10px 0 12px;
   height: 4px;
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-alias-interactive-bg-hover);
   overflow: hidden;
 }

+ 2 - 2
packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css

@@ -48,8 +48,8 @@
   bottom: 1px;
   left: 0;
   z-index: 0;
-  height: 1px;
-  background: var(--dsw-alias-border-l2);
+  height: 0.5px;
+  background: var(--dsw-alias-border-l3);
   pointer-events: none;
 }
 

+ 2 - 2
packages/client/ui-conversation/src/client/skeleton/HeroShell.module.css

@@ -50,7 +50,7 @@
   margin-top: 2px;
   margin-left: -3px;
   padding: 1px 7px 0;
-  border: 1px solid var(--dsw-alias-interactive-bg-hover);
+  border: 0.5px solid var(--dsw-alias-interactive-bg-hover);
   border-radius: 24px;
   background: var(--dsw-alias-state-business-tertiary);
   color: var(--dsw-alias-label-primary-bluish);
@@ -185,7 +185,7 @@
   width: 100%;
   height: 44px;
   padding: 7px 14px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 22px;
   outline: none;
   background: transparent;

+ 10 - 6
packages/client/ui-conversation/src/client/skeleton/InputBar.module.css

@@ -40,13 +40,14 @@
   width: 100%;
   max-width: var(--dsh-composer-card-max-width);
   padding-top: 10px;
-  /* Input stroke: black/0.10 light, white/0.06 dark (figma darkmode note says
-     the input border is one notch weaker than buttons) — exactly the
-     l2-darkmode-thin pair. Fill: the input surface token (elevated in dark). */
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  /* Input stroke: the light neutral (l2, one step above the menus' l1),
+     drawn as the elevation hairline so it costs no layout; the shadow is the
+     soft elevation tier. Fill: the input surface token (elevated in dark). */
+  border: 0;
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l2);
   border-radius: 22px;
   background: var(--dsw-specific-input-major);
-  box-shadow: var(--dsw-shadow-lv2);
+  box-shadow: var(--dsw-elevation-soft);
   /* The draft text follows the Settings font-size preference (the textarea,
      mirror, and backdrop all inherit from here, so the three layers stay in
      step); the line height keeps the default 10px leading via the shared px
@@ -69,7 +70,7 @@
    by an SVG dash ring (stroke-width 2 centered on the box edge = 1px visible
    inside), which keeps the 22px radius and both themes. */
 .cardWorkspaceTrigger {
-  border-color: transparent;
+  --dsw-elevation-stroke-color: transparent;
   cursor: pointer;
 }
 
@@ -141,6 +142,7 @@
   width: 8px;
   height: 8px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-state-business-primary);
   animation: input-pending 1s ease-in-out infinite alternate;
 }
@@ -266,6 +268,7 @@
   height: 28px;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-specific-selector);
   color: var(--dsw-alias-label-primary);
   cursor: pointer;
@@ -323,6 +326,7 @@
   height: 34px;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-alias-button-info-fill);
   /* Static white, not the foreground token: the arrow stays white on the blue
      fill in both themes (design 34:10465). */

+ 1 - 1
packages/client/ui-conversation/src/client/skeleton/TodoPanel.module.css

@@ -19,7 +19,7 @@
     var(--dsh-composer-dock-inset) -
     var(--dsh-composer-dock-inset)
   );
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 12px;
   background: var(--dsw-specific-tip);
   /* Elevated surface: `--dsw-specific-tip` is the same dark rung as the menu

+ 4 - 4
packages/client/ui-directory-picker-browse/src/client/DirectoryBrowser.module.css

@@ -29,7 +29,7 @@
   gap: 8px;
   flex: none;
   padding: 16px 14px 8px 24px;
-  border-bottom: 1px solid var(--dsw-alias-border-l3);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l3);
 }
 
 .title {
@@ -215,7 +215,7 @@
 
 .divider {
   flex: none;
-  width: 1px;
+  width: 0.5px;
   background: var(--dsw-alias-border-l3);
 }
 
@@ -323,7 +323,7 @@
   gap: 8px;
   flex: none;
   padding: 16px 24px;
-  border-top: 1px solid var(--dsw-alias-border-l3);
+  border-top: 0.5px solid var(--dsw-alias-border-l3);
 }
 
 /* Show-hidden toggle: a subtle fixed-label text button left of the gap;
@@ -400,7 +400,7 @@
   width: 100%;
   height: 44px;
   padding: 7px 14px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 22px;
   outline: none;
   background: transparent;

+ 3 - 2
packages/client/ui-goal/src/client/GoalBar.module.css

@@ -26,7 +26,7 @@
   height: 36px;
   margin: 0 auto;
   padding: 4px 5px 4px 12px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 12px;
   background: var(--dsw-specific-tip);
 }
@@ -76,7 +76,7 @@
   min-width: 0;
   height: 26px;
   padding: 0 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 6px;
   background: var(--dsw-alias-bg-base);
   font-size: 13px;
@@ -111,6 +111,7 @@
   padding: 0;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-tertiary);
   cursor: pointer;

+ 5 - 4
packages/client/ui-input-trigger/src/client/MenuView.module.css

@@ -17,10 +17,11 @@
   padding: 4px;
   display: flex;
   flex-direction: column;
-  border: 1px solid var(--dsw-alias-border-inverted);
-  border-radius: 12px;
+  border: 0;
+  border-radius: 20px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
 }
 
 .viewport {
@@ -198,7 +199,7 @@
   margin-bottom: 2px;
   /* l1, not border-inverted (the menu frame's dark-only stroke): the divider
      needs to survive light mode too; in dark both resolve to white/0.06. */
-  border-bottom: 1px solid var(--dsw-alias-border-l1);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l1);
 }
 /* Crumbs sit outside the listbox and take no keyboard highlight, so unlike
    .item they carry their own :hover tint with nothing to compete with. */

+ 4 - 3
packages/client/ui-jobs/src/client/JobListAction.module.css

@@ -54,12 +54,13 @@
   padding: 4px;
   overflow: auto;
   list-style: none;
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 12px;
+  border: 0;
+  border-radius: 20px;
   background: var(--dsw-specific-menu);
   --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
   --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
 }
 
 .row {

+ 3 - 3
packages/client/ui-layout/src/client/AppFrame.module.css

@@ -26,7 +26,7 @@
   min-width: 0;
   overflow: hidden;
   background: var(--dsw-specific-sidebar-fill);
-  border-right: 1px solid var(--dsw-alias-border-l1);
+  border-right: 0.5px solid var(--dsw-alias-border-l3);
 }
 
 .centerCol {
@@ -39,7 +39,7 @@
 .detailsCol {
   min-width: 0;
   overflow: hidden;
-  border-left: 1px solid var(--dsw-alias-border-l2);
+  border-left: 0.5px solid var(--dsw-alias-border-l3);
 }
 
 /* The details subtree stays mounted at zero width, so its border must not paint
@@ -86,7 +86,7 @@
   border-radius: 10px;
   box-sizing: border-box;
   background: var(--dsw-alias-button-floating-fill);
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  border: 0.5px solid var(--dsw-alias-border-l2-darkmode-thin);
   /* Hover affordance: the details pill hides until the pointer is over its
      column, the strip itself, or a drag. */
   opacity: 0;

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

@@ -78,10 +78,11 @@
   display: flex;
   flex-direction: column;
   gap: 8px;
-  border: 1px solid var(--dsw-alias-border-inverted);
+  border: 0;
   border-radius: 12px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
   --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
   --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
 }
@@ -90,7 +91,7 @@
   width: 100%;
   box-sizing: border-box;
   padding: 6px 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 8px;
   background: var(--dsw-alias-bg-layer-1);
   color: var(--dsw-alias-label-primary);

+ 4 - 3
packages/client/ui-model-selection/src/client/ModelSelect.module.css

@@ -82,10 +82,11 @@
   padding: 4px;
   /* Surface tokens match the Menu primitive card (ui-primitives
    * Menu.module.css) so every dropdown reads as the same material. */
-  border: 1px solid var(--dsw-alias-border-inverted);
-  border-radius: 12px;
+  border: 0;
+  border-radius: 20px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
   color: var(--dsw-alias-label-primary);
   /* Elevated surface: the scrollbar thumb takes the l2 elevation tokens.
      Declared here rather than on the scrolling `.groups` child so the

+ 1 - 1
packages/client/ui-permission-presets/src/client/PermissionRow.module.css

@@ -5,7 +5,7 @@
   align-items: center;
   gap: 8px;
   padding: 16px 0;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .rowText {

+ 1 - 0
packages/client/ui-plan/src/client/PlanModeControl.module.css

@@ -14,6 +14,7 @@
   padding: 2px 8px;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-alias-state-warn-tertiary);
   color: var(--dsw-alias-state-warn-label);
   font-size: 13px;

+ 1 - 1
packages/client/ui-primitives/src/Button.module.css

@@ -54,7 +54,7 @@
 
 /* Dialog Cancel (figma 451:18655): bordered capsule on transparent fill. */
 .outline {
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   background: transparent;
 }
 

+ 1 - 1
packages/client/ui-primitives/src/Input.module.css

@@ -4,7 +4,7 @@
   gap: 6px;
   height: 32px;
   padding: 0 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 8px;
   background: var(--dsw-alias-bg-layer-1);
 }

+ 7 - 6
packages/client/ui-primitives/src/Menu.module.css

@@ -4,7 +4,7 @@
 }
 
 /* Dropdown card (figma MenuDropdown 122:9481 / 419:16920): menu surface,
- * r12, inverted hairline border, shadow-lv3, 4px inset padding. */
+ * r12, elevation-prominent (hairline stroke in shadow), 4px inset padding. */
 .list,
 .submenu {
   /* min-widths below are the design's outer card widths — include the pad. */
@@ -13,10 +13,11 @@
   display: flex;
   flex-direction: column;
   gap: 0;
-  border: 1px solid var(--dsw-alias-border-inverted);
-  border-radius: 12px;
+  border: 0;
+  border-radius: 20px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
   /* Elevated surface: the scrollbar thumb takes the l2 elevation tokens. The
      declaration sits on the card rather than on `.scrollable .viewport`
      because the elevation is a property of this surface, and the custom
@@ -85,7 +86,7 @@
   flex-direction: column;
   margin-top: 4px;
   padding-top: 4px;
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .itemWrap {
@@ -212,7 +213,7 @@
 
 /* Separator cell (figma 122:9481): py 4 / px 2 around the hairline. */
 .separator {
-  height: 1px;
+  height: 0.5px;
   margin: 4px 2px;
   background: var(--dsw-alias-border-l1);
 }

+ 3 - 3
packages/client/ui-primitives/src/Modal.module.css

@@ -18,7 +18,7 @@
   backdrop-filter: var(--dsw-mask-blur);
 }
 
-/* Dialog card: r24, shadow-lv3, layer-2 fill, inverted border, pb 24. */
+/* Dialog card: r24, elevation-prominent, layer-2 fill, pb 24. */
 .dialog {
   position: relative;
   z-index: 1;
@@ -28,10 +28,10 @@
   width: min(380px, 100%);
   padding: 0 0 24px;
   overflow: hidden;
-  border: 1px solid var(--dsw-alias-border-inverted);
+  border: 0;
   border-radius: 24px;
   background: var(--dsw-alias-bg-layer-2);
-  box-shadow: var(--dsw-shadow-lv3);
+  box-shadow: var(--dsw-elevation-prominent);
 }
 
 .content {

+ 2 - 0
packages/client/ui-primitives/src/StateDot.module.css

@@ -18,6 +18,7 @@
   position: absolute;
   inset: 0;
   border-radius: 50%;
+  corner-shape: round;
   background: currentColor;
   opacity: 0.1;
 }
@@ -27,6 +28,7 @@
   position: absolute;
   inset: 20%;
   border-radius: 50%;
+  corner-shape: round;
   background: currentColor;
 }
 

+ 1 - 1
packages/client/ui-primitives/src/TerminalBlock.module.css

@@ -62,7 +62,7 @@
    divider the IN/OUT card draws between its sections. A running card is
    banner-only, so it draws none. */
 .block:not([data-running]) .header {
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 /* One row per command line. The prompt column is the only element allowed to

+ 1 - 1
packages/client/ui-primitives/src/markdown/JsonBlock.module.css

@@ -23,7 +23,7 @@
   max-height: 200px;
   overflow: auto;
   background: var(--dsw-alias-markdown-code-block);
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 6px;
   font-family: var(--ds-font-family-code);
   font-size: 11px;

+ 3 - 3
packages/client/ui-primitives/src/markdown/MarkdownText.module.css

@@ -126,7 +126,7 @@
 .markdown hr {
   display: block;
   border: none;
-  height: 1px;
+  height: 0.5px;
   margin: 32px 0;
   background: var(--dsw-alias-border-l2);
 }
@@ -225,7 +225,7 @@
 .tableScroll th {
   text-align: start;
   padding: 10px 16px;
-  border-bottom: 1px solid var(--dsw-alias-border-l3);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l3);
   border-top: none;
   font: var(--dsw-font-markdown-table-head);
   max-width: 320px;
@@ -235,7 +235,7 @@
 
 .tableScroll td {
   padding: 10px 16px;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   font: var(--dsw-font-markdown-table);
   max-width: 320px;
   max-width: min(30vw, 320px);

+ 5 - 3
packages/client/ui-schedule/src/client/ScheduleCatalogAction.module.css

@@ -52,12 +52,13 @@
   padding: 4px;
   overflow: auto;
   list-style: none;
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 12px;
+  border: 0;
+  border-radius: 20px;
   background: var(--dsw-specific-menu);
   --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
   --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
 }
 
 .row {
@@ -91,6 +92,7 @@
   height: 8px;
   flex: none;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-state-business-primary);
 }
 

+ 5 - 4
packages/client/ui-settings-general/src/client/SettingsRoot.module.css

@@ -48,6 +48,7 @@
   gap: 0;
   padding: 0;
   border-radius: 50%;
+  corner-shape: round;
 }
 
 .triggerLabel {
@@ -73,8 +74,8 @@
   backdrop-filter: var(--dsw-mask-blur);
 }
 
-/* Panel (figma Settings 501:29947): r24, white, lv3 shadow (figma effects
-   match --dsw-shadow-lv3 exactly); figma's 1080x700 is shrunk to 800 wide.
+/* Panel (figma Settings 501:29947): r24, white; the figma lv3 effects are
+   superseded by the shared elevation treatment; figma's 1080x700 is shrunk to 800 wide.
    One height for every section, taken from the viewport rather than the
    content: sections differ by hundreds of pixels (a settings list against the
    composition editor), and a content-sized panel would resize under the
@@ -86,10 +87,10 @@
   width: 800px;
   height: min(800px, calc(100vh - 48px));
   max-width: calc(100vw - 48px);
-  border-radius: 24px;
+  border-radius: 32px;
   overflow: hidden;
   background: var(--dsw-alias-bg-layer-2);
-  box-shadow: var(--dsw-shadow-lv3);
+  box-shadow: var(--dsw-elevation-prominent);
   /* Elevated surface: the scrollbar thumb takes the l2 elevation tokens.
      Declared on the panel rather than the scrolling `.options` child so the
      elevation choice sits with the surface; the custom properties inherit

+ 12 - 11
packages/client/ui-settings-models/src/client/ModelsSection.module.css

@@ -53,8 +53,8 @@
 /* A configured provider: outlined on the panel fill, so the filled editor
    card it expands into reads as the nested object. */
 .rowCard {
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 12px;
+  border: 0.5px solid var(--dsw-alias-border-l4);
+  border-radius: 16px;
   padding: 12px 14px;
   display: flex;
   flex-direction: column;
@@ -88,7 +88,7 @@
 .rowTag {
   flex: none;
   padding: 1px 6px;
-  border: 1px solid var(--dsw-alias-border-l3);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   border-radius: 4px;
   font-size: 11px;
   line-height: 16px;
@@ -102,6 +102,7 @@
   width: 8px;
   height: 8px;
   border-radius: 50%;
+  corner-shape: round;
 }
 
 .credentialDotConfigured {
@@ -151,7 +152,7 @@
 
 .secondaryButton,
 .addButton {
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   background: transparent;
   color: var(--dsw-alias-label-primary);
 }
@@ -323,7 +324,7 @@
   gap: 6px;
   height: 44px;
   border: 1px dashed var(--dsw-alias-border-l3);
-  border-radius: 12px;
+  border-radius: 16px;
 }
 
 .addCard,
@@ -345,7 +346,7 @@
 }
 
 .customized {
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
   padding-top: 10px;
 }
 
@@ -406,7 +407,7 @@
   flex-direction: column;
   gap: 10px;
   padding-top: 12px;
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .modelCatalogHeading {
@@ -450,8 +451,8 @@
 }
 
 .modelEntry {
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 8px;
+  border: 0.5px solid var(--dsw-alias-border-l4);
+  border-radius: 10px;
   padding: 6px;
 }
 
@@ -528,7 +529,7 @@
   gap: 4px;
   height: 28px;
   padding: 0 10px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   border-radius: 14px;
   background: transparent;
   color: var(--dsw-alias-label-primary);
@@ -547,7 +548,7 @@
   width: 100%;
   height: 32px;
   padding: 0 10px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 8px;
   font: inherit;
   font-size: 14px;

+ 1 - 1
packages/client/ui-settings-models/tests/styles.client.spec.ts

@@ -57,7 +57,7 @@ describe('ModelsSection theme styles', () => {
     // under the dark theme, so filling the row with either erases the nested
     // editor's boundary. The row is outlined; the fill is the editor's alone.
     expect(block('.editor')).toContain('background: var(--dsw-alias-bg-module-platform)')
-    expect(block('.rowCard')).toContain('border: 1px solid var(--dsw-alias-border-l2)')
+    expect(block('.rowCard')).toContain('border: 0.5px solid var(--dsw-alias-border-l4)')
     expect(block('.rowCard')).not.toMatch(/\bbackground\s*:/)
   })
 

+ 11 - 9
packages/client/ui-settings-plugin-inventory/src/client/PluginInventorySettingsTab.module.css

@@ -28,7 +28,7 @@
 }
 
 .failure button {
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   border-radius: 6px;
   padding: 4px 10px;
   background: transparent;
@@ -60,8 +60,8 @@
 .search input {
   width: 100%;
   height: 36px;
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 8px;
+  border: 0.5px solid var(--dsw-alias-border-l4);
+  border-radius: 10px;
   padding: 0 34px 0 36px;
   outline: none;
   background: var(--dsw-alias-bg-layer-1);
@@ -112,14 +112,15 @@
 .card {
   min-width: 0;
   overflow: hidden;
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 10px;
+  border: 0;
+  box-shadow: var(--dsw-elevation-stroke);
+  border-radius: 14px;
   background: var(--dsw-alias-bg-layer-3);
 }
 
 .card[data-open='true'] {
-  border-color: var(--dsw-alias-border-l1);
-  box-shadow: var(--dsw-shadow-lv1);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-panel);
 }
 
 .cardContent {
@@ -173,6 +174,7 @@
   height: 7px;
   flex: none;
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-alias-label-tertiary);
 }
 
@@ -235,7 +237,7 @@
 }
 
 .group + .group {
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
   padding-top: 14px;
 }
 
@@ -393,7 +395,7 @@
 }
 
 .cardDetails {
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
   padding: 10px 14px 12px;
   background: var(--dsw-alias-bg-module-platform);
 }

+ 5 - 4
packages/client/ui-settings-plugins/src/client/PluginCard.module.css

@@ -2,8 +2,8 @@
 
 .card {
   list-style: none;
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 12px;
+  border: 0.5px solid var(--dsw-alias-border-l4);
+  border-radius: 16px;
   background: var(--dsw-alias-bg-layer-3);
   transition: border-color .16s, background .16s;
 }
@@ -73,7 +73,7 @@
 }
 
 .body {
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
   margin: 0 16px;
   padding-bottom: 8px;
 }
@@ -89,6 +89,7 @@
 .pending {
   flex: none;
   border-radius: 999px;
+  corner-shape: round;
   padding: 1px 8px;
   font-size: 11px;
   line-height: 17px;
@@ -104,7 +105,7 @@
   justify-content: flex-end;
   gap: 8px;
   padding: 12px 0 4px;
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .failed {

+ 1 - 1
packages/client/ui-settings-plugins/src/client/PluginsSettingsSection.module.css

@@ -24,7 +24,7 @@
   display: flex;
   align-items: flex-end;
   gap: 22px;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   margin-top: 2px;
 }
 

+ 3 - 2
packages/client/ui-settings-plugins/src/client/SubagentModelSelectionCard.module.css

@@ -51,6 +51,7 @@
   width: 16px;
   height: 16px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-label-primary-foreground);
   transition: transform 120ms ease;
 }
@@ -108,7 +109,7 @@
   margin: 0;
   padding: 10px;
   overflow: auto;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 8px;
 }
 
@@ -126,7 +127,7 @@
 .modelGroup + .modelGroup {
   margin-top: 4px;
   padding-top: 10px;
-  border-top: 1px solid var(--dsw-alias-border-l3);
+  border-top: 0.5px solid var(--dsw-alias-border-l3);
 }
 
 .providerName {

+ 4 - 2
packages/client/ui-settings-plugins/src/client/fields.module.css

@@ -8,7 +8,7 @@
 }
 
 .field + .field {
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .head {
@@ -34,6 +34,7 @@
 
 .badge {
   border-radius: 999px;
+  corner-shape: round;
   padding: 1px 8px;
   font-size: 11px;
   line-height: 17px;
@@ -45,6 +46,7 @@
 
 .badgeMuted {
   border-radius: 999px;
+  corner-shape: round;
   padding: 1px 8px;
   font-size: 11px;
   line-height: 17px;
@@ -74,7 +76,7 @@
 .input {
   height: 34px;
   padding: 0 12px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 8px;
   background: var(--dsw-alias-bg-layer-3);
   font: inherit;

+ 2 - 1
packages/client/ui-sidebar/src/client/SidebarRoot.module.css

@@ -185,6 +185,7 @@
   height: 28px;
   border: none;
   border-radius: 50%;
+  corner-shape: round;
   padding: 0;
   background: transparent;
   cursor: pointer;
@@ -255,7 +256,7 @@
   padding: 8px 16px;
   margin: 0 2px 8px;
   box-sizing: border-box;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   border-radius: 12px;
   background: var(--dsw-alias-button-elevated-fill);
   color: var(--dsw-alias-label-primary);

+ 4 - 3
packages/client/ui-skill/src/client/SkillRow.module.css

@@ -118,7 +118,7 @@
   max-height: 260px;
   margin: 4px 0 4px 4px;
   overflow: hidden;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 12px;
   background: var(--dsw-alias-markdown-code-block);
 }
@@ -126,7 +126,7 @@
 .instructionsHeader {
   flex: none;
   padding: 8px 12px;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   background: var(--dsw-alias-markdown-code-block-banner);
   font-size: 11px;
   font-weight: 500;
@@ -168,8 +168,9 @@
   gap: 4px;
   margin: 4px 0 2px 4px;
   padding: 2px 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-alias-bg-base);
   color: var(--dsw-alias-label-secondary);
   font-size: 11px;

+ 5 - 4
packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css

@@ -103,11 +103,12 @@
   max-height: min(560px, calc(100vh - 140px));
   padding: 4px;
   overflow: auto;
-  border-radius: 12px;
+  border-radius: 20px;
   background: var(--dsw-specific-menu);
   --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
   --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
 }
 
 .node {
@@ -260,7 +261,7 @@
   content: '';
   position: absolute;
   left: 0;
-  border-left: 1px solid var(--dsw-alias-border-l2);
+  border-left: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .children::before {
@@ -289,7 +290,7 @@
   top: 16px;
   left: -4px;
   width: 14px;
-  border-top: 1px solid var(--dsw-alias-border-l2);
+  border-top: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .notice,

+ 1 - 1
packages/client/ui-subagent/src/client/SubagentReadOnlyComposer.module.css

@@ -6,7 +6,7 @@
   margin: 0 24px 20px;
   min-height: 54px;
   padding: 10px 16px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 14px;
   background: var(--dsw-alias-bg-layer-1);
   color: var(--dsw-alias-label-tertiary);

+ 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: 8d635f4523fe89c9c6d711f32f8d9cf6e4cfb627
-README.zh.md: dec856fde15aae690096bdb52fc84c00c66336ab
+README.md: c591501e274de0419ea3585feb731802985ac95b
+README.zh.md: e970141963cc592541e77d748c421e693cc9568f

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

@@ -51,9 +51,11 @@ The service owns theme and font-size state and publishes snapshots. The ui-layou
 
 ### Stylesheets
 
-`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.
+`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` 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. Outside the ladder, the user bubble and composer draft read the body pair directly, and flow-row titles and summaries read the secondary pair.
+`corner-shape.css` smooths every rounded corner: inside `@supports (corner-shape: superellipse(1.5))` it defines `--dsw-corner-shape` and applies it to all elements and their `::before`/`::after` through the universal selector, so engines without `corner-shape` keep circular corners. Full-round shapes — `border-radius: 50%` circles and pill radii — pair `corner-shape: round` with their radius in the owning component sheet because a superellipse deforms them; the corner-shape stylesheet spec enforces that pairing across every package stylesheet ([corner-smoothing note](../../../.agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.md)).
+
+`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. The sheet also owns the shadow scale (`--dsw-shadow-lv*`) and the elevation tokens: `--dsw-elevation-stroke` draws a 0.5px hairline through the rebindable `--dsw-elevation-stroke-color`, and `--dsw-elevation-panel`/`--dsw-elevation-prominent`/`--dsw-elevation-soft` (the composer's larger-blur, lower-alpha tier) layer two faint soft shadows over that stroke, so elevated surfaces set `border: 0` and carry no layout-consuming outline; the derived tokens are re-declared per element so a surface's stroke-color rebind takes effect ([elevation note](../../../.agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.md)).
 
 ### Scrollbar rebinding
 

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

@@ -51,9 +51,11 @@ 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` 之后。
+`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` 之后。
 
-`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 草稿直接读取正文档变量对,流内行的标题及摘要读取低一档变量对。
+`corner-shape.css` 平滑所有圆角:在 `@supports (corner-shape: superellipse(1.5))` 内定义 `--dsw-corner-shape`,并通过通配选择器应用到所有元素及其 `::before`/`::after`,因此不支持 `corner-shape` 的引擎保持普通圆弧。正圆形状——`border-radius: 50%` 的圆与胶囊半径——因超级椭圆会使其变形,须在所属组件样式表中把 `corner-shape: round` 与半径声明配对;corner-shape 样式表 spec 跨全部包样式表强制这一配对([圆角平滑笔记](../../../.agents/notes/implemented/feature/2026-09-01-web-superellipse-corner-smoothing.zh.md))。
+
+`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 草稿直接读取正文档变量对,流内行的标题及摘要读取低一档变量对。该表还持有阴影阶(`--dsw-shadow-lv*`)与 elevation token:`--dsw-elevation-stroke` 经可重绑的 `--dsw-elevation-stroke-color` 画 0.5px 发丝描边,`--dsw-elevation-panel`/`--dsw-elevation-prominent`/`--dsw-elevation-soft`(输入框专用的更大模糊、更低透明度档)在描边之上叠两层极淡柔光,因此高层级表面设 `border: 0`,不再有占布局的轮廓;派生 token 逐元素重声明,使表面对描边色的重绑真实生效([elevation 笔记](../../../.agents/notes/implemented/feature/2026-09-01-web-elevation-stroke-shadows.zh.md))。
 
 ### 滚动条重新绑定
 

+ 3 - 3
packages/client/ui-theme/src/client/AppearanceRow.module.css

@@ -6,7 +6,7 @@
   flex-direction: column;
   gap: 8px;
   padding: 16px 0;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .title {
@@ -35,8 +35,8 @@
   justify-content: center;
   gap: 4px;
   padding: 20px 32px;
-  border: 1px solid var(--dsw-alias-border-l2);
-  border-radius: 16px;
+  border: 0.5px solid var(--dsw-alias-border-l4);
+  border-radius: 20px;
   background: transparent;
   font: inherit;
   font-size: 14px;

+ 1 - 1
packages/client/ui-theme/src/client/FontSizeRow.module.css

@@ -7,7 +7,7 @@
   align-items: center;
   gap: 8px;
   padding: 16px 0;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .rowText {

+ 2 - 0
packages/client/ui-theme/src/client/styles.ts

@@ -1,5 +1,6 @@
 import type { Context } from '@deepseek-ai/cordis'
 import base from '../styles/base.css?inline'
+import cornerShape from '../styles/corner-shape.css?inline'
 import designPlatform from '../styles/design-platform.css?inline'
 import scrollbar from '../styles/scrollbar.css?inline'
 import gradientShadowText from '../styles/gradient-shadow-text.css?inline'
@@ -9,6 +10,7 @@ const PLUGIN_ID = '@deepseek-ai/dsh-client-ui-theme'
 
 const STYLES = [
   ['base.css', base],
+  ['corner-shape.css', cornerShape],
   ['design-platform.css', designPlatform],
   ['scrollbar.css', scrollbar],
   ['gradient-shadow-text.css', gradientShadowText],

+ 26 - 0
packages/client/ui-theme/src/styles/corner-shape.css

@@ -0,0 +1,26 @@
+/* Smooth corner curvature for every rounded surface. Inside the support
+ * guard, all corners curve along superellipse(1.5) — between a circular arc
+ * (round = superellipse(1)) and a squircle (superellipse(2)) — instead of the
+ * default arc; engines without corner-shape keep circular corners because the
+ * token and the rule exist only inside the guard.
+ *
+ * corner-shape does not inherit, so the universal selector applies the token
+ * to elements and their ::before/::after; other pseudo-elements (for example
+ * the ::-webkit-scrollbar parts) stay circular.
+ *
+ * Full-round shapes opt out where they are declared: a superellipse deforms a
+ * circle into a squircle and squares off capsule ends, so every effectively
+ * uncapped radius (50%, 100%, or a pill radius far above the box size) pairs
+ * `corner-shape: round` with its `border-radius` in the owning component
+ * sheet. The corner-shape stylesheet spec enforces that pairing. */
+@supports (corner-shape: superellipse(1.5)) {
+  :root {
+    --dsw-corner-shape: superellipse(1.5);
+  }
+
+  *,
+  *::before,
+  *::after {
+    corner-shape: var(--dsw-corner-shape);
+  }
+}

+ 23 - 0
packages/client/ui-theme/src/styles/gradient-shadow-text.css

@@ -7,10 +7,33 @@ body {
   --dsw-shadow-lv2: 0 4px 12px 0 rgba(0, 0, 0, 0.02), 0 2px 8px 0 rgba(0, 0, 0, 0.04);
   --dsw-shadow-lv3:
     0 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 4px 0 rgba(0, 0, 0, 0.02), 0 12px 32px 0 rgba(0, 0, 0, 0.08);
+  /* Elevation:浮层/面板/面型按钮的描边+投影三件套。0.5px 发丝描边画在
+     box-shadow 里(组件配 border: 0,不占布局),叠两层极淡柔光;深色主题下
+     柔光几乎不可见,由描边承担分离。描边颜色经 --dsw-elevation-stroke-color
+     间接,组件可重绑(菜单面重绑最浅的 l1、输入框重绑 l2,输入框的
+     workspace-trigger 态置 transparent 只保留柔光)。soft 供输入框:更大
+     模糊、更低透明度的柔光。默认色只声明在 body 上,让表面的重绑沿继承
+     传给真正消费投影的后代。 */
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l4);
   /* blur filter */
   --dsw-mask-blur: blur(2px);
 }
 
+/* 派生 elevation 值逐元素声明而非从 body 继承:继承传下来的是在 body 上就
+   已替换完 var() 的值,组件重绑 --dsw-elevation-stroke-color 将无法进入
+   var(--dsw-elevation-*);逐元素声明让每个元素按自己看到的描边色重新替换
+   (同 scrollbar.css 对 --dsh-scrollbar-thumb 的处理)。 */
+body,
+body * {
+  --dsw-elevation-stroke: 0 0 0 0.5px var(--dsw-elevation-stroke-color);
+  --dsw-elevation-panel:
+    var(--dsw-elevation-stroke), 0 3px 8px 0 rgba(0, 0, 0, 0.03), 0 0 16px 0 rgba(0, 0, 0, 0.02);
+  --dsw-elevation-prominent:
+    var(--dsw-elevation-stroke), 0 3px 8px 0 rgba(0, 0, 0, 0.04), 0 0 20px 0 rgba(0, 0, 0, 0.05);
+  --dsw-elevation-soft:
+    var(--dsw-elevation-stroke), 0 4px 16px 0 rgba(0, 0, 0, 0.03), 0 0 24px 0 rgba(0, 0, 0, 0.03);
+}
+
 body[data-ds-dark-theme] {
   --dsw-linear-gradient-think: linear-gradient(180deg, #151517 20.19%, rgba(21, 21, 23, 0) 100%);
   --dsw-linear-think-select: linear-gradient(180deg, #232325 20.19%, rgba(35, 35, 37, 0) 100%);

+ 1 - 0
packages/client/ui-theme/tests/client-styles.client.spec.ts

@@ -21,6 +21,7 @@ describe('ui-theme client styles', () => {
     const styles = [...document.head.querySelectorAll<HTMLStyleElement>(`style[data-plugin="${PLUGIN_ID}"]`)]
     expect(styles.map(style => style.dataset.pluginCss)).toEqual([
       `${PLUGIN_ID}/base.css`,
+      `${PLUGIN_ID}/corner-shape.css`,
       `${PLUGIN_ID}/design-platform.css`,
       `${PLUGIN_ID}/scrollbar.css`,
       `${PLUGIN_ID}/gradient-shadow-text.css`,

+ 91 - 0
packages/client/ui-theme/tests/corner-shape-styles.client.spec.ts

@@ -0,0 +1,91 @@
+/**
+ * Corner-shape stylesheet contract, asserted against the CSS text on disk:
+ * corner-shape.css smooths every rounded corner to the superellipse token
+ * strictly inside a `@supports` guard, and every effectively full-round radius
+ * in any package stylesheet pairs `corner-shape: round` in the same rule,
+ * because a superellipse deforms a circle into a squircle (a spinner would
+ * visibly wobble) and squares off capsule ends.
+ */
+import { readFileSync } from 'node:fs'
+import { fileURLToPath } from 'node:url'
+import { describe, expect, it } from 'vitest'
+import { atRuleBlock, packageStylesheets, parseRules } from './stylesheet-scan.ts'
+
+/** The support guard prelude, spelled exactly as the sheet must spell it. */
+const GUARD = '@supports (corner-shape: superellipse(1.5))'
+/** The smoothing token corner-shape.css owns. */
+const TOKEN = '--dsw-corner-shape'
+
+const sheetPath = fileURLToPath(new URL('../src/styles/corner-shape.css', import.meta.url))
+const sheetCss = readFileSync(sheetPath, 'utf8')
+
+/**
+ * Whether a border-radius value makes the element full-round: an uncapped
+ * fraction of the box (50%/100%) or a pill radius far above any box size.
+ * Component-local radius indirections stay below the pill threshold, so the
+ * check is lexical over literal components.
+ * @param value - a border-radius declaration value.
+ * @returns true when some component is full-round.
+ */
+function isFullRound(value: string): boolean {
+  return value.split(/\s+/).some(part =>
+    part === '50%' || part === '100%' || (part.endsWith('px') && Number.parseFloat(part) >= 99))
+}
+
+describe('corner-shape.css smoothing', () => {
+  const withoutComments = sheetCss.replace(/\/\*[\s\S]*?\*\//g, ' ')
+  const guard = atRuleBlock(withoutComments, GUARD)
+
+  it('declares the token and its application only inside the support guard', () => {
+    // Outside the guard the declarations would be dropped as invalid anyway,
+    // but only on engines without corner-shape; keeping everything inside the
+    // guard states that unsupporting engines keep plain circular corners.
+    expect(guard, GUARD).toBeDefined()
+    const before = withoutComments.slice(0, withoutComments.indexOf(GUARD))
+    const after = withoutComments.slice(guard!.end + 1)
+    expect(before.trim(), `content before ${GUARD}`).toBe('')
+    expect(after.trim(), `content after ${GUARD}`).toBe('')
+  })
+
+  it('defines the superellipse token on :root and applies it universally', () => {
+    // corner-shape does not inherit, so only the universal selector (with the
+    // generated ::before/::after) reaches every rounded surface.
+    const rules = parseRules(withoutComments.slice(guard!.start + 1, guard!.end))
+    const root = rules.find(rule => rule.selectors.includes(':root'))
+    expect(root?.declarations).toContainEqual([TOKEN, 'superellipse(1.5)'])
+    const universal = rules.find(rule => rule.selectors.includes('*'))
+    expect(universal?.selectors).toEqual(['*', '*::before', '*::after'])
+    expect(universal?.declarations).toContainEqual(['corner-shape', `var(${TOKEN})`])
+  })
+})
+
+/**
+ * Full-round rules missing the `corner-shape: round` pairing.
+ * @param css - stylesheet text.
+ * @returns the offending selectors, in source order.
+ */
+function unpairedFullRound(css: string): string[] {
+  return parseRules(css)
+    .filter(rule => rule.declarations
+      .some(([property, value]) => property === 'border-radius' && isFullRound(value)))
+    .filter(rule => !rule.declarations
+      .some(([property, value]) => property === 'corner-shape' && value === 'round'))
+    .map(rule => rule.selectors.join(', '))
+}
+
+describe('full-round radii keep circular corners', () => {
+  it('rejects a full-round radius without the pairing', () => {
+    expect(unpairedFullRound('.a { border-radius: 50%; }')).toEqual(['.a'])
+    expect(unpairedFullRound('.a { border-radius: 999px; }')).toEqual(['.a'])
+    expect(unpairedFullRound('.a { border-radius: 50%; corner-shape: round; }')).toEqual([])
+  })
+
+  it('pairs corner-shape: round with every full-round border-radius under packages/', () => {
+    // The universal superellipse reaches every element, so each circle and
+    // pill states its own arc back; a new one without the pairing regresses
+    // silently on supporting engines only, which no jsdom test renders.
+    const unpaired = packageStylesheets().flatMap(file =>
+      unpairedFullRound(readFileSync(file, 'utf8')).map(selectors => `${file} ${selectors}`))
+    expect(unpaired).toEqual([])
+  })
+})

+ 182 - 0
packages/client/ui-theme/tests/elevation-styles.client.spec.ts

@@ -0,0 +1,182 @@
+/**
+ * Elevation stylesheet contract, asserted against the CSS text on disk:
+ * gradient-shadow-text.css composes the elevation tokens from a rebindable
+ * 0.5px hairline stroke plus soft layers, and no package rule pairs an
+ * lv/elevation box-shadow with a neutral-border-token border — elevated
+ * surfaces draw their neutral stroke inside the elevation shadow (border: 0),
+ * never as a layout-consuming border beside it. State-colored borders (for
+ * example the warn approval panels) stay real borders and are out of scope.
+ */
+import { readFileSync } from 'node:fs'
+import { basename } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { describe, expect, it } from 'vitest'
+import { packageStylesheets, parseRules } from './stylesheet-scan.ts'
+
+/** Stroke-color indirection components may rebind per surface or state. */
+const STROKE_COLOR = '--dsw-elevation-stroke-color'
+/** Shadow-token references that mark a rule as an elevated surface. */
+const ELEVATED_SHADOW = /--dsw-(?:shadow-lv|elevation-)/
+/** Neutral border tokens; the state palette (--dsw-alias-state-*) stays allowed. */
+const NEUTRAL_BORDER = /--dsw-alias-border-/
+
+const sheetCss = readFileSync(
+  fileURLToPath(new URL('../src/styles/gradient-shadow-text.css', import.meta.url)), 'utf8')
+
+describe('elevation tokens', () => {
+  const rules = parseRules(sheetCss)
+  const bodyOnly = new Map(rules
+    .filter(rule => rule.selectors.length === 1 && rule.selectors[0] === 'body')
+    .flatMap(rule => rule.declarations))
+  const perElement = new Map(rules
+    .filter(rule => rule.selectors.includes('body *'))
+    .flatMap(rule => rule.declarations))
+
+  it('defaults the stroke color on body alone, so a surface rebind inherits', () => {
+    // Declared per element, `body *` would beat inheritance on every
+    // descendant and a surface's rebind could not reach the box that carries
+    // the shadow; declared on body alone, the rebind inherits down.
+    expect(bodyOnly.get(STROKE_COLOR)).toBe('var(--dsw-alias-border-l4)')
+    expect(perElement.has(STROKE_COLOR)).toBe(false)
+  })
+
+  it('declares the derived values per element, so a stroke rebind takes effect', () => {
+    // A custom property computes with var() already substituted, and
+    // descendants inherit that computed value: derived tokens declared only on
+    // body would bake in body's stroke color, making every
+    // --dsw-elevation-stroke-color rebind a no-op. Per-element declarations
+    // re-substitute against the color each element sees (the same contract
+    // scrollbar.css states for --dsh-scrollbar-thumb).
+    expect(perElement.get('--dsw-elevation-stroke')).toBe(`0 0 0 0.5px var(${STROKE_COLOR})`)
+    for (const name of ['--dsw-elevation-panel', '--dsw-elevation-prominent', '--dsw-elevation-soft']) {
+      expect(perElement.get(name), name).toMatch(/^var\(--dsw-elevation-stroke\), 0 /)
+      expect(bodyOnly.has(name), name).toBe(false)
+    }
+  })
+})
+
+/**
+ * Rules pairing an lv/elevation box-shadow with a neutral-border-token border.
+ * @param css - stylesheet text.
+ * @returns the offending selectors, in source order.
+ */
+function neutralBordersBesideElevation(css: string): string[] {
+  return parseRules(css)
+    .filter(rule => rule.declarations
+      .some(([property, value]) => property === 'box-shadow' && ELEVATED_SHADOW.test(value)))
+    .filter(rule => rule.declarations.some(([property, value]) =>
+      property.startsWith('border') && !property.startsWith('border-radius') && NEUTRAL_BORDER.test(value)))
+    .map(rule => rule.selectors.join(', '))
+}
+
+describe('elevated surfaces carry no neutral border', () => {
+  it('rejects a rule that pairs the shadow with a neutral border', () => {
+    expect(neutralBordersBesideElevation(
+      '.a { box-shadow: var(--dsw-elevation-panel); border: 0.5px solid var(--dsw-alias-border-l2); }',
+    )).toEqual(['.a'])
+    expect(neutralBordersBesideElevation(
+      '.a { box-shadow: var(--dsw-elevation-panel); border: 0; }',
+    )).toEqual([])
+  })
+
+  it('never pairs an lv/elevation shadow with a neutral border token under packages/', () => {
+    // A 1px border beside the elevation stroke double-draws the outline and
+    // shifts layout by the border width; the hairline belongs to the shadow.
+    const paired = packageStylesheets().flatMap(file =>
+      neutralBordersBesideElevation(readFileSync(file, 'utf8'))
+        .map(selectors => `${file} ${selectors}`))
+    expect(paired).toEqual([])
+  })
+})
+
+/** Border properties that carry a width in their shorthand. */
+const BORDER_EDGE = /^border(?:-top|-bottom|-left|-right)?$/
+
+/**
+ * Solid neutral-token borders wider than the 0.5px hairline. The width test is
+ * lexical and order-sensitive: `border: solid 0.5px …` would be reported (a
+ * loud false positive to normalize), while split `border-width`/`border-color`
+ * declarations fall outside BORDER_EDGE and are not seen; no sheet under test
+ * writes either form.
+ * @param css - stylesheet text.
+ * @param exempt - `<selector> <property>` pairs allowed to keep their width.
+ * @returns the offending `<selectors> <property>: <value>` lines, in source order.
+ */
+function wideNeutralBorders(css: string, exempt: Set<string> = new Set()): string[] {
+  const wide: string[] = []
+  for (const rule of parseRules(css)) {
+    for (const [property, value] of rule.declarations) {
+      if (!BORDER_EDGE.test(property)) continue
+      if (!value.includes('solid') || !NEUTRAL_BORDER.test(value)) continue
+      if (value.startsWith('0.5px ')) continue
+      if (rule.selectors.some(selector => exempt.has(selector))) continue
+      wide.push(`${rule.selectors.join(', ')} ${property}: ${value}`)
+    }
+  }
+  return wide
+}
+
+/**
+ * Filled divider lines (a border-token background on a 1px-tall or 1px-wide
+ * box) that keep the pre-hairline weight.
+ * @param css - stylesheet text.
+ * @returns the offending `<selectors> <property>: <value>` lines, in source order.
+ */
+function wideFilledDividers(css: string): string[] {
+  const wide: string[] = []
+  for (const rule of parseRules(css)) {
+    const paintsLine = rule.declarations.some(([property, value]) =>
+      (property === 'background' || property === 'background-color') && NEUTRAL_BORDER.test(value))
+    if (!paintsLine) continue
+    for (const [property, value] of rule.declarations) {
+      if ((property === 'height' || property === 'width') && value === '1px') {
+        wide.push(`${rule.selectors.join(', ')} ${property}: ${value}`)
+      }
+    }
+  }
+  return wide
+}
+
+describe('neutral solid borders are hairlines', () => {
+  /**
+   * Spinner ring tracks, keyed `<basename> <selector>`: the border is the
+   * drawn graphic (a rotating ring), not an outline, so it keeps its width.
+   */
+  const RING_TRACKS = new Set([
+    'boot-page.module.css .spinner',
+    'TrajectoryTable.module.css .historyLoadingSpinner',
+  ])
+
+  it('rejects a wide neutral border and a wide filled divider', () => {
+    expect(wideNeutralBorders('.a { border: 1px solid var(--dsw-alias-border-l2); }'))
+      .toEqual(['.a border: 1px solid var(--dsw-alias-border-l2)'])
+    expect(wideNeutralBorders('.a { border: 0.5px solid var(--dsw-alias-border-l2); }')).toEqual([])
+    expect(wideFilledDividers('.a { background: var(--dsw-alias-border-l2); height: 1px; }'))
+      .toEqual(['.a height: 1px'])
+    expect(wideFilledDividers('.a { background: var(--dsw-alias-border-l2); height: 0.5px; }')).toEqual([])
+  })
+
+  it('draws every solid neutral-token border at 0.5px under packages/', () => {
+    // Buttons, inputs, cards, and separators share the hairline weight;
+    // dashed affordances and state-colored borders are out of scope.
+    const wide = packageStylesheets().flatMap((file) => {
+      const base = basename(file)
+      const exempt = new Set([...RING_TRACKS]
+        .filter(track => track.startsWith(`${base} `))
+        .map(track => track.slice(base.length + 1)))
+      return wideNeutralBorders(readFileSync(file, 'utf8'), exempt)
+        .map(line => `${file} ${line}`)
+    })
+    expect(wide).toEqual([])
+  })
+
+  it('draws every filled divider line at 0.5px under packages/', () => {
+    // A separator drawn as a filled box — 1px tall or wide with a border-token
+    // background (menu separators, the conversation header seam, markdown hr,
+    // vertical rails) — is the same hairline as a border. Visually-hidden 1px
+    // clip boxes carry no border-token background and stay exempt.
+    const wide = packageStylesheets().flatMap(file =>
+      wideFilledDividers(readFileSync(file, 'utf8')).map(line => `${file} ${line}`))
+    expect(wide).toEqual([])
+  })
+})

+ 2 - 84
packages/client/ui-theme/tests/scrollbar-styles.client.spec.ts

@@ -8,19 +8,12 @@
  * design-platform.css, so adding, renaming, or dropping a scrollbar token
  * moves these assertions with it.
  */
-import { readdirSync, readFileSync } from 'node:fs'
-import { join } from 'node:path'
+import { readFileSync } from 'node:fs'
 import { fileURLToPath } from 'node:url'
 import { describe, expect, it } from 'vitest'
-
-/** One flattened CSS rule: its comma-separated selector parts and its declarations in source order. */
-interface CssRule {
-  selectors: string[]
-  declarations: [property: string, value: string][]
-}
+import { atRuleBlock, type CssRule, packageStylesheets, parseRules, varReferences } from './stylesheet-scan.ts'
 
 const STYLES = new URL('../src/styles/', import.meta.url)
-const PACKAGES_DIR = fileURLToPath(new URL('../../../', import.meta.url))
 const read = (name: string): string => readFileSync(fileURLToPath(new URL(name, STYLES)), 'utf8')
 
 const platformCss = read('design-platform.css')
@@ -40,81 +33,6 @@ const ELEVATED_REBIND = new Map([
   ['--dsh-scrollbar-thumb-hover', '--dsw-alias-scrollbar-hover-l2'],
 ].map(([property, token]) => [property!, `var(${token!})`]))
 
-/**
- * Flatten a stylesheet into rules. Whitespace, declaration order, and trailing
- * semicolons are normalized away; nesting and at-rules are not handled, which
- * no sheet under test uses for scrollbar declarations.
- * @param css - stylesheet text.
- * @returns one entry per rule, in source order.
- */
-function parseRules(css: string): CssRule[] {
-  const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, ' ')
-  const rules: CssRule[] = []
-  // Destructuring defaults only satisfy noUncheckedIndexedAccess; both groups
-  // are unconditional in the pattern.
-  for (const [, selector = '', body = ''] of withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
-    const declarations = body
-      .split(';')
-      .map(part => part.trim())
-      .filter(part => part.includes(':'))
-      .map((part): [string, string] => {
-        const colon = part.indexOf(':')
-        return [part.slice(0, colon).trim(), part.slice(colon + 1).trim()]
-      })
-    rules.push({ selectors: selector.split(',').map(part => part.trim()), declarations })
-  }
-  return rules
-}
-
-/**
- * Half-open source span of one at-rule's block, excluding its prelude.
- * @param css - stylesheet text.
- * @param prelude - exact at-rule prelude to locate, without the opening brace.
- * @returns the block's brace offsets, or undefined when the prelude is absent.
- */
-function atRuleBlock(css: string, prelude: string): { start: number; end: number } | undefined {
-  const opening = css.indexOf(`${prelude} {`)
-  if (opening === -1) return undefined
-  const start = css.indexOf('{', opening)
-  let depth = 0
-  for (let index = start; index < css.length; index += 1) {
-    if (css[index] === '{') depth += 1
-    else if (css[index] === '}') {
-      depth -= 1
-      if (depth === 0) return { start, end: index }
-    }
-  }
-  throw new Error(`unbalanced braces after ${prelude}`)
-}
-
-/**
- * Custom-property names a value reads.
- * @param value - declaration value, possibly with nested var() calls.
- * @returns every referenced custom-property name, in source order.
- */
-function varReferences(value: string): string[] {
-  return [...value.matchAll(/var\(\s*(--[\w-]+)/g)].map(([, name = '']) => name)
-}
-
-/**
- * Every CSS file shipped as package source, excluding build output and
- * installed dependencies.
- * @returns absolute paths of the stylesheets under packages/.
- */
-function packageStylesheets(): string[] {
-  const found: string[] = []
-  const walk = (dir: string): void => {
-    for (const entry of readdirSync(dir, { withFileTypes: true })) {
-      const path = join(dir, entry.name)
-      if (entry.isDirectory()) {
-        if (entry.name !== 'node_modules' && entry.name !== 'lib' && entry.name !== 'dist') walk(path)
-      } else if (entry.name.endsWith('.css')) found.push(path)
-    }
-  }
-  walk(PACKAGES_DIR)
-  return found
-}
-
 /**
  * Tokens a stylesheet reads through its rendering declarations, following its
  * own custom-property definitions transitively so a token reached only through

+ 91 - 0
packages/client/ui-theme/tests/stylesheet-scan.ts

@@ -0,0 +1,91 @@
+/**
+ * Shared helpers for stylesheet-contract specs: flatten CSS text on disk into
+ * rules and enumerate the package stylesheets those contracts range over.
+ */
+import { readdirSync } from 'node:fs'
+import { join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+/** One flattened CSS rule: its comma-separated selector parts and its declarations in source order. */
+export interface CssRule {
+  selectors: string[]
+  declarations: [property: string, value: string][]
+}
+
+/** Root the package-wide stylesheet scans walk. */
+const PACKAGES_DIR = fileURLToPath(new URL('../../../', import.meta.url))
+
+/**
+ * Flatten a stylesheet into rules. Whitespace, declaration order, and trailing
+ * semicolons are normalized away; nesting is not handled, which no sheet under
+ * test uses, and at-rule preludes surface as selector-less rule boundaries.
+ * @param css - stylesheet text.
+ * @returns one entry per rule, in source order.
+ */
+export function parseRules(css: string): CssRule[] {
+  const withoutComments = css.replace(/\/\*[\s\S]*?\*\//g, ' ')
+  const rules: CssRule[] = []
+  // Destructuring defaults only satisfy noUncheckedIndexedAccess; both groups
+  // are unconditional in the pattern.
+  for (const [, selector = '', body = ''] of withoutComments.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
+    const declarations = body
+      .split(';')
+      .map(part => part.trim())
+      .filter(part => part.includes(':'))
+      .map((part): [string, string] => {
+        const colon = part.indexOf(':')
+        return [part.slice(0, colon).trim(), part.slice(colon + 1).trim()]
+      })
+    rules.push({ selectors: selector.split(',').map(part => part.trim()), declarations })
+  }
+  return rules
+}
+
+/**
+ * Half-open source span of one at-rule's block, excluding its prelude.
+ * @param css - stylesheet text.
+ * @param prelude - exact at-rule prelude to locate, without the opening brace.
+ * @returns the block's brace offsets, or undefined when the prelude is absent.
+ */
+export function atRuleBlock(css: string, prelude: string): { start: number; end: number } | undefined {
+  const opening = css.indexOf(`${prelude} {`)
+  if (opening === -1) return undefined
+  const start = css.indexOf('{', opening)
+  let depth = 0
+  for (let index = start; index < css.length; index += 1) {
+    if (css[index] === '{') depth += 1
+    else if (css[index] === '}') {
+      depth -= 1
+      if (depth === 0) return { start, end: index }
+    }
+  }
+  throw new Error(`unbalanced braces after ${prelude}`)
+}
+
+/**
+ * Custom-property names a value reads.
+ * @param value - declaration value, possibly with nested var() calls.
+ * @returns every referenced custom-property name, in source order.
+ */
+export function varReferences(value: string): string[] {
+  return [...value.matchAll(/var\(\s*(--[\w-]+)/g)].map(([, name = '']) => name)
+}
+
+/**
+ * Every CSS file shipped as package source, excluding build output and
+ * installed dependencies.
+ * @returns absolute paths of the stylesheets under packages/.
+ */
+export function packageStylesheets(): string[] {
+  const found: string[] = []
+  const walk = (dir: string): void => {
+    for (const entry of readdirSync(dir, { withFileTypes: true })) {
+      const path = join(dir, entry.name)
+      if (entry.isDirectory()) {
+        if (entry.name !== 'node_modules' && entry.name !== 'lib' && entry.name !== 'dist') walk(path)
+      } else if (entry.name.endsWith('.css')) found.push(path)
+    }
+  }
+  walk(PACKAGES_DIR)
+  return found
+}

+ 1 - 1
packages/client/ui-tool/src/client/tool/ToolCallTree.module.css

@@ -8,5 +8,5 @@
   gap: 4px;
   margin: 4px 0 2px 22px;
   padding-left: 8px;
-  border-left: 1px solid var(--dsw-alias-border-l2);
+  border-left: 0.5px solid var(--dsw-alias-border-l2);
 }

+ 1 - 1
packages/client/ui-tool/src/client/tool/components/AskQuestionCard.module.css

@@ -6,7 +6,7 @@
   margin: 4px 0 4px 4px;
   padding: 16px 20px;
   overflow-y: auto;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 12px;
   background: var(--dsw-alias-bg-base);
 }

+ 5 - 4
packages/client/ui-tool/src/client/tool/components/ToolRow.module.css

@@ -172,8 +172,9 @@
   gap: 4px;
   margin: 4px 0 2px 4px;
   padding: 2px 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   border-radius: 999px;
+  corner-shape: round;
   /* Base background, not bg-overlay: the overlay token is a raised dark
      surface and reads too heavy for a quiet in-flow affordance. */
   background: var(--dsw-alias-bg-base);
@@ -212,7 +213,7 @@
   display: flex;
   flex-direction: column;
   margin: 4px 0 4px 4px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 12px;
   background: var(--dsw-alias-markdown-code-block);
   font: var(--dsw-font-markdown-code-block-small);
@@ -260,7 +261,7 @@
    (it sits between the padded sections, not inside their grid). */
 .ioDivider {
   flex: none;
-  height: 1px;
+  height: 0.5px;
   background: var(--dsw-alias-border-l2);
 }
 
@@ -317,7 +318,7 @@
   --dsl-terminal-font: var(--dsw-font-markdown-code-block-small);
   --dsl-terminal-line-height: 18px;
   --dsl-terminal-output-max-height: 224px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
 }
 
 /* Visually hidden run-state label for assistive technology: the StateDot and

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

@@ -17,7 +17,7 @@
   --dsl-terminal-line-height: 18px;
   --dsl-terminal-output-max-height: 224px;
   margin: 4px 0 4px 4px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
 }
 
 /* A bash execution error can settle without terminal-card material (for
@@ -27,7 +27,7 @@
   display: flex;
   flex-direction: column;
   margin: 4px 0 4px 4px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 12px;
   background: var(--dsw-alias-markdown-code-block);
   font: var(--dsw-font-markdown-code-block-small);
@@ -62,7 +62,7 @@
 
 .ioDivider {
   flex: none;
-  height: 1px;
+  height: 0.5px;
   background: var(--dsw-alias-border-l2);
 }
 
@@ -216,8 +216,9 @@
   gap: 4px;
   margin: 4px 0 2px 4px;
   padding: 2px 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 999px;
+  corner-shape: round;
   /* Base background, not bg-overlay: the overlay token reads too heavy. */
   background: var(--dsw-alias-bg-base);
   color: var(--dsw-alias-label-secondary);

+ 1 - 1
packages/client/ui-trajectory/src/client/TrajectoryCell.module.css

@@ -9,7 +9,7 @@
   padding: 0 8px 0 20px;
   gap: 24px;
   border-radius: 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   background: var(--dsw-alias-bg-layer-3);
   min-width: 0;
 }

+ 13 - 10
packages/client/ui-trajectory/src/client/TrajectoryTable.module.css

@@ -39,7 +39,7 @@
   justify-content: center;
   gap: 6px;
   box-sizing: border-box;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   background: var(--dsw-alias-bg-layer-1);
   color: var(--dsw-alias-label-secondary);
   font: var(--dsw-font-xxs-12);
@@ -52,6 +52,7 @@
   border: 1.5px solid var(--dsw-alias-border-l2);
   border-top-color: var(--dsw-alias-state-business-primary);
   border-radius: 50%;
+  corner-shape: round;
   animation: history-loading-spin 700ms linear infinite;
 }
 
@@ -148,7 +149,7 @@
   height: 30px;
   padding: 0 8px;
   overflow: hidden;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   color: var(--dsw-alias-label-tertiary);
   background: var(--dsw-specific-sidebar-fill);
   font: var(--dsw-font-xxs-12);
@@ -169,7 +170,7 @@
   height: 30px;
   padding: 0 8px;
   overflow: hidden;
-  border-bottom: 1px solid var(--dsw-alias-border-l1);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l1);
   text-overflow: ellipsis;
   white-space: nowrap;
 }
@@ -251,6 +252,7 @@
   width: 5px;
   height: 5px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-label-caption);
   box-shadow:
     0 0 0 2px var(--dsw-alias-bg-layer-1),
@@ -267,7 +269,7 @@
   left: 17px;
   width: max-content;
   padding: 0 4px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 2px;
   color: var(--dsw-alias-label-secondary);
   background: var(--dsw-alias-bg-layer-1);
@@ -447,9 +449,9 @@
 }
 
 .kindSlot :global([role='tooltip']) {
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0;
   background: var(--dsw-alias-bg-layer-2);
-  box-shadow: var(--dsw-shadow-lv2);
+  box-shadow: var(--dsw-elevation-panel);
   color: var(--dsw-alias-label-primary);
 }
 
@@ -870,7 +872,7 @@
   max-width: calc(100% - 280px);
   min-width: 0;
   min-height: 0;
-  border-left: 1px solid var(--dsw-alias-border-l2);
+  border-left: 0.5px solid var(--dsw-alias-border-l2);
   background: var(--dsw-alias-bg-layer-1);
 }
 
@@ -901,7 +903,7 @@
   box-sizing: border-box;
   height: 42px;
   padding: 0 8px 0 12px;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .detailsTitle {
@@ -917,6 +919,7 @@
   width: 5px;
   height: 5px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-label-secondary);
 }
 
@@ -968,7 +971,7 @@
   overflow-x: auto;
   overflow-y: hidden;
   gap: 1px;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   overscroll-behavior-x: contain;
   scrollbar-width: none;
   white-space: nowrap;
@@ -1644,7 +1647,7 @@
 }
 
 .toolCatalogItem {
-  border-bottom: 1px solid var(--dsw-alias-border-l1);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l1);
 }
 
 .toolCatalogSummary {

+ 3 - 3
packages/client/ui-trajectory/src/client/TrajectoryTimeline.module.css

@@ -3,7 +3,7 @@
   z-index: 1;
   isolation: isolate;
   flex: none;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   user-select: none;
 }
 
@@ -21,7 +21,7 @@
 
 .labels {
   position: relative;
-  border-right: 1px solid var(--dsw-alias-border-l1);
+  border-right: 0.5px solid var(--dsw-alias-border-l1);
   color: var(--dsw-alias-label-caption);
   font: var(--dsw-font-xs-13);
   font-size: 10px;
@@ -146,7 +146,7 @@
   top: 0;
   bottom: 0;
   left: var(--trajectory-turn-left);
-  width: 1px;
+  width: 0.5px;
   background: var(--dsw-alias-border-l2);
 }
 

+ 3 - 2
packages/client/ui-trajectory/src/client/TrajectoryToolbar.module.css

@@ -5,7 +5,7 @@
   box-sizing: border-box;
   width: 100%;
   height: var(--dsh-trajectory-toolbar-height);
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
   background: var(--dsw-alias-bg-layer-1);
 }
 
@@ -122,6 +122,7 @@
   width: 6px;
   height: 6px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-bg-layer-1);
   transition: transform 120ms var(--ds-ease-in-out);
 }
@@ -173,7 +174,7 @@
   margin-left: auto;
   padding: 0 6px;
   gap: 4px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 4px;
   color: var(--dsw-alias-label-caption);
   background: var(--dsw-alias-bg-layer-2);

+ 1 - 0
packages/client/ui-user-questions/src/client/PlanReviewPanel.module.css

@@ -50,6 +50,7 @@
   width: 8px;
   height: 8px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-state-warn-primary);
 }
 

+ 6 - 4
packages/client/ui-user-questions/src/client/QuestionComposer.module.css

@@ -19,10 +19,11 @@
      so header and footer actions stay reachable on long batches. */
   max-height: min(60vh, 520px);
   padding: 0 0 10px;
-  border: 1px solid var(--dsw-alias-border-l2-darkmode-thin);
+  border: 0;
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l2-darkmode-thin);
   border-radius: 20px;
   background: var(--dsw-specific-input-major);
-  box-shadow: var(--dsw-shadow-lv2);
+  box-shadow: var(--dsw-elevation-panel);
   color: var(--dsw-alias-label-primary);
   overflow: hidden;
   /* Elevated surface in dark, same as the menus: the body inside scrolls
@@ -128,6 +129,7 @@
   padding: 0;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-tertiary);
   cursor: pointer;
@@ -236,7 +238,7 @@
   grid-area: 1 / 1;
   width: 14px;
   height: 14px;
-  border: 1px solid var(--dsw-alias-border-l4);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 4px;
   transition: background-color 120ms ease, border-color 120ms ease;
 }
@@ -388,7 +390,7 @@
   min-height: 64px;
   flex-shrink: 0;
   margin: 0 12px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 10px;
   background: var(--dsw-alias-bg-module-platform);
   --dsh-answer-field-padding: 8px 12px;

+ 1 - 0
packages/client/ui-workflow-run/src/client/WorkflowRunPanel.module.css

@@ -138,6 +138,7 @@
   width: 2px;
   height: 2px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-label-tertiary);
 }
 

+ 1 - 1
packages/client/ui-workspace/src/client/rows/Rows.module.css

@@ -181,7 +181,7 @@
   font-size: 14px;
   line-height: 20px;
   padding: 0 2px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 4px;
   background: var(--dsw-alias-button-elevated-fill);
   color: inherit;

+ 6 - 2
packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css

@@ -23,6 +23,7 @@
   height: 28px;
   border: none;
   border-radius: 50%;
+  corner-shape: round;
   padding: 0;
   background: transparent;
   cursor: pointer;
@@ -139,6 +140,7 @@
   box-sizing: border-box;
   border: none;
   border-radius: 50%;
+  corner-shape: round;
   background: transparent;
   cursor: text;
   color: var(--dsw-alias-label-secondary);
@@ -155,7 +157,7 @@
   height: 30px;
   margin-inline: -2px;
   padding: 0 4px 0 0;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 10px;
   background: transparent;
   color: var(--dsw-alias-label-caption);
@@ -170,6 +172,7 @@
   height: 28px;
   border: none;
   border-radius: 50%;
+  corner-shape: round;
   padding: 0;
   background: transparent;
   cursor: pointer;
@@ -223,6 +226,7 @@
   height: 24px;
   border: none;
   border-radius: 50%;
+  corner-shape: round;
   padding: 0;
   background: transparent;
   cursor: pointer;
@@ -455,7 +459,7 @@
   width: 100%;
   height: 44px;
   padding: 7px 14px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 22px;
   outline: none;
   background: transparent;

+ 1 - 0
packages/client/web/src/boot-page.module.css

@@ -49,6 +49,7 @@
   width: 20px;
   height: 20px;
   border-radius: 50%;
+  corner-shape: round;
   border: 2px solid var(--dsw-alias-border-l2, var(--dsh-boot-border));
   animation: spin 0.8s linear infinite;
 }

+ 6 - 5
packages/experimental/client-ui-agent-team/src/client/TeamAction.module.css

@@ -52,10 +52,11 @@
   max-height: min(680px, calc(100vh - 120px));
   padding: 10px;
   overflow: auto;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0;
   border-radius: 12px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
 }
 
 .toolbar,
@@ -103,7 +104,7 @@
   gap: 8px;
   min-width: 0;
   padding: 8px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 8px;
   background: transparent;
   color: var(--dsw-alias-label-primary);
@@ -151,7 +152,7 @@
 .task,
 .form {
   padding: 9px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 9px;
   background: var(--dsw-alias-bg-base);
 }
@@ -192,7 +193,7 @@
 .form input,
 .form textarea {
   box-sizing: border-box;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 6px;
   background: var(--dsw-alias-bg-base);
   color: var(--dsw-alias-label-primary);

+ 4 - 3
packages/extensions/ui-cordis/src/client/CordisDefineRow.module.css

@@ -166,7 +166,7 @@
 .sourceTabs {
   display: flex;
   height: 32px;
-  border-bottom: 1px solid var(--dsw-alias-border-l2);
+  border-bottom: 0.5px solid var(--dsw-alias-border-l2);
 }
 
 .sourceTab {
@@ -240,7 +240,7 @@
 .output {
   margin: 0;
   padding: 8px 10px;
-  border: 1px solid var(--dsw-alias-border-l1);
+  border: 0.5px solid var(--dsw-alias-border-l1);
   border-radius: 8px;
   background: var(--dsw-alias-markdown-code-block);
   white-space: pre-wrap;
@@ -260,8 +260,9 @@
   gap: 4px;
   margin: 4px 0 2px 4px;
   padding: 2px 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 999px;
+  corner-shape: round;
   background: var(--dsw-alias-bg-base);
   color: var(--dsw-alias-label-secondary);
   font-size: 11px;

+ 9 - 5
packages/extensions/ui-cordis/src/client/CordisPanel.module.css

@@ -71,6 +71,7 @@
   height: 36px;
   padding: 0;
   border-radius: 50%;
+  corner-shape: round;
 }
 
 .rail .footerButtons {
@@ -89,10 +90,11 @@
   max-width: calc(100vw - 24px);
   max-height: 60vh;
   overflow: hidden;
-  border: 1px solid var(--dsw-alias-border-inverted);
+  border: 0;
   border-radius: 12px;
   background: var(--dsw-specific-menu);
-  box-shadow: var(--dsw-shadow-lv3);
+  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
+  box-shadow: var(--dsw-elevation-prominent);
   /* Only `.body` scrolls; the header remains fixed above it. */
   --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
   --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
@@ -159,7 +161,7 @@
   flex-direction: column;
   gap: 8px;
   padding: 14px 12px 10px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 12px;
 }
 
@@ -248,7 +250,7 @@
   flex: 1;
   height: 26px;
   padding: 0 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   border-radius: 7px;
   background: transparent;
   color: var(--dsw-alias-label-secondary);
@@ -297,8 +299,9 @@
 
 .transitionActions button {
   padding: 2px 8px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l3);
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-secondary);
   font: inherit;
@@ -331,6 +334,7 @@
   padding: 0;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-tertiary);
   cursor: pointer;

+ 4 - 2
packages/extensions/ui-cordis/src/client/CordisRunRow.module.css

@@ -31,6 +31,7 @@
   height: 2px;
   margin: 0 8px;
   border-radius: 50%;
+  corner-shape: round;
   background: var(--dsw-alias-state-business-primary);
 }
 
@@ -81,6 +82,7 @@
   padding: 0;
   border: none;
   border-radius: 999px;
+  corner-shape: round;
   background: transparent;
   color: var(--dsw-alias-label-tertiary);
   cursor: pointer;
@@ -108,7 +110,7 @@
 .business {
   min-width: 0;
   overflow: hidden;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 12px;
   background: var(--dsw-alias-bg-base);
 }
@@ -117,7 +119,7 @@
   margin: 0;
   padding: 10px 12px;
   overflow: auto;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 8px;
   background: var(--dsw-alias-markdown-code-block);
   color: var(--dsw-alias-label-secondary);

+ 1 - 1
packages/session-query/session-log-export/src/client/HeaderAction.module.css

@@ -6,7 +6,7 @@
   height: 32px;
   padding: 6px 12px;
   gap: 4px;
-  border: 1px solid var(--dsw-alias-border-l2);
+  border: 0.5px solid var(--dsw-alias-border-l4);
   border-radius: 18px;
   color: var(--dsw-alias-label-primary);
   background: transparent;