diff --git a/apps/desktop/src/renderer/components/settings/AgentResourceSection.tsx b/apps/desktop/src/renderer/components/settings/AgentResourceSection.tsx
index 60c9278a029..ceac3ce0552 100644
--- a/apps/desktop/src/renderer/components/settings/AgentResourceSection.tsx
+++ b/apps/desktop/src/renderer/components/settings/AgentResourceSection.tsx
@@ -10,11 +10,11 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
import { cn } from '@/lib/utils';
import { toast } from '@/lib/toast';
import { DefaultOverrideControls } from './DefaultOverrideControls';
+import { SettingsTextInput } from './SettingsTextInput';
type Wire = AgentResourceSettingsWire;
type SettingKey = 'maxConcurrentCommands' | 'processPriority' | 'capToolchainThreads';
@@ -41,7 +41,7 @@ const ROW_HINT_CLASS =
/** 行间分割线:左右缩进与行内边距对齐。 */
const DIVIDER_CLASS = 'mx-4 h-px bg-[var(--settings-theme-card-border)]';
-/** 数字输入走标准 Input md 档(32/36/40 中的 36px),原生步进器不自绘。 */
+/** 数字输入复用标准 Input md 档,设置封装保留旧局部主题覆盖,原生步进器不自绘。 */
/**
* 均衡档并发值:本机核数的一半,至少 2(与 main 侧 toolchain-thread-cap 的口径一致),
@@ -298,7 +298,7 @@ export function AgentResourceSection() {
{t('settings.agentResource.maxConcurrentHint')}
-
-
-
- ;
+}
+
export type {
InputSize as SettingsTextInputSize,
InputSurface as SettingsTextInputSurface,
diff --git a/apps/desktop/src/renderer/components/settings/__tests__/AgentResourceSectionRendering.test.tsx b/apps/desktop/src/renderer/components/settings/__tests__/AgentResourceSectionRendering.test.tsx
index 1ff07507ee1..26601de5670 100644
--- a/apps/desktop/src/renderer/components/settings/__tests__/AgentResourceSectionRendering.test.tsx
+++ b/apps/desktop/src/renderer/components/settings/__tests__/AgentResourceSectionRendering.test.tsx
@@ -109,6 +109,10 @@ describe('AgentResourceSection', () => {
expect(visiblePresetHint()).toBe('settings.agentResource.presetHints.full');
const numberInput = screen.getByRole('spinbutton') as HTMLInputElement;
expect(numberInput.value).toBe('0');
+ // DS-4 前数字框消费过这些局部主题键;迁标准件不能丢掉用户覆盖。
+ expect(numberInput.className).toContain('text-[var(--settings-input-text)]');
+ expect(numberInput.className).toContain('border-[var(--settings-input-border)]');
+ expect(numberInput.className).toContain('focus:border-[var(--settings-input-border-focus)]');
expect(screen.getByRole('switch').getAttribute('aria-checked')).toBe('false');
});
diff --git a/apps/desktop/src/renderer/components/settings/__tests__/CollaborationSectionRendering.test.tsx b/apps/desktop/src/renderer/components/settings/__tests__/CollaborationSectionRendering.test.tsx
index 18bb3bde6ee..c52ea44e7ee 100644
--- a/apps/desktop/src/renderer/components/settings/__tests__/CollaborationSectionRendering.test.tsx
+++ b/apps/desktop/src/renderer/components/settings/__tests__/CollaborationSectionRendering.test.tsx
@@ -79,6 +79,11 @@ describe('CollaborationSection', () => {
expect(screen.getByText('settings.collaboration.title')).toBeTruthy();
});
expect(limitInputs().map((i) => i.value)).toEqual(['5', '8', '0']);
+ for (const input of limitInputs()) {
+ expect(input.className).toContain('text-[var(--settings-input-text)]');
+ expect(input.className).toContain('border-[var(--settings-input-border)]');
+ expect(input.className).toContain('focus:border-[var(--settings-input-border-focus)]');
+ }
});
it('writes the soft limit on change without waiting for blur', async () => {
diff --git a/apps/desktop/src/renderer/components/settings/__tests__/CustomProviderDialogAccessibility.test.tsx b/apps/desktop/src/renderer/components/settings/__tests__/CustomProviderDialogAccessibility.test.tsx
index 49467d95b33..ebb79cb9b8b 100644
--- a/apps/desktop/src/renderer/components/settings/__tests__/CustomProviderDialogAccessibility.test.tsx
+++ b/apps/desktop/src/renderer/components/settings/__tests__/CustomProviderDialogAccessibility.test.tsx
@@ -125,6 +125,8 @@ async function renderImageGenerationHelp() {
const user = userEvent.setup();
render();
await waitFor(() => expect(customProviderMocks.readCustomProviderKey).toHaveBeenCalled());
+ // Let the dialog's initial focus settle before testing the help popover's focus behavior.
+ await waitForInitialDialogFocus();
const advanced = screen.getByRole('button', {
name: 'settings.providers.custom.fields.runtimeAdvanced',
});
diff --git a/apps/desktop/src/renderer/components/ui/__tests__/inputThemeCompatibility.test.tsx b/apps/desktop/src/renderer/components/ui/__tests__/inputThemeCompatibility.test.tsx
new file mode 100644
index 00000000000..300f6371909
--- /dev/null
+++ b/apps/desktop/src/renderer/components/ui/__tests__/inputThemeCompatibility.test.tsx
@@ -0,0 +1,133 @@
+// @vitest-environment jsdom
+
+import { cleanup, render } from '@testing-library/react';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+
+const sandbox = vi.hoisted(() => ({ home: '' }));
+vi.mock('node:os', async (importOriginal) => {
+ const actual = await importOriginal();
+ return { ...actual, default: { ...actual, homedir: () => sandbox.home }, homedir: () => sandbox.home };
+});
+vi.mock('../../../../main/logger.js', () => ({
+ createLogger: () => ({ debug: vi.fn(), info: vi.fn(), warn: vi.fn() }),
+}));
+vi.mock('react-i18next', () => ({
+ useTranslation: () => ({ t: (key: string) => key }),
+}));
+
+import { loadLocalThemesSync, resetLocalThemesMigrationForTest } from '../../../../main/local-themes/loader';
+import '../../../themes/colors';
+import { bootstrapLocalThemesSync, getLocalThemes } from '../../../themes/local-themes';
+import { exportThemeColors, resolveThemeValue } from '../../../themes/theme-service';
+import { builtinThemes } from '../../../themes/registry';
+import type { Theme, ThemeType } from '../../../themes/types';
+import { SettingsTextInput } from '../../settings/SettingsTextInput';
+import { Input } from '../input';
+
+const BINDINGS = [
+ ['text-', 'text-primary', 'settings-input-text'],
+ ['border-', 'border-default', 'settings-input-border'],
+ ['focus:border-', 'text-tertiary-stone', 'settings-input-border-focus'],
+ ['placeholder:text-', 'text-placeholder', 'settings-input-placeholder'],
+] as const;
+
+// 复用既有内置色板作为彼此不同的全局/局部覆盖,不另维护测试色值表。
+const SEMANTIC = Object.fromEntries(BINDINGS.map(([, semantic]) => [
+ semantic, resolved(builtinThemes['default-light'], semantic),
+]));
+const LEGACY = Object.fromEntries(BINDINGS.map(([, semantic, alias]) => [
+ alias, resolved(builtinThemes['default-dark'], semantic),
+]));
+
+// jsdom 不负责 Tailwind/CSS 变量计算。此处从真实 React 控件读取消费表达式,
+// 通过生产 registry 解析到值,补齐“文件还在但控件不再消费”的回归边界。
+// 浏览器最终 computed style 另由真实 Desktop 验证,不能把此测试冒充实机证据。
+function resolved(theme: Theme, id: string, depth = 0): string {
+ if (depth > 12) throw new Error(`Unexpected alias cycle: ${id}`);
+ const value = resolveThemeValue(theme, id);
+ if (value === null) throw new Error(`Missing color: ${id}`);
+ const alias = /^var\(--([\w-]+)\)$/.exec(value);
+ return alias ? resolved(theme, alias[1], depth + 1) : value;
+}
+
+function consumed(field: HTMLInputElement, prefix: string, theme: Theme): string {
+ const token = [...field.classList].find((item) => item.startsWith(`${prefix}[var(--`));
+ const id = token?.match(/var\(--([\w-]+)\)/)?.[1];
+ if (!id) throw new Error(`Missing ${prefix} color binding: ${field.className}`);
+ return resolved(theme, id);
+}
+
+function loadTheme(type: ThemeType, colors: Record): Theme {
+ const dir = path.join(sandbox.home, '.cindy', 'themes');
+ fs.mkdirSync(dir, { recursive: true });
+ const file = path.join(dir, 'input-compat.json');
+ const bytes = `${JSON.stringify({ id: 'input-compat', name: 'Input compatibility fixture', type, colors }, null, 2)}\n`;
+ fs.writeFileSync(file, bytes);
+ const payload = loadLocalThemesSync();
+ expect(payload.success).toBe(true);
+ vi.stubGlobal('electronAPI', { localThemes: { listSync: () => payload } });
+ bootstrapLocalThemesSync();
+ const first = getLocalThemes().find((theme) => theme.id === 'input-compat-local');
+ expect(first).toBeDefined();
+ bootstrapLocalThemesSync();
+ expect(getLocalThemes().find((theme) => theme.id === first!.id)).toEqual(first);
+ expect(fs.readFileSync(file, 'utf8')).toBe(bytes);
+ return first!;
+}
+
+beforeEach(() => {
+ sandbox.home = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ds4-input-compat-')));
+ resetLocalThemesMigrationForTest();
+});
+afterEach(() => {
+ cleanup();
+ vi.unstubAllGlobals();
+ fs.rmSync(sandbox.home, { recursive: true, force: true });
+});
+
+describe.each(['light', 'dark'] as const)('旧设置输入主题消费:%s', (type) => {
+ it.each([
+ ['仅全局覆盖', SEMANTIC],
+ ['旧局部覆盖与全局覆盖并存', { ...SEMANTIC, ...LEGACY }],
+ ['旧文件缺新 placeholder slot', LEGACY],
+ ['完整本地副本含局部覆盖', { ...exportThemeColors(builtinThemes[`default-${type}`]), ...SEMANTIC, ...LEGACY }],
+ ] as const)('%s:旧设置输入保留局部作用域,通用 Input 保持语义默认', (_name, colors) => {
+ const original = JSON.stringify(colors);
+ const theme = loadTheme(type, colors);
+ const { container } = render(<>
+ {}} />
+ {}} />
+ >);
+ const [legacy, general] = container.querySelectorAll('input');
+ for (const [prefix, semantic, alias] of BINDINGS) {
+ expect(consumed(legacy, prefix, theme), `${prefix} legacy input`).toBe(resolved(theme, alias));
+ expect(consumed(general, prefix, theme), `${prefix} general input`).toBe(resolved(theme, semantic));
+ }
+ expect(JSON.stringify(colors)).toBe(original);
+ });
+});
+
+it('所有内置主题未做局部覆盖时,旧设置封装与标准 Input 逐值相同', () => {
+ const { container } = render(<>
+ {}} />
+ {}} />
+ >);
+ const [legacy, general] = container.querySelectorAll('input');
+ for (const theme of Object.values(builtinThemes)) {
+ for (const [prefix] of BINDINGS) {
+ expect(consumed(legacy, prefix, theme), `${theme.id} ${prefix}`).toBe(consumed(general, prefix, theme));
+ }
+ }
+});
+
+it('局部主题兼容不能盖掉标准错误态', () => {
+ const theme = loadTheme('light', { ...SEMANTIC, ...LEGACY });
+ const { container } = render( {}} error />);
+ const field = container.querySelector('input')!;
+ expect(consumed(field, 'border-', theme)).toBe(resolved(theme, 'error-border'));
+ expect(consumed(field, 'focus:border-', theme)).toBe(resolved(theme, 'error-fg'));
+ expect(consumed(field, 'text-', theme)).toBe(LEGACY['settings-input-text']);
+});
diff --git a/apps/desktop/src/renderer/components/ui/input.tsx b/apps/desktop/src/renderer/components/ui/input.tsx
index b570098567e..31c501ac39c 100644
--- a/apps/desktop/src/renderer/components/ui/input.tsx
+++ b/apps/desktop/src/renderer/components/ui/input.tsx
@@ -74,12 +74,12 @@ const EYE_STYLES: Record = {
};
/**
- * 边框 / 文字 / placeholder 一律绑 Tier-1 slot,不继承 `--settings-input-*` 域 alias
+ * 边框 / 文字 / placeholder 默认绑 Tier-1 slot,不继承 `--settings-input-*` 域 alias
* (与 G5 对 button/secondary 的同一条判据:primitive 不继承域 alias,防设置页私有
* 决定泄漏成全局默认)。这几个 alias 的默认值本就 forward-resolve 到同一批 slot,
* 11 个内置主题与外部主题导入 allowlist 均未 override 它们,故默认外观逐值不变;
- * 变的是 override 面 —— 手写过 `settings-input-*` 的用户本地主题不再作用于本组件。
- * `--settings-input-border-focus` 亦同(其默认值 = `--text-tertiary-stone`)。
+ * 既有设置输入的局部主题合同由 SettingsTextInput 经 inputClassName 保留,
+ * 不把 settings 域 override 提升为所有 Input 或全局 semantic 的默认值。
*/
const FIELD_CHROME =
'text-[var(--text-primary)] placeholder:text-[var(--text-placeholder)] border border-[var(--border-default)] focus:border-[var(--text-tertiary-stone)] focus:ring-2 focus:ring-[var(--focus-ring)]';
@@ -107,6 +107,8 @@ export interface InputProps extends Omit,
error?: boolean;
/** 附加到**外层容器**(内层 input 恒为 w-full),供 flex 行传 `flex-1 min-w-0`。 */
className?: string;
+ /** 内层控件的样式扩展;供既有域封装保留局部主题合同,错误态仍优先。 */
+ inputClassName?: string;
}
export function Input({
@@ -122,6 +124,7 @@ export function Input({
trailing,
error = false,
className,
+ inputClassName,
disabled,
autoComplete,
spellCheck,
@@ -178,6 +181,7 @@ export function Input({
mono && 'font-mono',
FIELD_CHROME,
SURFACE_STYLES[surface],
+ inputClassName,
error && ERROR_CHROME,
disabled && 'cursor-not-allowed opacity-60',
)}
diff --git a/docs/design-evidence/2026-09-06/input-theme-compat.md b/docs/design-evidence/2026-09-06/input-theme-compat.md
new file mode 100644
index 00000000000..3ba7778e88a
--- /dev/null
+++ b/docs/design-evidence/2026-09-06/input-theme-compat.md
@@ -0,0 +1,47 @@
+# DS-4 旧设置输入主题兼容验证
+
+日期:2026-09-06。平台:Desktop / macOS arm64。基点:`5fc5881101753aba47922d089f3b3246e9b9ffce`,加本批次未提交修复。未使用真实用户主题、凭证或数据库。
+
+本批恢复此前消费 settings 域 alias 的输入框,不改变通用 Input 的 Tier-1 默认。原 SettingsTextInput 消费者与 AgentResource / Collaboration 的四个数字框共用同一标准组件;旧局部覆盖仅在设置封装内生效。
+
+**自动验证**
+
+- `inputThemeCompatibility.test.tsx`:真实临时主题文件 → main loader → renderer bootstrap / normalize → 实际 React 控件的 Token 消费表达式 → 生产 registry 解析。覆盖 Light/Dark、局部/全局覆盖、旧 placeholder 归一化、完整主题副本、重复加载和磁盘字节不变。
+- 所有内置主题没有局部覆盖时,标准 Input 与 SettingsTextInput 四项颜色逐值相同。
+- 错误态边框与 focus 环优先于局部兼容样式。四个数字框的现有渲染测试保留交互验证并增加局部 alias 消费断言。
+- jsdom 测试不声称验证 Tailwind 最终 CSS;浏览器测量见下。
+
+**真实 Desktop 构建内的组件对照**
+
+使用独立 dev 沙箱,包装启动与 `desktop-whoami` 均返回 ready / MATCH。通过 CDP 在该构建内挂载实际 Input / SettingsTextInput,以生产 Tailwind 和 ThemeService 渲染,读取 `getComputedStyle`(含 placeholder 伪元素)并采集 Light/Dark 截图。
+
+对照左侧是仍保持 Tier-1 默认的通用 Input(也即修复前直接 re-export 的效果),右侧是本批局部兼容封装。同屏使用故意不同的全局和局部覆盖,以证明作用域。这些数值是验证输入,不是新设计裁决。
+
+| 模式 | 项目 | 通用 Input | 兼容设置输入 |
+| --- | --- | --- | --- |
+| Light | 文字 RGB | 35,69,103 | 101,67,33 |
+| Light | 边框 RGB | 86,120,154 | 135,101,67 |
+| Light | focus 边框 RGB | 52,86,120 | 169,135,101 |
+| Light | placeholder RGB | 120,154,188 | 170,136,102 |
+| Dark | 文字 RGB | 170,204,238 | 238,203,170 |
+| Dark | 边框 RGB | 136,170,204 | 187,153,119 |
+| Dark | focus 边框 RGB | 187,221,255 | 255,221,187 |
+| Dark | placeholder RGB | 120,154,188 | 170,136,102 |
+
+两模式错误边框保持生产 error token;禁用 opacity 均为 0.6;focus 环仍为原来的 65,124,221(RGB)、2px。两张截图均已目检。
+
+**证据边界与待交接**
+
+- 这是实际 Desktop 构建中的受控组件样例,非 SSR、非手绘;不是登录后的完整设置页端到端验证。新沙箱没有同意登录协议,本次未代用户同意。完整设置页以及全部内置主题的实机检查未执行。
+- 没有宣称全局/局部同时覆盖时零视觉变化:有意恢复设置域旧覆盖;无局部覆盖的默认值由自动测试逐值核对。
+- 截图与 CDP 复现/测量脚本随桌面执行记录本地交付,栅格文件不入仓。尚未创建 PR 或上传附件;提交 PR 时需补真实可访问的附件链接,此项目前不记为完成。
+- 09-04 日志提出的跨 surface alias 全族收敛没有在本批执行。本批只处理已经迁移的既有输入框;通用标准件保持 Tier-1,旧局部 override 不提升成全局 semantic。
+
+采集时代码 SHA-256(便于核对未提交修改,不把基点当成采集版本):
+
+| 文件 | SHA-256 |
+| --- | --- |
+| `components/ui/input.tsx` | `88527dd4fb758d9ff7c1fe425f43d67bcc4770abb79f329ad913f95bfd53dba8` |
+| `components/settings/SettingsTextInput.tsx` | `fc25cad5aec84abd783904138d88448ec350795c15dd6e2b233c77832c088a57` |
+| `components/settings/AgentResourceSection.tsx` | `6f1a9700998f06e69aabbc33f8f78a3b574ad0d138acff9a584d8664f931e909` |
+| `components/settings/CollaborationSection.tsx` | `49ce0ba642e6d079b8d609843908279797f95d08ae16a63b6c261983c783d208` |
diff --git a/docs/design-rules/DESIGN.md b/docs/design-rules/DESIGN.md
index 2d645dc3f79..989586c23b3 100644
--- a/docs/design-rules/DESIGN.md
+++ b/docs/design-rules/DESIGN.md
@@ -272,6 +272,7 @@ input/text
```
- **Multi-line inputs (textarea) take the 8px inner-control radius — never the pill** (tall frames deform it), whether nested in a container or standing alone in a form (§5 three-tier scale; single rule, no nesting condition). Implementation: `components/ui/input.tsx` `Textarea`.
+- **Existing settings inputs retain their local theme contract** through `SettingsTextInput`, a thin wrapper over `ui/input`. It supplies the historical `settings-input-text`, `settings-input-border`, `settings-input-border-focus`, and `settings-input-placeholder` aliases via `inputClassName`. Their defaults still resolve to Tier-1; explicit local overrides stay local. Generic `Input` keeps the Tier-1 defaults above. Standard error-state border/ring takes precedence over wrapper styles. Do not promote these legacy overrides into global semantic slots or rewrite user theme files. Existing placeholder load-time normalization remains unchanged.
- Placeholders must **read as clearly empty** — Silver (`#a3a3a3`) is too prominent against either Card surface (≈5:1 Dark / ≈2.6:1 Light) and reads as real input; forbidden. **Every input surface's placeholder (chat / ask / settings / plan-action-fb) resolves to `--text-placeholder`** (2026-06 G3, archived in `design-decision-log.md`); non-default themes express their own placeholder color by overriding `text-placeholder`.
### Select & Dropdown
diff --git a/docs/design-rules/cindy-design-system.md b/docs/design-rules/cindy-design-system.md
index ac12756b8d6..e18ca62b2a0 100644
--- a/docs/design-rules/cindy-design-system.md
+++ b/docs/design-rules/cindy-design-system.md
@@ -23,6 +23,8 @@
## 版本记录
+- **2026-09-06(DS-4 旧设置输入主题兼容收口)**:`SettingsTextInput` 复用标准 Input 并保留既有局部主题 alias;AgentResource / Collaboration 的四个数字框走同一封装。通用 Input 的 Tier-1 默认、错误态、焦点环及主题磁盘文件保持原合同。新增真实主题加载到组件消费的回归验证;DS-4 主线回填 #3920。
+
- **2026-09-04(DS-4 Button 与 Input 标准组件)**:`components/ui/button.tsx` / `input.tsx` 落地;§4 回写高度 / hover 换色 / pressed / 字号字重 / secondary 绑 Tier-1 / ivory 登记债(拍板人 = 用户/设计师,2026-09-03)。影子包新建 component 层。路线图 DS-3 已是 #3798;DS-4 号待本张合入后回填。 同日 self-review 收口三处:hover / pressed 改为从本变体 rest 底色朝前景 color-mix 派生(初版 alias 到 `--surface-hover` 在四个暗色主题里状态不可区分,违反 §10 双模式门槛;字面量 pressed 不跟主题),新增守卫 `themes/__tests__/buttonStateContrast.test.ts` 锁 11 主题每档 ΔRGB ≥ 8;按钮 hover / active 加 `enabled:` 前缀,修禁用态仍会 hover 换色的行为回归;§4 单行输入 focus 环还原为 `--focus-ring-soft`,spec 与实现的偏差改为登记进 [`design-governance.md`](./design-governance.md) §10 待裁决表,不擅自统一。
- **2026-09-02(Desktop 登录成功回调页 UX 覆盖)**:成功态移除返回 Cindy 按钮,改为 560×500 紧凑内容流卡片,底部显示本地化 3 秒倒计时并在结束时先移除文字再调用 `window.close()`;失败 / Warning 继续使用 680×680 卡片与返回操作。同步更新 `DESIGN.md §16`、`figma-component-spec.md §6`、`token-decision-table.md §4` 与客户端模板测试。
diff --git a/docs/design-rules/design-governance.md b/docs/design-rules/design-governance.md
index 61d60f673c0..e861c42db89 100644
--- a/docs/design-rules/design-governance.md
+++ b/docs/design-rules/design-governance.md
@@ -276,7 +276,7 @@ ls apps/desktop/src/renderer/themes/builtin/*.ts | wc -l
| DS-2a | `test(design-system): DS-2a 生产 UI 台账` | 零视觉 | ✅ #3648(2026-08-31 合入) |
| DS-2b | `ci(design-system): DS-2b 主题兼容冻结守卫`(新增阻断需 §8 管理员审核) | CI 门禁 | ✅ #3700(2026-09-02 合入) |
| DS-3 | `feat(design-system): DS-3 最小语义 Token 影子层` | 零视觉 | ✅ #3798(2026-09-03 合入) |
-| DS-4 | `feat(design-system): DS-4 Button 与 Input 标准组件`(落入既有 `components/ui/`) | 有意可见 | — |
+| DS-4 | `feat(design-system): DS-4 Button 与 Input 标准组件`(落入既有 `components/ui/`) | 有意可见 | ✅ [3920](https://github.com/makecindy/cindy/pull/3920)(2026-09-04 合入) |
| DS-5 | `refactor(design-system): DS-5 AI 对话区 Pattern 迁移`(Tool Call / Reasoning / Message / Attachment) | 有意可见 | — |
| DS-6 | `refactor(design-system): DS-6 Permission Pattern 迁移`(裁决关闭后) | 有意可见 | — |
| DS-7 | `ci(design-system): DS-7 新增裸设计值棘轮` | CI 门禁 | — |
diff --git a/docs/design-rules/design-inventory.md b/docs/design-rules/design-inventory.md
index a1c18adbf5f..d22deb311ae 100644
--- a/docs/design-rules/design-inventory.md
+++ b/docs/design-rules/design-inventory.md
@@ -138,7 +138,7 @@ Mobile 本轮不展开顶层 screen,**待 DS-9 增量**。
| `desktop.overlay.toast` | unassigned | legacy | — | 待 DS-4 标准组件落地后按 Pattern 迁 | 保持现状;发现问题记下一动作,本张不修视觉 |
| `desktop.plugins.app-main` | unassigned | legacy | — | 待 DS-4 标准组件落地后按 Pattern 迁 | 保持现状;发现问题记下一动作,本张不修视觉 |
| `desktop.plugins.installed` | unassigned | legacy | — | 待 DS-4 标准组件落地后按 Pattern 迁 | 保持现状;发现问题记下一动作,本张不修视觉 |
-| `desktop.settings` | unassigned | pilot | DESIGN.md §10 语义豁免色族消费者;外部主题导入保护 token(资源用量类别色在独立窗) | DS-4 Button / Input 已在 ProvidersSection + SettingsTextInput 六消费者落地;其余 settings 控件仍按 Pattern 迁 | **DS-5 待办:`--settings-input-*` 四个域 alias 收口**(`-border` 138 处 / `-text` 108 处 / `-border-focus` 44 处 / `-placeholder` 33 处,去重 49 个文件;32 个在本 surface,另 17 个跨 `cindy-brain` / `components/new-chat` / `features/scheduler` / `features/skillhub` / `features/cc-agent` / `components/ui/confirm-dialog`,故须整族一次收口、不按 surface 零敲)——DS-4 已把 `ui/input` 换绑 Tier-1,这批未收口的消费者在「导出型本地主题」下会与标准输入框分裂,理由见 `design-decision-log.md` 09-04 条;ivory/elevated 收口(`surface="ivory"` 登记债,独立议题);confirm-dialog 按钮族只登记不迁 |
+| `desktop.settings` | unassigned | pilot | DESIGN.md §10 语义豁免色族消费者;外部主题导入保护 token(资源用量类别色在独立窗) | DS-4 Button / Input 已在 ProvidersSection 与设置输入落地;既有 SettingsTextInput 和四个数字框经同一标准组件的局部兼容封装消费,通用 Input 保持 Tier-1 | DS-4 兼容收口:旧 SettingsTextInput 与四个数字框经设置封装保留原局部主题覆盖,通用 Input 保持 Tier-1 默认;既有 alias 与 slot 并存时按作用域生效,不能仅为统一而删除用户局部配色。DS-5 的跨 surface alias 收敛仍需先明确兼容策略(历史范围见 decision-log 09-04,不沿用旧计数作当前清单);ivory/elevated 与 focus 环维持待裁决;confirm-dialog 按钮族另批迁移 |
| `desktop.shell.main-layout` | unassigned | legacy | DESIGN.md §15 CINDY 皮肤族(侧栏 vibrancy / 选中 pill);外部主题导入保护 token | 待 DS-4 标准组件落地后按 Pattern 迁 | 保持现状;发现问题记下一动作,本张不修视觉 |
| `desktop.skillhub.local` | unassigned | legacy | — | 待 DS-4 标准组件落地后按 Pattern 迁 | 保持现状;发现问题记下一动作,本张不修视觉 |
| `desktop.skillhub.market` | unassigned | legacy | — | 待 DS-4 标准组件落地后按 Pattern 迁 | 保持现状;发现问题记下一动作,本张不修视觉 |