{
  "tool": "ds-usability/scan-consumer-friction",
  "takenAt": "2026-09-04T02:55:36.650Z",
  "question": "设计系统对使用者来说好不好用 —— 以及「修哪一个能解掉最多摩擦」",
  "dsRepo": "/Users/nancy/Documents/AICoding/VS_Code/tvu-design-system",
  "dsVersion": "1.2.0",
  "dsRegisteredComponents": 34,
  "consumerApps": 2,
  "direction": {
    "historyPoints": 4,
    "distinctDates": [
      "2026-08-13",
      "2026-08-14",
      "2026-09-04"
    ],
    "spanDays": 22,
    "latestMeasuredAt": "2026-09-04",
    "previousMeasuredAt": "2026-08-14",
    "trendClaimAllowed": false,
    "note": "🔴 4 个数据点但只有 3 个**不同测量日**（2026-08-13 · 2026-08-14 · 2026-09-04） ⇒ ⛔ 不下趋势结论，只报「动没动」",
    "perApp": {
      "graphics-insertion-app": {
        "now": {
          "componentComplianceRate": 0.167,
          "tokenDirectUseRate": 0.822,
          "missed": 10
        },
        "previous": {
          "componentComplianceRate": 0.167,
          "tokenDirectUseRate": 0.822,
          "missed": 10
        },
        "moved": false
      },
      "vue-app": {
        "now": {
          "componentComplianceRate": 0.027,
          "tokenDirectUseRate": 0.88,
          "missed": 36
        },
        "previous": {
          "componentComplianceRate": 0.027,
          "tokenDirectUseRate": 0.88,
          "missed": 36
        },
        "moved": false
      }
    }
  },
  "criteriaTouchedSincePrevPoint": 0,
  "rootCauses": [
    {
      "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
      "count": 43,
      "apps": [
        "graphics-insertion-app",
        "vue-app"
      ],
      "kinds": [
        "LIBRARY GAP"
      ],
      "fileCount": 13
    },
    {
      "reason": "broadcast content — team brand colour (not a UI surface), see file header",
      "count": 2,
      "apps": [
        "graphics-insertion-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R2)"
      ],
      "fileCount": 1
    },
    {
      "reason": "prose in a hint label (\"converted to\"), rendered as text not an icon",
      "count": 1,
      "apps": [
        "graphics-insertion-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R1)"
      ],
      "fileCount": 1
    },
    {
      "reason": "broadcast content — scorebug text on team fill, not a UI surface",
      "count": 1,
      "apps": [
        "graphics-insertion-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R2)"
      ],
      "fileCount": 1
    },
    {
      "reason": "broadcast content — lower-third plate, not a UI surface",
      "count": 1,
      "apps": [
        "graphics-insertion-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R2)"
      ],
      "fileCount": 1
    },
    {
      "reason": "broadcast content — ticker text, not a UI surface",
      "count": 1,
      "apps": [
        "graphics-insertion-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R2)"
      ],
      "fileCount": 1
    },
    {
      "reason": "broadcast content — graphic text, not a UI surface",
      "count": 1,
      "apps": [
        "graphics-insertion-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R2)"
      ],
      "fileCount": 1
    },
    {
      "reason": "alpha-channel checkerboard pattern, not a themed surface",
      "count": 1,
      "apps": [
        "graphics-insertion-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R2)"
      ],
      "fileCount": 1
    },
    {
      "reason": "hover/focus live on the variant classes (.btn-secondary/.btn-primary/.btn-danger); the base class is layout only",
      "count": 1,
      "apps": [
        "vue-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R9)"
      ],
      "fileCount": 1
    },
    {
      "reason": "inside a self-contained generated HTML doc with no TVU token scope",
      "count": 1,
      "apps": [
        "vue-app"
      ],
      "kinds": [
        "AUDIT-IGNORE(R2S)"
      ],
      "fileCount": 1
    }
  ],
  "perApp": [
    {
      "repo": "MicroApps",
      "app": "graphics-insertion-app",
      "pinned": "^1.2.0",
      "dir": "MicroApps/graphics-insertion-app",
      "sourceFiles": 13,
      "frictionState": "measured",
      "frictionCount": 16,
      "frictions": [
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/BottomToolbar.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/BottomToolbar.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/BottomToolbar.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/CenterCutColumn.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R1",
          "reason": "prose in a hint label (\"converted to\"), rendered as text not an icon",
          "file": "src/components/CenterCutColumn.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2",
          "reason": "broadcast content — team brand colour (not a UI surface), see file header",
          "file": "src/components/LayerGraphic.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2",
          "reason": "broadcast content — team brand colour (not a UI surface), see file header",
          "file": "src/components/LayerGraphic.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2",
          "reason": "broadcast content — scorebug text on team fill, not a UI surface",
          "file": "src/components/LayerGraphic.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2",
          "reason": "broadcast content — lower-third plate, not a UI surface",
          "file": "src/components/LayerGraphic.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2",
          "reason": "broadcast content — ticker text, not a UI surface",
          "file": "src/components/LayerGraphic.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2",
          "reason": "broadcast content — graphic text, not a UI surface",
          "file": "src/components/LayerGraphic.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/LayerPanel.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2",
          "reason": "alpha-channel checkerboard pattern, not a themed surface",
          "file": "src/components/LayerPanel.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/PreviewMonitor.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/PreviewMonitor.vue"
        }
      ],
      "localComponents": [
        {
          "name": "App",
          "file": "src/App.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "AudioMeter",
          "file": "src/components/AudioMeter.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "BottomToolbar",
          "file": "src/components/BottomToolbar.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "CenterCutColumn",
          "file": "src/components/CenterCutColumn.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "LayerGraphic",
          "file": "src/components/LayerGraphic.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "LayerPanel",
          "file": "src/components/LayerPanel.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "PreviewMonitor",
          "file": "src/components/PreviewMonitor.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "SrtUrlRow",
          "file": "src/components/SrtUrlRow.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "TvuIcon",
          "file": "src/components/TvuIcon.vue",
          "verdict": "alias",
          "dsHasSameName": false,
          "importsDs": true,
          "hasGapMarker": false
        }
      ],
      "verdictTally": {
        "gap": 5,
        "unknown": 3,
        "alias": 1
      }
    },
    {
      "repo": "MicroApps",
      "app": "vue-app",
      "pinned": "^1.2.0",
      "dir": "MicroApps/vue-app",
      "sourceFiles": 19,
      "frictionState": "measured",
      "frictionCount": 37,
      "frictions": [
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/App.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/AppSidebar.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/AppSidebar.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/AppSidebar.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/AutoRefresh.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/AutoRefresh.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/ConfirmDialog.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/ConfirmDialog.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/ConfirmDialog.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R9",
          "reason": "hover/focus live on the variant classes (.btn-secondary/.btn-primary/.btn-danger); the base class is layout only",
          "file": "src/components/ConfirmDialog.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/EmptyState.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/Pagination.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/Pagination.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/Pagination.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionRow.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "LIBRARY GAP",
          "rule": null,
          "reason": "DS control swap blocked on bundle size; see docs/decisions/2026-08-07-ds-native-control-exemptions.md",
          "file": "src/components/SessionTable.vue"
        },
        {
          "kind": "AUDIT-IGNORE",
          "rule": "R2S",
          "reason": "inside a self-contained generated HTML doc with no TVU token scope",
          "file": "src/utils/openMicroApp.js"
        }
      ],
      "localComponents": [
        {
          "name": "App",
          "file": "src/App.vue",
          "verdict": "alias",
          "dsHasSameName": false,
          "importsDs": true,
          "hasGapMarker": true
        },
        {
          "name": "AppOverview",
          "file": "src/components/AppOverview.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "AppOverviewCard",
          "file": "src/components/AppOverviewCard.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "AppSidebar",
          "file": "src/components/AppSidebar.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "AutoRefresh",
          "file": "src/components/AutoRefresh.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "ConfirmDialog",
          "file": "src/components/ConfirmDialog.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "EmptyState",
          "file": "src/components/EmptyState.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "EventOwnerCell",
          "file": "src/components/EventOwnerCell.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "Pagination",
          "file": "src/components/Pagination.vue",
          "verdict": "bypass",
          "dsHasSameName": true,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "SessionRow",
          "file": "src/components/SessionRow.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "SessionTable",
          "file": "src/components/SessionTable.vue",
          "verdict": "gap",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": true
        },
        {
          "name": "Skeleton",
          "file": "src/components/Skeleton.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "ToastStack",
          "file": "src/components/ToastStack.vue",
          "verdict": "unknown",
          "dsHasSameName": false,
          "importsDs": false,
          "hasGapMarker": false
        },
        {
          "name": "TvuIcon",
          "file": "src/components/TvuIcon.vue",
          "verdict": "alias",
          "dsHasSameName": false,
          "importsDs": true,
          "hasGapMarker": false
        }
      ],
      "verdictTally": {
        "alias": 2,
        "unknown": 5,
        "gap": 6,
        "bypass": 1
      }
    }
  ],
  "boundaries": [
    "本器 ⛔ 不判「使用者写得对不对」，只报「他们用不用得动 DS，以及他们自己写下的理由」。",
    "三分法里 `unknown` 是**如实的未定**，⛔ 不是「没问题」—— 它需要人打开文件看。",
    "`bypass` 判据是**同名**（大小写不敏感）⇒ 只抓得到同名的那一类；角色相同但换了名字的绕过**抓不到**，是下界。",
    "摩擦标记只在**使用者写了标记**时才可见 ⇒ 没写标记的绕过一律不在分母内（`never-audited` 那一档尤其）。",
    "本器是**观测器不是闸**：数据完整性 fail closed，但 ⛔ 不对任何指标设阈值。"
  ]
}