@gobing-ai/knowledge-kit 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/board/collect/index.js +725 -1101
  3. package/dist/board/daily-voice/index.js +722 -101
  4. package/dist/board/itc/index.js +13 -0
  5. package/dist/index.js +1796 -102
  6. package/package.json +1 -1
  7. package/plugins/generations/content-gen/dist/index.js +4 -0
  8. package/plugins/generations/core-facts-gen/dist/index.js +4 -0
  9. package/plugins/generations/daily-article-gen/dist/index.js +4 -0
  10. package/plugins/generations/dailynews-gen/dist/index.js +4 -0
  11. package/plugins/generations/episode-plan-gen/dist/index.js +102 -1
  12. package/plugins/generations/episode-plan-gen/src/index.ts +2 -4
  13. package/plugins/generations/gemini-tts/dist/index.js +4 -0
  14. package/plugins/generations/image-gen/dist/index.js +4 -0
  15. package/plugins/generations/news-report-gen/dist/index.js +4 -0
  16. package/plugins/generations/voice-gen/dist/index.js +4 -0
  17. package/plugins/generations/voxcpm-tts/dist/index.js +4 -0
  18. package/plugins/ingestions/aihot-ingest/dist/index.js +4 -0
  19. package/plugins/ingestions/horizon-ingest/dist/index.js +28 -1
  20. package/plugins/ingestions/horizon-ingest/src/index.ts +34 -0
  21. package/plugins/ingestions/last30days-ingest/dist/index.js +11 -3
  22. package/plugins/ingestions/last30days-ingest/src/index.ts +14 -3
  23. package/plugins/ingestions/web-search/dist/index.js +4 -0
  24. package/plugins/kk/config.example.yaml +8 -0
  25. package/plugins/kk/plugin.json +1 -1
  26. package/plugins/kk/publish-presets/daily-ai-voice.json +123 -0
  27. package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +20 -0
  28. package/plugins/kk/schedules/kk-daily-publish3-overseas.cron.tmpl +17 -0
  29. package/plugins/kk/scripts/daily-stages.ts +665 -78
  30. package/plugins/kk/scripts/kk-workflow-stages.ts +11 -1
  31. package/plugins/kk/scripts/sentences.ts +134 -0
  32. package/plugins/kk/scripts/series-names.ts +124 -0
  33. package/plugins/kk/scripts/topic-boundary.json +18 -83
  34. package/plugins/kk/workflows/kk-collect.yaml +2 -0
  35. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +112 -9
  36. package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +842 -0
  37. package/plugins/kk/workflows/kk-daily-publish3.yaml +282 -0
  38. package/plugins/kk/workflows/kk-daily-safety.yaml +50 -5
  39. package/plugins/publishings/bilibili-pub/dist/index.js +4 -0
  40. package/plugins/publishings/cnblogs-pub/dist/index.js +4 -0
  41. package/plugins/publishings/csdn-pub/dist/index.js +4 -0
  42. package/plugins/publishings/csdn-pub/dist/profile-check.js +4 -0
  43. package/plugins/publishings/emdash-pub/dist/index.js +4 -0
  44. package/plugins/publishings/infoq-pub/dist/index.js +4 -0
  45. package/plugins/publishings/infoq-pub/dist/profile-check.js +4 -0
  46. package/plugins/publishings/jike-pub/dist/index.js +4 -0
  47. package/plugins/publishings/jike-pub/dist/profile-check.js +4 -0
  48. package/plugins/publishings/juejin-pub/dist/index.js +4 -0
  49. package/plugins/publishings/juejin-pub/dist/profile-check.js +4 -0
  50. package/plugins/publishings/medium-pub/dist/index.js +4 -0
  51. package/plugins/publishings/podcast-pub/README.md +65 -0
  52. package/plugins/publishings/podcast-pub/dist/index.js +1906 -374
  53. package/plugins/publishings/podcast-pub/plugin.json +1 -1
  54. package/plugins/publishings/podcast-pub/src/backfill.ts +465 -0
  55. package/plugins/publishings/podcast-pub/src/index.ts +211 -34
  56. package/plugins/publishings/podcast-pub/src/microfeed-client.ts +32 -3
  57. package/plugins/publishings/podcast-pub/src/seo.ts +1079 -0
  58. package/plugins/publishings/podcast-pub/src/show-notes.ts +74 -14
  59. package/plugins/publishings/qiita-pub/dist/index.js +4 -0
  60. package/plugins/publishings/shipinhao-pub/dist/index.js +4 -0
  61. package/plugins/publishings/sspai-pub/dist/index.js +4 -0
  62. package/plugins/publishings/substack-pub/dist/index.js +4 -0
  63. package/plugins/publishings/substack-pub/dist/profile-check.js +4 -0
  64. package/plugins/publishings/surfdash-pub/dist/index.js +313 -32
  65. package/plugins/publishings/surfdash-pub/src/digest-check.ts +218 -0
  66. package/plugins/publishings/surfdash-pub/src/index.ts +170 -39
  67. package/plugins/publishings/surfdash-pub/src/patch.ts +44 -7
  68. package/plugins/publishings/threads-pub/dist/index.js +4 -0
  69. package/plugins/publishings/wechatmp-pub/dist/index.js +4 -0
  70. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +4 -0
  71. package/plugins/publishings/weibo-pub/dist/index.js +4 -0
  72. package/plugins/publishings/weibo-pub/dist/profile-check.js +4 -0
  73. package/plugins/publishings/x-pub/dist/index.js +4 -0
  74. package/plugins/publishings/x-pub/dist/profile-check.js +4 -0
  75. package/plugins/publishings/xhs-pub/dist/index.js +4 -0
  76. package/plugins/publishings/xhs-pub/dist/profile-check.js +4 -0
  77. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +4 -0
  78. package/plugins/publishings/zenn-pub/dist/index.js +4 -0
@@ -37,6 +37,14 @@ function storeSelectionsQueryString(query) {
37
37
  }
38
38
  return params.toString();
39
39
  }
40
+ function storeChannelsQueryString(query) {
41
+ const params = new URLSearchParams({
42
+ from: String(query.fromMs),
43
+ until: String(query.untilMs),
44
+ page: String(query.page)
45
+ });
46
+ return params.toString();
47
+ }
40
48
  var DEFAULT_READER_ORIGIN = "http://127.0.0.1:4340";
41
49
  function readerOriginFrom(search, defaultOrigin = DEFAULT_READER_ORIGIN) {
42
50
  const override = new URLSearchParams(search).get("kkApi");
@@ -112,6 +120,10 @@ function createBoardClient(options) {
112
120
  if (!isNativeSpur && !isLoopbackHttpOrigin(options.readerOrigin)) {
113
121
  throw new Error(`reader origin must be loopback http, got '${options.readerOrigin}'`);
114
122
  }
123
+ const aggregateReader = origin(options.aggregateReaderOrigin ?? DEFAULT_READER_ORIGIN);
124
+ if (!isLoopbackHttpOrigin(options.aggregateReaderOrigin ?? DEFAULT_READER_ORIGIN)) {
125
+ throw new Error(`aggregate reader origin must be loopback http, got '${options.aggregateReaderOrigin}'`);
126
+ }
115
127
  const doFetch = options.fetchImpl ?? fetch;
116
128
  async function getJson(url) {
117
129
  const response = await doFetch(url, { headers: { accept: "application/json" } });
@@ -506,11 +518,190 @@ function createBoardClient(options) {
506
518
  return getJson(path);
507
519
  },
508
520
  storeSelections: (query = {}) => isNativeSpur ? nativeStoreSelections(query) : getJson(`${reader}/api/workflows/kk-collect/store/selections?${storeSelectionsQueryString(query)}`),
521
+ storeChannels: (query) => getJson(`${aggregateReader}/api/workflows/kk-collect/store/channels?${storeChannelsQueryString(query)}`),
509
522
  runDetail: (runId) => getJson(`${board}/api/runs/${encodeURIComponent(runId)}`),
510
523
  progress: (runId) => getJson(`${board}/api/runs/${encodeURIComponent(runId)}/progress`)
511
524
  };
512
525
  }
513
526
 
527
+ // ../board/src/shared/channel-yield.tsx
528
+ import { jsx, jsxs } from "react/jsx-runtime";
529
+ function localDayKey(ms, timeZone) {
530
+ return new Intl.DateTimeFormat("en-CA", {
531
+ timeZone,
532
+ year: "numeric",
533
+ month: "2-digit",
534
+ day: "2-digit"
535
+ }).format(ms);
536
+ }
537
+ function isLocalMidnight(ms, timeZone) {
538
+ return new Intl.DateTimeFormat("en-GB", {
539
+ timeZone,
540
+ hour: "2-digit",
541
+ minute: "2-digit",
542
+ second: "2-digit",
543
+ hour12: false
544
+ }).format(ms) === "00:00:00";
545
+ }
546
+ function isPartialDay(day, data) {
547
+ const fromDay = localDayKey(data.fromMs, data.timeZone);
548
+ const untilDay = localDayKey(Math.max(data.fromMs, data.untilMs - 1), data.timeZone);
549
+ if (day === fromDay && !isLocalMidnight(data.fromMs, data.timeZone))
550
+ return true;
551
+ if (day === untilDay && !isLocalMidnight(data.untilMs, data.timeZone))
552
+ return true;
553
+ return false;
554
+ }
555
+ function lastPositiveLabel(at) {
556
+ return at === null ? "unknown" : `${new Date(at).toLocaleString()} (${new Date(at).toISOString()})`;
557
+ }
558
+ function ChannelYieldTable({ data }) {
559
+ const { sources, byDay, bySource } = data;
560
+ if (byDay.length === 0) {
561
+ return /* @__PURE__ */ jsx("p", {
562
+ className: "kk-board-state",
563
+ children: "No new items in this range."
564
+ });
565
+ }
566
+ const grandTotal = sources.reduce((sum, source) => sum + (bySource[source] ?? 0), 0);
567
+ return /* @__PURE__ */ jsxs("table", {
568
+ className: "kk-table kk-channel-yield-table",
569
+ children: [
570
+ /* @__PURE__ */ jsx("thead", {
571
+ children: /* @__PURE__ */ jsxs("tr", {
572
+ children: [
573
+ /* @__PURE__ */ jsx("th", {
574
+ scope: "col",
575
+ children: "Day"
576
+ }),
577
+ sources.map((source) => /* @__PURE__ */ jsx("th", {
578
+ scope: "col",
579
+ children: source
580
+ }, source)),
581
+ /* @__PURE__ */ jsx("th", {
582
+ scope: "col",
583
+ children: "Total"
584
+ })
585
+ ]
586
+ })
587
+ }),
588
+ /* @__PURE__ */ jsxs("tbody", {
589
+ children: [
590
+ byDay.map((day) => /* @__PURE__ */ jsxs("tr", {
591
+ children: [
592
+ /* @__PURE__ */ jsxs("td", {
593
+ children: [
594
+ day.day,
595
+ isPartialDay(day.day, data) ? " (partial)" : ""
596
+ ]
597
+ }),
598
+ sources.map((source) => /* @__PURE__ */ jsx("td", {
599
+ children: day.sources[source] ?? 0
600
+ }, source)),
601
+ /* @__PURE__ */ jsx("td", {
602
+ children: day.total
603
+ })
604
+ ]
605
+ }, day.day)),
606
+ /* @__PURE__ */ jsxs("tr", {
607
+ className: "kk-table-total",
608
+ children: [
609
+ /* @__PURE__ */ jsx("td", {
610
+ children: "Total"
611
+ }),
612
+ sources.map((source) => /* @__PURE__ */ jsx("td", {
613
+ children: bySource[source] ?? 0
614
+ }, source)),
615
+ /* @__PURE__ */ jsx("td", {
616
+ children: grandTotal
617
+ })
618
+ ]
619
+ })
620
+ ]
621
+ })
622
+ ]
623
+ });
624
+ }
625
+ function PluginRunTable({ data }) {
626
+ const { plugins, runsByDay, lastPositiveAt } = data;
627
+ if (runsByDay.length === 0) {
628
+ return /* @__PURE__ */ jsx("p", {
629
+ className: "kk-board-state",
630
+ children: "No collection ticks in this range."
631
+ });
632
+ }
633
+ return /* @__PURE__ */ jsxs("div", {
634
+ className: "kk-plugin-run",
635
+ children: [
636
+ /* @__PURE__ */ jsxs("table", {
637
+ className: "kk-table kk-plugin-run-table",
638
+ children: [
639
+ /* @__PURE__ */ jsx("caption", {
640
+ className: "kk-plugin-run-caption",
641
+ children: "Zero recorded contributions — plugin invocation health unknown."
642
+ }),
643
+ /* @__PURE__ */ jsx("thead", {
644
+ children: /* @__PURE__ */ jsxs("tr", {
645
+ children: [
646
+ /* @__PURE__ */ jsx("th", {
647
+ scope: "col",
648
+ children: "Day"
649
+ }),
650
+ /* @__PURE__ */ jsx("th", {
651
+ scope: "col",
652
+ children: "Runs"
653
+ }),
654
+ plugins.map((plugin) => /* @__PURE__ */ jsx("th", {
655
+ scope: "col",
656
+ children: plugin
657
+ }, plugin))
658
+ ]
659
+ })
660
+ }),
661
+ /* @__PURE__ */ jsx("tbody", {
662
+ children: runsByDay.map((day) => {
663
+ const pluginCounts = day.plugins;
664
+ return /* @__PURE__ */ jsxs("tr", {
665
+ children: [
666
+ /* @__PURE__ */ jsx("td", {
667
+ children: day.day
668
+ }),
669
+ /* @__PURE__ */ jsx("td", {
670
+ children: day.runs
671
+ }),
672
+ pluginCounts === null ? /* @__PURE__ */ jsx("td", {
673
+ colSpan: plugins.length,
674
+ className: "kk-board-state",
675
+ children: "unknown"
676
+ }) : plugins.map((plugin) => /* @__PURE__ */ jsx("td", {
677
+ children: pluginCounts[plugin] ?? 0
678
+ }, plugin))
679
+ ]
680
+ }, day.day);
681
+ })
682
+ })
683
+ ]
684
+ }),
685
+ /* @__PURE__ */ jsx("dl", {
686
+ className: "kk-board-facts",
687
+ children: plugins.map((plugin) => /* @__PURE__ */ jsxs("div", {
688
+ children: [
689
+ /* @__PURE__ */ jsxs("dt", {
690
+ children: [
691
+ "Last positive ",
692
+ plugin
693
+ ]
694
+ }),
695
+ /* @__PURE__ */ jsx("dd", {
696
+ children: lastPositiveLabel(lastPositiveAt[plugin] ?? null)
697
+ })
698
+ ]
699
+ }, plugin))
700
+ })
701
+ ]
702
+ });
703
+ }
704
+
514
705
  // ../board/src/shared/format.ts
515
706
  function availabilityLabel(availability) {
516
707
  switch (availability) {
@@ -581,6 +772,10 @@ function artifactViews(results, readerOrigin) {
581
772
  var ACTIVE_POLL_MS = 5000;
582
773
  var HISTORY_POLL_MS = 30000;
583
774
  var MAX_BACKOFF_MS = 30000;
775
+ function requestSequence() {
776
+ let newest = 0;
777
+ return { begin: () => ++newest, isCurrent: (token) => token === newest };
778
+ }
584
779
  function initialRefresh(intervalMs) {
585
780
  return { pending: false, failures: 0, backoffMs: intervalMs, lastSuccessAt: null, failingSince: null };
586
781
  }
@@ -849,9 +1044,79 @@ function dailySummaryFacts(results, now) {
849
1044
  ];
850
1045
  return facts;
851
1046
  }
1047
+ var GATE_NAMES = {
1048
+ "episode-duration": "Duration floor refusal",
1049
+ script: "Script refusal",
1050
+ "qc-floor": "QC floor refusal",
1051
+ "empty-pool": "Empty pool",
1052
+ "qc-empty": "QC emptied the pool",
1053
+ unknown: "unknown — no gate proven"
1054
+ };
1055
+ function isFunnelGate(value) {
1056
+ return value === "episode-duration" || value === "script" || value === "qc-floor" || value === "empty-pool" || value === "qc-empty" || value === "unknown";
1057
+ }
1058
+ function funnelView(value) {
1059
+ const record = asRecord(value);
1060
+ const gate = isFunnelGate(record?.gate) ? record.gate : null;
1061
+ return {
1062
+ poolCount: asNumber(record?.poolCount),
1063
+ annotatedCount: asNumber(record?.annotatedCount),
1064
+ qcRejectedCount: asNumber(record?.qcRejectedCount),
1065
+ topicKeptCount: asNumber(record?.topicKeptCount),
1066
+ dedupKeptCount: asNumber(record?.dedupKeptCount),
1067
+ plannedCount: asNumber(record?.plannedCount),
1068
+ estimatedMinutes: asNumber(record?.estimatedMinutes),
1069
+ measuredMinutes: asNumber(record?.measuredMinutes),
1070
+ minMinutes: asNumber(record?.minMinutes),
1071
+ maxMinutes: asNumber(record?.maxMinutes),
1072
+ gate,
1073
+ gateName: gate === null ? "not applicable — a content run has no gate" : GATE_NAMES[gate],
1074
+ exclusions: Array.isArray(record?.exclusions) ? record.exclusions.map((entry) => {
1075
+ const row = asRecord(entry);
1076
+ return row === null || typeof row.rule !== "string" ? null : { rule: row.rule, matched: typeof row.matched === "string" ? row.matched : "" };
1077
+ }).filter((row) => row !== null) : [],
1078
+ evidence: record === null ? {} : Object.fromEntries(Object.entries(record.evidence ?? {}).filter((entry) => typeof entry[1] === "string")),
1079
+ diagnostics: Array.isArray(record?.diagnostics) ? record.diagnostics.map((entry) => {
1080
+ const row = asRecord(entry);
1081
+ return row === null ? null : { code: String(row.code ?? ""), message: String(row.message ?? "") };
1082
+ }).filter((row) => row !== null) : []
1083
+ };
1084
+ }
1085
+ var WINDOW_UNITS = {
1086
+ s: 1000,
1087
+ m: 60000,
1088
+ h: 3600000,
1089
+ d: 86400000
1090
+ };
1091
+ function parseWindowMs(raw) {
1092
+ if (raw === null || raw === undefined)
1093
+ return null;
1094
+ const match = /^(\d+)([smhd])$/.exec(raw.trim());
1095
+ if (match === null)
1096
+ return null;
1097
+ const [, amountText, unitText] = match;
1098
+ const unit = unitText === "s" || unitText === "m" || unitText === "h" || unitText === "d" ? unitText : null;
1099
+ if (amountText === undefined || unit === null)
1100
+ return null;
1101
+ const milliseconds = Number(amountText) * WINDOW_UNITS[unit];
1102
+ return milliseconds > 0 ? milliseconds : null;
1103
+ }
1104
+ function supplyContextOf(windowSetting, startedAt) {
1105
+ const windowMs = parseWindowMs(windowSetting);
1106
+ const started = startedAt === null || startedAt === undefined ? null : Date.parse(startedAt);
1107
+ if (windowMs === null || started === null || !Number.isFinite(started))
1108
+ return null;
1109
+ return {
1110
+ windowMs,
1111
+ fromMs: started - windowMs,
1112
+ untilMs: started,
1113
+ fromLabel: formatTimestamp(new Date(started - windowMs).toISOString()),
1114
+ untilLabel: formatTimestamp(new Date(started).toISOString())
1115
+ };
1116
+ }
852
1117
 
853
1118
  // ../board/src/daily-voice/index.tsx
854
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
1119
+ import { jsx as jsx2, jsxs as jsxs2, Fragment } from "react/jsx-runtime";
855
1120
  var WORKFLOW = "kk-daily-ai-voice2";
856
1121
  var MODULE_PATH = "/board/modules/kk-daily-ai-voice2";
857
1122
  var TAB_LABELS = {
@@ -863,6 +1128,9 @@ var TAB_LABELS = {
863
1128
  function messageOf(error) {
864
1129
  return error instanceof Error ? error.message : String(error);
865
1130
  }
1131
+ function isOfflineError(message) {
1132
+ return message.includes("Failed to fetch") || message.includes("NetworkError") || message.includes("ECONNREFUSED");
1133
+ }
866
1134
  function usePolling(loader, intervalMs, refresh) {
867
1135
  const loaderRef = useRef(loader);
868
1136
  loaderRef.current = loader;
@@ -887,20 +1155,30 @@ function usePolling(loader, intervalMs, refresh) {
887
1155
  function DailyVoiceModule() {
888
1156
  const [link] = useState(() => parseDeepLink(window.location.search));
889
1157
  const [reader] = useState(() => readerOriginFrom(window.location.search));
1158
+ const [aggregateReader] = useState(() => readerOriginFrom(window.location.search, DEFAULT_READER_ORIGIN));
890
1159
  const readerOrigin = reader.origin;
891
- const client = useMemo(() => createBoardClient({ readerOrigin, boardOrigin: window.location.origin }), [readerOrigin]);
1160
+ const client = useMemo(() => createBoardClient({
1161
+ readerOrigin,
1162
+ aggregateReaderOrigin: aggregateReader.origin,
1163
+ boardOrigin: window.location.origin
1164
+ }), [readerOrigin, aggregateReader.origin]);
892
1165
  const [rows, setRows] = useState([]);
893
1166
  const [cursor, setCursor] = useState(null);
894
1167
  const [selection, setSelection] = useState({ runId: link.runId ?? null, tab: link.tab ?? "summary" });
895
1168
  const [diagnostics, setDiagnostics] = useState(() => [
896
1169
  ...link.diagnostics,
897
- ...reader.diagnostic === null ? [] : [reader.diagnostic]
1170
+ ...reader.diagnostic === null ? [] : [reader.diagnostic],
1171
+ ...aggregateReader.diagnostic === null ? [] : [aggregateReader.diagnostic]
898
1172
  ]);
899
1173
  const [historyRefresh, setHistoryRefresh] = useState(() => initialRefresh(HISTORY_POLL_MS));
900
1174
  const [readerRefresh, setReaderRefresh] = useState(() => initialRefresh(ACTIVE_POLL_MS));
901
1175
  const [results, setResults] = useState(null);
902
1176
  const [config, setConfig] = useState(null);
903
1177
  const [filterQuery, setFilterQuery] = useState("");
1178
+ const [supply, setSupply] = useState(null);
1179
+ const [storeOffline, setStoreOffline] = useState(false);
1180
+ const supplyRequests = useRef(requestSequence());
1181
+ const supplyKeyRef = useRef(null);
904
1182
  const historyBusy = useRef(false);
905
1183
  const readerBusy = useRef(false);
906
1184
  const selectionRef = useRef(selection);
@@ -933,15 +1211,24 @@ function DailyVoiceModule() {
933
1211
  return;
934
1212
  readerBusy.current = true;
935
1213
  setReaderRefresh((state) => tick(state));
1214
+ let stale = false;
936
1215
  try {
937
1216
  const [nextResults, nextConfig] = await Promise.all([
938
1217
  client.results(WORKFLOW, runId),
939
1218
  client.config(WORKFLOW, runId)
940
1219
  ]);
1220
+ if (selectionRef.current.runId !== runId) {
1221
+ stale = true;
1222
+ return;
1223
+ }
941
1224
  setResults(nextResults);
942
1225
  setConfig(nextConfig);
943
1226
  setReaderRefresh(() => succeeded(Date.now(), ACTIVE_POLL_MS));
944
1227
  } catch (error) {
1228
+ if (selectionRef.current.runId !== runId) {
1229
+ stale = true;
1230
+ return;
1231
+ }
945
1232
  setDiagnostics((existing) => [
946
1233
  ...existing,
947
1234
  { code: "board.reader.unavailable", message: messageOf(error) }
@@ -949,6 +1236,8 @@ function DailyVoiceModule() {
949
1236
  setReaderRefresh((state) => failed(state, Date.now()));
950
1237
  } finally {
951
1238
  readerBusy.current = false;
1239
+ if (stale)
1240
+ loadReader();
952
1241
  }
953
1242
  }, [client]);
954
1243
  usePolling(loadHistory, HISTORY_POLL_MS, historyRefresh);
@@ -965,6 +1254,42 @@ function DailyVoiceModule() {
965
1254
  const now = Date.now();
966
1255
  const summary = results === null ? null : dailySummary(results);
967
1256
  const facts = results === null ? [] : dailySummaryFacts(results, now);
1257
+ const funnel = funnelView(results?.summary.funnel);
1258
+ const context = supplyContextOf(config?.settings.window ?? null, summary?.startedAt ?? null);
1259
+ useEffect(() => {
1260
+ const bounds = tab === "summary" && context !== null ? context : null;
1261
+ const key = bounds === null ? null : `${selection.runId}:${bounds.fromMs}:${bounds.untilMs}`;
1262
+ if (key === supplyKeyRef.current)
1263
+ return;
1264
+ supplyKeyRef.current = key;
1265
+ if (bounds === null) {
1266
+ setSupply(null);
1267
+ return;
1268
+ }
1269
+ const token = supplyRequests.current.begin();
1270
+ let cancelled = false;
1271
+ (async () => {
1272
+ try {
1273
+ const page = await client.storeChannels({ fromMs: bounds.fromMs, untilMs: bounds.untilMs, page: 1 });
1274
+ if (cancelled || !supplyRequests.current.isCurrent(token))
1275
+ return;
1276
+ setSupply(page);
1277
+ setStoreOffline(false);
1278
+ } catch (error) {
1279
+ if (cancelled || !supplyRequests.current.isCurrent(token))
1280
+ return;
1281
+ setSupply(null);
1282
+ const message = messageOf(error);
1283
+ if (isOfflineError(message))
1284
+ setStoreOffline(true);
1285
+ else
1286
+ setDiagnostics((existing) => [...existing, { code: "board.store.channels-unavailable", message }]);
1287
+ }
1288
+ })();
1289
+ return () => {
1290
+ cancelled = true;
1291
+ };
1292
+ }, [client, tab, context, selection.runId]);
968
1293
  const artifacts = results === null ? [] : artifactViews(results, readerOrigin);
969
1294
  const selectedRow = rows.find((row) => row.id === selection.runId);
970
1295
  const filteredRows = useMemo(() => {
@@ -973,31 +1298,31 @@ function DailyVoiceModule() {
973
1298
  return rows;
974
1299
  return rows.filter((r) => r.id.toLowerCase().includes(q) || r.status.toLowerCase().includes(q));
975
1300
  }, [rows, filterQuery]);
976
- return /* @__PURE__ */ jsxs("div", {
1301
+ return /* @__PURE__ */ jsxs2("div", {
977
1302
  className: "kk-board kk-board-daily-voice",
978
1303
  children: [
979
- /* @__PURE__ */ jsxs("header", {
1304
+ /* @__PURE__ */ jsxs2("header", {
980
1305
  className: "kk-board-header",
981
1306
  children: [
982
- /* @__PURE__ */ jsxs("div", {
1307
+ /* @__PURE__ */ jsxs2("div", {
983
1308
  className: "kk-board-header-left",
984
1309
  children: [
985
- /* @__PURE__ */ jsxs("div", {
1310
+ /* @__PURE__ */ jsxs2("div", {
986
1311
  className: "kk-board-header-title-row",
987
1312
  children: [
988
- /* @__PURE__ */ jsx("h1", {
1313
+ /* @__PURE__ */ jsx2("h1", {
989
1314
  children: "Daily AI Voice"
990
1315
  }),
991
- selectedRow ? /* @__PURE__ */ jsx("span", {
1316
+ selectedRow ? /* @__PURE__ */ jsx2("span", {
992
1317
  className: `kk-badge ${selectedRow.status === "done" ? "kk-badge-success" : selectedRow.status === "failed" ? "kk-badge-failed" : "kk-badge-running"}`,
993
1318
  children: selectedRow.status
994
1319
  }) : null
995
1320
  ]
996
1321
  }),
997
- /* @__PURE__ */ jsxs("p", {
1322
+ /* @__PURE__ */ jsxs2("p", {
998
1323
  children: [
999
1324
  "Run ",
1000
- /* @__PURE__ */ jsx("code", {
1325
+ /* @__PURE__ */ jsx2("code", {
1001
1326
  children: selection.runId ?? "none selected"
1002
1327
  }),
1003
1328
  " · ",
@@ -1007,13 +1332,13 @@ function DailyVoiceModule() {
1007
1332
  })
1008
1333
  ]
1009
1334
  }),
1010
- /* @__PURE__ */ jsxs("div", {
1335
+ /* @__PURE__ */ jsxs2("div", {
1011
1336
  className: "kk-board-header-right",
1012
1337
  children: [
1013
- /* @__PURE__ */ jsxs("div", {
1338
+ /* @__PURE__ */ jsxs2("div", {
1014
1339
  className: "kk-board-run-filter-group",
1015
1340
  children: [
1016
- /* @__PURE__ */ jsx("input", {
1341
+ /* @__PURE__ */ jsx2("input", {
1017
1342
  type: "text",
1018
1343
  className: "kk-board-run-filter-input",
1019
1344
  placeholder: "Filter runs...",
@@ -1021,19 +1346,19 @@ function DailyVoiceModule() {
1021
1346
  onChange: (e) => setFilterQuery(e.target.value),
1022
1347
  "aria-label": "Filter runs"
1023
1348
  }),
1024
- /* @__PURE__ */ jsxs("label", {
1349
+ /* @__PURE__ */ jsxs2("label", {
1025
1350
  className: "kk-board-run-selector",
1026
1351
  children: [
1027
1352
  "Run",
1028
- /* @__PURE__ */ jsxs("select", {
1353
+ /* @__PURE__ */ jsxs2("select", {
1029
1354
  value: selection.runId ?? "",
1030
1355
  onChange: (event) => setSelection((state) => ({ ...state, runId: event.target.value || null })),
1031
1356
  children: [
1032
- /* @__PURE__ */ jsx("option", {
1357
+ /* @__PURE__ */ jsx2("option", {
1033
1358
  value: "",
1034
1359
  children: rows.length === 0 ? "No runs recorded" : filteredRows.length === 0 ? "No matching runs" : "Select a run"
1035
1360
  }),
1036
- filteredRows.map((row) => /* @__PURE__ */ jsxs("option", {
1361
+ filteredRows.map((row) => /* @__PURE__ */ jsxs2("option", {
1037
1362
  value: row.id,
1038
1363
  children: [
1039
1364
  row.id,
@@ -1047,13 +1372,13 @@ function DailyVoiceModule() {
1047
1372
  })
1048
1373
  ]
1049
1374
  }),
1050
- /* @__PURE__ */ jsx("button", {
1375
+ /* @__PURE__ */ jsx2("button", {
1051
1376
  type: "button",
1052
1377
  onClick: () => void loadReader(),
1053
1378
  title: "Refresh run data",
1054
1379
  children: "↻ Refresh"
1055
1380
  }),
1056
- selection.runId === null ? null : /* @__PURE__ */ jsx("a", {
1381
+ selection.runId === null ? null : /* @__PURE__ */ jsx2("a", {
1057
1382
  href: selectionPath(selection, window.location.search, MODULE_PATH),
1058
1383
  className: "kk-btn-action",
1059
1384
  title: "Permanent link",
@@ -1063,53 +1388,58 @@ function DailyVoiceModule() {
1063
1388
  })
1064
1389
  ]
1065
1390
  }),
1066
- /* @__PURE__ */ jsx("nav", {
1391
+ /* @__PURE__ */ jsx2("nav", {
1067
1392
  className: "kk-board-tabs",
1068
1393
  "aria-label": "Daily AI Voice tabs",
1069
- children: BOARD_TABS.map((candidate) => /* @__PURE__ */ jsx("button", {
1394
+ children: BOARD_TABS.map((candidate) => /* @__PURE__ */ jsx2("button", {
1070
1395
  type: "button",
1071
1396
  "aria-current": candidate === tab ? "page" : undefined,
1072
1397
  onClick: () => setSelection((state) => ({ ...state, tab: candidate })),
1073
1398
  children: TAB_LABELS[candidate]
1074
1399
  }, candidate))
1075
1400
  }),
1076
- rows.length === 0 ? /* @__PURE__ */ jsxs("p", {
1401
+ rows.length === 0 ? /* @__PURE__ */ jsxs2("p", {
1077
1402
  className: "kk-board-empty",
1078
1403
  children: [
1079
1404
  "No runs recorded for kk-daily-ai-voice2. Configure the workflow in",
1080
1405
  " ",
1081
- /* @__PURE__ */ jsx("a", {
1406
+ /* @__PURE__ */ jsx2("a", {
1082
1407
  href: "/board/settings/workflows",
1083
1408
  children: "Settings → Workflows"
1084
1409
  }),
1085
1410
  "."
1086
1411
  ]
1087
1412
  }) : null,
1088
- tab === "summary" ? /* @__PURE__ */ jsx(SummaryPanel, {
1413
+ tab === "summary" ? /* @__PURE__ */ jsx2(SummaryPanel, {
1089
1414
  facts,
1090
- results
1415
+ results,
1416
+ funnel,
1417
+ artifacts,
1418
+ context,
1419
+ supply,
1420
+ storeOffline
1091
1421
  }) : null,
1092
- tab === "results" ? /* @__PURE__ */ jsx(ResultsPanel, {
1422
+ tab === "results" ? /* @__PURE__ */ jsx2(ResultsPanel, {
1093
1423
  artifacts,
1094
1424
  channels: summary?.channels ?? []
1095
1425
  }) : null,
1096
- tab === "progress" ? /* @__PURE__ */ jsx(ProgressPanel, {
1426
+ tab === "progress" ? /* @__PURE__ */ jsx2(ProgressPanel, {
1097
1427
  progress: summary?.progress ?? null
1098
1428
  }) : null,
1099
- tab === "config" ? /* @__PURE__ */ jsx(ConfigPanel, {
1429
+ tab === "config" ? /* @__PURE__ */ jsx2(ConfigPanel, {
1100
1430
  config
1101
1431
  }) : null,
1102
- dedupeDiagnostics([...diagnostics, ...results?.diagnostics ?? []]).map((diagnostic) => /* @__PURE__ */ jsx("p", {
1432
+ dedupeDiagnostics([...diagnostics, ...results?.diagnostics ?? []]).map((diagnostic) => /* @__PURE__ */ jsx2("p", {
1103
1433
  role: "status",
1104
1434
  className: "kk-board-diagnostic",
1105
1435
  children: diagnosticLine(diagnostic)
1106
1436
  }, diagnostic.code)),
1107
- cursor === null ? null : /* @__PURE__ */ jsx("button", {
1437
+ cursor === null ? null : /* @__PURE__ */ jsx2("button", {
1108
1438
  type: "button",
1109
1439
  onClick: () => void loadHistory(),
1110
1440
  children: "Load more runs"
1111
1441
  }),
1112
- /* @__PURE__ */ jsxs("p", {
1442
+ /* @__PURE__ */ jsxs2("p", {
1113
1443
  className: "kk-board-footnote",
1114
1444
  children: [
1115
1445
  "Run history every ",
@@ -1122,44 +1452,52 @@ function DailyVoiceModule() {
1122
1452
  ]
1123
1453
  });
1124
1454
  }
1125
- function SummaryPanel({ facts, results }) {
1455
+ function SummaryPanel({
1456
+ facts,
1457
+ results,
1458
+ funnel,
1459
+ artifacts,
1460
+ context,
1461
+ supply,
1462
+ storeOffline
1463
+ }) {
1126
1464
  if (results === null)
1127
- return /* @__PURE__ */ jsx("p", {
1465
+ return /* @__PURE__ */ jsx2("p", {
1128
1466
  children: "No captured results for the selected run."
1129
1467
  });
1130
1468
  const summary = dailySummary(results);
1131
1469
  const liveChannels = summary.channels.filter((c) => c.state === "live").length;
1132
- return /* @__PURE__ */ jsxs("div", {
1470
+ return /* @__PURE__ */ jsxs2("div", {
1133
1471
  style: { display: "flex", flexDirection: "column", gap: "0.875rem" },
1134
1472
  children: [
1135
- /* @__PURE__ */ jsxs("div", {
1473
+ /* @__PURE__ */ jsxs2("div", {
1136
1474
  className: "kk-stat-grid",
1137
1475
  children: [
1138
- /* @__PURE__ */ jsxs("div", {
1476
+ /* @__PURE__ */ jsxs2("div", {
1139
1477
  className: "kk-stat-card",
1140
1478
  children: [
1141
- /* @__PURE__ */ jsx("span", {
1479
+ /* @__PURE__ */ jsx2("span", {
1142
1480
  className: "kk-stat-label",
1143
1481
  children: "Episode Outcome"
1144
1482
  }),
1145
- /* @__PURE__ */ jsx("span", {
1483
+ /* @__PURE__ */ jsx2("span", {
1146
1484
  className: "kk-stat-value",
1147
1485
  children: summary.outcome ?? "—"
1148
1486
  }),
1149
- /* @__PURE__ */ jsx("span", {
1487
+ /* @__PURE__ */ jsx2("span", {
1150
1488
  className: "kk-stat-sub",
1151
1489
  children: summary.articleTitle ? summary.articleTitle.slice(0, 32) : "Daily podcast"
1152
1490
  })
1153
1491
  ]
1154
1492
  }),
1155
- /* @__PURE__ */ jsxs("div", {
1493
+ /* @__PURE__ */ jsxs2("div", {
1156
1494
  className: "kk-stat-card",
1157
1495
  children: [
1158
- /* @__PURE__ */ jsx("span", {
1496
+ /* @__PURE__ */ jsx2("span", {
1159
1497
  className: "kk-stat-label",
1160
1498
  children: "Distribution"
1161
1499
  }),
1162
- /* @__PURE__ */ jsxs("span", {
1500
+ /* @__PURE__ */ jsxs2("span", {
1163
1501
  className: "kk-stat-value",
1164
1502
  children: [
1165
1503
  liveChannels,
@@ -1167,41 +1505,41 @@ function SummaryPanel({ facts, results }) {
1167
1505
  summary.channels.length
1168
1506
  ]
1169
1507
  }),
1170
- /* @__PURE__ */ jsx("span", {
1508
+ /* @__PURE__ */ jsx2("span", {
1171
1509
  className: "kk-stat-sub",
1172
1510
  children: "Channels published live"
1173
1511
  })
1174
1512
  ]
1175
1513
  }),
1176
- /* @__PURE__ */ jsxs("div", {
1514
+ /* @__PURE__ */ jsxs2("div", {
1177
1515
  className: "kk-stat-card",
1178
1516
  children: [
1179
- /* @__PURE__ */ jsx("span", {
1517
+ /* @__PURE__ */ jsx2("span", {
1180
1518
  className: "kk-stat-label",
1181
1519
  children: "Quality Score"
1182
1520
  }),
1183
- /* @__PURE__ */ jsx("span", {
1521
+ /* @__PURE__ */ jsx2("span", {
1184
1522
  className: "kk-stat-value",
1185
1523
  children: summary.quality?.overallScore ?? "—"
1186
1524
  }),
1187
- /* @__PURE__ */ jsx("span", {
1525
+ /* @__PURE__ */ jsx2("span", {
1188
1526
  className: "kk-stat-sub",
1189
1527
  children: summary.quality?.passed ? "Passed QC standard" : "Under evaluation"
1190
1528
  })
1191
1529
  ]
1192
1530
  }),
1193
- /* @__PURE__ */ jsxs("div", {
1531
+ /* @__PURE__ */ jsxs2("div", {
1194
1532
  className: "kk-stat-card",
1195
1533
  children: [
1196
- /* @__PURE__ */ jsx("span", {
1534
+ /* @__PURE__ */ jsx2("span", {
1197
1535
  className: "kk-stat-label",
1198
1536
  children: "Safety Verdict"
1199
1537
  }),
1200
- /* @__PURE__ */ jsx("span", {
1538
+ /* @__PURE__ */ jsx2("span", {
1201
1539
  className: "kk-stat-value",
1202
1540
  children: summary.safety?.verdict ?? "—"
1203
1541
  }),
1204
- /* @__PURE__ */ jsxs("span", {
1542
+ /* @__PURE__ */ jsxs2("span", {
1205
1543
  className: "kk-stat-sub",
1206
1544
  children: [
1207
1545
  "Confidence: ",
@@ -1212,18 +1550,18 @@ function SummaryPanel({ facts, results }) {
1212
1550
  })
1213
1551
  ]
1214
1552
  }),
1215
- /* @__PURE__ */ jsx("dl", {
1553
+ /* @__PURE__ */ jsx2("dl", {
1216
1554
  className: "kk-board-facts",
1217
- children: facts.map((fact) => /* @__PURE__ */ jsxs("div", {
1555
+ children: facts.map((fact) => /* @__PURE__ */ jsxs2("div", {
1218
1556
  children: [
1219
- /* @__PURE__ */ jsx("dt", {
1557
+ /* @__PURE__ */ jsx2("dt", {
1220
1558
  children: fact.label
1221
1559
  }),
1222
- /* @__PURE__ */ jsxs("dd", {
1560
+ /* @__PURE__ */ jsxs2("dd", {
1223
1561
  children: [
1224
1562
  fact.value,
1225
1563
  " ",
1226
- /* @__PURE__ */ jsx("span", {
1564
+ /* @__PURE__ */ jsx2("span", {
1227
1565
  className: "kk-board-state",
1228
1566
  children: fact.availability
1229
1567
  })
@@ -1231,25 +1569,308 @@ function SummaryPanel({ facts, results }) {
1231
1569
  })
1232
1570
  ]
1233
1571
  }, fact.label))
1572
+ }),
1573
+ /* @__PURE__ */ jsx2(FunnelPanel, {
1574
+ funnel,
1575
+ artifacts
1576
+ }),
1577
+ /* @__PURE__ */ jsx2(SupplyPanel, {
1578
+ context,
1579
+ supply,
1580
+ storeOffline
1581
+ })
1582
+ ]
1583
+ });
1584
+ }
1585
+ function funnelCount(value) {
1586
+ return value === null ? "unavailable" : String(value);
1587
+ }
1588
+ function funnelMinutes(value) {
1589
+ return value === null ? "unavailable" : `${value.toFixed(1)} min`;
1590
+ }
1591
+ function EvidenceChip({
1592
+ field,
1593
+ funnel,
1594
+ artifacts
1595
+ }) {
1596
+ const evidenceId = funnel.evidence[field];
1597
+ if (evidenceId === undefined)
1598
+ return /* @__PURE__ */ jsx2("span", {
1599
+ className: "kk-board-state",
1600
+ children: "no evidence"
1601
+ });
1602
+ if (evidenceId === "captured-settings")
1603
+ return /* @__PURE__ */ jsx2("code", {
1604
+ children: "captured-settings"
1605
+ });
1606
+ const artifact = artifacts.find((candidate) => candidate.id === evidenceId);
1607
+ return artifact === undefined || artifact.href === null ? /* @__PURE__ */ jsxs2("code", {
1608
+ children: [
1609
+ evidenceId,
1610
+ " (",
1611
+ artifact?.state ?? "missing",
1612
+ ")"
1613
+ ]
1614
+ }) : /* @__PURE__ */ jsx2("a", {
1615
+ href: artifact.href,
1616
+ children: /* @__PURE__ */ jsx2("code", {
1617
+ children: evidenceId
1618
+ })
1619
+ });
1620
+ }
1621
+ function FunnelPanel({ funnel, artifacts }) {
1622
+ const rows = [
1623
+ { label: "1 · Episode pool", value: funnelCount(funnel.poolCount), field: "poolCount" },
1624
+ { label: "2 · Annotated", value: funnelCount(funnel.annotatedCount), field: "annotatedCount" },
1625
+ { label: "3 · Topic filter kept", value: funnelCount(funnel.topicKeptCount), field: "topicKeptCount" },
1626
+ { label: "4 · QC rejected", value: funnelCount(funnel.qcRejectedCount), field: "qcRejectedCount" },
1627
+ {
1628
+ label: "5 · Post-dedup survivors",
1629
+ value: funnel.dedupKeptCount === null && (funnel.gate === "episode-duration" || funnel.gate === "script" || funnel.gate === "qc-floor") ? "unknown — cleared by the refusal" : funnel.dedupKeptCount === null && funnel.plannedCount !== null ? "unknown — plan recorded" : funnelCount(funnel.dedupKeptCount),
1630
+ field: "dedupKeptCount"
1631
+ },
1632
+ { label: "6 · Episode plan", value: funnelCount(funnel.plannedCount), field: "plannedCount" }
1633
+ ];
1634
+ return /* @__PURE__ */ jsxs2("section", {
1635
+ className: "kk-card kk-daily-funnel",
1636
+ children: [
1637
+ /* @__PURE__ */ jsxs2("div", {
1638
+ className: "kk-card-header",
1639
+ children: [
1640
+ /* @__PURE__ */ jsx2("h2", {
1641
+ children: "Episode funnel"
1642
+ }),
1643
+ /* @__PURE__ */ jsx2("span", {
1644
+ className: "kk-stat-sub",
1645
+ children: "artifact-backed by this run"
1646
+ })
1647
+ ]
1648
+ }),
1649
+ /* @__PURE__ */ jsxs2("dl", {
1650
+ children: [
1651
+ rows.map((row) => /* @__PURE__ */ jsxs2("div", {
1652
+ children: [
1653
+ /* @__PURE__ */ jsx2("dt", {
1654
+ children: row.label
1655
+ }),
1656
+ /* @__PURE__ */ jsxs2("dd", {
1657
+ children: [
1658
+ row.value,
1659
+ " · evidence",
1660
+ " ",
1661
+ /* @__PURE__ */ jsx2(EvidenceChip, {
1662
+ field: row.field,
1663
+ funnel,
1664
+ artifacts
1665
+ })
1666
+ ]
1667
+ })
1668
+ ]
1669
+ }, row.label)),
1670
+ /* @__PURE__ */ jsxs2("div", {
1671
+ children: [
1672
+ /* @__PURE__ */ jsx2("dt", {
1673
+ children: "Duration ladder"
1674
+ }),
1675
+ /* @__PURE__ */ jsxs2("dd", {
1676
+ children: [
1677
+ "measured ",
1678
+ funnelMinutes(funnel.measuredMinutes),
1679
+ " · estimated",
1680
+ " ",
1681
+ funnelMinutes(funnel.estimatedMinutes),
1682
+ " · floor ",
1683
+ funnelMinutes(funnel.minMinutes),
1684
+ " · ceiling",
1685
+ " ",
1686
+ funnelMinutes(funnel.maxMinutes)
1687
+ ]
1688
+ })
1689
+ ]
1690
+ }, "minutes"),
1691
+ /* @__PURE__ */ jsxs2("div", {
1692
+ children: [
1693
+ /* @__PURE__ */ jsx2("dt", {
1694
+ children: "No-content gate"
1695
+ }),
1696
+ /* @__PURE__ */ jsxs2("dd", {
1697
+ children: [
1698
+ funnel.gateName,
1699
+ " · evidence ",
1700
+ /* @__PURE__ */ jsx2(EvidenceChip, {
1701
+ field: "gate",
1702
+ funnel,
1703
+ artifacts
1704
+ })
1705
+ ]
1706
+ })
1707
+ ]
1708
+ }, "gate")
1709
+ ]
1710
+ }),
1711
+ funnel.exclusions.length === 0 ? null : /* @__PURE__ */ jsxs2("div", {
1712
+ children: [
1713
+ /* @__PURE__ */ jsx2("h3", {
1714
+ children: "Topic-filter exclusions"
1715
+ }),
1716
+ /* @__PURE__ */ jsx2("ul", {
1717
+ className: "kk-board-channels",
1718
+ children: funnel.exclusions.map((exclusion) => /* @__PURE__ */ jsxs2("li", {
1719
+ children: [
1720
+ /* @__PURE__ */ jsx2("strong", {
1721
+ children: exclusion.rule
1722
+ }),
1723
+ exclusion.matched === "" ? null : ` — matched “${exclusion.matched}”`
1724
+ ]
1725
+ }, `${exclusion.rule}::${exclusion.matched}`))
1726
+ })
1727
+ ]
1728
+ }),
1729
+ funnel.diagnostics.length === 0 ? null : /* @__PURE__ */ jsx2("ul", {
1730
+ children: funnel.diagnostics.map((diagnostic) => /* @__PURE__ */ jsxs2("li", {
1731
+ role: "status",
1732
+ className: "kk-board-diagnostic",
1733
+ children: [
1734
+ diagnostic.code,
1735
+ ": ",
1736
+ diagnostic.message
1737
+ ]
1738
+ }, diagnostic.code))
1739
+ })
1740
+ ]
1741
+ });
1742
+ }
1743
+ function SupplyPanel({
1744
+ context,
1745
+ supply,
1746
+ storeOffline
1747
+ }) {
1748
+ return /* @__PURE__ */ jsxs2("section", {
1749
+ className: "kk-card kk-daily-supply",
1750
+ children: [
1751
+ /* @__PURE__ */ jsxs2("div", {
1752
+ className: "kk-card-header",
1753
+ children: [
1754
+ /* @__PURE__ */ jsx2("h2", {
1755
+ children: "Supply context (configured)"
1756
+ }),
1757
+ /* @__PURE__ */ jsx2("span", {
1758
+ className: "kk-stat-sub",
1759
+ children: "read from the kk-collect store"
1760
+ })
1761
+ ]
1762
+ }),
1763
+ context === null ? /* @__PURE__ */ jsx2("p", {
1764
+ children: "The configured supply interval is unknown — no captured window setting or run start was recorded, so no store query was issued."
1765
+ }) : /* @__PURE__ */ jsxs2("p", {
1766
+ children: [
1767
+ "Window ",
1768
+ /* @__PURE__ */ jsx2("code", {
1769
+ children: context.fromLabel
1770
+ }),
1771
+ " → ",
1772
+ /* @__PURE__ */ jsx2("code", {
1773
+ children: context.untilLabel
1774
+ }),
1775
+ supply === null ? "" : ` · time zone ${supply.timeZone}`,
1776
+ " — configured context; the effective extraction window (ladder bounds) is unknown, not persisted."
1777
+ ]
1778
+ }),
1779
+ storeOffline ? /* @__PURE__ */ jsx2("p", {
1780
+ children: "The store reader is offline; supply counts are unavailable."
1781
+ }) : null,
1782
+ supply !== null && supply.availability === "unavailable" ? /* @__PURE__ */ jsx2("p", {
1783
+ children: "The store could not be read for this window; no counts are shown."
1784
+ }) : null,
1785
+ supply !== null && supply.availability === "unavailable" && supply.diagnostics.length > 0 ? /* @__PURE__ */ jsx2("ul", {
1786
+ children: supply.diagnostics.map((diagnostic) => /* @__PURE__ */ jsxs2("li", {
1787
+ className: "kk-board-diagnostic",
1788
+ children: [
1789
+ diagnostic.code,
1790
+ ": ",
1791
+ diagnostic.message
1792
+ ]
1793
+ }, diagnostic.code))
1794
+ }) : null,
1795
+ supply !== null && supply.availability === "available" ? /* @__PURE__ */ jsxs2("div", {
1796
+ className: "kk-summary-grids",
1797
+ children: [
1798
+ /* @__PURE__ */ jsxs2("div", {
1799
+ className: "kk-card",
1800
+ children: [
1801
+ /* @__PURE__ */ jsxs2("div", {
1802
+ className: "kk-card-header",
1803
+ children: [
1804
+ /* @__PURE__ */ jsx2("h2", {
1805
+ children: "New items by day and source"
1806
+ }),
1807
+ /* @__PURE__ */ jsxs2("span", {
1808
+ className: "kk-stat-sub",
1809
+ children: [
1810
+ supply.sources.length,
1811
+ " sources"
1812
+ ]
1813
+ })
1814
+ ]
1815
+ }),
1816
+ /* @__PURE__ */ jsx2("div", {
1817
+ className: "kk-table-container",
1818
+ children: /* @__PURE__ */ jsx2(ChannelYieldTable, {
1819
+ data: supply
1820
+ })
1821
+ })
1822
+ ]
1823
+ }),
1824
+ /* @__PURE__ */ jsxs2("div", {
1825
+ className: "kk-card",
1826
+ children: [
1827
+ /* @__PURE__ */ jsxs2("div", {
1828
+ className: "kk-card-header",
1829
+ children: [
1830
+ /* @__PURE__ */ jsx2("h2", {
1831
+ children: "Collection ticks and plugin contributions"
1832
+ }),
1833
+ /* @__PURE__ */ jsxs2("span", {
1834
+ className: "kk-stat-sub",
1835
+ children: [
1836
+ supply.plugins.length,
1837
+ " plugins"
1838
+ ]
1839
+ })
1840
+ ]
1841
+ }),
1842
+ /* @__PURE__ */ jsx2("div", {
1843
+ className: "kk-table-container",
1844
+ children: /* @__PURE__ */ jsx2(PluginRunTable, {
1845
+ data: supply
1846
+ })
1847
+ })
1848
+ ]
1849
+ })
1850
+ ]
1851
+ }) : null,
1852
+ /* @__PURE__ */ jsx2("p", {
1853
+ className: "kk-board-footnote",
1854
+ children: "A source absent from this window recorded 0 contributions; its health is unknown. The episode funnel above stays artifact-backed — supply here is context, not the run’s consumption record."
1234
1855
  })
1235
1856
  ]
1236
1857
  });
1237
1858
  }
1238
1859
  function ChannelList({ channels }) {
1239
1860
  if (channels.length === 0)
1240
- return /* @__PURE__ */ jsx("p", {
1861
+ return /* @__PURE__ */ jsx2("p", {
1241
1862
  children: "No channel outcomes recorded for the selected run."
1242
1863
  });
1243
- return /* @__PURE__ */ jsx("ul", {
1864
+ return /* @__PURE__ */ jsx2("ul", {
1244
1865
  className: "kk-board-channels",
1245
- children: channels.map((channel) => /* @__PURE__ */ jsxs("li", {
1866
+ children: channels.map((channel) => /* @__PURE__ */ jsxs2("li", {
1246
1867
  children: [
1247
- /* @__PURE__ */ jsx("strong", {
1868
+ /* @__PURE__ */ jsx2("strong", {
1248
1869
  children: channel.label
1249
1870
  }),
1250
1871
  " — ",
1251
1872
  channel.state,
1252
- channel.detail === null ? null : /* @__PURE__ */ jsxs("span", {
1873
+ channel.detail === null ? null : /* @__PURE__ */ jsxs2("span", {
1253
1874
  className: "kk-board-state",
1254
1875
  children: [
1255
1876
  " (",
@@ -1257,10 +1878,10 @@ function ChannelList({ channels }) {
1257
1878
  ")"
1258
1879
  ]
1259
1880
  }),
1260
- channel.url === null ? null : /* @__PURE__ */ jsxs(Fragment, {
1881
+ channel.url === null ? null : /* @__PURE__ */ jsxs2(Fragment, {
1261
1882
  children: [
1262
1883
  " ",
1263
- /* @__PURE__ */ jsx("a", {
1884
+ /* @__PURE__ */ jsx2("a", {
1264
1885
  href: channel.url,
1265
1886
  rel: "noreferrer",
1266
1887
  children: "open"
@@ -1276,30 +1897,30 @@ function ResultsPanel({
1276
1897
  channels
1277
1898
  }) {
1278
1899
  if (artifacts.length === 0)
1279
- return /* @__PURE__ */ jsx("p", {
1900
+ return /* @__PURE__ */ jsx2("p", {
1280
1901
  children: "No artifact evidence for the selected run."
1281
1902
  });
1282
1903
  const media = artifacts.filter((artifact) => artifact.kind === "image" || artifact.kind === "audio");
1283
1904
  const files = artifacts.filter((artifact) => artifact.kind !== "image" && artifact.kind !== "audio");
1284
- return /* @__PURE__ */ jsxs("div", {
1905
+ return /* @__PURE__ */ jsxs2("div", {
1285
1906
  className: "kk-board-results",
1286
1907
  children: [
1287
- media.map((artifact) => artifact.href === null ? /* @__PURE__ */ jsxs("p", {
1908
+ media.map((artifact) => artifact.href === null ? /* @__PURE__ */ jsxs2("p", {
1288
1909
  children: [
1289
1910
  artifact.label,
1290
1911
  " — ",
1291
1912
  artifact.state
1292
1913
  ]
1293
- }, artifact.id) : artifact.kind === "audio" ? /* @__PURE__ */ jsxs("p", {
1914
+ }, artifact.id) : artifact.kind === "audio" ? /* @__PURE__ */ jsxs2("p", {
1294
1915
  children: [
1295
- /* @__PURE__ */ jsx("audio", {
1916
+ /* @__PURE__ */ jsx2("audio", {
1296
1917
  controls: true,
1297
1918
  src: artifact.href,
1298
- children: /* @__PURE__ */ jsx("track", {
1919
+ children: /* @__PURE__ */ jsx2("track", {
1299
1920
  kind: "captions"
1300
1921
  })
1301
1922
  }),
1302
- /* @__PURE__ */ jsxs("a", {
1923
+ /* @__PURE__ */ jsxs2("a", {
1303
1924
  href: artifact.href,
1304
1925
  download: true,
1305
1926
  children: [
@@ -1311,9 +1932,9 @@ function ResultsPanel({
1311
1932
  "— ",
1312
1933
  artifact.state
1313
1934
  ]
1314
- }, artifact.id) : /* @__PURE__ */ jsxs("p", {
1935
+ }, artifact.id) : /* @__PURE__ */ jsxs2("p", {
1315
1936
  children: [
1316
- /* @__PURE__ */ jsx("img", {
1937
+ /* @__PURE__ */ jsx2("img", {
1317
1938
  src: artifact.href,
1318
1939
  alt: artifact.label
1319
1940
  }),
@@ -1323,16 +1944,16 @@ function ResultsPanel({
1323
1944
  artifact.state
1324
1945
  ]
1325
1946
  }, artifact.id)),
1326
- /* @__PURE__ */ jsx("ul", {
1947
+ /* @__PURE__ */ jsx2("ul", {
1327
1948
  className: "kk-board-artifacts",
1328
- children: files.map((artifact) => /* @__PURE__ */ jsx("li", {
1329
- children: artifact.href === null ? /* @__PURE__ */ jsxs("span", {
1949
+ children: files.map((artifact) => /* @__PURE__ */ jsx2("li", {
1950
+ children: artifact.href === null ? /* @__PURE__ */ jsxs2("span", {
1330
1951
  children: [
1331
1952
  artifact.label,
1332
1953
  " — ",
1333
1954
  artifact.state
1334
1955
  ]
1335
- }) : /* @__PURE__ */ jsxs("a", {
1956
+ }) : /* @__PURE__ */ jsxs2("a", {
1336
1957
  href: artifact.href,
1337
1958
  children: [
1338
1959
  artifact.label,
@@ -1342,7 +1963,7 @@ function ResultsPanel({
1342
1963
  })
1343
1964
  }, artifact.id))
1344
1965
  }),
1345
- /* @__PURE__ */ jsx(ChannelList, {
1966
+ /* @__PURE__ */ jsx2(ChannelList, {
1346
1967
  channels
1347
1968
  })
1348
1969
  ]
@@ -1350,24 +1971,24 @@ function ResultsPanel({
1350
1971
  }
1351
1972
  function ProgressPanel({ progress }) {
1352
1973
  if (progress === null || progress.nodes.length === 0) {
1353
- return /* @__PURE__ */ jsx("p", {
1974
+ return /* @__PURE__ */ jsx2("p", {
1354
1975
  children: "Recorded progress is unavailable for the selected run."
1355
1976
  });
1356
1977
  }
1357
- return /* @__PURE__ */ jsxs("div", {
1978
+ return /* @__PURE__ */ jsxs2("div", {
1358
1979
  className: "kk-board-progress",
1359
1980
  children: [
1360
- /* @__PURE__ */ jsx("ul", {
1361
- children: progress.nodes.map((node) => /* @__PURE__ */ jsxs("li", {
1981
+ /* @__PURE__ */ jsx2("ul", {
1982
+ children: progress.nodes.map((node) => /* @__PURE__ */ jsxs2("li", {
1362
1983
  children: [
1363
- /* @__PURE__ */ jsx("strong", {
1984
+ /* @__PURE__ */ jsx2("strong", {
1364
1985
  children: node.id
1365
1986
  }),
1366
1987
  " — ",
1367
1988
  node.status,
1368
1989
  " · ",
1369
1990
  node.timing,
1370
- node.attempts > 1 ? /* @__PURE__ */ jsxs("span", {
1991
+ node.attempts > 1 ? /* @__PURE__ */ jsxs2("span", {
1371
1992
  className: "kk-board-state",
1372
1993
  children: [
1373
1994
  " ",
@@ -1378,30 +1999,30 @@ function ProgressPanel({ progress }) {
1378
1999
  ]
1379
2000
  }, node.id))
1380
2001
  }),
1381
- progress.edgesKnown && progress.edges !== null ? /* @__PURE__ */ jsx("ul", {
2002
+ progress.edgesKnown && progress.edges !== null ? /* @__PURE__ */ jsx2("ul", {
1382
2003
  className: "kk-board-edges",
1383
- children: progress.edges.map((edge) => /* @__PURE__ */ jsxs("li", {
2004
+ children: progress.edges.map((edge) => /* @__PURE__ */ jsxs2("li", {
1384
2005
  children: [
1385
2006
  edge.from,
1386
2007
  " → ",
1387
2008
  edge.to
1388
2009
  ]
1389
2010
  }, `${edge.from}->${edge.to}`))
1390
- }) : /* @__PURE__ */ jsxs("p", {
2011
+ }) : /* @__PURE__ */ jsxs2("p", {
1391
2012
  children: [
1392
2013
  "Dependency edges are unknown — no digest-verified topology was captured. See",
1393
2014
  " ",
1394
- /* @__PURE__ */ jsx("a", {
2015
+ /* @__PURE__ */ jsx2("a", {
1395
2016
  href: "/board/settings/workflows",
1396
2017
  children: "Settings → Workflows"
1397
2018
  }),
1398
2019
  " for the current graph."
1399
2020
  ]
1400
2021
  }),
1401
- progress.childRunId === null ? null : /* @__PURE__ */ jsxs("p", {
2022
+ progress.childRunId === null ? null : /* @__PURE__ */ jsxs2("p", {
1402
2023
  children: [
1403
2024
  "Nested safety run: ",
1404
- /* @__PURE__ */ jsx("code", {
2025
+ /* @__PURE__ */ jsx2("code", {
1405
2026
  children: progress.childRunId
1406
2027
  })
1407
2028
  ]
@@ -1411,44 +2032,44 @@ function ProgressPanel({ progress }) {
1411
2032
  }
1412
2033
  function ConfigPanel({ config }) {
1413
2034
  if (config === null)
1414
- return /* @__PURE__ */ jsx("p", {
2035
+ return /* @__PURE__ */ jsx2("p", {
1415
2036
  children: "No captured configuration for the selected run."
1416
2037
  });
1417
2038
  const entries = Object.entries(config.settings);
1418
- return /* @__PURE__ */ jsxs("div", {
2039
+ return /* @__PURE__ */ jsxs2("div", {
1419
2040
  className: "kk-board-config",
1420
2041
  children: [
1421
- /* @__PURE__ */ jsxs("p", {
2042
+ /* @__PURE__ */ jsxs2("p", {
1422
2043
  children: [
1423
2044
  "Captured ",
1424
2045
  formatTimestamp(config.capturedAt)
1425
2046
  ]
1426
2047
  }),
1427
- entries.length === 0 ? /* @__PURE__ */ jsx("p", {
2048
+ entries.length === 0 ? /* @__PURE__ */ jsx2("p", {
1428
2049
  children: "Recorded configuration unavailable for this run."
1429
2050
  }) : null,
1430
- /* @__PURE__ */ jsx("dl", {
1431
- children: entries.map(([key, value]) => /* @__PURE__ */ jsxs("div", {
2051
+ /* @__PURE__ */ jsx2("dl", {
2052
+ children: entries.map(([key, value]) => /* @__PURE__ */ jsxs2("div", {
1432
2053
  children: [
1433
- /* @__PURE__ */ jsx("dt", {
2054
+ /* @__PURE__ */ jsx2("dt", {
1434
2055
  children: key
1435
2056
  }),
1436
- /* @__PURE__ */ jsx("dd", {
2057
+ /* @__PURE__ */ jsx2("dd", {
1437
2058
  children: value
1438
2059
  })
1439
2060
  ]
1440
2061
  }, key))
1441
2062
  }),
1442
- config.unavailableKeys.length === 0 ? null : /* @__PURE__ */ jsxs("p", {
2063
+ config.unavailableKeys.length === 0 ? null : /* @__PURE__ */ jsxs2("p", {
1443
2064
  children: [
1444
2065
  "Uncaptured keys: ",
1445
2066
  config.unavailableKeys.join(", ")
1446
2067
  ]
1447
2068
  }),
1448
- /* @__PURE__ */ jsxs("p", {
2069
+ /* @__PURE__ */ jsxs2("p", {
1449
2070
  children: [
1450
2071
  "Current definitions and defaults live in ",
1451
- /* @__PURE__ */ jsx("a", {
2072
+ /* @__PURE__ */ jsx2("a", {
1452
2073
  href: "/board/settings/workflows",
1453
2074
  children: "Settings → Workflows"
1454
2075
  }),