Xisound 模块 UI 实现规范 v1.0
文档定位
- 权威性:所有模块 UI 元素的强制性实施规范。任何智能体或工程师在实现/修改模块 UI 时必须遵守,未遵守视同 PR 不合规(CI 将逐步加入自动校验)
- 适用范围:XiStudio LinkEditor 画布 + ModuleLibrary 左侧库 + Tuning Dialog 弹窗 + XiForge UI Designer 预览;涵盖所有 DSP 算法模块(source/sink/filter/gain/mixer/...所有 20 类分类)
- 产生背景:2026-05-08 Source/Sink 重构发现智能体遗漏 8 处 UI 元素(toggle 开关、组件库 icon+名字、画布开关、output port 设定、preset UI 位置、preset 同步、PC/DSP 切换、source 诊断 UI)· 根因是缺失前端 UI 强制规范
- 上游:D3-FE-ARCH-003 共享 UI kit 架构 §2 @xi/ui-kit · D3-FE-ARCH-001 顶层架构 §2.2 三画布分工 · Source/Sink Review v7.0
- 下游:D3-FE-ARCH-008 运行时模式切换 · D3-FE-ARCH-009 Preset 全链路
本文档对智能体的阅读要求
- 修改模块 UI 代码前必须先读本文档相关章节
- 提交 PR 前必须通过 §9 自查清单的所有项
- 规范用 ✅ / ❌ 样板对照,未通过对照即视为不合格
1. 文档使命与根本原则
1.1 为什么需要本文档
现有 frontend_vue3/ 代码中 8 个具体 UI 缺陷反映了前端实施层面缺少统一的强制规范。分散在各处 tech-arch / UI kit 的 API 定义粒度太粗,智能体难以据此产出一致正确的实现。
本文档填补这一空白:
- 每个模块必须有什么 UI 元素 · 不许少
- 每个 UI 元素必须放在什么位置 · 不许乱放
- 每个交互必须走什么数据流 · 不许绕路
1.2 四条根本原则(所有智能体必须认同)
| 编号 | 原则 | 说明 |
|---|---|---|
| P-01 | 规范即契约 | 本文档约束的 UI 元素是产品契约的一部分,不是可选的"美观建议"。删除 toggle、漏图标、漏 preset 入口都属于功能缺失,不是"视觉偏好"。 |
| P-02 | 所有模块一致 | 20 类分类下的所有模块(source / sink / gain / filter / eq / ...)在 UI 上共享同一套 Shell 结构(toggle + preset + argument + runtime mode 等)。模块 A 能用 B 也必须能用。 |
| P-03 | 样式可变,结构不变 | 允许不同模块的图标/颜色/tooltip 文案不同,但强制元素的位置/存在性不允许省略。比如 toggle 必须在左上角,不能挪到右下角或删掉。 |
| P-04 | UI-Param-Preset 三向绑定 | 用户改 UI → 实时更新 param → 实时保存到 preset 草稿;加载 preset → 恢复 param → UI 自动刷新。三者不能脱节(§5 详述)。 |
2. ModuleNode 画布卡片 Shell 规范(强制)
2.1 视觉与功能结构
每个画布上的 ModuleNode 必须包含以下 7 个元素,位置固定:
┌───────────────────────────────────────────┐
│ ①toggle ②icon ③name ⑦⚙ │ ← 顶栏(固定高 24px)
├───────────────────────────────────────────┤
│ ●④输入端口 ⑤输出端口● │
│ ● ● │ ← 内容区(端口 + 可选波形预览)
│ ● ● │
├───────────────────────────────────────────┤
│ ⑥status: running / idle / error │ ← 底栏(固定高 18px)
└───────────────────────────────────────────┘
| 序号 | 元素 | 强制 | 说明 |
|---|---|---|---|
| ① | Toggle 开关(左上角) | ✅ | 启停模块 · 读写 linkStore.isModuleEnabled()/setModuleEnabled() · 与 Tuning Dialog 右上角开关双向同步 |
| ② | 模块 Icon(toggle 右侧 16x16) | ✅ | 每个模块必须有 icon · 从 moduleLibrary.ts 的 metadata 读取 · fallback 到分类默认 icon |
| ③ | 模块名称 + 实例编号(icon 右侧) | ✅ | 格式 EQ #3 / Gain #1 · 文案用 displayName || typeName · 字号 12px |
| ④ | 输入端口(左侧排列) | ✅ | 从 moduleDef.ports.inputs 读取 · 端口数量取决于模块定义 |
| ⑤ | 输出端口(右侧排列) | ✅ | 从 moduleDef.ports.outputs 读取 · 动态格式信息(channels/sampleRate)可点击查看 |
| ⑥ | 运行状态指示(底栏) | ✅ | 颜色+文字:running(绿)/ idle(灰)/ error(红)· 从 audioEngineStore 订阅 |
| ⑦ | 快捷设置按钮(⚙,右上角) | ✅ | 点击打开 Tuning Dialog · 双击模块 body 也可 |
2.2 Toggle 开关的强制实现
<!-- ✅ 正确:ModuleNode.vue 中必须包含 -->
<template>
<g class="module-node" :class="{ disabled: !isEnabled }">
<!-- 顶栏 -->
<g class="top-bar">
<!-- ① toggle(左上角)-->
<foreignObject :x="x + 4" :y="y + 4" width="20" height="20">
<XiSwitch
:model-value="isEnabled"
size="xs"
@update:model-value="onToggleEnabled"
@click.stop
:title="isEnabled ? '禁用该模块(旁路)' : '启用该模块'"
/>
</foreignObject>
<!-- ② icon -->
<image :x="x + 28" :y="y + 6" width="16" height="16" :href="iconUrl" />
<!-- ③ name -->
<text :x="x + 48" :y="y + 18" class="module-name">
{{ displayName }} #{{ module.instanceIndex }}
</text>
<!-- ⑦ settings -->
<foreignObject :x="x + width - 24" :y="y + 4" width="20" height="20">
<button class="icon-btn" @click.stop="openTuningDialog">⚙</button>
</foreignObject>
</g>
<!-- 端口 + 内容区(略) -->
<!-- ⑥ status -->
<g class="bottom-bar">
<circle :cx="x + 8" :cy="y + height - 9" r="3" :fill="statusColor" />
<text :x="x + 16" :y="y + height - 6" class="status-text">{{ statusText }}</text>
</g>
</g>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useLinkStore } from '@/stores/linkStore'
import { useAudioEngineStore } from '@/stores/audioEngineStore'
import { XiSwitch } from '@xi/ui-kit'
const props = defineProps<{ module: ModuleInstance; x: number; y: number; width: number; height: number }>()
const linkStore = useLinkStore()
const audioEngineStore = useAudioEngineStore()
const isEnabled = computed(() => linkStore.isModuleEnabled(props.module.instanceId))
function onToggleEnabled(value: boolean) {
// 1. 写入 linkStore(同时会通过 WS 发 control-command)
linkStore.setModuleEnabled(props.module.instanceId, value)
// 2. Tuning Dialog 如果打开会自动刷新(响应式)
}
const statusColor = computed(() => { /* 从 audioEngineStore 订阅实例状态 */ })
const statusText = computed(() => { /* 同上 */ })
</script>
<!-- ❌ 错误:常见错误示例 -->
<!-- 错误 1:没有 toggle -->
<g class="module-node">
<text>EQ #3</text> <!-- 漏掉 toggle ① -->
</g>
<!-- 错误 2:toggle 在右上角(位置错)-->
<foreignObject :x="x + width - 24" :y="y + 4">
<XiSwitch /> <!-- 必须在左上角 -->
</foreignObject>
<!-- 错误 3:只改本地 state,没同步到 linkStore -->
<XiSwitch v-model="localEnabled" /> <!-- ❌ Dialog 打不开同步 -->
2.3 Icon 资源与 fallback
- 图标目录:
packages/ui-kit/src/assets/module-icons/{typeName}.svg(Monorepo)或src/assets/module-icons/(单体项目) - 命名规则:
{typeName}.svg· 例:eq-parametric.svg/source-tone.svg - 尺寸:24x24 原图 · 使用时 CSS
width/height: 16px缩放 - Fallback 策略:
- Level 1:具体模块 icon(如
eq-parametric.svg) - Level 2:分类 icon(如
filter.svg,对应categoryDefinitions[module.category].iconemoji 渲染) - Level 3:通用占位 icon(
module-default.svg,灰色方块+类型首字母)
- Level 1:具体模块 icon(如
- 集成点:
moduleLibrary.ts的ModuleDefinition必须有iconUrl: string字段 · 没有时由 fallback 逻辑填充
2.4 Status 状态颜色统一
| 状态 | 颜色(xi 设计令牌) | 场景 |
|---|---|---|
| running | xi.success (#10B981) |
模块正在处理音频 |
| idle | xi.ink-500 (#6B7280) |
链路未启动或模块被 disable |
| error | xi.danger (#EF4444) |
模块报错(如加载 wav 失败) |
| loading | xi.warning (#F59E0B) |
初始化中(如 source_wav 解码) |
3. ModuleLibrary 列表项渲染契约(强制)
3.1 视觉结构
左侧 ModuleLibrary 面板的每个模块卡片必须包含:
┌─────────────────────────┐
│ [Icon] 模块名称 │ ← 必须元素
│ 分类 · 简短描述 │ ← 必须元素(副标题 · 灰色)
└─────────────────────────┘
↑ hover 时显示完整 tooltip · 拖拽时显示预览
| 元素 | 强制 | 说明 |
|---|---|---|
| Icon(24x24 或 32x32) | ✅ | 同 §2.3 fallback 策略 |
| 模块中文名(主文案) | ✅ | 来自 moduleDef.displayName || moduleDef.typeName · 优先中文(i18n) |
| 分类标签(副文案) | ✅ | 从 categoryDefinitions[moduleDef.category].name 读取 |
| 简短描述(副文案继续) | ✅ | 来自 moduleDef.description(单行截断到 30 字) |
| Tooltip(hover 显示) | ✅ | 完整描述 + 作者 + 版本 + 示例连接图 |
| 拖拽预览(drag 时) | ✅ | 半透明 ghost 卡片跟随光标 |
3.2 正确与错误样板
<!-- ✅ 正确:ModuleLibraryItem.vue -->
<template>
<div
class="module-lib-item"
:draggable="true"
@dragstart="onDragStart"
:title="tooltipText"
>
<img :src="iconUrl" :alt="moduleDef.displayName" class="lib-icon" width="24" height="24" />
<div class="lib-text">
<div class="lib-name">{{ moduleDef.displayName || moduleDef.typeName }}</div>
<div class="lib-meta">
<span class="lib-category">{{ categoryName }}</span>
<span class="lib-desc">{{ truncate(moduleDef.description, 30) }}</span>
</div>
</div>
</div>
</template>
<!-- ❌ 错误:常见反模式 -->
<!-- 错误 1:只显示类型名,没 icon 没描述 -->
<div class="module-lib-item" :draggable="true">
{{ moduleDef.typeName }} <!-- 用户看到 "source_wav_v1" 不知道什么意思 -->
</div>
<!-- 错误 2:有 icon 但无 alt/fallback -->
<img :src="moduleDef.iconUrl" /> <!-- iconUrl 未定义时崩溃 -->
<!-- 错误 3:分类信息丢失 -->
<div>{{ moduleDef.displayName }}</div> <!-- 20 类模块混在一起无法快速筛选 -->
3.3 分类与检索
- 左侧 Library 必须按 20 类 categoryDefinitions 分组折叠展示(参考
moduleLibrary.tsL46-L67) - 顶部必须有搜索框 + 分类筛选 chips + 认证级别筛选(Official/Verified/Community,XiVST 场景)
- 默认展开 "source" / "sink" / "gain" 三个高频分类,其他收起
4. Tuning Dialog 统一布局规范(强制)
4.1 垂直分区
所有模块的 Tuning Dialog 必须用三段式布局:
┌──────────────────────────────────────────────┐
│ ▲ 顶部:Preset 管理区 │ ← 强制 · 高度 48-56px
│ [下拉选 preset][保存当前为 preset][删除] │
│ (预设名) [Toggle 开关] │
├──────────────────────────────────────────────┤
│ ● 中部:Argument 参数区 │ ← 强制 · 占据主要空间
│ │
│ ┌─ 输入参数 ─────────────────────────┐ │
│ │ (参数 1 控件) │ │
│ │ (参数 2 控件) │ │
│ │ ... │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌─ 输出端口信息 ────────────────────┐ │
│ │ 通道数: [2 ▾] 采样率: [48000 ▾] │ │
│ │ 数据类型: Float32 | Block: 64 │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌─ 性能监控(可选)────────────────┐ │
│ │ CPU: 2.3% Memory: 128KB │ │
│ └───────────────────────────────────┘ │
├──────────────────────────────────────────────┤
│ ▼ 底部:操作区 │ ← 强制 · 高度 40px
│ [关闭][应用][保存并关闭] [? 帮助] │
└──────────────────────────────────────────────┘
| 区域 | 强制 | 说明 |
|---|---|---|
| 顶部 Preset 管理区 | ✅ | 所有模块都必须有 · 位置固定在 Dialog 最上方 · 详见 D3-FE-ARCH-009 Preset 全链路 |
| 顶部 Toggle 开关 | ✅ | 与 ModuleNode 左上角 toggle 双向同步 · 格式:[🔘 启用] 文字+开关 |
| 中部 Argument 区 | ✅ | 至少分三块:输入参数 / 输出端口信息 / 性能监控(可选) |
| 输入参数块 | ✅ | 从 moduleDef.params 渲染 · 用 <XiPropertyEditor> · 按 group 字段折叠 |
| 输出端口信息块 | ✅ | 显示每个输出端口的 channels / sampleRate / blockSize / dataType · 可选编辑(对 source 模块) |
| 性能监控块 | ⚠️ 可选 | Phase 8 后 · CPU/Memory · 错误计数 |
| 底部操作区 | ✅ | 关闭(丢弃变更)· 应用(写入不关)· 保存并关闭(写入 + 关闭) |
4.2 Argument 输入参数块的分组与类型
// moduleDef.params 的 ParamDef schema
interface ParamDef {
key: string
label: string
type: 'number' | 'string' | 'boolean' | 'enum' | 'slider' | 'color' | 'freq' | 'dbGain' | 'file' | 'device'
defaultValue: any
min?: number
max?: number
step?: number
options?: Array<{ label: string; value: any }>
unit?: string
tooltip?: string
readonly?: boolean
group?: string // 分组键 · 如 'eq-band-1' / 'advanced'
groupLabel?: string // 分组标题
structural?: boolean // 是否为结构性参数(见 D3-FE-ARCH-009-preset-lifecycle.md §3)
realtime: boolean // 是否支持运行时热更新
externalBind?: ExternalBindRef // Signal Flow 绑定(见 signal-flow)
}
- 每个参数都必须渲染为对应控件:
slider→ XiSlider /enum→ XiSelect /freq→ 频率输入框(支持 Hz/kHz 单位)/file→ 文件选择器(带 wav 预览)/device→ 设备下拉列表 - 分组:相同
group字段的参数聚在一个折叠块 · 默认展开第一组 - structural 参数:UI 上标红外边框 + 图标 🔒 · hover 提示"修改此参数会重建 DSP 实例"
4.3 输出端口信息块(不可省略)
这是 Bug #4 的根因修复点。每个模块的输出端口必须显示:
┌─ 输出端口信息 ─────────────────────────────────┐
│ out0: [2ch] 48000Hz Float32 64 samples │
│ out1: [2ch] 48000Hz Float32 64 samples │
│ [配置] │
└────────────────────────────────────────────────┘
- 对 source 模块(source_wav / source_device / source_tone):通道数/采样率 用户可编辑(影响 DSP 初始化)
- 对其他模块:通道数/采样率 只读显示(由 PortInfo propagation 自动推断,见 Source/Sink Review v7.0 §16)
- 点击"配置":仅对 source 模块可用 · 展开高级端口设置(声道映射 / blockSize 等)
5. 参数-Preset-UI 三向同步流程(强制 · 核心数据流)
5.1 黄金法则
改参数 → 立即更新 UI + 立即更新 preset 草稿 + 发送 WS control-command 到 DSP 加载 preset → 后端返回 param set → 更新 linkStore.paramValues → UI 响应式刷新
三者不能脱节。以下时序图必须严格遵守:
5.2 用户改参数时(UI → Param → Preset)
sequenceDiagram
participant User
participant UI as TuningDialog
participant Store as linkStore + presetStore
participant WS as WebSocket
participant BE as Backend
User->>UI: 拖动 Gain slider(从 0dB 到 +3dB)
UI->>UI: 触发 @update:modelValue
UI->>Store: linkStore.updateParam(moduleId, 'gain', 3.0)
Store->>Store: 1. 更新 paramValues(响应式 · UI 自动刷新)
Store->>Store: 2. 标记 presetStore.dirty = true
Store->>Store: 3. 如果启用 autoSaveDraft · 每 300ms 写 preset draft
Store->>WS: 4. 发送 control-command {type:'update-param', moduleId, key:'gain', value:3.0}
WS->>BE: 传递
BE->>BE: 写入 DSP
BE-->>WS: ack(可选)
Note over UI,Store: UI / Store / DSP 三者现在一致
Note over Store: presetStore.hasUnsavedChanges = true · 标题栏显示 ● 脏标记
5.3 用户加载 preset 时(Preset → Param → UI)
sequenceDiagram
participant User
participant UI as PresetDropdown
participant Store as presetStore + linkStore
participant WS as WebSocket
participant BE as Backend
User->>UI: 选择 preset "Rock Bass"
UI->>Store: presetStore.loadPreset(moduleId, presetId)
Store->>Store: 1. fetchPreset → 拿到完整 param set
Store->>Store: 2. 批量 linkStore.updateParamBulk(moduleId, params)
Store->>Store: 3. 响应式触发 · UI 自动刷新所有控件
Store->>WS: 4. 批量 control-command(或 load-preset 专用命令)
WS->>BE: 传递
BE->>BE: 批量写入 DSP(淡入淡出避免爆音)
Note over UI: 所有 slider/select 自动跳到新值
Note over Store: presetStore.activePresetId = presetId
Note over Store: presetStore.hasUnsavedChanges = false
5.4 实现要点
- 单向数据流:UI 永远不保存 local state · 永远 binding 到
linkStore.paramValues(响应式) - 批量更新用 updateParamBulk:避免 N 次 WS 调用(每个 slider 单改走 updateParam;preset 加载走 updateParamBulk 一次发送)
- 淡入淡出由后端负责:前端只管下发新值 · 后端 apply_link 路径自带淡入淡出(见 Source/Sink Review v7.0 §12.3.1)
- 脏标记
hasUnsavedChanges:在 Tuning Dialog 标题栏显示●· 用户关闭时弹确认框 - 详细契约:见 D3-FE-ARCH-009 Preset 全链路
5.5 常见错误
// ❌ 错误 1:UI 保存 local state(不同步到 store)
const localGain = ref(0)
function onGainChange(v: number) {
localGain.value = v // ❌ store / DSP 都不知道
}
// ❌ 错误 2:只改 store 不发 WS(DSP 不知道)
linkStore.paramValues.set(moduleId, { ...params, gain: v }) // ❌ 漏了 control-command
// ❌ 错误 3:加载 preset 时逐个 update(性能差 + 中间态不一致)
for (const [key, value] of Object.entries(preset.params)) {
await linkStore.updateParam(moduleId, key, value) // ❌ 用 updateParamBulk 一次搞定
}
// ✅ 正确
await linkStore.updateParamBulk(moduleId, preset.params)
6. 特殊模块的 UI 补充规范
本节为特定模块类型(非所有通用模块)追加的 UI 要求。
6.1 Source 模块(source_tone / source_wav / source_device)
除通用 Shell 外,额外必须:
| 元素 | 强制 | 说明 |
|---|---|---|
| 波形/频谱预览(内容区中间) | ⚠️ 可选 | 对 source_wav 必须 · 显示当前 wav 波形缩略图 · 拖动进度条 |
| 文件选择器(argument 区) | ✅(仅 wav) | 支持本地上传 + 服务器文件列表 · 预览时长 |
| 设备下拉(argument 区) | ✅(仅 device) | 实时扫描 WASAPI 设备列表 · 显示当前采样率/通道数 |
| 加载状态指示(底栏扩展) | ✅ | loading(解码中)/ ready / error · 配合 §2.4 status 颜色 |
| 诊断按钮 🔍(底栏右侧) | ⚠️ 强烈建议 | 点击显示:文件路径 / 解码器 / 内存占用 / 采样率协商日志 |
修复 Bug #8(wav/device 不出声):
- source_wav 加载失败时:status = error(红) + 底栏显示短提示("文件不存在"/"解码失败") + 点击诊断按钮展开详情
- source_device 设备不存在时:status = error + 设备下拉显示最近一次扫描时间戳 + "重新扫描"按钮
6.2 Sink 模块(sink_device / sink_file)
| 元素 | 强制 | 说明 |
|---|---|---|
| 目标设备/文件显示(argument 区) | ✅ | 当前输出到哪里 · 清晰可见 |
| 音量标(VU meter)(内容区) | ✅ | 实时显示输出电平 · 每通道一条 |
| wasapi_log.wav 查看按钮 | ⚠️ 可选 | 对 sink_device + debug 模式 · 打开最近的 log 文件 |
6.3 Mixer 模块
| 元素 | 强制 | 说明 |
|---|---|---|
| 路由矩阵可视化(内容区) | ✅ | N×M 网格 · cell 显示增益 · 点击 cell 修改 |
| 矩阵预设(argument 区) | ✅ | 常用预设:identity / stereo-split / surround-downmix |
7. 与 Signal Flow 系统的集成点
7.1 参数外部绑定标识
如果某参数的 externalBind 非空(见 D3-FE-ARCH-001 §5.8),在 Tuning Dialog 中:
- 该参数控件变只读(不可拖动/不可输入)
- 右侧显示绑定的信号名 + 当前实时值(小字灰色)
- 悬停显示"已绑定到 Signal Flow"+ 跳转链接
┌─ 输入参数 ──────────────────────────────────────┐
│ Gain: [----●---------] +3.0 dB [🔗 unbind] │
│ (锁定) bind to: vehicle.speed.smoothed │
│ 实时值: 0.62 (80 km/h) │
└─────────────────────────────────────────────────┘
7.2 Signal Flow 不影响 UI Shell 结构
即使启用了 Signal Flow 绑定,ModuleNode 卡片 Shell 的 7 个强制元素(§2.1)依然必须全部存在。只是 argument 区的某些参数显示为只读。
8. 可访问性(a11y)与 i18n 要求
- 键盘导航:Tab 顺序:toggle → settings → argument 区控件 → 底部操作
- ARIA 标签:toggle 用
<input type="checkbox" role="switch">或等效 ARIA · 所有图标按钮必须有aria-label - i18n:所有文案走
@xi/store-core的 useI18nStore · 禁止硬编码中文/英文 - 对比度:所有文字对比度 ≥ WCAG AA(正常文本 4.5:1,大文本 3:1)
- 颜色不作为唯一信息:status 除颜色外还有文字标签("running"/"error")· 色盲友好
9. 智能体提交 PR 前自查清单(强制)
修改任何模块 UI 代码后,提交 PR 前必须逐项确认:
9.1 ModuleNode(画布卡片)
- 左上角有 toggle 开关
- Toggle 与 Tuning Dialog 右上角开关双向同步(在 Dialog 里改 Toggle,画布立即响应;反之亦然)
- 有模块 icon(fallback 链完整)
- 有模块名称 + 实例编号(
EQ #3格式) - 输入输出端口完整渲染
- 底栏有 status 指示(颜色 + 文字)
- 右上角有设置按钮 ⚙(点击打开 Tuning Dialog)
- Disabled 状态视觉区分(整体半透明或灰色叠加)
9.2 ModuleLibrary(左侧库)
- 每个模块卡片有 icon(24x24)
- 显示模块中文名 + 类型名
- 显示分类标签 + 简短描述
- Hover 有完整 tooltip
- 可拖拽(拖拽时 ghost 预览)
- 按 20 类分类分组折叠
- 顶部有搜索框 + 分类筛选
9.3 Tuning Dialog(弹窗)
- 顶部有 Preset 管理区(下拉 + 保存 + 删除 + 脏标记●)
- 顶部右侧有 Toggle 开关(与 ModuleNode 同步)
- 中部有 Argument 区(输入参数 / 输出端口信息 / 性能监控)
- 输出端口信息完整(通道数/采样率/dataType/blockSize)
- Argument 参数按 group 分组折叠
- structural 参数视觉标红 + 🔒 图标
- 底部有操作区(关闭/应用/保存并关闭)
- externalBind 的参数视觉锁定 + 显示绑定信号名与实时值
9.4 数据流(三向同步)
- UI 改参数立即更新 linkStore.paramValues
- UI 改参数立即发 WS control-command
- UI 改参数立即标记 preset dirty(● 脏标记)
- 加载 preset 用 updateParamBulk(批量)
- 加载 preset 后 UI 所有控件自动刷新到新值
- 无 local state(UI 永远 bind 到 store)
9.5 Source/Sink 特殊
- source_wav 有波形预览 + 加载/错误状态
- source_device 有设备下拉 + 重新扫描按钮
- 加载失败时 status=error(红)+ 底栏诊断按钮
- sink_device/sink_file 有 VU meter 实时显示
9.6 可访问性
- 所有图标按钮有 aria-label
- Toggle 用 role="switch"
- 所有文案走 i18n(无硬编码中英文)
- 键盘 Tab 顺序合理
- 对比度 ≥ WCAG AA
任何一项不通过,PR 必须修改后重新审核。
10. 版本与变更记录
| 版本 | 日期 | 作者 | 说明 |
|---|---|---|---|
| v1.0 | 2026-05-09 | work-cline | 初稿 · 修复 2026-05-08 发现的 8 个 UI 缺陷 · 建立模块 UI 统一规范 · 核心:ModuleNode 7 元素 + ModuleLibrary icon+name + Tuning Dialog 三段式 + 三向同步时序 + 9 项自查清单 |
附录 A · 引用
上游: - D3-FE-ARCH-001 顶层架构(§2.2 三画布分工 · §5.8 Signal Flow) - D3-FE-ARCH-003 共享 UI kit 架构(§2 @xi/ui-kit 组件 API) - Source/Sink Review v7.0(§4.6 source/sink · §11.5 参数分类 · §16 PortInfo propagation)
同级(实现规范束): - D3-FE-ARCH-008 运行时模式(PC/DSP)切换规范 - D3-FE-ARCH-009 Preset 全链路规范
下游: - 各 app tech-arch(D2-P1 XiStudio / D2-P6 XiTune / D2-P9 XiForge 等)具体落地本规范 - Phase 8.5 实现时强制遵守本规范(见 D3-FE-ARCH-004 §2.10a PLAN-011)
规范引用: - MD 写作规范 v1.1 - 文档编号规范 v1.0 - 文档代码同步政策 v1.0(D3 层 Docs-First 初稿)