我已走完本仓库的强制 onboarding（L-core 四份 + AGENTS 硬规则/Onboarding Gate/必读链路、design-process、domain-tvu、code-conventions 起手必读段 + R2/R3/R6/R7/R14/R15/R19/R23），并且在仓库里找到了**这条需求自己的已批准设计合同**和**产品自有模块索引**——这改变了做法，先把关键几条说清楚。

## 分类与依据

- **US-3**（既有页加 feature）+ **intent classified as 场景 2.2**（已有页面加新 feature/element）。Path B（HTML 代码交付），走 `code-conventions.md`。
- 命中 AGENTS §必读链路第 11 条（查**本产品自己**有没有做过这个模块）。Bash 被停用，我改读该条的数据真源 `docs/internal/_generated/product-pattern-index.json`，**命中 `✅ covered`**：

| 产品自有模块（Config-T `rJJjWWs51n2iFOlCIC7aYG`） | 证据 |
|---|---|
| **`IP Source Item`** COMPONENT_SET `1012:8880`，变体 `Tag=No Source` / `Tag=Video Source`，84 instances | 来源卡片的产品自有范式 → 卡片按**两态**画，不是 6 张同款 |
| `Button` COMPONENT_SET `59:7772`（`Tag=First/Second/Third/Warning`），12 instances | 该产品有 file-local 按钮集 |

- 更关键的一条：`docs/_archive/_design-kickoffs/v4-1827-ndi-into-ipsource.md` 是**这条需求（V4-1827）已批准的 M50 kickoff packet**，Round 3 方向与你这四条要求逐条对应。所以文案、Keep/Change、组件映射我**照它执行**，不自创：`Type of source:` / `HDMI/SDI/USB` / `IP Source` / `NDI` / `Source Name` / `URL: ndi://` / `Apply` / `Merge All IP Source` / `Local input active — no configuration required.` 全部逐字取自它的 §8 Content Contract。

## 一处必须如实登记的边界

M17 要求「开建前打开宿主 surface 的 canonical 范式帧实测取值」。本 session 没有 Figma 通道（无 `use_figma`、无 Figma MCP），**我没能 probe `1011:7877` / `7862:1729`**。所以：控件颜色/间距/字阶全部来自 `src/tokens/variables.css` 与 canonical 组件源码（逐行 mirror，可复核），但**页面级几何、导航页签的完整集合与顺序、`Merge All IP Source` 的落位、No Source 卡三个控件是否置灰，都是未实测的重建**。这几条我写进了交付物自己的「已知偏差」段，没有藏起来。

```html
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Config-T · Type of source → 3-way (NDI) · V4-1827 · TVU Design System</title>
<style>
/* ============================================================================
   TVU Design System tokens — copied VERBATIM from
   tvu-design-system/src/tokens/variables.css (dark mode, the DS default).
   Single-file deliverable => no external CDN, so the token source is inlined
   instead of <link>-ing playground-dist/tvu-tokens.css (R2.1 build route).
   Values are the SoT; do NOT hand-edit (AGENTS hard rule #4, R2).
   Light mode intentionally omitted: domain-tvu M4 — device / operator-console
   surfaces ship dark only.
   ========================================================================== */
:root{
  --color-white:#ffffff;
  --color-grey-2:#f8f8f8;
  --color-grey-3:#f0f0f0;
  --color-grey-4:#dbdbdb;
  --color-grey-5:#cccccc;
  --color-grey-6:#9e9e9e;
  --color-grey-7:#7b7b7b;
  --color-grey-8:#595959;
  --color-grey-9:#434343;
  --color-grey-10:#353535;
  --color-grey-11:#262626;
  --color-grey-12:#1f1f1f;
  --color-grey-13:#141414;
  --color-grey-14:#000000;

  --brand:#2fb54e;
  --brand-hover:#41c760;
  --brand-match:#0b2b13;
  --brand-disable:#1a652c;
  --red:#ea4233;

  --text-heading:#ffffff;
  --text-body:#f8f8f8;
  --text-2:#cccccc;
  --text-tips:#9e9e9e;
  --text-placeholder:#7b7b7b;
  --text-disabled:#595959;
  --text-primary-btn:#f8f8f8;

  --icon-active:#f8f8f8;
  --icon-default:#9e9e9e;
  --icon-disabled:#595959;
  --icon-placeholder:#7b7b7b;

  --bg-layer1:#141414;
  --bg-layer2:#1f1f1f;
  --bg-layer3:#262626;
  --bg-layer4:#353535;
  --bg-topbar:#000000;
  --bg-grey-btn-hv:#595959;
  --bg-grey-btn-en:#434343;
  --bg-grey-btn-dis:#434343;

  --line-border:#595959;
  --line-deep:#353535;
  --line-light:#434343;

  --input-line-border:var(--color-grey-8);
  --select-line-border:var(--color-grey-8);
  --input-filled-bg:var(--bg-layer4);
  --line-divider-light:var(--line-light);

  --sp-xxs:4px;
  --sp-xs:8px;
  --sp-s:12px;
  --sp-m:16px;
  --sp-l:24px;
  --sp-xl:32px;
  --sp-xxl:40px;
  --sp-xxxl:56px;

  --r-xs:2px;
  --r-s:4px;
  --r-m:8px;
  --r-l:12px;

  --bp-xl:1280px;
  --container-prose:840px;

  --font-family-en:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,'Fira Sans','Droid Sans','Helvetica Neue',sans-serif;
  --font-family-zh:'PingFang SC','Microsoft YaHei',Helvetica,Arial,sans-serif;
  --font-family-base:var(--font-family-en),var(--font-family-zh);

  --text-style-tips:400 12px/16px var(--font-family-base);
  --text-style-body:400 14px/21px var(--font-family-base);
  --text-style-pop-title:600 16px/24px var(--font-family-base);
  --text-style-headline-2:600 18px/27px var(--font-family-base);
  --font-size-tips:12px;
  --font-size-body:14px;
  --line-height-tips:16px;
  --line-height-body:21px;
  --font-weight-regular:400;
  --font-weight-semibold:600;

  --radio-circle-size:16px;
  --radio-circle-border-width:1.2px;
  --radio-selected-dot-size:6px;
  --radio-default-border:var(--text-tips);
  --radio-selected-dot:var(--bg-layer1);

  --form-item-row-height:32px;
  --form-item-label-width-120:120px;
  --form-item-root-width-single-line:638px;
  --control-height-xxs:32px;
}

/* ============================================================================
   Sheet chrome — annotation layer (NOT product UI).
   ========================================================================== */
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
  background:var(--color-grey-14);
  color:var(--text-body);
  font:var(--text-style-body);
  font-family:var(--font-family-base);
  padding:var(--sp-xl) var(--sp-l) var(--sp-xxxl);
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:var(--sp-xl);
}
.sheet{width:100%;max-width:var(--bp-xl);display:flex;flex-direction:column;gap:var(--sp-xs)}

/* M23 mirror in code: the Figma delivery card's navy #2B2D42 + #33A4FD stroke are
   Figma-annotation-only colours with no DS token behind them. Inlining those two
   hexes would break AGENTS hard rule #4 / R2, so the annotation layer here is
   expressed with DS tokens: bg-layer2 surface + 4px (--sp-xxs) brand rail. */
.note{
  background:var(--bg-layer2);
  border-left:var(--sp-xxs) solid var(--brand);
  border-radius:0 var(--r-s) var(--r-s) 0;
  padding:var(--sp-m) var(--sp-l);
  display:flex;flex-direction:column;gap:var(--sp-s);
}
.note h1{font:var(--text-style-headline-2);color:var(--text-heading);margin:0}
.note h2{font:var(--text-style-pop-title);color:var(--text-heading);margin:var(--sp-xs) 0 0}
.note p{margin:0;color:var(--text-2)}
.note .zh{color:var(--text-tips);font:var(--text-style-tips)}
.note ol,.note ul{margin:0;padding-left:var(--sp-l);color:var(--text-2);display:flex;flex-direction:column;gap:var(--sp-xxs)}
.note code{font-family:monospace;font-size:var(--font-size-tips);color:var(--text-body);background:var(--bg-layer3);padding:1px var(--sp-xxs);border-radius:var(--r-xs)}
.note table{width:100%;border-collapse:collapse;font:var(--text-style-tips)}
.note th{text-align:left;color:var(--text-tips);font-weight:var(--font-weight-semibold);padding:var(--sp-xxs) var(--sp-xs);border-bottom:1px solid var(--line-deep);vertical-align:top}
.note td{color:var(--text-2);padding:var(--sp-xxs) var(--sp-xs);border-bottom:1px solid var(--line-deep);vertical-align:top}
.note .meta{display:flex;flex-wrap:wrap;gap:var(--sp-xs) var(--sp-l);color:var(--text-tips);font:var(--text-style-tips)}
.tag{
  display:inline-flex;align-items:center;height:var(--sp-l);padding:0 var(--sp-xs);
  border-radius:var(--r-s);font:var(--text-style-tips);font-weight:var(--font-weight-semibold);
  background:var(--bg-layer3);color:var(--text-2);
}
.tag--new{background:var(--brand);color:var(--text-primary-btn)}
.tag--base{background:var(--bg-layer4);color:var(--text-2)}

.framecap{display:flex;align-items:baseline;gap:var(--sp-s);flex-wrap:wrap;padding-left:var(--sp-xxs)}
.framecap__id{font:var(--text-style-pop-title);color:var(--text-heading)}
.framecap__sub{font:var(--text-style-tips);color:var(--text-tips)}

/* ============================================================================
   Product frame — Config-T Local UI page (WYSIWYG panorama, one per state)
   ========================================================================== */
.frame{
  width:100%;
  background:var(--bg-layer1);
  border:1px solid var(--line-deep);
  border-radius:var(--r-m);
  overflow:hidden;
  display:flex;
  flex-direction:column;
}

/* Top nav — KEEP / DO NOT TOUCH. No NDI tab is added (requirement 4).
   Mirrors DS Tab/Item variant Tag=line, activeColor=green
   (src/components/Tab/TabItem.vue: height 32px, padding 0 var(--sp-m),
   normal --text-tips, active --brand + 1px brand bottom border + 700). */
.nav{
  background:var(--bg-topbar);
  border-bottom:1px solid var(--line-deep);
  display:flex;
  align-items:flex-end;
  gap:var(--sp-xxs);
  padding:0 var(--sp-m);
}
.nav__item{
  display:inline-flex;align-items:center;
  height:var(--control-height-xxs);
  padding:0 var(--sp-m);
  font:var(--text-style-body);
  color:var(--text-tips);
  border-bottom:1px solid transparent;
  margin-bottom:-1px;
  white-space:nowrap;
}
.nav__item--active{color:var(--brand);border-bottom-color:var(--brand);font-weight:700}

.page{display:flex;flex-direction:column;gap:var(--sp-m);padding:var(--sp-l)}
.page__head{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-m)}
.page__title{font:var(--text-style-pop-title);color:var(--text-heading)}

/* Tx1-4 pager (KEEP, top-right) */
.pager{display:flex;align-items:center;gap:var(--sp-xs)}
.pager__btn{
  display:inline-flex;align-items:center;justify-content:center;
  width:var(--sp-l);height:var(--sp-l);
  border:1px solid var(--line-light);
  border-radius:var(--r-s);
  background:transparent;
  color:var(--icon-default);
  cursor:pointer;
}
.pager__label{font:var(--text-style-body);color:var(--text-body);min-width:var(--sp-xxl);text-align:center}
.pager__count{font:var(--text-style-tips);color:var(--text-tips)}

/* ── Type of source: row ── */
.srcrow{display:flex;align-items:center;gap:var(--sp-l);flex-wrap:wrap}
.srcrow__label{font:var(--text-style-body);color:var(--text-body);white-space:nowrap}
.radiogroup{display:flex;align-items:center;gap:var(--sp-xl);flex-wrap:wrap}

/* Radio — 1:1 mirror of src/components/Radio/Radio.vue (base of canonical Radio.vue) */
.radio{
  display:inline-flex;align-items:center;gap:var(--sp-xs);
  height:21px;cursor:pointer;
  font:var(--text-style-body);color:var(--text-body);
  user-select:none;
}
.radio:focus-visible{outline:2px solid var(--brand);outline-offset:2px;border-radius:var(--r-xs)}
.radio-circle{
  flex-shrink:0;
  width:var(--radio-circle-size);height:var(--radio-circle-size);
  box-sizing:border-box;border-radius:50%;
  border:var(--radio-circle-border-width) solid var(--radio-default-border);
  background:transparent;
  display:flex;align-items:center;justify-content:center;
}
.radio-dot{
  width:var(--radio-selected-dot-size);height:var(--radio-selected-dot-size);
  border-radius:50%;background:var(--radio-selected-dot);
}
.radio[aria-checked="true"] .radio-circle{background:var(--brand);border-color:var(--brand)}

/* ── Divider between the radio row and the content area (NEW this round) ── */
.divider{height:1px;background:var(--line-divider-light);border:0;margin:0;flex:none}

/* ── Content area ── */
.content{display:flex;flex-direction:column;gap:var(--sp-m);min-height:360px}

/* State 1 — passive inline hint (empty-state), no control, no action */
.hint{
  flex:1;display:flex;align-items:center;justify-content:center;
  font:var(--text-style-body);color:var(--text-tips);text-align:center;
}

/* State 2 — IP Source: 6-card grid, mirrors product-local component set
   `IP Source Item` 1012:8880 (Tag=Video Source / Tag=No Source) */
.contentbar{display:flex;align-items:center;justify-content:flex-end;gap:var(--sp-s)}
.cardgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-m)}
.card{
  background:var(--bg-layer2);
  border:1px solid var(--line-deep);
  border-radius:var(--r-s);
  padding:var(--sp-s);
  display:flex;flex-direction:column;gap:var(--sp-s);
}
.card__preview{
  aspect-ratio:16/9;
  background:var(--bg-layer3);
  border-radius:var(--r-xs);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-xxs);
  color:var(--icon-default);
}
.card__preview--empty{color:var(--icon-disabled)}
.card__previewlabel{font:var(--text-style-tips);color:var(--text-tips)}
.card__fields{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:var(--sp-xs) var(--sp-xs)}
.card__label{font:var(--text-style-tips);color:var(--text-tips);white-space:nowrap}
.card__foot{display:flex;justify-content:flex-end}

/* State 3 — NDI form (Form Item, Layout=1 line, Label Width=120 px) */
.form{
  width:100%;max-width:var(--form-item-root-width-single-line);
  display:flex;flex-direction:column;gap:var(--sp-s);
}
.formitem{display:flex;align-items:center;gap:var(--sp-s);min-height:var(--form-item-row-height)}
.formitem__label{
  width:var(--form-item-label-width-120);flex:none;
  font:var(--text-style-body);color:var(--text-2);
}
.formitem__ctrl{flex:1;min-width:0}
.formitem--actions{padding-left:calc(var(--form-item-label-width-120) + var(--sp-s))}

/* ── Input — 1:1 mirror of src/components/Input/Input.vue (line, size M) ── */
.tvu-input{
  display:block;width:100%;
  padding:var(--sp-xs) var(--sp-s);
  height:var(--control-height-xxs);
  background:transparent;
  border:1px solid var(--input-line-border);
  border-radius:var(--r-s);
  color:var(--text-body);
  font-size:var(--font-size-body);
  font-family:var(--font-family-base);
  outline:none;
  text-overflow:ellipsis;
}
.tvu-input::placeholder{color:var(--text-placeholder)}
.tvu-input:hover,.tvu-input:focus{border-color:var(--brand)}

/* ── Select / combobox — 1:1 mirror of src/canonical/SelectBoxBase.vue
      (variant=line, size M): 32px, 1px --select-line-border, --r-s,
      padding var(--sp-xs) var(--sp-s), arrow right 12px --icon-default ── */
.select-wrap{position:relative;display:block;width:100%}
.select-trigger{
  display:flex;align-items:center;width:100%;
  height:var(--control-height-xxs);
  padding:var(--sp-xs) var(--sp-s);
  padding-right:calc(var(--sp-s) * 3);
  background:transparent;
  border:1px solid var(--select-line-border);
  border-radius:var(--r-s);
  color:var(--text-body);
  font:var(--text-style-body);
  text-align:left;cursor:pointer;outline:none;
}
.select-trigger:hover,.select-trigger:focus-visible{border-color:var(--brand)}
.select-value{
  flex:1;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.select-value--placeholder{color:var(--text-placeholder)}
/* overlay: dropdown chevron anchored inside the trigger box, non-interactive —
   mirrors SelectBoxBase .select-arrow (R14: absolute positioning is an overlay,
   not a layout shortcut). */
.select-arrow{
  position:absolute;right:var(--sp-s);top:50%;transform:translateY(-50%);
  pointer-events:none;color:var(--icon-default);display:flex;align-items:center;
}

/* ── Button — 1:1 mirror of src/components/Button/Button.vue,
      canonical size M / radius square: 32px, padding-inline 16px, r 4px ── */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:0;
  height:var(--control-height-xxs);
  padding-inline:var(--sp-m);
  border:1px solid transparent;
  border-radius:var(--r-s);
  font-family:var(--font-family-base);
  font-size:var(--font-size-body);
  font-weight:var(--font-weight-regular);
  line-height:var(--line-height-body);
  cursor:pointer;white-space:nowrap;outline:none;user-select:none;
}
.btn:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.btn--fill-green{background:var(--brand);color:var(--text-primary-btn)}
.btn--fill-green:hover{background:var(--brand-hover)}
.btn--fill-gray{background:var(--bg-grey-btn-en);color:var(--text-2)}
.btn--fill-gray:hover{background:var(--bg-grey-btn-hv)}

/* icon sizing */
.ic{width:var(--sp-m);height:var(--sp-m);display:block;flex:none}
.ic--l{width:var(--sp-xl);height:var(--sp-xl)}
.ic--ndi{width:20px;height:10px;display:block;flex:none}
</style>
</head>
<body>

<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <!-- Icons taken verbatim from tvu-design-system/dist/icons/svg/** (R1 / R15 §icons:
       never hand-draw an SVG). Registry canonical names in comments. -->
  <symbol id="i-dropdown" viewBox="0 0 16 16"><!-- Arrow/Dropdown 1150:23504 · navigation/arrow-dropdown -->
    <path d="M3.05146 6.20128C3.27161 5.95374 3.65086 5.93145 3.89853 6.15148L7.86707 9.67727C7.94284 9.74458 8.05698 9.74458 8.13274 9.67727L12.1013 6.15148C12.349 5.93145 12.7282 5.95374 12.9484 6.20128C13.1685 6.44883 13.1462 6.82788 12.8985 7.04791L8.39841 11.046C8.17112 11.2479 7.8287 11.2479 7.60141 11.046L3.10129 7.04791C2.85362 6.82788 2.83131 6.44883 3.05146 6.20128Z" fill="currentColor"/>
  </symbol>
  <symbol id="i-prev" viewBox="0 0 16 16"><!-- Arrow/Previous 1149:23143 -->
    <path d="M10.3058 13.8253C10.0715 14.0596 9.69156 14.0596 9.45724 13.8253L4.26787 8.63595C3.9164 8.28448 3.9164 7.71463 4.26787 7.36316L9.45724 2.17378C9.69156 1.93947 10.0715 1.93947 10.3058 2.17378C10.5401 2.4081 10.5401 2.788 10.3058 3.02231L5.46995 7.85813C5.39185 7.93624 5.39185 8.06287 5.46995 8.14097L10.3058 12.9768C10.5401 13.2111 10.5401 13.591 10.3058 13.8253Z" fill="currentColor"/>
  </symbol>
  <symbol id="i-next" viewBox="0 0 16 16"><!-- Arrow/Next 1149:23171 -->
    <path d="M5.69826 13.8212C5.93257 14.0555 6.31247 14.0555 6.54679 13.8212L11.7321 8.63583C12.0836 8.28435 12.0836 7.7145 11.7321 7.36303L6.54679 2.17769C6.31247 1.94337 5.93257 1.94337 5.69826 2.17769C5.46394 2.412 5.46394 2.7919 5.69826 3.02622L10.53 7.85801C10.6082 7.93611 10.6082 8.06275 10.53 8.14085L5.69826 12.9726C5.46394 13.207 5.46394 13.5869 5.69826 13.8212Z" fill="currentColor"/>
  </symbol>
  <symbol id="i-camera" viewBox="0 0 16 16"><!-- Video/Camera 1269:21285 -->
    <path d="M13.1916 4.51507C13.9915 4.0445 15 4.62129 15 5.54939V10.4513C15 11.3794 13.9915 11.9562 13.1916 11.4856L11.6 10.5494V11.2003C11.6 12.1945 10.7941 13.0003 9.8 13.0003H2.8C1.80589 13.0003 1 12.1945 1 11.2003V4.8C1 3.80586 1.80594 2.99996 2.80008 3L9.80007 3.00029C10.7942 3.00033 11.6 3.80621 11.6 4.80029V5.45129L13.1916 4.51507ZM13.4986 10.274C13.6319 10.3524 13.8 10.2563 13.8 10.1016V5.89907C13.8 5.74439 13.6319 5.64826 13.4986 5.72669L11.4549 6.92885C10.9883 7.20335 10.4 6.86689 10.4 6.3255V4.80029C10.4 4.46893 10.1314 4.2003 9.80002 4.20029L2.80003 4.2C2.46865 4.19999 2.2 4.46862 2.2 4.8V11.2003C2.2 11.5317 2.46863 11.8003 2.8 11.8003H9.8C10.1314 11.8003 10.4 11.5317 10.4 11.2003V9.67518C10.4 9.13379 10.9883 8.79733 11.4549 9.07183L13.4986 10.274Z" fill="currentColor"/>
  </symbol>
  <symbol id="i-camera-off" viewBox="0 0 16 16"><!-- Video/Camera-off 1283:40267 -->
    <path d="M1.17569 13.9763C0.941403 14.2107 0.94144 14.5906 1.17578 14.8249C1.41012 15.0592 1.79001 15.0591 2.02431 14.8248L14.8243 2.02227C15.0586 1.78793 15.0585 1.40803 14.8242 1.17374C14.5899 0.93945 14.21 0.939487 13.9757 1.17382L1.17569 13.9763ZM9.80001 12.9995H5.54732L6.74708 11.7995H9.80001C10.1314 11.7995 10.4 11.5309 10.4 11.1995L10.4 9.67433C10.4 9.13294 10.9883 8.79648 11.4549 9.07097L13.4986 10.2731C13.6319 10.3516 13.8 10.2554 13.8 10.1008V5.89821C13.8 5.74353 13.6319 5.6474 13.4986 5.72583L11.8499 6.69564L14.1462 4.39894C14.6225 4.54433 15 4.98067 15 5.54853V10.4504C15 11.3785 13.9916 11.9553 13.1916 11.4848L11.6 10.5485L11.6 11.1995C11.6 12.1936 10.7941 12.9995 9.80001 12.9995ZM2.80005 4.19914L9.25499 4.19941L10.3645 3.08971C10.187 3.03114 9.99722 2.99944 9.80009 2.99943L2.8001 2.99914C1.80596 2.9991 1.00002 3.805 1.00002 4.79914V11.1995C1.00002 11.5574 1.10451 11.891 1.28466 12.1713L2.20241 11.2534C2.20083 11.2356 2.20002 11.2176 2.20002 11.1995V4.79914C2.20002 4.46776 2.46867 4.19913 2.80005 4.19914Z" fill="currentColor"/>
  </symbol>
  <symbol id="i-ndi" viewBox="0 0 20 10"><!-- indicator/NDI 2429:16375 -->
    <path d="M0 0V10H2.28483V5.77483C2.28483 5.50993 2.43302 5.32451 2.66769 5.32451C2.86528 5.32451 3.00113 5.44371 3.22344 5.81456L5.68119 10H8.17594V0H5.89113V4.27815C5.89113 4.54305 5.74292 4.72849 5.50827 4.72849C5.31066 4.72849 5.17481 4.60927 4.9525 4.23841L2.49478 0H0ZM17.292 4.99337C17.292 2.23841 15.1307 0 12.4877 0H8.8073V10H12.4877C15.143 10 17.292 7.74834 17.292 4.99337ZM14.9454 4.98012C14.9454 6.58278 13.8462 7.74834 12.3395 7.74834H11.3907C11.2258 7.74834 11.0921 7.60498 11.0921 7.42815V2.57185C11.0921 2.39502 11.2258 2.25166 11.3907 2.25166H12.3395C13.8462 2.25166 14.9454 3.41722 14.9454 4.98012ZM17.7152 10V0H20V10H17.7152Z" fill="currentColor"/>
  </symbol>
</svg>

<!-- ══════════════════ CONTEXT ══════════════════ -->
<div class="sheet">
  <div class="note">
    <h1>Config-T Local UI · <code>Type of source:</code> 2-way → 3-way (NDI)</h1>
    <p>V4-1827 — RPS One to support NDI as Live Input via ETH module. US-3 existing-product increment · intent 场景 2.2 · deliverable = single-file HTML hi-fi mockup.</p>
    <p class="zh">既有页增量：来源类型从二选一改为三选一，新增 NDI；内容区随选中项切换；选择行与内容区之间加分隔线；顶部导航不新增页签。</p>
    <div class="meta">
      <span>Author / 作者 · Nancy</span>
      <span>Design contract · <code>docs/_archive/_design-kickoffs/v4-1827-ndi-into-ipsource.md</code></span>
      <span>Product file · Config-T <code>rJJjWWs51n2iFOlCIC7aYG</code></span>
      <span>Theme · dark only (domain-tvu M4)</span>
    </div>
  </div>
</div>

<!-- ══════════════════ BEFORE ══════════════════ -->
<div class="sheet">
  <div class="framecap">
    <span class="tag tag--base">BEFORE</span>
    <span class="framecap__id">Baseline — 2-option exclusive</span>
    <span class="framecap__sub">Shipped product (Config-T v7.7–8.0): <code>HDMI/SDI/USB</code> · <code>IP Source</code> only · no divider · no NDI anywhere / 现网基线：二选一、无分隔线、产品无 NDI</span>
  </div>

  <div class="frame">
    <nav class="nav" aria-label="Config-T pages">
      <span class="nav__item">Encoder</span>
      <span class="nav__item">Config for live</span>
      <span class="nav__item nav__item--active" aria-current="page">IP Source</span>
      <span class="nav__item">MediaMind</span>
      <span class="nav__item">Return Video</span>
      <span class="nav__item">VoIP/IFB</span>
    </nav>

    <div class="page">
      <div class="page__head">
        <span class="page__title">IP Source</span>
        <div class="pager" aria-label="Transmitter pager">
          <button class="pager__btn" type="button" aria-label="Previous transmitter"><svg class="ic" aria-hidden="true"><use href="#i-prev"></use></svg></button>
          <span class="pager__label">Tx1</span>
          <button class="pager__btn" type="button" aria-label="Next transmitter"><svg class="ic" aria-hidden="true"><use href="#i-next"></use></svg></button>
          <span class="pager__count">1 / 4</span>
        </div>
      </div>

      <div class="srcrow">
        <span class="srcrow__label" id="lbl-before">Type of source:</span>
        <div class="radiogroup" role="radiogroup" aria-labelledby="lbl-before">
          <span class="radio" role="radio" aria-checked="false" tabindex="-1">
            <span class="radio-circle"></span>HDMI/SDI/USB
          </span>
          <span class="radio" role="radio" aria-checked="true" tabindex="0">
            <span class="radio-circle"><span class="radio-dot"></span></span>IP Source
          </span>
        </div>
      </div>

      <div class="content">
        <div class="contentbar">
          <button class="btn btn--fill-gray" type="button">Merge All IP Source</button>
        </div>
        <div class="cardgrid">
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.11:5000" aria-label="IP" title="239.100.10.11:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 1</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.12:5000" aria-label="IP" title="239.100.10.12:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 2</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview card__preview--empty"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera-off"></use></svg><span class="card__previewlabel">No Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" placeholder="rtp / udp / rtsp address" aria-label="IP">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value select-value--placeholder">Select</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.14:5000" aria-label="IP" title="239.100.10.14:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 3</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.15:5000" aria-label="IP" title="239.100.10.15:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 4</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview card__preview--empty"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera-off"></use></svg><span class="card__previewlabel">No Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" placeholder="rtp / udp / rtsp address" aria-label="IP">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value select-value--placeholder">Select</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- ══════════════════ AFTER · STATE 1 / 3 ══════════════════ -->
<div class="sheet">
  <div class="framecap">
    <span class="tag tag--new">AFTER · 1 / 3</span>
    <span class="framecap__id">Selected = <code>HDMI/SDI/USB</code></span>
    <span class="framecap__sub">Local input active → content area shows a passive inline hint only (no control, no action) / 本机输入生效，内容区只放一条被动提示，不放任何控件</span>
  </div>

  <div class="frame">
    <nav class="nav" aria-label="Config-T pages">
      <span class="nav__item">Encoder</span>
      <span class="nav__item">Config for live</span>
      <span class="nav__item nav__item--active" aria-current="page">IP Source</span>
      <span class="nav__item">MediaMind</span>
      <span class="nav__item">Return Video</span>
      <span class="nav__item">VoIP/IFB</span>
    </nav>

    <div class="page">
      <div class="page__head">
        <span class="page__title">IP Source</span>
        <div class="pager" aria-label="Transmitter pager">
          <button class="pager__btn" type="button" aria-label="Previous transmitter"><svg class="ic" aria-hidden="true"><use href="#i-prev"></use></svg></button>
          <span class="pager__label">Tx1</span>
          <button class="pager__btn" type="button" aria-label="Next transmitter"><svg class="ic" aria-hidden="true"><use href="#i-next"></use></svg></button>
          <span class="pager__count">1 / 4</span>
        </div>
      </div>

      <div class="srcrow">
        <span class="srcrow__label" id="lbl-s1">Type of source:</span>
        <div class="radiogroup" role="radiogroup" aria-labelledby="lbl-s1">
          <span class="radio" role="radio" aria-checked="true" tabindex="0">
            <span class="radio-circle"><span class="radio-dot"></span></span>HDMI/SDI/USB
          </span>
          <span class="radio" role="radio" aria-checked="false" tabindex="-1">
            <span class="radio-circle"></span>IP Source
          </span>
          <span class="radio" role="radio" aria-checked="false" tabindex="-1">
            <span class="radio-circle"></span>NDI
          </span>
        </div>
      </div>

      <hr class="divider">

      <div class="content">
        <p class="hint">Local input active — no configuration required.</p>
      </div>
    </div>
  </div>
</div>

<!-- ══════════════════ AFTER · STATE 2 / 3 ══════════════════ -->
<div class="sheet">
  <div class="framecap">
    <span class="tag tag--new">AFTER · 2 / 3</span>
    <span class="framecap__id">Selected = <code>IP Source</code></span>
    <span class="framecap__sub">Existing 6-card source grid kept verbatim (preview + IP + To: + Apply) + <code>Merge All IP Source</code>; only the divider above it is new / 既有六卡网格与合并按钮原样保留，上方分隔线是本轮新增</span>
  </div>

  <div class="frame">
    <nav class="nav" aria-label="Config-T pages">
      <span class="nav__item">Encoder</span>
      <span class="nav__item">Config for live</span>
      <span class="nav__item nav__item--active" aria-current="page">IP Source</span>
      <span class="nav__item">MediaMind</span>
      <span class="nav__item">Return Video</span>
      <span class="nav__item">VoIP/IFB</span>
    </nav>

    <div class="page">
      <div class="page__head">
        <span class="page__title">IP Source</span>
        <div class="pager" aria-label="Transmitter pager">
          <button class="pager__btn" type="button" aria-label="Previous transmitter"><svg class="ic" aria-hidden="true"><use href="#i-prev"></use></svg></button>
          <span class="pager__label">Tx1</span>
          <button class="pager__btn" type="button" aria-label="Next transmitter"><svg class="ic" aria-hidden="true"><use href="#i-next"></use></svg></button>
          <span class="pager__count">1 / 4</span>
        </div>
      </div>

      <div class="srcrow">
        <span class="srcrow__label" id="lbl-s2">Type of source:</span>
        <div class="radiogroup" role="radiogroup" aria-labelledby="lbl-s2">
          <span class="radio" role="radio" aria-checked="false" tabindex="-1">
            <span class="radio-circle"></span>HDMI/SDI/USB
          </span>
          <span class="radio" role="radio" aria-checked="true" tabindex="0">
            <span class="radio-circle"><span class="radio-dot"></span></span>IP Source
          </span>
          <span class="radio" role="radio" aria-checked="false" tabindex="-1">
            <span class="radio-circle"></span>NDI
          </span>
        </div>
      </div>

      <hr class="divider">

      <div class="content">
        <div class="contentbar">
          <button class="btn btn--fill-gray" type="button">Merge All IP Source</button>
        </div>
        <div class="cardgrid">
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.11:5000" aria-label="IP" title="239.100.10.11:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 1</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.12:5000" aria-label="IP" title="239.100.10.12:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 2</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview card__preview--empty"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera-off"></use></svg><span class="card__previewlabel">No Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" placeholder="rtp / udp / rtsp address" aria-label="IP">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value select-value--placeholder">Select</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.14:5000" aria-label="IP" title="239.100.10.14:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 3</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera"></use></svg><span class="card__previewlabel">Video Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" value="239.100.10.15:5000" aria-label="IP" title="239.100.10.15:5000">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value">Transmitter 4</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
          <div class="card">
            <div class="card__preview card__preview--empty"><svg class="ic ic--l" aria-hidden="true"><use href="#i-camera-off"></use></svg><span class="card__previewlabel">No Source</span></div>
            <div class="card__fields">
              <span class="card__label">IP:</span>
              <input class="tvu-input" type="text" placeholder="rtp / udp / rtsp address" aria-label="IP">
              <span class="card__label">To:</span>
              <span class="select-wrap"><button class="select-trigger" type="button" aria-label="To"><span class="select-value select-value--placeholder">Select</span></button><span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span></span>
            </div>
            <div class="card__foot"><button class="btn btn--fill-green" type="button">Apply</button></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- ══════════════════ AFTER · STATE 3 / 3 ══════════════════ -->
<div class="sheet">
  <div class="framecap">
    <span class="tag tag--new">AFTER · 3 / 3</span>
    <span class="framecap__id">Selected = <code>NDI</code> — NEW</span>
    <span class="framecap__sub">Single-source NDI form; no per-Tx enable toggle (selecting <code>NDI</code> already means “this Tx uses NDI”) / NDI 单源表单，不设 per-Tx 启用开关：选中 NDI 本身即代表启用</span>
  </div>

  <div class="frame">
    <nav class="nav" aria-label="Config-T pages">
      <span class="nav__item">Encoder</span>
      <span class="nav__item">Config for live</span>
      <span class="nav__item nav__item--active" aria-current="page">IP Source</span>
      <span class="nav__item">MediaMind</span>
      <span class="nav__item">Return Video</span>
      <span class="nav__item">VoIP/IFB</span>
    </nav>

    <div class="page">
      <div class="page__head">
        <span class="page__title">IP Source</span>
        <div class="pager" aria-label="Transmitter pager">
          <button class="pager__btn" type="button" aria-label="Previous transmitter"><svg class="ic" aria-hidden="true"><use href="#i-prev"></use></svg></button>
          <span class="pager__label">Tx1</span>
          <button class="pager__btn" type="button" aria-label="Next transmitter"><svg class="ic" aria-hidden="true"><use href="#i-next"></use></svg></button>
          <span class="pager__count">1 / 4</span>
        </div>
      </div>

      <div class="srcrow">
        <span class="srcrow__label" id="lbl-s3">Type of source:</span>
        <div class="radiogroup" role="radiogroup" aria-labelledby="lbl-s3">
          <span class="radio" role="radio" aria-checked="false" tabindex="-1">
            <span class="radio-circle"></span>HDMI/SDI/USB
          </span>
          <span class="radio" role="radio" aria-checked="false" tabindex="-1">
            <span class="radio-circle"></span>IP Source
          </span>
          <span class="radio" role="radio" aria-checked="true" tabindex="0">
            <span class="radio-circle"><span class="radio-dot"></span></span>NDI
          </span>
        </div>
      </div>

      <hr class="divider">

      <div class="content">
        <div class="form">
          <div class="formitem">
            <label class="formitem__label" for="ndi-name">Source Name</label>
            <span class="formitem__ctrl">
              <input class="tvu-input" id="ndi-name" type="text" value="STUDIO-A (CAM 1)" title="STUDIO-A (CAM 1)">
            </span>
          </div>
          <div class="formitem">
            <span class="formitem__label" id="ndi-url-label">URL: ndi://</span>
            <span class="formitem__ctrl">
              <span class="select-wrap">
                <button class="select-trigger" type="button" aria-labelledby="ndi-url-label" aria-haspopup="listbox" aria-expanded="false">
                  <span class="select-value" title="ndi://192.168.20.41:5961/STUDIO-A%20(CAM%201)">ndi://192.168.20.41:5961/STUDIO-A%20(CAM%201)</span>
                </button>
                <span class="select-arrow"><svg class="ic" aria-hidden="true"><use href="#i-dropdown"></use></svg></span>
              </span>
            </span>
          </div>
          <div class="formitem formitem--actions">
            <button class="btn btn--fill-green" type="button">Apply</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- ══════════════════ UX DELIVERY NOTES ══════════════════ -->
<div class="sheet">
  <div class="note">
    <h1>UX delivery notes / UX 交付说明</h1>

    <h2>1 · Delta — what changed this round / 本轮改动</h2>
    <ol>
      <li><code>Type of source:</code> 2-option → <strong>3-option exclusive</strong>, 3rd option = <code>NDI</code>. One control, one signal per Tx. <span class="zh">同一个控件从二选一改三选一，第三项 <code>NDI</code>；每个 Tx 仍只有一路信号。</span></li>
      <li>Content area gains a <strong>3rd state</strong> and becomes switchable by the selected radio: <code>HDMI/SDI/USB</code> → passive hint · <code>IP Source</code> → existing 6-card grid · <code>NDI</code> → NDI form. <span class="zh">内容区新增第三态，随选中项切换。</span></li>
      <li><strong>Divider added</strong> between the radio row and the content area — anchors “what is below belongs to the type selected above”. <span class="zh">选择行与内容区之间新增分隔线，用来锚定「下方配置属于上方所选类型」。</span></li>
      <li><strong>Top nav unchanged — no NDI tab.</strong> NDI is consolidated into the page where users already manage sources; no cross-page hunting. <span class="zh">顶部导航不新增页签，NDI 并入用户本来就在管理来源的这一页。</span></li>
    </ol>

    <h2>2 · Kept / do not touch / 保留不动</h2>
    <ul>
      <li>IP Source 6-card grid internals (preview + IP + To: + Apply) and <code>Merge All IP Source</code> — reused verbatim. <span class="zh">六卡网格内部与合并按钮原样复用。</span></li>
      <li>Tx1–4 pager (top-right) and per-Tx memory of the selected source type. <span class="zh">右上 Tx1–4 分页器与「每个 Tx 各记自己的来源类型」。</span></li>
      <li>Radio selected / unselected component styles; top nav structure; all other Config-T pages (Encoder / Config for live / MediaMind / Return Video / VoIP·IFB) stay visible and untouched. <span class="zh">同级页签全部保持可见——scope 收窄只收窄改动范围，不收窄可见范围。</span></li>
      <li>NDI form deliberately has <strong>no per-Tx enable toggle</strong>: selecting the <code>NDI</code> radio already carries that meaning, a separate toggle would duplicate it. <span class="zh">刻意不加 per-Tx 启用开关——选中 NDI 本身就是启用，再加开关是语义重复。</span></li>
    </ul>

    <h2>3 · Element → source mapping (Phase 0 / R0)</h2>
    <table>
      <thead><tr><th>Element</th><th>Source</th><th>Evidence</th><th>State</th></tr></thead>
      <tbody>
        <tr><td>Source-type radio ×3</td><td>DS library <code>radio</code> (dark theme=on, status off/on)</td><td>catalog key <code>9f07746f…</code>; visuals mirrored from <code>src/components/Radio/Radio.vue</code></td><td>reused; 3rd option fills the already-reserved hidden group — no new element</td></tr>
        <tr><td>Source card ×6</td><td><strong>product-local</strong> <code>IP Source Item</code> set <code>1012:8880</code>, <code>Tag=No Source</code> / <code>Tag=Video Source</code></td><td><code>_generated/product-pattern-index.json</code> — 84 instances in this file</td><td>reused (product pattern beats re-deriving from DS)</td></tr>
        <tr><td>Divider</td><td>library <code>line</code> (horizontal) → <code>--line-divider-light</code></td><td>design contract §9</td><td><strong>new</strong></td></tr>
        <tr><td><code>Apply</code> ×N + <code>Merge All IP Source</code></td><td>DS Button — <code>filling</code>, size <code>M</code>, radius <code>square</code>; green = primary confirm, gray = bulk action</td><td>mirrored from <code>src/components/Button/Button.vue</code></td><td>one single variant for every <code>Apply</code> (consistency is a contract acceptance item)</td></tr>
        <tr><td><code>Source Name</code></td><td><code>input box/line</code> size M inside Form Item (<code>Layout=1 line</code>, <code>Label Width=120 px</code>)</td><td>mirrored from <code>src/components/Input/Input.vue</code> + <code>--form-item-*</code> tokens</td><td><strong>new</strong></td></tr>
        <tr><td><code>URL: ndi://</code></td><td><code>select box/line</code> combobox (dual mode: dropdown = Discovery, typing = Manual)</td><td>mirrored from <code>src/canonical/SelectBoxBase.vue</code></td><td><strong>new</strong>; registered deviation — <code>Drop down List/Select</code> was unavailable</td></tr>
        <tr><td>Top nav</td><td>DS <code>Tab/Item</code> <code>Tag=line</code>, <code>activeColor=green</code></td><td>mirrored from <code>src/components/Tab/TabItem.vue</code></td><td>unchanged, no tab added</td></tr>
        <tr><td>Icons</td><td><code>Arrow/Dropdown</code> · <code>Arrow/Previous</code> · <code>Arrow/Next</code> · <code>Video/Camera</code> · <code>Video/Camera-off</code></td><td>paths copied verbatim from <code>dist/icons/svg/**</code></td><td>library-only, zero hand-drawn SVG</td></tr>
        <tr><td>Colour / spacing / type</td><td><code>src/tokens/variables.css</code> dark block, inlined verbatim</td><td>no hex outside that block</td><td>—</td></tr>
      </tbody>
    </table>
    <p class="zh">备注：库里存在 <code>indicator/NDI</code> 图标（<code>2429:16375</code>）。本稿没有用它——设计合同的内容清单里 NDI 只是一个 radio 文本项，加图标等于新增未批准元素。若 owner 想给 NDI 选项加品牌标识，这个图标已经在库里、可直接取。</p>

    <h2>4 · Known deviations — not measured / 已知偏差（未实测）</h2>
    <ul>
      <li><strong>Host-page geometry was not probed.</strong> This session had no Figma channel, so <code>1011:7877</code> / <code>7862:1729</code> were never opened. Control colours, borders, radii, type scale and control heights are mirrored line-for-line from DS source and are checkable; <strong>page-level geometry is a reconstruction</strong> — column widths, paddings, card size and the exact frame width (here: the <code>--bp-xl</code> 1280px token). <span class="zh">M17 要求先打开宿主 canonical 帧实测再建；本轮做不到，页面级几何是重建值，不是实测值，交给 owner/dev 按真帧校正。</span></li>
      <li><strong>Nav tab set and order</strong>: only the five “do not touch” page names are evidenced. The active tab is labelled <code>IP Source</code> because the contract calls this “the IP Source page”; the real label, the full tab list and its order are unverified. <span class="zh">页签集合与顺序未实测。</span></li>
      <li><strong><code>Merge All IP Source</code> position</strong> (here: content-area toolbar, right-aligned) and the <strong>Tx pager form</strong> (here: <code>‹ Tx1 ›</code> + <code>1 / 4</code>) are not probed — only their existence and “top-right” are.</li>
      <li><strong><code>Tag=No Source</code> card interior</strong>: the variant axis is evidenced, but whether its IP / To: / Apply are greyed out is not. This mockup takes the minimal reading — <em>only the preview differs, controls stay enabled</em> — so the operator can still configure and Apply. Owner call if that is wrong.</li>
      <li><strong>Field semantics</strong>: the contract writes “To:Transmitter”, the brief describes it as 目标接收端. Same slot; the label here is the contract’s evidenced string. All IPs / names / transmitter values are sample data.</li>
      <li><strong>R23 (constrained-cell overflow)</strong>: (a) ellipsis and (c) hover reveal via <code>title</code> are in; (b) JS truncation detection is not — a static mockup carries no script, so <code>title</code> is always-on rather than detection-gated. Dev must implement (b) in the real page.</li>
      <li><strong>M23 card colours not transplanted</strong>: the Figma delivery card’s navy <code>#2B2D42</code> / <code>#33A4FD</code> have no DS token behind them, and inlining raw hex would break the “colour hardcoding = bug” hard rule. This notes block therefore uses DS tokens (layer-2 surface + 4px brand rail) instead. <span class="zh">注释层配色按 code 侧硬规则用 token 表达，不搬 Figma 注释卡专用 hex。</span></li>
      <li><strong>Dark only</strong>, per the device / operator-console rule. No light-mode block is shipped.</li>
    </ul>

    <h2>5 · Explicitly out of scope / 明确不在本轮</h2>
    <p>Source-empty · scanning · discovery-failed · connecting · Apply success/fail · oversized source name · 4-Tx binding overview. These are carried in the approved spec’s backlog, <strong>not silently dropped</strong> — the only feedback state in scope is the passive <code>HDMI/SDI/USB</code> hint, which exists solely to keep the content area from reading as a broken blank page.</p>
    <p class="zh">这些反馈态在已批准 spec 里挂在 backlog，本轮不做；唯一在范围内的反馈是 HDMI/SDI/USB 那条被动提示，它的作用只是避免内容区看起来像坏页。</p>
  </div>
</div>

</body>
</html>
```

## 落在稿子里的几个判断，值得你复核

1. **卡片走产品自有模块，不走 DS 重推**。因为 pattern index 实证 `IP Source Item` 有两个变体，六张卡我按 4 个 `Video Source` + 2 个 `No Source` 画——这正是 AGENTS 第 11 条要治的「用了设计库组件、没用当前产品已有的组件」。
2. **两个 `Apply` 用同一个 variant**（filling/green/M/square）。卡片里放 32px 按钮偏大，但合同的 Consistency 验收项逐字要求「both Apply buttons reuse the same library Button variant」，所以我没按视觉舒适度改成 S。
3. **没给 NDI 选项加 `indicator/NDI` 图标**，尽管库里有。内容合同里 NDI 只是一个 radio 文本项，加图标是新增未批准元素。要加的话素材已就位。
4. **同级页签全部保留可见**（Scope Handling：「本期不动 X」≠ 隐藏 X），并额外给了一个 BEFORE 帧，让「二选一 → 三选一 + 新增分隔线」这个 delta 自己看得见。
5. **一处我判定不能自己拍的**：`No Source` 卡里三个控件是否置灰。我取了「只有 preview 区分、控件保持可用」的最小解释并写进已知偏差——这需要你或 probe 真帧来定。

最实的一条限制：**页面级几何是重建的，不是实测的**。M17 要求先开宿主 canonical 帧取值再建，本 session 无 Figma 通道做不到。控件层（颜色/边框/圆角/字阶/控件高）逐行对得上 DS 源码、可复核；列宽、内边距、卡片尺寸、帧宽需要拿真帧校一遍。