@gobing-ai/knowledge-kit 0.1.4 → 0.1.5

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 (68) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/board/collect/index.css +447 -0
  3. package/dist/board/collect/index.js +2505 -453
  4. package/dist/board/daily-voice/index.css +447 -0
  5. package/dist/board/daily-voice/index.js +415 -8
  6. package/dist/board/itc/index.css +447 -0
  7. package/dist/board/itc/index.js +415 -8
  8. package/dist/index.js +475 -136
  9. package/package.json +6 -5
  10. package/plugins/generations/content-gen/dist/index.js +9 -9
  11. package/plugins/generations/core-facts-gen/dist/index.js +12 -12
  12. package/plugins/generations/daily-article-gen/dist/index.js +12 -12
  13. package/plugins/generations/dailynews-gen/dist/index.js +9 -9
  14. package/plugins/generations/episode-plan-gen/dist/index.js +12 -12
  15. package/plugins/generations/gemini-tts/dist/index.js +9 -9
  16. package/plugins/generations/image-gen/dist/index.js +28 -12
  17. package/plugins/generations/image-gen/src/compose/card.ts +19 -3
  18. package/plugins/generations/news-report-gen/dist/index.js +12 -12
  19. package/plugins/generations/voice-gen/dist/index.js +9 -9
  20. package/plugins/generations/voxcpm-tts/dist/index.js +9 -9
  21. package/plugins/ingestions/aihot-ingest/dist/index.js +12 -12
  22. package/plugins/ingestions/horizon-ingest/dist/index.js +12 -12
  23. package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
  24. package/plugins/ingestions/last30days-ingest/dist/index.js +12 -12
  25. package/plugins/ingestions/web-search/dist/index.js +10 -10
  26. package/plugins/kk/decisions/kk-podcast.yaml +44 -0
  27. package/plugins/kk/decisions/kk-safety.yaml +46 -0
  28. package/plugins/kk/plugin.json +1 -1
  29. package/plugins/kk/scripts/daily-stages.ts +117 -0
  30. package/plugins/kk/scripts/kk-workflow-stages.ts +10 -0
  31. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +6 -3
  32. package/plugins/kk/workflows/kk-daily-safety.yaml +37 -10
  33. package/plugins/kk/workflows/kk-solo-podcast.yaml +65 -1
  34. package/plugins/publishings/bilibili-pub/dist/index.js +22 -22
  35. package/plugins/publishings/bilibili-pub/dist/profile-check.js +22 -22
  36. package/plugins/publishings/cnblogs-pub/dist/index.js +22 -22
  37. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +22 -22
  38. package/plugins/publishings/csdn-pub/dist/index.js +22 -22
  39. package/plugins/publishings/csdn-pub/dist/profile-check.js +22 -22
  40. package/plugins/publishings/emdash-pub/dist/index.js +9 -9
  41. package/plugins/publishings/infoq-pub/dist/index.js +22 -22
  42. package/plugins/publishings/infoq-pub/dist/profile-check.js +22 -22
  43. package/plugins/publishings/jike-pub/dist/index.js +22 -22
  44. package/plugins/publishings/jike-pub/dist/profile-check.js +22 -22
  45. package/plugins/publishings/juejin-pub/dist/index.js +22 -22
  46. package/plugins/publishings/juejin-pub/dist/profile-check.js +22 -22
  47. package/plugins/publishings/medium-pub/dist/index.js +9 -9
  48. package/plugins/publishings/podcast-pub/dist/index.js +9 -9
  49. package/plugins/publishings/qiita-pub/dist/index.js +9 -9
  50. package/plugins/publishings/shipinhao-pub/dist/index.js +22 -22
  51. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +22 -22
  52. package/plugins/publishings/sspai-pub/dist/index.js +22 -22
  53. package/plugins/publishings/sspai-pub/dist/profile-check.js +22 -22
  54. package/plugins/publishings/substack-pub/dist/index.js +22 -22
  55. package/plugins/publishings/substack-pub/dist/profile-check.js +22 -22
  56. package/plugins/publishings/surfdash-pub/dist/index.js +9 -9
  57. package/plugins/publishings/threads-pub/dist/index.js +9 -9
  58. package/plugins/publishings/wechatmp-pub/dist/index.js +22 -22
  59. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +22 -22
  60. package/plugins/publishings/weibo-pub/dist/index.js +22 -22
  61. package/plugins/publishings/weibo-pub/dist/profile-check.js +22 -22
  62. package/plugins/publishings/x-pub/dist/index.js +22 -22
  63. package/plugins/publishings/x-pub/dist/profile-check.js +22 -22
  64. package/plugins/publishings/xhs-pub/dist/index.js +22 -22
  65. package/plugins/publishings/xhs-pub/dist/profile-check.js +22 -22
  66. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +22 -22
  67. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +22 -22
  68. package/plugins/publishings/zenn-pub/dist/index.js +9 -9
@@ -23,17 +23,35 @@ function storeItemsQueryString(query) {
23
23
  }
24
24
  if (query.selection !== undefined)
25
25
  params.set("selection", query.selection);
26
+ if (query.search !== undefined && query.search.trim() !== "") {
27
+ params.set("search", query.search.trim());
28
+ }
29
+ return params.toString();
30
+ }
31
+ function storeSelectionsQueryString(query) {
32
+ const params = new URLSearchParams;
33
+ if (query.page !== undefined)
34
+ params.set("page", String(query.page));
35
+ if (query.topic !== undefined && query.topic !== "")
36
+ params.set("topic", query.topic);
37
+ if (query.status !== undefined)
38
+ params.set("status", query.status);
39
+ if (query.runId !== undefined && query.runId !== "")
40
+ params.set("runId", query.runId);
41
+ if (query.search !== undefined && query.search.trim() !== "") {
42
+ params.set("search", query.search.trim());
43
+ }
26
44
  return params.toString();
27
45
  }
28
46
  var DEFAULT_READER_ORIGIN = "http://127.0.0.1:4340";
29
- function readerOriginFrom(search) {
47
+ function readerOriginFrom(search, defaultOrigin = DEFAULT_READER_ORIGIN) {
30
48
  const override = new URLSearchParams(search).get("kkApi");
31
49
  if (override === null)
32
- return { origin: DEFAULT_READER_ORIGIN, diagnostic: null };
50
+ return { origin: defaultOrigin, diagnostic: null };
33
51
  if (isLoopbackHttpOrigin(override))
34
52
  return { origin: override, diagnostic: null };
35
53
  return {
36
- origin: DEFAULT_READER_ORIGIN,
54
+ origin: defaultOrigin,
37
55
  diagnostic: {
38
56
  code: "board.reader.origin-invalid",
39
57
  message: "Ignored a non-loopback kkApi origin; using the default reader."
@@ -58,6 +76,25 @@ function isLoopbackHttpOrigin(origin) {
58
76
  function origin(base) {
59
77
  return base.replace(/\/+$/, "");
60
78
  }
79
+ function parseSpurDbRows(pageData) {
80
+ const { columns, rows } = pageData;
81
+ if (!columns || !rows)
82
+ return [];
83
+ return rows.map((rowCells) => {
84
+ const rowObj = {};
85
+ columns.forEach((col, idx) => {
86
+ const cell = rowCells[idx];
87
+ if (!cell || cell.t === "null") {
88
+ rowObj[col] = null;
89
+ } else if (cell.t === "num" || cell.t === "text") {
90
+ rowObj[col] = cell.v;
91
+ } else {
92
+ rowObj[col] = null;
93
+ }
94
+ });
95
+ return rowObj;
96
+ });
97
+ }
61
98
  function createBoardClient(options) {
62
99
  if (!isLoopbackHttpOrigin(options.readerOrigin)) {
63
100
  throw new Error(`reader origin must be loopback http, got '${options.readerOrigin}'`);
@@ -68,6 +105,7 @@ function createBoardClient(options) {
68
105
  const reader = origin(options.readerOrigin);
69
106
  const board = origin(options.boardOrigin);
70
107
  const doFetch = options.fetchImpl ?? fetch;
108
+ const isNativeSpur = reader === board;
71
109
  async function getJson(url) {
72
110
  const response = await doFetch(url, { headers: { accept: "application/json" } });
73
111
  if (!response.ok) {
@@ -75,10 +113,370 @@ function createBoardClient(options) {
75
113
  }
76
114
  return await response.json();
77
115
  }
116
+ async function nativeStoreOverview(runIds) {
117
+ try {
118
+ const pageData = await getJson(`${board}/api/project/database/tables/collect_runs/rows?pageSize=100&sort=started_at&dir=desc`);
119
+ const dbRows = parseSpurDbRows(pageData);
120
+ const byId = new Map;
121
+ let maxFinishedAt = 0;
122
+ for (const row of dbRows) {
123
+ let sourceCounts = {};
124
+ try {
125
+ if (row.source_counts_json) {
126
+ const parsed = JSON.parse(row.source_counts_json);
127
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
128
+ sourceCounts = parsed;
129
+ }
130
+ }
131
+ } catch {
132
+ sourceCounts = {};
133
+ }
134
+ if (row.finished_at && typeof row.finished_at === "number" && row.finished_at > maxFinishedAt) {
135
+ maxFinishedAt = row.finished_at;
136
+ }
137
+ byId.set(row.run_id, {
138
+ runId: row.run_id,
139
+ sourceCounts,
140
+ inserted: row.inserted ?? 0,
141
+ updated: row.updated ?? 0,
142
+ labelRows: 0,
143
+ labeledItems: 0,
144
+ labelRowsBySource: {},
145
+ yield: { selected: 0, published: 0, dropped: 0 }
146
+ });
147
+ }
148
+ const rowsMap = {};
149
+ for (const id of runIds) {
150
+ rowsMap[id] = byId.get(id) ?? null;
151
+ }
152
+ return {
153
+ schemaVersion: 1,
154
+ availability: "available",
155
+ lastStoredAt: maxFinishedAt > 0 ? new Date(maxFinishedAt).toISOString() : null,
156
+ rows: rowsMap,
157
+ diagnostics: []
158
+ };
159
+ } catch {
160
+ return {
161
+ schemaVersion: 1,
162
+ availability: "available",
163
+ lastStoredAt: null,
164
+ rows: Object.fromEntries(runIds.map((id) => [id, null])),
165
+ diagnostics: []
166
+ };
167
+ }
168
+ }
169
+ async function nativeStoreRun(runId) {
170
+ try {
171
+ const runRes = await getJson(`${board}/api/project/database/tables/collect_runs/rows?filterColumn=run_id&filter=${encodeURIComponent(runId)}`);
172
+ const runRows = parseSpurDbRows(runRes);
173
+ const rowData = runRows[0];
174
+ let runStoreRow = null;
175
+ if (rowData) {
176
+ let sourceCounts = {};
177
+ try {
178
+ if (rowData.source_counts_json) {
179
+ const parsed = JSON.parse(rowData.source_counts_json);
180
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
181
+ sourceCounts = parsed;
182
+ }
183
+ }
184
+ } catch {
185
+ sourceCounts = {};
186
+ }
187
+ runStoreRow = {
188
+ runId: rowData.run_id,
189
+ sourceCounts,
190
+ inserted: rowData.inserted ?? 0,
191
+ updated: rowData.updated ?? 0,
192
+ labelRows: 0,
193
+ labeledItems: 0,
194
+ labelRowsBySource: {},
195
+ yield: { selected: 0, published: 0, dropped: 0 }
196
+ };
197
+ }
198
+ let labelQuality = [];
199
+ try {
200
+ const labelsRes = await getJson(`${board}/api/project/database/tables/labels/rows?filterColumn=run_id&filter=${encodeURIComponent(runId)}&pageSize=200`);
201
+ const labelRows = parseSpurDbRows(labelsRes);
202
+ const decisionMap = new Map;
203
+ for (const l of labelRows) {
204
+ const dec = l.decision_id || "unknown";
205
+ const val = String(l.value ?? "none");
206
+ const conf = l.confidence || "none";
207
+ let valMap = decisionMap.get(dec);
208
+ if (!valMap) {
209
+ valMap = new Map;
210
+ decisionMap.set(dec, valMap);
211
+ }
212
+ let counts = valMap.get(val);
213
+ if (!counts) {
214
+ counts = { HIGH: 0, MEDIUM: 0, LOW: 0, none: 0 };
215
+ valMap.set(val, counts);
216
+ }
217
+ if (conf === "HIGH" || conf === "MEDIUM" || conf === "LOW") {
218
+ counts[conf] = (counts[conf] ?? 0) + 1;
219
+ } else {
220
+ counts.none = (counts.none ?? 0) + 1;
221
+ }
222
+ }
223
+ labelQuality = Array.from(decisionMap.entries()).map(([decisionId, valMap]) => ({
224
+ decisionId,
225
+ run: Array.from(valMap.entries()).map(([value, counts]) => ({
226
+ value,
227
+ ...counts
228
+ })),
229
+ effective: Array.from(valMap.entries()).map(([value, counts]) => ({
230
+ value,
231
+ count: counts.HIGH + counts.MEDIUM + counts.LOW + counts.none
232
+ }))
233
+ }));
234
+ } catch {
235
+ labelQuality = [];
236
+ }
237
+ return {
238
+ schemaVersion: 1,
239
+ availability: "available",
240
+ runId,
241
+ row: runStoreRow,
242
+ labelQuality,
243
+ diagnostics: []
244
+ };
245
+ } catch {
246
+ return {
247
+ schemaVersion: 1,
248
+ availability: "available",
249
+ runId,
250
+ row: null,
251
+ labelQuality: [],
252
+ diagnostics: []
253
+ };
254
+ }
255
+ }
256
+ async function nativeStoreItems(runId, query = {}) {
257
+ const pageNum = query.page ?? 1;
258
+ const params = new URLSearchParams({
259
+ page: String(pageNum),
260
+ pageSize: "25",
261
+ sort: "first_seen_at",
262
+ dir: "desc"
263
+ });
264
+ if (query.search?.trim()) {
265
+ params.set("filterColumn", "title");
266
+ params.set("filter", query.search.trim());
267
+ } else if (query.source) {
268
+ params.set("filterColumn", "source");
269
+ params.set("filter", query.source);
270
+ } else if (runId?.trim()) {
271
+ params.set("filterColumn", "first_seen_run");
272
+ params.set("filter", runId.trim());
273
+ }
274
+ try {
275
+ const pageData = await getJson(`${board}/api/project/database/tables/items/rows?${params.toString()}`);
276
+ const rows = parseSpurDbRows(pageData);
277
+ const itemsWithLabels = await Promise.all(rows.map(async (row) => {
278
+ const labels = {};
279
+ const labelDetails = {};
280
+ try {
281
+ const labRes = await getJson(`${board}/api/project/database/tables/labels/rows?filterColumn=item_id&filter=${encodeURIComponent(row.id)}&pageSize=50`);
282
+ const labRows = parseSpurDbRows(labRes);
283
+ for (const l of labRows) {
284
+ if (l.decision_id) {
285
+ labels[l.decision_id] = String(l.value ?? "");
286
+ labelDetails[l.decision_id] = {
287
+ value: String(l.value ?? ""),
288
+ confidence: l.confidence ?? null,
289
+ maker: l.maker ?? null,
290
+ score: typeof l.score === "number" ? l.score : null
291
+ };
292
+ }
293
+ }
294
+ } catch {}
295
+ const url = row.canonical_url && /^https?:\/\//i.test(row.canonical_url) ? row.canonical_url : null;
296
+ const seen = row.first_seen_run && row.last_seen_run && row.first_seen_run !== row.last_seen_run ? "reseen" : "new";
297
+ return {
298
+ id: row.id,
299
+ title: (row.title || "").slice(0, 300),
300
+ url,
301
+ source: row.source || "unknown",
302
+ storyId: row.story_id ?? null,
303
+ seen,
304
+ labels,
305
+ labelDetails,
306
+ selection: "none",
307
+ domain: row.domain ?? null,
308
+ lang: row.lang ?? null,
309
+ bodyChars: row.body_chars ?? null,
310
+ engagement: row.engagement ?? null,
311
+ firstSeenAt: row.first_seen_at ?? null,
312
+ publishedAt: row.published_at ?? null
313
+ };
314
+ }));
315
+ return {
316
+ schemaVersion: 1,
317
+ availability: "available",
318
+ runId: runId ?? "",
319
+ page: pageNum,
320
+ pageSize: 25,
321
+ total: pageData.total ?? itemsWithLabels.length,
322
+ items: itemsWithLabels,
323
+ sources: ["aihot", "hackernews", "youtube", "reddit", "polymarket", "github", "grounding", "rss"],
324
+ diagnostics: []
325
+ };
326
+ } catch {
327
+ return {
328
+ schemaVersion: 1,
329
+ availability: "available",
330
+ runId: runId ?? "",
331
+ page: pageNum,
332
+ pageSize: 25,
333
+ total: 0,
334
+ items: [],
335
+ sources: [],
336
+ diagnostics: []
337
+ };
338
+ }
339
+ }
340
+ async function nativeStoreSelections(query = {}) {
341
+ const pageNum = query.page ?? 1;
342
+ const params = new URLSearchParams({
343
+ page: String(pageNum),
344
+ pageSize: "25",
345
+ sort: "selected_at",
346
+ dir: "desc"
347
+ });
348
+ if (query.topic) {
349
+ params.set("filterColumn", "topic");
350
+ params.set("filter", query.topic);
351
+ } else if (query.status) {
352
+ params.set("filterColumn", "status");
353
+ params.set("filter", query.status);
354
+ } else if (query.runId) {
355
+ params.set("filterColumn", "run_id");
356
+ params.set("filter", query.runId);
357
+ }
358
+ try {
359
+ const pageData = await getJson(`${board}/api/project/database/tables/selections/rows?${params.toString()}`);
360
+ const rows = parseSpurDbRows(pageData);
361
+ const selectionsWithItems = await Promise.all(rows.map(async (row) => {
362
+ let title = null;
363
+ let source = row.workflow ?? null;
364
+ let url = null;
365
+ try {
366
+ const itRes = await getJson(`${board}/api/project/database/tables/items/rows?filterColumn=id&filter=${encodeURIComponent(row.item_id)}&pageSize=1`);
367
+ const itRows = parseSpurDbRows(itRes);
368
+ if (itRows[0]) {
369
+ title = itRows[0].title ?? null;
370
+ source = itRows[0].source ?? source;
371
+ url = itRows[0].canonical_url && /^https?:\/\//i.test(itRows[0].canonical_url) ? itRows[0].canonical_url : null;
372
+ }
373
+ } catch {}
374
+ return {
375
+ topic: row.topic,
376
+ itemId: row.item_id,
377
+ runId: row.run_id,
378
+ storyId: row.story_id,
379
+ workflow: row.workflow ?? null,
380
+ rank: typeof row.rank === "number" ? row.rank : null,
381
+ reason: row.reason ?? null,
382
+ status: row.status ?? "selected",
383
+ selectedAt: row.selected_at,
384
+ publishedAt: row.published_at ?? null,
385
+ title: title || row.item_id,
386
+ source: source || "unknown",
387
+ url
388
+ };
389
+ }));
390
+ return {
391
+ schemaVersion: 1,
392
+ availability: "available",
393
+ page: pageNum,
394
+ pageSize: 25,
395
+ total: pageData.total ?? selectionsWithItems.length,
396
+ selections: selectionsWithItems,
397
+ topics: ["ai-news"],
398
+ diagnostics: []
399
+ };
400
+ } catch {
401
+ return {
402
+ schemaVersion: 1,
403
+ availability: "available",
404
+ page: pageNum,
405
+ pageSize: 25,
406
+ total: 0,
407
+ selections: [],
408
+ topics: ["ai-news"],
409
+ diagnostics: []
410
+ };
411
+ }
412
+ }
413
+ async function nativeResults(workflow, runId) {
414
+ try {
415
+ const runData = await getJson(`${board}/api/runs/${encodeURIComponent(runId)}`);
416
+ const totalDuration = runData.actions?.reduce((acc, a) => acc + (a.durationMs || 0), 0) ?? 0;
417
+ return {
418
+ schemaVersion: 1,
419
+ workflow,
420
+ runId,
421
+ availability: "available",
422
+ summary: {
423
+ status: runData.run?.status ?? "done",
424
+ startedAt: runData.run?.startedAt,
425
+ completedAt: runData.run?.completedAt,
426
+ durationMs: totalDuration,
427
+ actions: runData.actions?.length ?? 0,
428
+ phases: runData.phases?.length ?? 0
429
+ },
430
+ artifacts: [],
431
+ diagnostics: []
432
+ };
433
+ } catch {
434
+ return {
435
+ schemaVersion: 1,
436
+ workflow,
437
+ runId,
438
+ availability: "missing",
439
+ summary: {},
440
+ artifacts: [],
441
+ diagnostics: []
442
+ };
443
+ }
444
+ }
445
+ async function nativeConfig(workflow, runId) {
446
+ try {
447
+ const runData = await getJson(`${board}/api/runs/${encodeURIComponent(runId)}`);
448
+ return {
449
+ schemaVersion: 1,
450
+ workflow,
451
+ runId,
452
+ capturedAt: runData.run?.startedAt ?? null,
453
+ settings: {
454
+ workflowName: runData.run?.workflowName ?? workflow,
455
+ status: runData.run?.status ?? "done",
456
+ mode: runData.run?.mode ?? "state-machine"
457
+ },
458
+ unavailableKeys: [],
459
+ diagnostics: []
460
+ };
461
+ } catch {
462
+ return {
463
+ schemaVersion: 1,
464
+ workflow,
465
+ runId,
466
+ capturedAt: null,
467
+ settings: {},
468
+ unavailableKeys: [],
469
+ diagnostics: []
470
+ };
471
+ }
472
+ }
78
473
  return {
79
- health: () => getJson(`${reader}/api/health`),
80
- results: (workflow, runId) => getJson(`${reader}/api/workflows/${encodeURIComponent(workflow)}/runs/${encodeURIComponent(runId)}/results`),
81
- config: (workflow, runId) => getJson(`${reader}/api/workflows/${encodeURIComponent(workflow)}/runs/${encodeURIComponent(runId)}/config`),
474
+ health: () => isNativeSpur ? getJson(`${board}/api/health`).then(() => ({
475
+ schemaVersion: 1,
476
+ projectPath: "knowledge-kit"
477
+ })) : getJson(`${reader}/api/health`),
478
+ results: (workflow, runId) => isNativeSpur ? nativeResults(workflow, runId) : getJson(`${reader}/api/workflows/${encodeURIComponent(workflow)}/runs/${encodeURIComponent(runId)}/results`),
479
+ config: (workflow, runId) => isNativeSpur ? nativeConfig(workflow, runId) : getJson(`${reader}/api/workflows/${encodeURIComponent(workflow)}/runs/${encodeURIComponent(runId)}/config`),
82
480
  artifactUrl: (workflow, runId, artifactId) => `${reader}/api/workflows/${encodeURIComponent(workflow)}/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(artifactId)}`,
83
481
  runs: (workflow, cursor) => {
84
482
  const params = new URLSearchParams({ workflow, limit: "25" });
@@ -87,11 +485,20 @@ function createBoardClient(options) {
87
485
  return getJson(`${board}/api/runs?${params.toString()}`);
88
486
  },
89
487
  storeOverview: (runIds) => {
488
+ if (isNativeSpur)
489
+ return nativeStoreOverview(runIds);
90
490
  const params = new URLSearchParams({ runs: runIds.slice(0, MAX_OVERVIEW_RUNS).join(",") });
91
491
  return getJson(`${reader}/api/workflows/kk-collect/store/overview?${params.toString()}`);
92
492
  },
93
- storeRun: (runId) => getJson(`${reader}/api/workflows/kk-collect/runs/${encodeURIComponent(runId)}/store`),
94
- storeItems: (runId, query = {}) => getJson(`${reader}/api/workflows/kk-collect/runs/${encodeURIComponent(runId)}/store/items?${storeItemsQueryString(query)}`),
493
+ storeRun: (runId) => isNativeSpur ? nativeStoreRun(runId) : getJson(`${reader}/api/workflows/kk-collect/runs/${encodeURIComponent(runId)}/store`),
494
+ storeItems: (runId, query = {}) => {
495
+ if (isNativeSpur)
496
+ return nativeStoreItems(runId, query);
497
+ const qs = storeItemsQueryString(query);
498
+ const path = runId !== undefined && runId.trim() !== "" ? `${reader}/api/workflows/kk-collect/runs/${encodeURIComponent(runId)}/store/items?${qs}` : `${reader}/api/workflows/kk-collect/store/items?${qs}`;
499
+ return getJson(path);
500
+ },
501
+ storeSelections: (query = {}) => isNativeSpur ? nativeStoreSelections(query) : getJson(`${reader}/api/workflows/kk-collect/store/selections?${storeSelectionsQueryString(query)}`),
95
502
  runDetail: (runId) => getJson(`${board}/api/runs/${encodeURIComponent(runId)}`),
96
503
  progress: (runId) => getJson(`${board}/api/runs/${encodeURIComponent(runId)}/progress`)
97
504
  };
@@ -462,20 +869,16 @@ function failed(state, now) {
462
869
  function delayMs(state, intervalMs) {
463
870
  return state.failures > 0 ? state.backoffMs : intervalMs;
464
871
  }
465
- function isStale(state) {
466
- return state.failures > 0;
467
- }
468
- function freshnessLabel(state) {
469
- if (state.lastSuccessAt === null)
470
- return "Not yet loaded";
471
- const updated = new Date(state.lastSuccessAt).toISOString();
472
- return isStale(state) ? `Stale — last success ${updated}` : `Updated ${updated}`;
473
- }
474
872
 
475
873
  // ../board/src/shared/run-selection.ts
476
874
  var BOARD_TABS = ["summary", "results", "progress", "config"];
477
- var COLLECT_TABS = ["overview", "summary", "items", "progress", "config"];
478
- var COLLECT_TAB_ALIASES = { results: "summary" };
875
+ var COLLECT_TABS = ["summary", "runs", "items", "selections"];
876
+ var COLLECT_TAB_ALIASES = {
877
+ overview: "summary",
878
+ results: "runs",
879
+ progress: "runs",
880
+ config: "runs"
881
+ };
479
882
  function parseDeepLink(search, tabs = BOARD_TABS, aliases = {}) {
480
883
  const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
481
884
  const diagnostics = [];
@@ -579,65 +982,131 @@ function StoreState({ diagnostics }) {
579
982
  function storeCellText(status) {
580
983
  return status === "running" ? "in progress" : "no store row";
581
984
  }
582
- function OverviewPanel({
985
+ function Sparkline({ data, color = "var(--color-spur-accent)" }) {
986
+ if (data.length < 2)
987
+ return null;
988
+ const min = Math.min(...data);
989
+ const max = Math.max(...data);
990
+ const range = max - min || 1;
991
+ const width = 72;
992
+ const height = 28;
993
+ const padding = 2;
994
+ const points = data.map((val, idx) => {
995
+ const x = idx / (data.length - 1) * (width - padding * 2) + padding;
996
+ const y = height - padding - (val - min) / range * (height - padding * 2);
997
+ return `${x.toFixed(1)},${y.toFixed(1)}`;
998
+ });
999
+ const pathD = `M ${points.join(" L ")}`;
1000
+ return /* @__PURE__ */ jsx("svg", {
1001
+ className: "kk-sparkline-svg",
1002
+ viewBox: `0 0 ${width} ${height}`,
1003
+ "aria-hidden": "true",
1004
+ children: /* @__PURE__ */ jsx("path", {
1005
+ d: pathD,
1006
+ fill: "none",
1007
+ stroke: color,
1008
+ strokeWidth: "1.75",
1009
+ strokeLinecap: "round",
1010
+ strokeLinejoin: "round"
1011
+ })
1012
+ });
1013
+ }
1014
+ var TIME_RANGES = ["1h", "4h", "24h", "7d", "30d", "All"];
1015
+ function SummaryPanel({
583
1016
  runs,
584
1017
  overview,
585
1018
  now,
586
- runHref
1019
+ timeRange = "24h",
1020
+ onTimeRangeChange,
1021
+ onInspectRun
587
1022
  }) {
588
1023
  const page = runs.slice(0, MAX_OVERVIEW_RUNS);
589
1024
  const storeState = overview === null ? "loading" : overview.availability;
590
1025
  const counts = overview !== null && storeState === "available" ? bannerCounts(page, overview.rows) : null;
591
1026
  const cadence = overview === null ? null : cadenceState(overview.lastStoredAt, now);
1027
+ let totalInserted = 0;
1028
+ let totalUpdated = 0;
1029
+ let totalLabelRows = 0;
1030
+ let totalLabeledItems = 0;
1031
+ let totalYieldSelected = 0;
1032
+ let totalYieldPublished = 0;
1033
+ let totalYieldDropped = 0;
1034
+ const sourceTotals = new Map;
1035
+ const insertedTrend = [];
1036
+ const yieldTrend = [];
1037
+ if (overview !== null && storeState === "available") {
1038
+ for (const run of page) {
1039
+ const row = overview.rows[run.id];
1040
+ if (row) {
1041
+ totalInserted += row.inserted;
1042
+ totalUpdated += row.updated;
1043
+ totalLabelRows += row.labelRows;
1044
+ totalLabeledItems += row.labeledItems;
1045
+ totalYieldSelected += row.yield.selected;
1046
+ totalYieldPublished += row.yield.published;
1047
+ totalYieldDropped += row.yield.dropped;
1048
+ insertedTrend.push(row.inserted + row.updated);
1049
+ yieldTrend.push(row.yield.selected);
1050
+ for (const [source, count] of Object.entries(row.sourceCounts)) {
1051
+ sourceTotals.set(source, (sourceTotals.get(source) ?? 0) + count);
1052
+ }
1053
+ }
1054
+ }
1055
+ }
1056
+ const totalCollected = totalInserted + totalUpdated;
1057
+ const completedRuns = page.filter((r) => r.status === "done").length;
1058
+ const failedRuns = page.filter((r) => r.status === "failed").length;
1059
+ const successRate = page.length > 0 ? Math.round(completedRuns / page.length * 100) : 100;
1060
+ const totalSelections = totalYieldSelected + totalYieldPublished + totalYieldDropped;
1061
+ const yieldRate = totalCollected > 0 ? Math.round(totalSelections / totalCollected * 100) : 0;
1062
+ const sortedSources = [...sourceTotals.entries()].sort((a, b) => b[1] - a[1]);
1063
+ const chartRuns = page.slice(0, 16).reverse();
592
1064
  return /* @__PURE__ */ jsxs("section", {
593
1065
  className: "kk-card kk-collect-overview",
594
1066
  children: [
595
1067
  /* @__PURE__ */ jsxs("div", {
596
- className: "kk-collect-banner",
1068
+ className: "kk-summary-topbar",
597
1069
  children: [
598
1070
  /* @__PURE__ */ jsxs("div", {
599
- className: "kk-collect-banner-cell",
1071
+ className: "kk-summary-topbar-left",
600
1072
  children: [
601
- /* @__PURE__ */ jsx("span", {
602
- className: "kk-stat-label",
603
- children: "Collection cadence"
1073
+ /* @__PURE__ */ jsx("div", {
1074
+ className: "kk-time-pills",
1075
+ children: TIME_RANGES.map((range) => /* @__PURE__ */ jsx("button", {
1076
+ type: "button",
1077
+ className: `kk-time-pill ${timeRange === range ? "active" : ""}`,
1078
+ onClick: () => onTimeRangeChange?.(range),
1079
+ children: range
1080
+ }, range))
604
1081
  }),
605
- /* @__PURE__ */ jsx("span", {
606
- className: "kk-collect-banner-value",
607
- children: /* @__PURE__ */ jsx("span", {
608
- className: `kk-badge ${cadenceBadge(cadence)}`,
609
- children: cadence?.state ?? "unknown"
610
- })
1082
+ /* @__PURE__ */ jsxs("span", {
1083
+ className: `kk-badge ${cadenceBadge(cadence)}`,
1084
+ children: [
1085
+ "● ",
1086
+ cadence?.state ?? "cadence: pending"
1087
+ ]
611
1088
  }),
612
- /* @__PURE__ */ jsx("span", {
613
- className: "kk-stat-sub",
614
- children: cadence?.detail ?? "The store has not answered yet."
615
- })
1089
+ counts !== null && counts.failed + counts.doneWithoutRow > 0 ? /* @__PURE__ */ jsxs("span", {
1090
+ className: "kk-badge kk-badge-warning",
1091
+ children: [
1092
+ counts.failed + counts.doneWithoutRow,
1093
+ " needs attention"
1094
+ ]
1095
+ }) : null
616
1096
  ]
617
1097
  }),
618
- counts === null ? null : /* @__PURE__ */ jsxs("div", {
619
- className: "kk-collect-banner-cell",
1098
+ /* @__PURE__ */ jsxs("div", {
1099
+ className: "kk-summary-kpi-footer",
620
1100
  children: [
621
1101
  /* @__PURE__ */ jsx("span", {
622
- className: "kk-stat-label",
623
- children: "Needs attention"
624
- }),
625
- /* @__PURE__ */ jsx("span", {
626
- className: "kk-stat-value",
627
- children: counts.failed + counts.doneWithoutRow
1102
+ children: cadence?.detail ?? "The store has not answered yet."
628
1103
  }),
629
- /* @__PURE__ */ jsxs("span", {
630
- className: "kk-stat-sub",
1104
+ overview?.lastStoredAt ? /* @__PURE__ */ jsxs("span", {
631
1105
  children: [
632
- counts.failed,
633
- " failed · ",
634
- counts.doneWithoutRow,
635
- " done without a store row ·",
636
- " ",
637
- counts.inProgress,
638
- " in progress"
1106
+ "· Last store: ",
1107
+ formatTimestamp(overview.lastStoredAt)
639
1108
  ]
640
- })
1109
+ }) : null
641
1110
  ]
642
1111
  })
643
1112
  ]
@@ -655,100 +1124,889 @@ function OverviewPanel({
655
1124
  overview === null ? /* @__PURE__ */ jsx("p", {
656
1125
  children: "The store overview has not loaded yet."
657
1126
  }) : null,
658
- /* @__PURE__ */ jsx("div", {
659
- className: "kk-table-container",
660
- children: /* @__PURE__ */ jsxs("table", {
661
- className: "kk-table kk-collect-table",
662
- children: [
663
- /* @__PURE__ */ jsx("thead", {
664
- children: /* @__PURE__ */ jsxs("tr", {
1127
+ /* @__PURE__ */ jsxs("div", {
1128
+ className: "kk-summary-kpis",
1129
+ children: [
1130
+ /* @__PURE__ */ jsxs("div", {
1131
+ className: "kk-summary-kpi-card",
1132
+ children: [
1133
+ /* @__PURE__ */ jsxs("div", {
1134
+ className: "kk-summary-kpi-header",
665
1135
  children: [
666
- /* @__PURE__ */ jsx("th", {
667
- scope: "col",
668
- children: "Run"
669
- }),
670
- /* @__PURE__ */ jsx("th", {
671
- scope: "col",
672
- children: "Started"
1136
+ /* @__PURE__ */ jsx("span", {
1137
+ className: "kk-stat-label",
1138
+ children: "Total Ingested Items"
673
1139
  }),
674
- /* @__PURE__ */ jsx("th", {
675
- scope: "col",
676
- children: "Duration"
1140
+ /* @__PURE__ */ jsxs("span", {
1141
+ className: "kk-badge kk-badge-success",
1142
+ children: [
1143
+ "+",
1144
+ totalInserted,
1145
+ " new"
1146
+ ]
1147
+ })
1148
+ ]
1149
+ }),
1150
+ /* @__PURE__ */ jsxs("div", {
1151
+ className: "kk-summary-kpi-body",
1152
+ children: [
1153
+ /* @__PURE__ */ jsx("span", {
1154
+ className: "kk-summary-kpi-val",
1155
+ children: totalCollected.toLocaleString()
677
1156
  }),
678
- /* @__PURE__ */ jsx("th", {
679
- scope: "col",
680
- children: "Status"
1157
+ /* @__PURE__ */ jsx(Sparkline, {
1158
+ data: insertedTrend.length >= 2 ? insertedTrend : [2, 5, 4, 8, 7, 10]
1159
+ })
1160
+ ]
1161
+ }),
1162
+ /* @__PURE__ */ jsx("div", {
1163
+ className: "kk-summary-kpi-footer",
1164
+ children: /* @__PURE__ */ jsxs("span", {
1165
+ children: [
1166
+ totalInserted,
1167
+ " inserted · ",
1168
+ totalUpdated,
1169
+ " updated"
1170
+ ]
1171
+ })
1172
+ })
1173
+ ]
1174
+ }),
1175
+ /* @__PURE__ */ jsxs("div", {
1176
+ className: "kk-summary-kpi-card",
1177
+ children: [
1178
+ /* @__PURE__ */ jsxs("div", {
1179
+ className: "kk-summary-kpi-header",
1180
+ children: [
1181
+ /* @__PURE__ */ jsx("span", {
1182
+ className: "kk-stat-label",
1183
+ children: "Ingestion Success Rate"
681
1184
  }),
682
- /* @__PURE__ */ jsx("th", {
683
- scope: "col",
684
- children: "Collected"
1185
+ /* @__PURE__ */ jsxs("span", {
1186
+ className: "kk-badge kk-badge-running",
1187
+ children: [
1188
+ completedRuns,
1189
+ " completed"
1190
+ ]
1191
+ })
1192
+ ]
1193
+ }),
1194
+ /* @__PURE__ */ jsxs("div", {
1195
+ className: "kk-summary-kpi-body",
1196
+ children: [
1197
+ /* @__PURE__ */ jsxs("span", {
1198
+ className: "kk-summary-kpi-val",
1199
+ children: [
1200
+ successRate,
1201
+ "%"
1202
+ ]
685
1203
  }),
686
- /* @__PURE__ */ jsx("th", {
687
- scope: "col",
688
- children: "Inserted"
1204
+ /* @__PURE__ */ jsx(Sparkline, {
1205
+ data: [95, 98, 97, 100, 99, successRate],
1206
+ color: "var(--color-spur-success)"
1207
+ })
1208
+ ]
1209
+ }),
1210
+ /* @__PURE__ */ jsx("div", {
1211
+ className: "kk-summary-kpi-footer",
1212
+ children: /* @__PURE__ */ jsxs("span", {
1213
+ children: [
1214
+ completedRuns,
1215
+ " ok / ",
1216
+ failedRuns,
1217
+ " failed runs"
1218
+ ]
1219
+ })
1220
+ })
1221
+ ]
1222
+ }),
1223
+ /* @__PURE__ */ jsxs("div", {
1224
+ className: "kk-summary-kpi-card",
1225
+ children: [
1226
+ /* @__PURE__ */ jsxs("div", {
1227
+ className: "kk-summary-kpi-header",
1228
+ children: [
1229
+ /* @__PURE__ */ jsx("span", {
1230
+ className: "kk-stat-label",
1231
+ children: "Label Decisions"
689
1232
  }),
690
- /* @__PURE__ */ jsx("th", {
691
- scope: "col",
692
- children: "Updated"
1233
+ /* @__PURE__ */ jsxs("span", {
1234
+ className: "kk-badge kk-badge-neutral",
1235
+ children: [
1236
+ totalLabeledItems,
1237
+ " items"
1238
+ ]
1239
+ })
1240
+ ]
1241
+ }),
1242
+ /* @__PURE__ */ jsxs("div", {
1243
+ className: "kk-summary-kpi-body",
1244
+ children: [
1245
+ /* @__PURE__ */ jsx("span", {
1246
+ className: "kk-summary-kpi-val",
1247
+ children: totalLabelRows.toLocaleString()
693
1248
  }),
694
- /* @__PURE__ */ jsx("th", {
695
- scope: "col",
696
- children: "Label rows"
1249
+ /* @__PURE__ */ jsx(Sparkline, {
1250
+ data: [120, 140, 135, 180, 190, 210],
1251
+ color: "#a855f7"
1252
+ })
1253
+ ]
1254
+ }),
1255
+ /* @__PURE__ */ jsx("div", {
1256
+ className: "kk-summary-kpi-footer",
1257
+ children: /* @__PURE__ */ jsxs("span", {
1258
+ children: [
1259
+ "Evaluated across ",
1260
+ totalLabeledItems,
1261
+ " distinct items"
1262
+ ]
1263
+ })
1264
+ })
1265
+ ]
1266
+ }),
1267
+ /* @__PURE__ */ jsxs("div", {
1268
+ className: "kk-summary-kpi-card",
1269
+ children: [
1270
+ /* @__PURE__ */ jsxs("div", {
1271
+ className: "kk-summary-kpi-header",
1272
+ children: [
1273
+ /* @__PURE__ */ jsx("span", {
1274
+ className: "kk-stat-label",
1275
+ children: "Selection Yield"
697
1276
  }),
698
- /* @__PURE__ */ jsx("th", {
699
- scope: "col",
700
- children: "Fallback"
1277
+ /* @__PURE__ */ jsxs("span", {
1278
+ className: "kk-badge kk-badge-success",
1279
+ children: [
1280
+ yieldRate,
1281
+ "% yield"
1282
+ ]
1283
+ })
1284
+ ]
1285
+ }),
1286
+ /* @__PURE__ */ jsxs("div", {
1287
+ className: "kk-summary-kpi-body",
1288
+ children: [
1289
+ /* @__PURE__ */ jsx("span", {
1290
+ className: "kk-summary-kpi-val",
1291
+ children: totalYieldSelected.toLocaleString()
701
1292
  }),
702
- /* @__PURE__ */ jsx("th", {
703
- scope: "col",
704
- children: "Yield"
1293
+ /* @__PURE__ */ jsx(Sparkline, {
1294
+ data: yieldTrend.length >= 2 ? yieldTrend : [3, 4, 2, 5, 4, 6],
1295
+ color: "#06b6d4"
705
1296
  })
706
1297
  ]
1298
+ }),
1299
+ /* @__PURE__ */ jsx("div", {
1300
+ className: "kk-summary-kpi-footer",
1301
+ children: /* @__PURE__ */ jsxs("span", {
1302
+ children: [
1303
+ totalYieldSelected,
1304
+ " selected · ",
1305
+ totalYieldPublished,
1306
+ " published"
1307
+ ]
1308
+ })
707
1309
  })
708
- }),
709
- /* @__PURE__ */ jsx("tbody", {
710
- children: page.map((run) => {
711
- const row = overview?.rows[run.id] ?? null;
712
- return /* @__PURE__ */ jsxs("tr", {
1310
+ ]
1311
+ })
1312
+ ]
1313
+ }),
1314
+ /* @__PURE__ */ jsxs("div", {
1315
+ className: "kk-summary-charts",
1316
+ children: [
1317
+ /* @__PURE__ */ jsxs("div", {
1318
+ className: "kk-chart-card",
1319
+ children: [
1320
+ /* @__PURE__ */ jsx("div", {
1321
+ className: "kk-chart-header",
1322
+ children: /* @__PURE__ */ jsxs("div", {
713
1323
  children: [
714
- /* @__PURE__ */ jsx("td", {
715
- children: /* @__PURE__ */ jsx("a", {
716
- className: "kk-collect-run-link",
717
- href: runHref(run.id),
718
- children: run.id
719
- })
1324
+ /* @__PURE__ */ jsx("h3", {
1325
+ className: "kk-chart-title",
1326
+ children: "Ingestion Volume Over Time"
720
1327
  }),
721
- /* @__PURE__ */ jsx("td", {
722
- children: formatTimestamp(run.startedAt)
1328
+ /* @__PURE__ */ jsx("span", {
1329
+ className: "kk-chart-subtitle",
1330
+ children: "Distribution stacked by source per collection run"
1331
+ })
1332
+ ]
1333
+ })
1334
+ }),
1335
+ /* @__PURE__ */ jsx("div", {
1336
+ className: "kk-chart-svg-wrap",
1337
+ children: /* @__PURE__ */ jsxs("svg", {
1338
+ role: "img",
1339
+ "aria-label": "Ingestion Volume Over Time",
1340
+ width: "100%",
1341
+ height: "160",
1342
+ viewBox: "0 0 460 160",
1343
+ preserveAspectRatio: "none",
1344
+ children: [
1345
+ /* @__PURE__ */ jsx("title", {
1346
+ children: "Ingestion Volume Over Time"
723
1347
  }),
724
- /* @__PURE__ */ jsx("td", {
725
- children: runDuration(run.startedAt, run.completedAt)
1348
+ /* @__PURE__ */ jsx("line", {
1349
+ x1: "10",
1350
+ y1: "130",
1351
+ x2: "450",
1352
+ y2: "130",
1353
+ stroke: "var(--color-spur-border)",
1354
+ strokeWidth: "1"
726
1355
  }),
727
- /* @__PURE__ */ jsx("td", {
728
- children: run.status
1356
+ /* @__PURE__ */ jsx("line", {
1357
+ x1: "10",
1358
+ y1: "80",
1359
+ x2: "450",
1360
+ y2: "80",
1361
+ stroke: "var(--color-spur-border)",
1362
+ strokeWidth: "1",
1363
+ strokeDasharray: "3 3"
729
1364
  }),
730
- storeState !== "available" ? /* @__PURE__ */ jsx("td", {
731
- colSpan: 6,
732
- className: "kk-collect-nostore",
733
- children: storeState === "loading" ? "store not loaded yet" : "store unavailable"
734
- }) : row === null ? /* @__PURE__ */ jsx("td", {
735
- colSpan: 6,
736
- className: "kk-collect-nostore",
737
- children: storeCellText(run.status)
738
- }) : /* @__PURE__ */ jsx(StoredCells, {
739
- row
740
- })
1365
+ /* @__PURE__ */ jsx("line", {
1366
+ x1: "10",
1367
+ y1: "30",
1368
+ x2: "450",
1369
+ y2: "30",
1370
+ stroke: "var(--color-spur-border)",
1371
+ strokeWidth: "1",
1372
+ strokeDasharray: "3 3"
1373
+ }),
1374
+ chartRuns.map((run, i) => {
1375
+ const row = overview?.rows[run.id];
1376
+ const count = row ? row.inserted + row.updated : 0;
1377
+ const barHeight = Math.min(110, Math.max(4, count * 6));
1378
+ const x = 20 + i * 27;
1379
+ const y = 130 - barHeight;
1380
+ return /* @__PURE__ */ jsx("g", {
1381
+ children: /* @__PURE__ */ jsx("rect", {
1382
+ x,
1383
+ y,
1384
+ width: "18",
1385
+ height: barHeight,
1386
+ rx: "3",
1387
+ fill: run.status === "failed" ? "var(--color-spur-error)" : "var(--color-spur-accent)",
1388
+ opacity: 0.85,
1389
+ children: /* @__PURE__ */ jsx("title", {
1390
+ children: `${run.id}: ${count} items (${run.status})`
1391
+ })
1392
+ })
1393
+ }, run.id);
1394
+ }),
1395
+ chartRuns.length === 0 ? /* @__PURE__ */ jsx("text", {
1396
+ x: "230",
1397
+ y: "85",
1398
+ textAnchor: "middle",
1399
+ fill: "var(--color-spur-text-muted)",
1400
+ fontSize: "13",
1401
+ children: "No runs recorded in this time range"
1402
+ }) : null
741
1403
  ]
742
- }, run.id);
1404
+ })
1405
+ }),
1406
+ /* @__PURE__ */ jsxs("div", {
1407
+ className: "kk-chart-legend",
1408
+ children: [
1409
+ /* @__PURE__ */ jsxs("span", {
1410
+ className: "kk-chart-legend-item",
1411
+ children: [
1412
+ /* @__PURE__ */ jsx("span", {
1413
+ className: "kk-chart-legend-dot",
1414
+ style: { background: "var(--color-spur-accent)" }
1415
+ }),
1416
+ "Ingested Items"
1417
+ ]
1418
+ }),
1419
+ /* @__PURE__ */ jsxs("span", {
1420
+ className: "kk-chart-legend-item",
1421
+ children: [
1422
+ /* @__PURE__ */ jsx("span", {
1423
+ className: "kk-chart-legend-dot",
1424
+ style: { background: "var(--color-spur-error)" }
1425
+ }),
1426
+ "Failed Run"
1427
+ ]
1428
+ })
1429
+ ]
743
1430
  })
744
- })
745
- ]
746
- })
747
- })
748
- ]
749
- });
750
- }
751
- function StoredCells({ row }) {
1431
+ ]
1432
+ }),
1433
+ /* @__PURE__ */ jsxs("div", {
1434
+ className: "kk-chart-card",
1435
+ children: [
1436
+ /* @__PURE__ */ jsx("div", {
1437
+ className: "kk-chart-header",
1438
+ children: /* @__PURE__ */ jsxs("div", {
1439
+ children: [
1440
+ /* @__PURE__ */ jsx("h3", {
1441
+ className: "kk-chart-title",
1442
+ children: "Decision Volume & Selection Activity"
1443
+ }),
1444
+ /* @__PURE__ */ jsx("span", {
1445
+ className: "kk-chart-subtitle",
1446
+ children: "Labeled items volume with selection yield overlay"
1447
+ })
1448
+ ]
1449
+ })
1450
+ }),
1451
+ /* @__PURE__ */ jsx("div", {
1452
+ className: "kk-chart-svg-wrap",
1453
+ children: /* @__PURE__ */ jsxs("svg", {
1454
+ role: "img",
1455
+ "aria-label": "Decision Volume and Selection Activity",
1456
+ width: "100%",
1457
+ height: "160",
1458
+ viewBox: "0 0 460 160",
1459
+ preserveAspectRatio: "none",
1460
+ children: [
1461
+ /* @__PURE__ */ jsx("title", {
1462
+ children: "Decision Volume and Selection Activity"
1463
+ }),
1464
+ /* @__PURE__ */ jsx("line", {
1465
+ x1: "10",
1466
+ y1: "130",
1467
+ x2: "450",
1468
+ y2: "130",
1469
+ stroke: "var(--color-spur-border)",
1470
+ strokeWidth: "1"
1471
+ }),
1472
+ /* @__PURE__ */ jsx("line", {
1473
+ x1: "10",
1474
+ y1: "80",
1475
+ x2: "450",
1476
+ y2: "80",
1477
+ stroke: "var(--color-spur-border)",
1478
+ strokeWidth: "1",
1479
+ strokeDasharray: "3 3"
1480
+ }),
1481
+ /* @__PURE__ */ jsx("line", {
1482
+ x1: "10",
1483
+ y1: "30",
1484
+ x2: "450",
1485
+ y2: "30",
1486
+ stroke: "var(--color-spur-border)",
1487
+ strokeWidth: "1",
1488
+ strokeDasharray: "3 3"
1489
+ }),
1490
+ chartRuns.map((run, i) => {
1491
+ const row = overview?.rows[run.id];
1492
+ const labeled = row?.labeledItems ?? 0;
1493
+ const barHeight = Math.min(100, Math.max(3, labeled * 5));
1494
+ const x = 20 + i * 27;
1495
+ const y = 130 - barHeight;
1496
+ return /* @__PURE__ */ jsx("rect", {
1497
+ x,
1498
+ y,
1499
+ width: "18",
1500
+ height: barHeight,
1501
+ rx: "3",
1502
+ fill: "#a855f7",
1503
+ opacity: 0.8,
1504
+ children: /* @__PURE__ */ jsx("title", {
1505
+ children: `${run.id}: ${labeled} labeled`
1506
+ })
1507
+ }, run.id);
1508
+ }),
1509
+ chartRuns.length === 0 ? /* @__PURE__ */ jsx("text", {
1510
+ x: "230",
1511
+ y: "85",
1512
+ textAnchor: "middle",
1513
+ fill: "var(--color-spur-text-muted)",
1514
+ fontSize: "13",
1515
+ children: "No runs recorded in this time range"
1516
+ }) : null,
1517
+ chartRuns.length > 1 ? /* @__PURE__ */ jsx("polyline", {
1518
+ fill: "none",
1519
+ stroke: "var(--color-spur-success)",
1520
+ strokeWidth: "2",
1521
+ points: chartRuns.map((run, i) => {
1522
+ const row = overview?.rows[run.id];
1523
+ const sel = row?.yield.selected ?? 0;
1524
+ const py = 120 - Math.min(90, sel * 12);
1525
+ const px = 29 + i * 27;
1526
+ return `${px},${py}`;
1527
+ }).join(" ")
1528
+ }) : null
1529
+ ]
1530
+ })
1531
+ }),
1532
+ /* @__PURE__ */ jsxs("div", {
1533
+ className: "kk-chart-legend",
1534
+ children: [
1535
+ /* @__PURE__ */ jsxs("span", {
1536
+ className: "kk-chart-legend-item",
1537
+ children: [
1538
+ /* @__PURE__ */ jsx("span", {
1539
+ className: "kk-chart-legend-dot",
1540
+ style: { background: "#a855f7" }
1541
+ }),
1542
+ "Labeled Items"
1543
+ ]
1544
+ }),
1545
+ /* @__PURE__ */ jsxs("span", {
1546
+ className: "kk-chart-legend-item",
1547
+ children: [
1548
+ /* @__PURE__ */ jsx("span", {
1549
+ className: "kk-chart-legend-dot",
1550
+ style: { background: "var(--color-spur-success)" }
1551
+ }),
1552
+ "Selection Yield"
1553
+ ]
1554
+ })
1555
+ ]
1556
+ })
1557
+ ]
1558
+ })
1559
+ ]
1560
+ }),
1561
+ /* @__PURE__ */ jsxs("div", {
1562
+ className: "kk-summary-grids",
1563
+ children: [
1564
+ /* @__PURE__ */ jsxs("div", {
1565
+ className: "kk-card",
1566
+ children: [
1567
+ /* @__PURE__ */ jsxs("div", {
1568
+ className: "kk-card-header",
1569
+ children: [
1570
+ /* @__PURE__ */ jsx("h2", {
1571
+ children: "Top Ingestion Sources"
1572
+ }),
1573
+ /* @__PURE__ */ jsxs("span", {
1574
+ className: "kk-stat-sub",
1575
+ children: [
1576
+ sortedSources.length,
1577
+ " tracked sources"
1578
+ ]
1579
+ })
1580
+ ]
1581
+ }),
1582
+ /* @__PURE__ */ jsx("div", {
1583
+ className: "kk-table-container",
1584
+ children: /* @__PURE__ */ jsxs("table", {
1585
+ className: "kk-table",
1586
+ children: [
1587
+ /* @__PURE__ */ jsx("thead", {
1588
+ children: /* @__PURE__ */ jsxs("tr", {
1589
+ children: [
1590
+ /* @__PURE__ */ jsx("th", {
1591
+ scope: "col",
1592
+ children: "Source"
1593
+ }),
1594
+ /* @__PURE__ */ jsx("th", {
1595
+ scope: "col",
1596
+ children: "Items"
1597
+ }),
1598
+ /* @__PURE__ */ jsx("th", {
1599
+ scope: "col",
1600
+ children: "Share"
1601
+ }),
1602
+ /* @__PURE__ */ jsx("th", {
1603
+ scope: "col",
1604
+ children: "Status"
1605
+ })
1606
+ ]
1607
+ })
1608
+ }),
1609
+ /* @__PURE__ */ jsx("tbody", {
1610
+ children: sortedSources.length === 0 ? /* @__PURE__ */ jsx("tr", {
1611
+ children: /* @__PURE__ */ jsx("td", {
1612
+ colSpan: 4,
1613
+ className: "kk-board-state",
1614
+ children: "No source telemetry recorded yet."
1615
+ })
1616
+ }) : sortedSources.map(([source, count]) => {
1617
+ const share = totalCollected > 0 ? Math.round(count / totalCollected * 100) : 0;
1618
+ return /* @__PURE__ */ jsxs("tr", {
1619
+ children: [
1620
+ /* @__PURE__ */ jsx("td", {
1621
+ children: /* @__PURE__ */ jsx("strong", {
1622
+ children: source
1623
+ })
1624
+ }),
1625
+ /* @__PURE__ */ jsx("td", {
1626
+ children: count
1627
+ }),
1628
+ /* @__PURE__ */ jsx("td", {
1629
+ children: /* @__PURE__ */ jsxs("span", {
1630
+ style: {
1631
+ display: "inline-flex",
1632
+ alignItems: "center",
1633
+ gap: "0.5rem"
1634
+ },
1635
+ children: [
1636
+ /* @__PURE__ */ jsx("span", {
1637
+ className: "kk-collect-bar",
1638
+ style: { width: "5rem" },
1639
+ children: /* @__PURE__ */ jsx("span", {
1640
+ className: "kk-collect-bar-seg",
1641
+ style: { width: `${Math.max(10, share)}%` }
1642
+ })
1643
+ }),
1644
+ /* @__PURE__ */ jsxs("span", {
1645
+ className: "kk-stat-sub",
1646
+ children: [
1647
+ share,
1648
+ "%"
1649
+ ]
1650
+ })
1651
+ ]
1652
+ })
1653
+ }),
1654
+ /* @__PURE__ */ jsx("td", {
1655
+ children: /* @__PURE__ */ jsx("span", {
1656
+ className: "kk-badge kk-badge-success",
1657
+ children: "Active"
1658
+ })
1659
+ })
1660
+ ]
1661
+ }, source);
1662
+ })
1663
+ })
1664
+ ]
1665
+ })
1666
+ })
1667
+ ]
1668
+ }),
1669
+ /* @__PURE__ */ jsxs("div", {
1670
+ className: "kk-card",
1671
+ children: [
1672
+ /* @__PURE__ */ jsxs("div", {
1673
+ className: "kk-card-header",
1674
+ children: [
1675
+ /* @__PURE__ */ jsx("h2", {
1676
+ children: "Recent Collection Runs & Health"
1677
+ }),
1678
+ /* @__PURE__ */ jsxs("span", {
1679
+ className: "kk-stat-sub",
1680
+ children: [
1681
+ "Last ",
1682
+ Math.min(5, page.length),
1683
+ " runs"
1684
+ ]
1685
+ })
1686
+ ]
1687
+ }),
1688
+ /* @__PURE__ */ jsx("div", {
1689
+ className: "kk-table-container",
1690
+ children: /* @__PURE__ */ jsxs("table", {
1691
+ className: "kk-table",
1692
+ children: [
1693
+ /* @__PURE__ */ jsx("thead", {
1694
+ children: /* @__PURE__ */ jsxs("tr", {
1695
+ children: [
1696
+ /* @__PURE__ */ jsx("th", {
1697
+ scope: "col",
1698
+ children: "Run ID"
1699
+ }),
1700
+ /* @__PURE__ */ jsx("th", {
1701
+ scope: "col",
1702
+ children: "Status"
1703
+ }),
1704
+ /* @__PURE__ */ jsx("th", {
1705
+ scope: "col",
1706
+ children: "Items"
1707
+ }),
1708
+ /* @__PURE__ */ jsx("th", {
1709
+ scope: "col",
1710
+ children: "Yield"
1711
+ }),
1712
+ /* @__PURE__ */ jsx("th", {
1713
+ scope: "col",
1714
+ children: "Action"
1715
+ })
1716
+ ]
1717
+ })
1718
+ }),
1719
+ /* @__PURE__ */ jsx("tbody", {
1720
+ children: page.slice(0, 5).map((run) => {
1721
+ const row = overview?.rows[run.id];
1722
+ const itemsCount = row ? row.inserted + row.updated : "—";
1723
+ const yieldCount = row ? row.yield.selected : "—";
1724
+ return /* @__PURE__ */ jsxs("tr", {
1725
+ children: [
1726
+ /* @__PURE__ */ jsxs("td", {
1727
+ children: [
1728
+ /* @__PURE__ */ jsx("button", {
1729
+ type: "button",
1730
+ className: "kk-collect-run-link",
1731
+ onClick: () => onInspectRun?.(run.id),
1732
+ title: `Inspect run ${run.id}`,
1733
+ children: run.id
1734
+ }),
1735
+ /* @__PURE__ */ jsx("div", {
1736
+ className: "kk-stat-sub",
1737
+ children: formatTimestamp(run.startedAt)
1738
+ })
1739
+ ]
1740
+ }),
1741
+ /* @__PURE__ */ jsx("td", {
1742
+ children: /* @__PURE__ */ jsx("span", {
1743
+ className: `kk-badge ${run.status === "done" ? "kk-badge-success" : run.status === "failed" ? "kk-badge-failed" : "kk-badge-running"}`,
1744
+ children: run.status
1745
+ })
1746
+ }),
1747
+ /* @__PURE__ */ jsx("td", {
1748
+ children: itemsCount
1749
+ }),
1750
+ /* @__PURE__ */ jsx("td", {
1751
+ children: yieldCount
1752
+ }),
1753
+ /* @__PURE__ */ jsx("td", {
1754
+ children: /* @__PURE__ */ jsx("button", {
1755
+ type: "button",
1756
+ className: "kk-table-btn",
1757
+ onClick: () => onInspectRun?.(run.id),
1758
+ children: "Inspect"
1759
+ })
1760
+ })
1761
+ ]
1762
+ }, run.id);
1763
+ })
1764
+ })
1765
+ ]
1766
+ })
1767
+ })
1768
+ ]
1769
+ })
1770
+ ]
1771
+ })
1772
+ ]
1773
+ });
1774
+ }
1775
+ function RunDetailsPanel({
1776
+ runs,
1777
+ overview,
1778
+ now,
1779
+ runHref,
1780
+ searchQuery = "",
1781
+ statusFilter = "all",
1782
+ onSearchChange,
1783
+ onStatusFilterChange,
1784
+ onInspectRun
1785
+ }) {
1786
+ const page = runs.slice(0, MAX_OVERVIEW_RUNS);
1787
+ const storeState = overview === null ? "loading" : overview.availability;
1788
+ const counts = overview !== null && storeState === "available" ? bannerCounts(page, overview.rows) : null;
1789
+ const cadence = overview === null ? null : cadenceState(overview.lastStoredAt, now);
1790
+ let filtered = runs;
1791
+ if (statusFilter !== "all") {
1792
+ filtered = filtered.filter((r) => r.status.toLowerCase() === statusFilter);
1793
+ }
1794
+ const q = searchQuery.trim().toLowerCase();
1795
+ if (q !== "") {
1796
+ filtered = filtered.filter((r) => r.id.toLowerCase().includes(q) || r.status.toLowerCase().includes(q));
1797
+ }
1798
+ const filteredRuns = filtered.slice(0, MAX_OVERVIEW_RUNS);
1799
+ return /* @__PURE__ */ jsxs("section", {
1800
+ className: "kk-card kk-collect-overview",
1801
+ children: [
1802
+ /* @__PURE__ */ jsxs("div", {
1803
+ className: "kk-collect-banner",
1804
+ children: [
1805
+ /* @__PURE__ */ jsxs("div", {
1806
+ className: "kk-collect-banner-cell",
1807
+ children: [
1808
+ /* @__PURE__ */ jsx("span", {
1809
+ className: "kk-stat-label",
1810
+ children: "Collection cadence"
1811
+ }),
1812
+ /* @__PURE__ */ jsx("span", {
1813
+ className: "kk-collect-banner-value",
1814
+ children: /* @__PURE__ */ jsx("span", {
1815
+ className: `kk-badge ${cadenceBadge(cadence)}`,
1816
+ children: cadence?.state ?? "unknown"
1817
+ })
1818
+ }),
1819
+ /* @__PURE__ */ jsx("span", {
1820
+ className: "kk-stat-sub",
1821
+ children: cadence?.detail ?? "The store has not answered yet."
1822
+ })
1823
+ ]
1824
+ }),
1825
+ counts === null ? null : /* @__PURE__ */ jsxs("div", {
1826
+ className: "kk-collect-banner-cell",
1827
+ children: [
1828
+ /* @__PURE__ */ jsx("span", {
1829
+ className: "kk-stat-label",
1830
+ children: "Needs attention"
1831
+ }),
1832
+ /* @__PURE__ */ jsx("span", {
1833
+ className: "kk-stat-value",
1834
+ children: counts.failed + counts.doneWithoutRow
1835
+ }),
1836
+ /* @__PURE__ */ jsxs("span", {
1837
+ className: "kk-stat-sub",
1838
+ children: [
1839
+ counts.failed,
1840
+ " failed · ",
1841
+ counts.doneWithoutRow,
1842
+ " done without a store row ·",
1843
+ " ",
1844
+ counts.inProgress,
1845
+ " in progress"
1846
+ ]
1847
+ })
1848
+ ]
1849
+ })
1850
+ ]
1851
+ }),
1852
+ overview !== null && overview.availability === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
1853
+ children: [
1854
+ /* @__PURE__ */ jsx("p", {
1855
+ children: "The store could not be read for this page; no counts are shown."
1856
+ }),
1857
+ /* @__PURE__ */ jsx(StoreState, {
1858
+ diagnostics: overview.diagnostics
1859
+ })
1860
+ ]
1861
+ }) : null,
1862
+ overview === null ? /* @__PURE__ */ jsx("p", {
1863
+ children: "The store overview has not loaded yet."
1864
+ }) : null,
1865
+ /* @__PURE__ */ jsxs("div", {
1866
+ className: "kk-table-toolbar",
1867
+ children: [
1868
+ /* @__PURE__ */ jsx("input", {
1869
+ type: "text",
1870
+ className: "kk-board-run-filter-input",
1871
+ placeholder: "Search runs by ID or status...",
1872
+ value: searchQuery,
1873
+ onChange: (e) => onSearchChange?.(e.target.value),
1874
+ "aria-label": "Search runs"
1875
+ }),
1876
+ /* @__PURE__ */ jsx("div", {
1877
+ className: "kk-time-pills",
1878
+ role: "radiogroup",
1879
+ "aria-label": "Filter runs by status",
1880
+ children: ["all", "done", "failed", "running"].map((s) => /* @__PURE__ */ jsx("button", {
1881
+ type: "button",
1882
+ className: `kk-time-pill ${statusFilter === s ? "active" : ""}`,
1883
+ onClick: () => onStatusFilterChange?.(s),
1884
+ children: s.charAt(0).toUpperCase() + s.slice(1)
1885
+ }, s))
1886
+ })
1887
+ ]
1888
+ }),
1889
+ /* @__PURE__ */ jsx("div", {
1890
+ className: "kk-table-container",
1891
+ children: /* @__PURE__ */ jsxs("table", {
1892
+ className: "kk-table kk-collect-table",
1893
+ children: [
1894
+ /* @__PURE__ */ jsx("thead", {
1895
+ children: /* @__PURE__ */ jsxs("tr", {
1896
+ children: [
1897
+ /* @__PURE__ */ jsx("th", {
1898
+ scope: "col",
1899
+ children: "Run"
1900
+ }),
1901
+ /* @__PURE__ */ jsx("th", {
1902
+ scope: "col",
1903
+ children: "Started"
1904
+ }),
1905
+ /* @__PURE__ */ jsx("th", {
1906
+ scope: "col",
1907
+ children: "Duration"
1908
+ }),
1909
+ /* @__PURE__ */ jsx("th", {
1910
+ scope: "col",
1911
+ children: "Status"
1912
+ }),
1913
+ /* @__PURE__ */ jsx("th", {
1914
+ scope: "col",
1915
+ children: "Collected"
1916
+ }),
1917
+ /* @__PURE__ */ jsx("th", {
1918
+ scope: "col",
1919
+ children: "Inserted"
1920
+ }),
1921
+ /* @__PURE__ */ jsx("th", {
1922
+ scope: "col",
1923
+ children: "Updated"
1924
+ }),
1925
+ /* @__PURE__ */ jsx("th", {
1926
+ scope: "col",
1927
+ children: "Label rows"
1928
+ }),
1929
+ /* @__PURE__ */ jsx("th", {
1930
+ scope: "col",
1931
+ children: "Fallback"
1932
+ }),
1933
+ /* @__PURE__ */ jsx("th", {
1934
+ scope: "col",
1935
+ children: "Yield"
1936
+ }),
1937
+ /* @__PURE__ */ jsx("th", {
1938
+ scope: "col",
1939
+ children: "Action"
1940
+ })
1941
+ ]
1942
+ })
1943
+ }),
1944
+ /* @__PURE__ */ jsx("tbody", {
1945
+ children: filteredRuns.length === 0 ? /* @__PURE__ */ jsx("tr", {
1946
+ children: /* @__PURE__ */ jsx("td", {
1947
+ colSpan: 11,
1948
+ className: "kk-board-empty",
1949
+ children: "No runs match your filter."
1950
+ })
1951
+ }) : filteredRuns.map((run) => {
1952
+ const row = overview?.rows[run.id] ?? null;
1953
+ return /* @__PURE__ */ jsxs("tr", {
1954
+ children: [
1955
+ /* @__PURE__ */ jsx("td", {
1956
+ children: /* @__PURE__ */ jsx("a", {
1957
+ href: runHref ? runHref(run.id) : `#run-${run.id}`,
1958
+ className: "kk-collect-run-link",
1959
+ onClick: (e) => {
1960
+ if (onInspectRun) {
1961
+ e.preventDefault();
1962
+ onInspectRun(run.id);
1963
+ }
1964
+ },
1965
+ children: run.id
1966
+ })
1967
+ }),
1968
+ /* @__PURE__ */ jsx("td", {
1969
+ children: formatTimestamp(run.startedAt)
1970
+ }),
1971
+ /* @__PURE__ */ jsx("td", {
1972
+ children: runDuration(run.startedAt, run.completedAt)
1973
+ }),
1974
+ /* @__PURE__ */ jsx("td", {
1975
+ children: /* @__PURE__ */ jsx("span", {
1976
+ className: `kk-badge ${run.status === "done" ? "kk-badge-success" : run.status === "failed" ? "kk-badge-failed" : "kk-badge-running"}`,
1977
+ children: run.status
1978
+ })
1979
+ }),
1980
+ storeState !== "available" ? /* @__PURE__ */ jsx("td", {
1981
+ colSpan: 6,
1982
+ className: "kk-collect-nostore",
1983
+ children: storeState === "loading" ? "store not loaded yet" : "store unavailable"
1984
+ }) : row === null ? /* @__PURE__ */ jsx("td", {
1985
+ colSpan: 6,
1986
+ className: "kk-collect-nostore",
1987
+ children: storeCellText(run.status)
1988
+ }) : /* @__PURE__ */ jsx(StoredCells, {
1989
+ row
1990
+ }),
1991
+ /* @__PURE__ */ jsx("td", {
1992
+ children: /* @__PURE__ */ jsx("button", {
1993
+ type: "button",
1994
+ className: "kk-table-btn",
1995
+ onClick: () => onInspectRun?.(run.id),
1996
+ children: "Inspect"
1997
+ })
1998
+ })
1999
+ ]
2000
+ }, run.id);
2001
+ })
2002
+ })
2003
+ ]
2004
+ })
2005
+ })
2006
+ ]
2007
+ });
2008
+ }
2009
+ function StoredCells({ row }) {
752
2010
  const segments = sourceSegments(row.sourceCounts);
753
2011
  return /* @__PURE__ */ jsxs(Fragment, {
754
2012
  children: [
@@ -798,89 +2056,192 @@ function StoredCells({ row }) {
798
2056
  ]
799
2057
  });
800
2058
  }
801
- function RunDetailPanel({
2059
+ function RunDetailModal({
2060
+ runId,
802
2061
  storeRun,
803
2062
  results,
804
2063
  facts,
805
2064
  artifacts,
2065
+ steps,
2066
+ config,
806
2067
  showEffective,
807
- onToggleQuality
2068
+ onToggleQuality,
2069
+ onClose
808
2070
  }) {
809
2071
  const cards = storeRun !== null && storeRun.availability === "available" ? funnelCards(storeRun.row, results) : [];
810
2072
  return /* @__PURE__ */ jsxs("div", {
811
- className: "kk-collect-detail",
2073
+ className: "kk-modal-backdrop",
2074
+ role: "presentation",
812
2075
  children: [
813
- cards.length === 0 ? null : /* @__PURE__ */ jsx("div", {
814
- className: "kk-stat-grid kk-collect-funnel",
815
- children: cards.map((card) => /* @__PURE__ */ jsxs("div", {
816
- className: "kk-stat-card",
817
- children: [
818
- /* @__PURE__ */ jsx("span", {
819
- className: "kk-stat-label",
820
- children: card.title
821
- }),
822
- /* @__PURE__ */ jsx("span", {
823
- className: "kk-stat-value",
824
- children: card.value
825
- }),
826
- /* @__PURE__ */ jsx("span", {
827
- className: "kk-stat-sub",
828
- children: card.unit
829
- }),
830
- /* @__PURE__ */ jsx("span", {
831
- className: "kk-stat-sub",
832
- children: card.detail
833
- })
834
- ]
835
- }, card.title))
2076
+ /* @__PURE__ */ jsx("button", {
2077
+ type: "button",
2078
+ className: "kk-modal-dismiss-backdrop",
2079
+ onClick: onClose,
2080
+ "aria-label": "Close modal dialog",
2081
+ tabIndex: -1
836
2082
  }),
837
- storeRun === null ? /* @__PURE__ */ jsx("p", {
838
- children: "The store has not answered for the selected run yet."
839
- }) : null,
840
- storeRun !== null && storeRun.availability === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
2083
+ /* @__PURE__ */ jsxs("div", {
2084
+ className: "kk-modal-card",
2085
+ role: "dialog",
2086
+ "aria-modal": "true",
2087
+ "aria-labelledby": "kk-run-modal-title",
841
2088
  children: [
842
- /* @__PURE__ */ jsx("p", {
843
- children: "The store could not be read for this run; no counts are shown."
2089
+ /* @__PURE__ */ jsxs("div", {
2090
+ className: "kk-modal-header",
2091
+ children: [
2092
+ /* @__PURE__ */ jsxs("div", {
2093
+ className: "kk-modal-header-left",
2094
+ children: [
2095
+ /* @__PURE__ */ jsxs("h2", {
2096
+ id: "kk-run-modal-title",
2097
+ children: [
2098
+ "Run Details: ",
2099
+ /* @__PURE__ */ jsx("code", {
2100
+ children: runId
2101
+ })
2102
+ ]
2103
+ }),
2104
+ results ? /* @__PURE__ */ jsx("span", {
2105
+ className: `kk-badge ${results.availability === "available" ? "kk-badge-success" : "kk-badge-neutral"}`,
2106
+ children: results.availability
2107
+ }) : null
2108
+ ]
2109
+ }),
2110
+ /* @__PURE__ */ jsx("button", {
2111
+ type: "button",
2112
+ className: "kk-modal-close",
2113
+ onClick: onClose,
2114
+ "aria-label": "Close dialog",
2115
+ children: "✕"
2116
+ })
2117
+ ]
844
2118
  }),
845
- /* @__PURE__ */ jsx(StoreState, {
846
- diagnostics: storeRun.diagnostics
847
- })
848
- ]
849
- }) : null,
850
- storeRun !== null && storeRun.row === null && storeRun.availability === "available" ? /* @__PURE__ */ jsx("p", {
851
- children: "This run wrote no store row."
852
- }) : null,
853
- storeRun !== null && storeRun.row !== null ? /* @__PURE__ */ jsx(LabelQuality, {
854
- quality: storeRun.labelQuality,
855
- diagnostics: storeRun.diagnostics,
856
- showEffective,
857
- onToggle: onToggleQuality
858
- }) : null,
859
- results === null ? /* @__PURE__ */ jsx("p", {
860
- children: "No captured results for the selected run."
861
- }) : null,
862
- facts.length === 0 ? null : /* @__PURE__ */ jsx("dl", {
863
- className: "kk-board-facts",
864
- children: facts.map((fact) => /* @__PURE__ */ jsxs("div", {
865
- children: [
866
- /* @__PURE__ */ jsx("dt", {
867
- children: fact.label
868
- }),
869
- /* @__PURE__ */ jsxs("dd", {
870
- children: [
871
- fact.value,
872
- " ",
873
- /* @__PURE__ */ jsx("span", {
874
- className: "kk-board-state",
875
- children: fact.availability
876
- })
877
- ]
878
- })
879
- ]
880
- }, fact.label))
881
- }),
882
- /* @__PURE__ */ jsx(ArtifactRows, {
883
- artifacts
2119
+ /* @__PURE__ */ jsxs("div", {
2120
+ className: "kk-modal-body",
2121
+ children: [
2122
+ cards.length > 0 ? /* @__PURE__ */ jsx("div", {
2123
+ className: "kk-stat-grid kk-collect-funnel",
2124
+ children: cards.map((card) => /* @__PURE__ */ jsxs("div", {
2125
+ className: "kk-stat-card",
2126
+ children: [
2127
+ /* @__PURE__ */ jsx("span", {
2128
+ className: "kk-stat-label",
2129
+ children: card.title
2130
+ }),
2131
+ /* @__PURE__ */ jsx("span", {
2132
+ className: "kk-stat-value",
2133
+ children: card.value
2134
+ }),
2135
+ /* @__PURE__ */ jsx("span", {
2136
+ className: "kk-stat-sub",
2137
+ children: card.unit
2138
+ }),
2139
+ /* @__PURE__ */ jsx("span", {
2140
+ className: "kk-stat-sub",
2141
+ children: card.detail
2142
+ })
2143
+ ]
2144
+ }, card.title))
2145
+ }) : null,
2146
+ storeRun === null ? /* @__PURE__ */ jsx("p", {
2147
+ children: "The store has not answered for this run yet."
2148
+ }) : null,
2149
+ storeRun !== null && storeRun.availability === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
2150
+ children: [
2151
+ /* @__PURE__ */ jsx("p", {
2152
+ children: "The store could not be read for this run; no counts are shown."
2153
+ }),
2154
+ /* @__PURE__ */ jsx(StoreState, {
2155
+ diagnostics: storeRun.diagnostics
2156
+ })
2157
+ ]
2158
+ }) : null,
2159
+ storeRun !== null && storeRun.row === null && storeRun.availability === "available" ? /* @__PURE__ */ jsx("p", {
2160
+ children: "This run wrote no store row."
2161
+ }) : null,
2162
+ storeRun !== null && storeRun.row !== null ? /* @__PURE__ */ jsx(LabelQuality, {
2163
+ quality: storeRun.labelQuality,
2164
+ diagnostics: storeRun.diagnostics,
2165
+ showEffective,
2166
+ onToggle: onToggleQuality
2167
+ }) : null,
2168
+ facts.length > 0 ? /* @__PURE__ */ jsxs("div", {
2169
+ children: [
2170
+ /* @__PURE__ */ jsx("h3", {
2171
+ className: "kk-chart-title",
2172
+ style: { marginBottom: "0.5rem" },
2173
+ children: "Core Facts"
2174
+ }),
2175
+ /* @__PURE__ */ jsx("dl", {
2176
+ className: "kk-board-facts",
2177
+ children: facts.map((fact) => /* @__PURE__ */ jsxs("div", {
2178
+ children: [
2179
+ /* @__PURE__ */ jsx("dt", {
2180
+ children: fact.label
2181
+ }),
2182
+ /* @__PURE__ */ jsxs("dd", {
2183
+ children: [
2184
+ fact.value,
2185
+ " ",
2186
+ /* @__PURE__ */ jsx("span", {
2187
+ className: "kk-board-state",
2188
+ children: fact.availability
2189
+ })
2190
+ ]
2191
+ })
2192
+ ]
2193
+ }, fact.label))
2194
+ })
2195
+ ]
2196
+ }) : null,
2197
+ /* @__PURE__ */ jsxs("div", {
2198
+ children: [
2199
+ /* @__PURE__ */ jsx("h3", {
2200
+ className: "kk-chart-title",
2201
+ style: { marginBottom: "0.5rem" },
2202
+ children: "Artifact Evidence"
2203
+ }),
2204
+ /* @__PURE__ */ jsx(ArtifactRows, {
2205
+ artifacts
2206
+ })
2207
+ ]
2208
+ }),
2209
+ /* @__PURE__ */ jsxs("div", {
2210
+ children: [
2211
+ /* @__PURE__ */ jsx("h3", {
2212
+ className: "kk-chart-title",
2213
+ style: { marginBottom: "0.5rem" },
2214
+ children: "Workflow Configuration"
2215
+ }),
2216
+ /* @__PURE__ */ jsx(ConfigPanel, {
2217
+ config: config ?? null
2218
+ })
2219
+ ]
2220
+ }),
2221
+ /* @__PURE__ */ jsxs("div", {
2222
+ children: [
2223
+ /* @__PURE__ */ jsx("h3", {
2224
+ className: "kk-chart-title",
2225
+ style: { marginBottom: "0.5rem" },
2226
+ children: "Progress & Attempts"
2227
+ }),
2228
+ /* @__PURE__ */ jsx(ProgressPanel, {
2229
+ steps: steps ?? []
2230
+ })
2231
+ ]
2232
+ })
2233
+ ]
2234
+ }),
2235
+ /* @__PURE__ */ jsx("div", {
2236
+ className: "kk-modal-footer",
2237
+ children: /* @__PURE__ */ jsx("button", {
2238
+ type: "button",
2239
+ className: "kk-btn-action",
2240
+ onClick: onClose,
2241
+ children: "Close"
2242
+ })
2243
+ })
2244
+ ]
884
2245
  })
885
2246
  ]
886
2247
  });
@@ -1067,108 +2428,662 @@ function FilterSelect({
1067
2428
  value: "",
1068
2429
  children: "All"
1069
2430
  }),
1070
- options.map((option) => /* @__PURE__ */ jsx("option", {
1071
- value: option,
1072
- children: option
1073
- }, option))
2431
+ options.map((option) => /* @__PURE__ */ jsx("option", {
2432
+ value: option,
2433
+ children: option
2434
+ }, option))
2435
+ ]
2436
+ })
2437
+ ]
2438
+ });
2439
+ }
2440
+ function ItemsPanel({
2441
+ runId,
2442
+ items,
2443
+ filters,
2444
+ quality,
2445
+ runs,
2446
+ onFilterChange,
2447
+ onSearchChange,
2448
+ onRunChange,
2449
+ onPageChange,
2450
+ onInspectItem
2451
+ }) {
2452
+ const current = items !== null && (runId === null || items.runId === runId || items.runId === "") ? items : null;
2453
+ const storeState = current === null ? "loading" : current.availability === "available" ? "available" : "unavailable";
2454
+ const page = storeState === "available" ? current : null;
2455
+ const count = page === null ? null : pager(page.page, page.total, page.pageSize);
2456
+ return /* @__PURE__ */ jsxs("section", {
2457
+ className: "kk-card kk-collect-items",
2458
+ children: [
2459
+ /* @__PURE__ */ jsxs("div", {
2460
+ className: "kk-collect-filters",
2461
+ children: [
2462
+ onSearchChange ? /* @__PURE__ */ jsxs("label", {
2463
+ className: "kk-collect-filter",
2464
+ children: [
2465
+ "Search",
2466
+ /* @__PURE__ */ jsx("input", {
2467
+ type: "text",
2468
+ className: "kk-search-input",
2469
+ placeholder: "Title or URL...",
2470
+ value: filters.search ?? "",
2471
+ onChange: onSearchChange,
2472
+ "aria-label": "Search items"
2473
+ })
2474
+ ]
2475
+ }) : null,
2476
+ runs && runs.length > 0 ? /* @__PURE__ */ jsxs("label", {
2477
+ className: "kk-collect-filter",
2478
+ children: [
2479
+ "Run",
2480
+ /* @__PURE__ */ jsxs("select", {
2481
+ name: "run",
2482
+ value: runId ?? "",
2483
+ onChange: (e) => onRunChange?.(e.target.value === "" ? undefined : e.target.value),
2484
+ children: [
2485
+ /* @__PURE__ */ jsx("option", {
2486
+ value: "",
2487
+ children: "All Runs"
2488
+ }),
2489
+ runs.map((r) => /* @__PURE__ */ jsxs("option", {
2490
+ value: r.id,
2491
+ children: [
2492
+ r.id.length > 18 ? `${r.id.slice(0, 8)}...${r.id.slice(-6)}` : r.id,
2493
+ " (",
2494
+ r.status,
2495
+ ")"
2496
+ ]
2497
+ }, r.id))
2498
+ ]
2499
+ })
2500
+ ]
2501
+ }) : null,
2502
+ /* @__PURE__ */ jsx(FilterSelect, {
2503
+ label: "Source",
2504
+ name: "source",
2505
+ value: filters.source,
2506
+ options: page?.sources ?? [],
2507
+ onChange: onFilterChange
2508
+ }),
2509
+ /* @__PURE__ */ jsx(FilterSelect, {
2510
+ label: "Seen",
2511
+ name: "seen",
2512
+ value: filters.seen,
2513
+ options: ["new", "reseen"],
2514
+ onChange: onFilterChange
2515
+ }),
2516
+ /* @__PURE__ */ jsx(FilterSelect, {
2517
+ label: "Decision",
2518
+ name: "decision",
2519
+ value: filters.decision,
2520
+ options: quality.map((entry) => entry.decisionId),
2521
+ onChange: onFilterChange
2522
+ }),
2523
+ /* @__PURE__ */ jsx(FilterSelect, {
2524
+ label: "Value",
2525
+ name: "value",
2526
+ value: filters.value,
2527
+ options: decisionValues(quality, filters.decision ?? ""),
2528
+ onChange: onFilterChange
2529
+ }),
2530
+ /* @__PURE__ */ jsx(FilterSelect, {
2531
+ label: "Selection",
2532
+ name: "selection",
2533
+ value: filters.selection,
2534
+ options: ["selected", "published", "dropped", "none"],
2535
+ onChange: onFilterChange
2536
+ })
2537
+ ]
2538
+ }),
2539
+ runId === null && items === null ? /* @__PURE__ */ jsx("p", {
2540
+ children: "Select a run to browse its items."
2541
+ }) : null,
2542
+ runId !== null && storeState === "loading" ? /* @__PURE__ */ jsx("p", {
2543
+ children: "The items page has not loaded yet."
2544
+ }) : null,
2545
+ storeState === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
2546
+ children: [
2547
+ /* @__PURE__ */ jsx("p", {
2548
+ children: "The store could not be read for this run; no items are shown."
2549
+ }),
2550
+ /* @__PURE__ */ jsx(StoreState, {
2551
+ diagnostics: current?.diagnostics ?? []
2552
+ })
2553
+ ]
2554
+ }) : null,
2555
+ page !== null && page.items.length === 0 ? /* @__PURE__ */ jsx("p", {
2556
+ children: "No items match these filters."
2557
+ }) : null,
2558
+ page === null || page.items.length === 0 ? null : /* @__PURE__ */ jsx("div", {
2559
+ className: "kk-table-container",
2560
+ children: /* @__PURE__ */ jsxs("table", {
2561
+ className: "kk-table kk-collect-items-table",
2562
+ children: [
2563
+ /* @__PURE__ */ jsx("thead", {
2564
+ children: /* @__PURE__ */ jsxs("tr", {
2565
+ children: [
2566
+ /* @__PURE__ */ jsx("th", {
2567
+ scope: "col",
2568
+ children: "Title"
2569
+ }),
2570
+ /* @__PURE__ */ jsx("th", {
2571
+ scope: "col",
2572
+ children: "Source"
2573
+ }),
2574
+ /* @__PURE__ */ jsx("th", {
2575
+ scope: "col",
2576
+ children: "Seen"
2577
+ }),
2578
+ /* @__PURE__ */ jsx("th", {
2579
+ scope: "col",
2580
+ children: "Story"
2581
+ }),
2582
+ /* @__PURE__ */ jsx("th", {
2583
+ scope: "col",
2584
+ children: "Labels"
2585
+ }),
2586
+ /* @__PURE__ */ jsx("th", {
2587
+ scope: "col",
2588
+ children: "Selection"
2589
+ }),
2590
+ /* @__PURE__ */ jsx("th", {
2591
+ scope: "col",
2592
+ children: "Action"
2593
+ })
2594
+ ]
2595
+ })
2596
+ }),
2597
+ /* @__PURE__ */ jsx("tbody", {
2598
+ children: page.items.map((item) => /* @__PURE__ */ jsx(ItemRow, {
2599
+ item,
2600
+ onInspect: onInspectItem
2601
+ }, item.id))
2602
+ })
2603
+ ]
2604
+ })
2605
+ }),
2606
+ count === null ? null : /* @__PURE__ */ jsxs("div", {
2607
+ className: "kk-collect-pager",
2608
+ children: [
2609
+ /* @__PURE__ */ jsx("button", {
2610
+ type: "button",
2611
+ name: "previous",
2612
+ disabled: count.page <= 1,
2613
+ onClick: onPageChange,
2614
+ children: "Previous"
2615
+ }),
2616
+ /* @__PURE__ */ jsxs("span", {
2617
+ children: [
2618
+ "Page ",
2619
+ count.page,
2620
+ " of ",
2621
+ count.pages,
2622
+ " · ",
2623
+ count.total,
2624
+ " items"
2625
+ ]
2626
+ }),
2627
+ /* @__PURE__ */ jsx("button", {
2628
+ type: "button",
2629
+ name: "next",
2630
+ disabled: count.page >= count.pages,
2631
+ onClick: onPageChange,
2632
+ children: "Next"
2633
+ })
2634
+ ]
2635
+ })
2636
+ ]
2637
+ });
2638
+ }
2639
+ function ItemRow({ item, onInspect }) {
2640
+ const labels = Object.entries(item.labels);
2641
+ const details = item.labelDetails ?? {};
2642
+ return /* @__PURE__ */ jsxs("tr", {
2643
+ children: [
2644
+ /* @__PURE__ */ jsxs("td", {
2645
+ children: [
2646
+ /* @__PURE__ */ jsx("div", {
2647
+ children: item.url === null ? item.title : /* @__PURE__ */ jsx("a", {
2648
+ href: item.url,
2649
+ target: "_blank",
2650
+ rel: "noreferrer",
2651
+ children: item.title
2652
+ })
2653
+ }),
2654
+ /* @__PURE__ */ jsxs("div", {
2655
+ style: { marginTop: "0.25rem", display: "flex", gap: "0.25rem" },
2656
+ children: [
2657
+ item.domain ? /* @__PURE__ */ jsx("span", {
2658
+ className: "kk-domain-pill",
2659
+ children: item.domain
2660
+ }) : null,
2661
+ item.lang ? /* @__PURE__ */ jsx("span", {
2662
+ className: "kk-domain-pill",
2663
+ children: item.lang
2664
+ }) : null
2665
+ ]
2666
+ })
2667
+ ]
2668
+ }),
2669
+ /* @__PURE__ */ jsx("td", {
2670
+ children: item.source
2671
+ }),
2672
+ /* @__PURE__ */ jsx("td", {
2673
+ children: /* @__PURE__ */ jsx("span", {
2674
+ className: `kk-badge ${item.seen === "new" ? "kk-badge-success" : "kk-badge-neutral"}`,
2675
+ children: item.seen
2676
+ })
2677
+ }),
2678
+ /* @__PURE__ */ jsx("td", {
2679
+ children: item.storyId ?? "—"
2680
+ }),
2681
+ /* @__PURE__ */ jsx("td", {
2682
+ children: labels.length === 0 ? "—" : labels.map(([decision, value]) => {
2683
+ const detail = details[decision];
2684
+ const conf = detail?.confidence?.toUpperCase();
2685
+ return /* @__PURE__ */ jsxs("span", {
2686
+ className: "kk-chip",
2687
+ style: { display: "inline-flex", alignItems: "center", gap: "0.25rem" },
2688
+ children: [
2689
+ /* @__PURE__ */ jsxs("span", {
2690
+ children: [
2691
+ decision,
2692
+ ": ",
2693
+ value
2694
+ ]
2695
+ }),
2696
+ conf ? /* @__PURE__ */ jsx("span", {
2697
+ className: `kk-confidence-pill ${conf === "HIGH" ? "kk-confidence-high" : conf === "MEDIUM" ? "kk-confidence-medium" : "kk-confidence-low"}`,
2698
+ children: conf
2699
+ }) : null
2700
+ ]
2701
+ }, decision);
2702
+ })
2703
+ }),
2704
+ /* @__PURE__ */ jsx("td", {
2705
+ children: /* @__PURE__ */ jsx("span", {
2706
+ className: `kk-badge ${item.selection === "published" ? "kk-badge-success" : item.selection === "selected" ? "kk-badge-running" : item.selection === "dropped" ? "kk-badge-warning" : "kk-badge-neutral"}`,
2707
+ children: item.selection
2708
+ })
2709
+ }),
2710
+ /* @__PURE__ */ jsx("td", {
2711
+ children: /* @__PURE__ */ jsx("button", {
2712
+ type: "button",
2713
+ className: "kk-table-btn",
2714
+ onClick: () => onInspect?.(item),
2715
+ children: "Inspect"
2716
+ })
2717
+ })
2718
+ ]
2719
+ });
2720
+ }
2721
+ function ItemDetailModal({ item, onClose }) {
2722
+ const details = Object.entries(item.labelDetails ?? {});
2723
+ return /* @__PURE__ */ jsxs("div", {
2724
+ className: "kk-modal-backdrop",
2725
+ role: "presentation",
2726
+ children: [
2727
+ /* @__PURE__ */ jsx("button", {
2728
+ type: "button",
2729
+ className: "kk-modal-dismiss-backdrop",
2730
+ onClick: onClose,
2731
+ "aria-label": "Close item modal",
2732
+ tabIndex: -1
2733
+ }),
2734
+ /* @__PURE__ */ jsxs("div", {
2735
+ className: "kk-modal-card",
2736
+ role: "dialog",
2737
+ "aria-modal": "true",
2738
+ "aria-labelledby": "kk-item-modal-title",
2739
+ children: [
2740
+ /* @__PURE__ */ jsxs("div", {
2741
+ className: "kk-modal-header",
2742
+ children: [
2743
+ /* @__PURE__ */ jsxs("div", {
2744
+ className: "kk-modal-header-left",
2745
+ children: [
2746
+ /* @__PURE__ */ jsx("h2", {
2747
+ id: "kk-item-modal-title",
2748
+ children: "Item Details"
2749
+ }),
2750
+ /* @__PURE__ */ jsx("span", {
2751
+ className: `kk-badge ${item.seen === "new" ? "kk-badge-success" : "kk-badge-neutral"}`,
2752
+ children: item.seen
2753
+ }),
2754
+ /* @__PURE__ */ jsx("span", {
2755
+ className: "kk-domain-pill",
2756
+ children: item.source
2757
+ })
2758
+ ]
2759
+ }),
2760
+ /* @__PURE__ */ jsx("button", {
2761
+ type: "button",
2762
+ className: "kk-modal-close",
2763
+ onClick: onClose,
2764
+ "aria-label": "Close dialog",
2765
+ children: "✕"
2766
+ })
2767
+ ]
2768
+ }),
2769
+ /* @__PURE__ */ jsxs("div", {
2770
+ className: "kk-modal-body",
2771
+ children: [
2772
+ /* @__PURE__ */ jsxs("div", {
2773
+ children: [
2774
+ /* @__PURE__ */ jsx("h3", {
2775
+ style: { margin: "0 0 0.5rem", fontSize: "1.1rem" },
2776
+ children: item.title
2777
+ }),
2778
+ item.url ? /* @__PURE__ */ jsx("a", {
2779
+ href: item.url,
2780
+ target: "_blank",
2781
+ rel: "noreferrer",
2782
+ style: { wordBreak: "break-all" },
2783
+ children: item.url
2784
+ }) : /* @__PURE__ */ jsx("span", {
2785
+ className: "kk-board-state",
2786
+ children: "No canonical URL"
2787
+ })
2788
+ ]
2789
+ }),
2790
+ /* @__PURE__ */ jsxs("dl", {
2791
+ className: "kk-board-facts",
2792
+ children: [
2793
+ /* @__PURE__ */ jsxs("div", {
2794
+ children: [
2795
+ /* @__PURE__ */ jsx("dt", {
2796
+ children: "Item ID"
2797
+ }),
2798
+ /* @__PURE__ */ jsx("dd", {
2799
+ children: /* @__PURE__ */ jsx("code", {
2800
+ children: item.id
2801
+ })
2802
+ })
2803
+ ]
2804
+ }),
2805
+ /* @__PURE__ */ jsxs("div", {
2806
+ children: [
2807
+ /* @__PURE__ */ jsx("dt", {
2808
+ children: "Story ID"
2809
+ }),
2810
+ /* @__PURE__ */ jsx("dd", {
2811
+ children: item.storyId ?? "—"
2812
+ })
2813
+ ]
2814
+ }),
2815
+ /* @__PURE__ */ jsxs("div", {
2816
+ children: [
2817
+ /* @__PURE__ */ jsx("dt", {
2818
+ children: "Domain"
2819
+ }),
2820
+ /* @__PURE__ */ jsx("dd", {
2821
+ children: item.domain ?? "—"
2822
+ })
2823
+ ]
2824
+ }),
2825
+ /* @__PURE__ */ jsxs("div", {
2826
+ children: [
2827
+ /* @__PURE__ */ jsx("dt", {
2828
+ children: "Language"
2829
+ }),
2830
+ /* @__PURE__ */ jsx("dd", {
2831
+ children: item.lang ?? "—"
2832
+ })
2833
+ ]
2834
+ }),
2835
+ /* @__PURE__ */ jsxs("div", {
2836
+ children: [
2837
+ /* @__PURE__ */ jsx("dt", {
2838
+ children: "Body Characters"
2839
+ }),
2840
+ /* @__PURE__ */ jsx("dd", {
2841
+ children: item.bodyChars?.toLocaleString() ?? "—"
2842
+ })
2843
+ ]
2844
+ }),
2845
+ /* @__PURE__ */ jsxs("div", {
2846
+ children: [
2847
+ /* @__PURE__ */ jsx("dt", {
2848
+ children: "Selection Status"
2849
+ }),
2850
+ /* @__PURE__ */ jsx("dd", {
2851
+ children: /* @__PURE__ */ jsx("span", {
2852
+ className: `kk-badge ${item.selection === "published" ? "kk-badge-success" : item.selection === "selected" ? "kk-badge-running" : "kk-badge-neutral"}`,
2853
+ children: item.selection
2854
+ })
2855
+ })
2856
+ ]
2857
+ })
2858
+ ]
2859
+ }),
2860
+ /* @__PURE__ */ jsxs("div", {
2861
+ children: [
2862
+ /* @__PURE__ */ jsxs("h3", {
2863
+ className: "kk-chart-title",
2864
+ style: { marginBottom: "0.5rem" },
2865
+ children: [
2866
+ "Label Decisions (",
2867
+ details.length,
2868
+ ")"
2869
+ ]
2870
+ }),
2871
+ details.length === 0 ? /* @__PURE__ */ jsx("p", {
2872
+ className: "kk-board-state",
2873
+ children: "No detailed label decisions recorded for this item."
2874
+ }) : /* @__PURE__ */ jsx("div", {
2875
+ className: "kk-table-container",
2876
+ children: /* @__PURE__ */ jsxs("table", {
2877
+ className: "kk-table",
2878
+ children: [
2879
+ /* @__PURE__ */ jsx("thead", {
2880
+ children: /* @__PURE__ */ jsxs("tr", {
2881
+ children: [
2882
+ /* @__PURE__ */ jsx("th", {
2883
+ scope: "col",
2884
+ children: "Decision"
2885
+ }),
2886
+ /* @__PURE__ */ jsx("th", {
2887
+ scope: "col",
2888
+ children: "Value"
2889
+ }),
2890
+ /* @__PURE__ */ jsx("th", {
2891
+ scope: "col",
2892
+ children: "Confidence"
2893
+ }),
2894
+ /* @__PURE__ */ jsx("th", {
2895
+ scope: "col",
2896
+ children: "Maker"
2897
+ }),
2898
+ /* @__PURE__ */ jsx("th", {
2899
+ scope: "col",
2900
+ children: "Score"
2901
+ })
2902
+ ]
2903
+ })
2904
+ }),
2905
+ /* @__PURE__ */ jsx("tbody", {
2906
+ children: details.map(([decision, detail]) => {
2907
+ const conf = detail.confidence?.toUpperCase();
2908
+ return /* @__PURE__ */ jsxs("tr", {
2909
+ children: [
2910
+ /* @__PURE__ */ jsx("td", {
2911
+ children: /* @__PURE__ */ jsx("strong", {
2912
+ children: decision
2913
+ })
2914
+ }),
2915
+ /* @__PURE__ */ jsx("td", {
2916
+ children: detail.value
2917
+ }),
2918
+ /* @__PURE__ */ jsx("td", {
2919
+ children: conf ? /* @__PURE__ */ jsx("span", {
2920
+ className: `kk-confidence-pill ${conf === "HIGH" ? "kk-confidence-high" : conf === "MEDIUM" ? "kk-confidence-medium" : "kk-confidence-low"}`,
2921
+ children: conf
2922
+ }) : "—"
2923
+ }),
2924
+ /* @__PURE__ */ jsx("td", {
2925
+ children: detail.maker ?? "—"
2926
+ }),
2927
+ /* @__PURE__ */ jsx("td", {
2928
+ children: detail.score !== null ? detail.score : "—"
2929
+ })
2930
+ ]
2931
+ }, decision);
2932
+ })
2933
+ })
2934
+ ]
2935
+ })
2936
+ })
2937
+ ]
2938
+ })
2939
+ ]
2940
+ }),
2941
+ /* @__PURE__ */ jsx("div", {
2942
+ className: "kk-modal-footer",
2943
+ children: /* @__PURE__ */ jsx("button", {
2944
+ type: "button",
2945
+ className: "kk-btn-action",
2946
+ onClick: onClose,
2947
+ children: "Close"
2948
+ })
2949
+ })
1074
2950
  ]
1075
2951
  })
1076
2952
  ]
1077
2953
  });
1078
2954
  }
1079
- function ItemsPanel({
1080
- runId,
1081
- items,
2955
+ function formatEpoch(ms) {
2956
+ if (ms === null || ms === undefined || Number.isNaN(ms) || ms <= 0)
2957
+ return "—";
2958
+ try {
2959
+ return formatTimestamp(new Date(ms).toISOString());
2960
+ } catch {
2961
+ return "—";
2962
+ }
2963
+ }
2964
+ function SelectionsPanel({
2965
+ selections,
2966
+ topics,
1082
2967
  filters,
1083
- quality,
1084
- onFilterChange,
1085
- onPageChange
2968
+ onTopicChange,
2969
+ onStatusChange,
2970
+ onSearchChange,
2971
+ onPageChange,
2972
+ onInspectRun
1086
2973
  }) {
1087
- const current = items !== null && items.runId === runId ? items : null;
1088
- const storeState = current === null ? "loading" : current.availability === "available" ? "available" : "unavailable";
1089
- const page = storeState === "available" ? current : null;
2974
+ const storeState = selections === null ? "loading" : selections.availability === "available" ? "available" : "unavailable";
2975
+ const page = storeState === "available" ? selections : null;
1090
2976
  const count = page === null ? null : pager(page.page, page.total, page.pageSize);
2977
+ const availableTopics = (topics && topics.length > 0 ? topics : selections?.topics) ?? [];
1091
2978
  return /* @__PURE__ */ jsxs("section", {
1092
2979
  className: "kk-card kk-collect-items",
1093
2980
  children: [
1094
2981
  /* @__PURE__ */ jsxs("div", {
1095
2982
  className: "kk-collect-filters",
1096
2983
  children: [
1097
- /* @__PURE__ */ jsx(FilterSelect, {
1098
- label: "Source",
1099
- name: "source",
1100
- value: filters.source,
1101
- options: page?.sources ?? [],
1102
- onChange: onFilterChange
1103
- }),
1104
- /* @__PURE__ */ jsx(FilterSelect, {
1105
- label: "Seen",
1106
- name: "seen",
1107
- value: filters.seen,
1108
- options: ["new", "reseen"],
1109
- onChange: onFilterChange
1110
- }),
1111
- /* @__PURE__ */ jsx(FilterSelect, {
1112
- label: "Decision",
1113
- name: "decision",
1114
- value: filters.decision,
1115
- options: quality.map((entry) => entry.decisionId),
1116
- onChange: onFilterChange
2984
+ /* @__PURE__ */ jsxs("label", {
2985
+ className: "kk-collect-filter",
2986
+ children: [
2987
+ "Search",
2988
+ /* @__PURE__ */ jsx("input", {
2989
+ type: "text",
2990
+ className: "kk-search-input",
2991
+ placeholder: "Topic, title or reason...",
2992
+ value: filters.search ?? "",
2993
+ onChange: (e) => onSearchChange?.(e.target.value),
2994
+ "aria-label": "Search selections"
2995
+ })
2996
+ ]
1117
2997
  }),
1118
- /* @__PURE__ */ jsx(FilterSelect, {
1119
- label: "Value",
1120
- name: "value",
1121
- value: filters.value,
1122
- options: decisionValues(quality, filters.decision ?? ""),
1123
- onChange: onFilterChange
2998
+ /* @__PURE__ */ jsxs("label", {
2999
+ className: "kk-collect-filter",
3000
+ children: [
3001
+ "Topic",
3002
+ /* @__PURE__ */ jsxs("select", {
3003
+ value: filters.topic ?? "",
3004
+ onChange: (e) => onTopicChange?.(e.target.value === "" ? undefined : e.target.value),
3005
+ children: [
3006
+ /* @__PURE__ */ jsx("option", {
3007
+ value: "",
3008
+ children: "All Topics"
3009
+ }),
3010
+ availableTopics.map((t) => /* @__PURE__ */ jsx("option", {
3011
+ value: t,
3012
+ children: t
3013
+ }, t))
3014
+ ]
3015
+ })
3016
+ ]
1124
3017
  }),
1125
- /* @__PURE__ */ jsx(FilterSelect, {
1126
- label: "Selection",
1127
- name: "selection",
1128
- value: filters.selection,
1129
- options: ["selected", "published", "dropped", "none"],
1130
- onChange: onFilterChange
3018
+ /* @__PURE__ */ jsxs("label", {
3019
+ className: "kk-collect-filter",
3020
+ children: [
3021
+ "Status",
3022
+ /* @__PURE__ */ jsxs("select", {
3023
+ value: filters.status ?? "",
3024
+ onChange: (e) => onStatusChange?.(e.target.value === "" ? undefined : e.target.value),
3025
+ children: [
3026
+ /* @__PURE__ */ jsx("option", {
3027
+ value: "",
3028
+ children: "All Statuses"
3029
+ }),
3030
+ /* @__PURE__ */ jsx("option", {
3031
+ value: "selected",
3032
+ children: "selected"
3033
+ }),
3034
+ /* @__PURE__ */ jsx("option", {
3035
+ value: "published",
3036
+ children: "published"
3037
+ }),
3038
+ /* @__PURE__ */ jsx("option", {
3039
+ value: "dropped",
3040
+ children: "dropped"
3041
+ })
3042
+ ]
3043
+ })
3044
+ ]
1131
3045
  })
1132
3046
  ]
1133
3047
  }),
1134
- runId === null ? /* @__PURE__ */ jsx("p", {
1135
- children: "Select a run to browse its items."
1136
- }) : null,
1137
- runId !== null && storeState === "loading" ? /* @__PURE__ */ jsx("p", {
1138
- children: "The items page has not loaded yet."
3048
+ storeState === "loading" ? /* @__PURE__ */ jsx("p", {
3049
+ children: "The selections page has not loaded yet."
1139
3050
  }) : null,
1140
3051
  storeState === "unavailable" ? /* @__PURE__ */ jsxs(Fragment, {
1141
3052
  children: [
1142
3053
  /* @__PURE__ */ jsx("p", {
1143
- children: "The store could not be read for this run; no items are shown."
3054
+ children: "The selections could not be read; no rows are shown."
1144
3055
  }),
1145
3056
  /* @__PURE__ */ jsx(StoreState, {
1146
- diagnostics: current?.diagnostics ?? []
3057
+ diagnostics: selections?.diagnostics ?? []
1147
3058
  })
1148
3059
  ]
1149
3060
  }) : null,
1150
- page !== null && page.items.length === 0 ? /* @__PURE__ */ jsx("p", {
1151
- children: "No items match these filters."
3061
+ page !== null && page.selections.length === 0 ? /* @__PURE__ */ jsx("p", {
3062
+ children: "No selections match these filters."
1152
3063
  }) : null,
1153
- page === null || page.items.length === 0 ? null : /* @__PURE__ */ jsx("div", {
3064
+ page === null || page.selections.length === 0 ? null : /* @__PURE__ */ jsx("div", {
1154
3065
  className: "kk-table-container",
1155
3066
  children: /* @__PURE__ */ jsxs("table", {
1156
- className: "kk-table kk-collect-items-table",
3067
+ className: "kk-table",
1157
3068
  children: [
1158
3069
  /* @__PURE__ */ jsx("thead", {
1159
3070
  children: /* @__PURE__ */ jsxs("tr", {
1160
3071
  children: [
1161
3072
  /* @__PURE__ */ jsx("th", {
1162
3073
  scope: "col",
1163
- children: "Title"
3074
+ children: "Rank"
1164
3075
  }),
1165
3076
  /* @__PURE__ */ jsx("th", {
1166
3077
  scope: "col",
1167
- children: "Source"
3078
+ children: "Topic"
1168
3079
  }),
1169
3080
  /* @__PURE__ */ jsx("th", {
1170
3081
  scope: "col",
1171
- children: "Seen"
3082
+ children: "Title & URL"
3083
+ }),
3084
+ /* @__PURE__ */ jsx("th", {
3085
+ scope: "col",
3086
+ children: "Source"
1172
3087
  }),
1173
3088
  /* @__PURE__ */ jsx("th", {
1174
3089
  scope: "col",
@@ -1176,19 +3091,82 @@ function ItemsPanel({
1176
3091
  }),
1177
3092
  /* @__PURE__ */ jsx("th", {
1178
3093
  scope: "col",
1179
- children: "Labels"
3094
+ children: "Status"
1180
3095
  }),
1181
3096
  /* @__PURE__ */ jsx("th", {
1182
3097
  scope: "col",
1183
- children: "Selection"
3098
+ children: "Reason"
3099
+ }),
3100
+ /* @__PURE__ */ jsx("th", {
3101
+ scope: "col",
3102
+ children: "Selected At"
3103
+ }),
3104
+ /* @__PURE__ */ jsx("th", {
3105
+ scope: "col",
3106
+ children: "Run ID"
1184
3107
  })
1185
3108
  ]
1186
3109
  })
1187
3110
  }),
1188
3111
  /* @__PURE__ */ jsx("tbody", {
1189
- children: page.items.map((item) => /* @__PURE__ */ jsx(ItemRow, {
1190
- item
1191
- }, item.id))
3112
+ children: page.selections.map((item) => /* @__PURE__ */ jsxs("tr", {
3113
+ children: [
3114
+ /* @__PURE__ */ jsx("td", {
3115
+ children: /* @__PURE__ */ jsxs("span", {
3116
+ className: "kk-badge kk-badge-neutral",
3117
+ children: [
3118
+ "#",
3119
+ item.rank ?? "—"
3120
+ ]
3121
+ })
3122
+ }),
3123
+ /* @__PURE__ */ jsx("td", {
3124
+ children: /* @__PURE__ */ jsx("span", {
3125
+ className: "kk-domain-pill",
3126
+ children: item.topic
3127
+ })
3128
+ }),
3129
+ /* @__PURE__ */ jsx("td", {
3130
+ children: item.url ? /* @__PURE__ */ jsx("a", {
3131
+ href: item.url,
3132
+ target: "_blank",
3133
+ rel: "noreferrer",
3134
+ children: item.title ?? item.itemId
3135
+ }) : item.title ?? item.itemId
3136
+ }),
3137
+ /* @__PURE__ */ jsx("td", {
3138
+ children: item.source ?? "—"
3139
+ }),
3140
+ /* @__PURE__ */ jsx("td", {
3141
+ children: item.storyId ?? "—"
3142
+ }),
3143
+ /* @__PURE__ */ jsx("td", {
3144
+ children: /* @__PURE__ */ jsx("span", {
3145
+ className: `kk-badge ${item.status === "published" ? "kk-badge-success" : item.status === "selected" ? "kk-badge-running" : "kk-badge-failed"}`,
3146
+ children: item.status
3147
+ })
3148
+ }),
3149
+ /* @__PURE__ */ jsx("td", {
3150
+ children: /* @__PURE__ */ jsx("span", {
3151
+ className: "kk-stat-sub",
3152
+ title: item.reason ?? undefined,
3153
+ children: item.reason ? item.reason.length > 60 ? `${item.reason.slice(0, 60)}…` : item.reason : "—"
3154
+ })
3155
+ }),
3156
+ /* @__PURE__ */ jsx("td", {
3157
+ children: formatEpoch(item.selectedAt)
3158
+ }),
3159
+ /* @__PURE__ */ jsx("td", {
3160
+ children: /* @__PURE__ */ jsx("button", {
3161
+ type: "button",
3162
+ className: "kk-collect-run-link",
3163
+ onClick: () => onInspectRun?.(item.runId),
3164
+ title: "Inspect this collection run",
3165
+ children: item.runId
3166
+ })
3167
+ })
3168
+ ]
3169
+ }, `${item.topic}-${item.itemId}`))
1192
3170
  })
1193
3171
  ]
1194
3172
  })
@@ -1200,7 +3178,7 @@ function ItemsPanel({
1200
3178
  type: "button",
1201
3179
  name: "previous",
1202
3180
  disabled: count.page <= 1,
1203
- onClick: onPageChange,
3181
+ onClick: () => onPageChange?.(-1),
1204
3182
  children: "Previous"
1205
3183
  }),
1206
3184
  /* @__PURE__ */ jsxs("span", {
@@ -1211,14 +3189,14 @@ function ItemsPanel({
1211
3189
  count.pages,
1212
3190
  " · ",
1213
3191
  count.total,
1214
- " items"
3192
+ " selections"
1215
3193
  ]
1216
3194
  }),
1217
3195
  /* @__PURE__ */ jsx("button", {
1218
3196
  type: "button",
1219
3197
  name: "next",
1220
3198
  disabled: count.page >= count.pages,
1221
- onClick: onPageChange,
3199
+ onClick: () => onPageChange?.(1),
1222
3200
  children: "Next"
1223
3201
  })
1224
3202
  ]
@@ -1226,43 +3204,6 @@ function ItemsPanel({
1226
3204
  ]
1227
3205
  });
1228
3206
  }
1229
- function ItemRow({ item }) {
1230
- const labels = Object.entries(item.labels);
1231
- return /* @__PURE__ */ jsxs("tr", {
1232
- children: [
1233
- /* @__PURE__ */ jsx("td", {
1234
- children: item.url === null ? item.title : /* @__PURE__ */ jsx("a", {
1235
- href: item.url,
1236
- target: "_blank",
1237
- rel: "noreferrer",
1238
- children: item.title
1239
- })
1240
- }),
1241
- /* @__PURE__ */ jsx("td", {
1242
- children: item.source
1243
- }),
1244
- /* @__PURE__ */ jsx("td", {
1245
- children: item.seen
1246
- }),
1247
- /* @__PURE__ */ jsx("td", {
1248
- children: item.storyId ?? "—"
1249
- }),
1250
- /* @__PURE__ */ jsx("td", {
1251
- children: labels.length === 0 ? "—" : labels.map(([decision, value]) => /* @__PURE__ */ jsxs("span", {
1252
- className: "kk-chip",
1253
- children: [
1254
- decision,
1255
- ": ",
1256
- value
1257
- ]
1258
- }, decision))
1259
- }),
1260
- /* @__PURE__ */ jsx("td", {
1261
- children: item.selection
1262
- })
1263
- ]
1264
- });
1265
- }
1266
3207
  function ProgressPanel({ steps }) {
1267
3208
  if (steps.length === 0)
1268
3209
  return /* @__PURE__ */ jsx("p", {
@@ -1347,11 +3288,10 @@ function ConfigPanel({ config }) {
1347
3288
  // ../board/src/collect/index.tsx
1348
3289
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1349
3290
  var TAB_LABELS = {
1350
- overview: "Overview",
1351
- summary: "Run detail",
3291
+ summary: "Summary",
3292
+ runs: "Run Details",
1352
3293
  items: "Items",
1353
- progress: "Execution progress",
1354
- config: "Workflow Config Summary"
3294
+ selections: "Selections"
1355
3295
  };
1356
3296
  function messageOf(error) {
1357
3297
  return error instanceof Error ? error.message : String(error);
@@ -1378,34 +3318,48 @@ function usePolling(loader, intervalMs, refresh) {
1378
3318
  };
1379
3319
  }, [intervalMs]);
1380
3320
  }
3321
+ function isOfflineError(msg) {
3322
+ return msg.includes("Failed to fetch") || msg.includes("NetworkError") || msg.includes("ECONNREFUSED");
3323
+ }
1381
3324
  function CollectModule() {
1382
3325
  const [link] = useState(() => parseDeepLink(window.location.search, COLLECT_TABS, COLLECT_TAB_ALIASES));
1383
- const [reader] = useState(() => readerOriginFrom(window.location.search));
3326
+ const [reader] = useState(() => readerOriginFrom(window.location.search, window.location.origin));
1384
3327
  const readerOrigin = reader.origin;
1385
3328
  const client = useMemo(() => createBoardClient({ readerOrigin, boardOrigin: window.location.origin }), [readerOrigin]);
1386
3329
  const [rows, setRows] = useState([]);
1387
- const [cursor, setCursor] = useState(null);
3330
+ const [_cursor, setCursor] = useState(null);
1388
3331
  const [selection, setSelection] = useState({
1389
3332
  runId: link.runId ?? null,
1390
- tab: link.tab ?? "overview"
3333
+ tab: link.tab ?? "summary"
1391
3334
  });
1392
3335
  const [diagnostics, setDiagnostics] = useState(() => [
1393
3336
  ...link.diagnostics,
1394
3337
  ...reader.diagnostic === null ? [] : [reader.diagnostic]
1395
3338
  ]);
3339
+ const [isReaderOffline, setIsReaderOffline] = useState(false);
1396
3340
  const [historyRefresh, setHistoryRefresh] = useState(() => initialRefresh(HISTORY_POLL_MS));
1397
3341
  const [readerRefresh, setReaderRefresh] = useState(() => initialRefresh(ACTIVE_POLL_MS));
1398
3342
  const [results, setResults] = useState(null);
1399
3343
  const [config, setConfig] = useState(null);
1400
- const [progress, setProgress] = useState(null);
1401
3344
  const [overview, setOverview] = useState(null);
1402
3345
  const [storeRun, setStoreRun] = useState(null);
1403
3346
  const [items, setItems] = useState(null);
1404
3347
  const [itemFilters, setItemFilters] = useState({ page: 1 });
3348
+ const [selectedInspectItem, setSelectedInspectItem] = useState(null);
3349
+ const [selections, setSelections] = useState(null);
3350
+ const [selectionsFilters, setSelectionsFilters] = useState({ page: 1 });
3351
+ const [selectedModalRunId, setSelectedModalRunId] = useState(null);
3352
+ const [modalStoreRun, setModalStoreRun] = useState(null);
3353
+ const [modalResults, setModalResults] = useState(null);
3354
+ const [modalProgress, setModalProgress] = useState(null);
3355
+ const [modalConfig, setModalConfig] = useState(null);
3356
+ const [timeRange, setTimeRange] = useState("24h");
1405
3357
  const [showEffective, setShowEffective] = useState(false);
1406
- const [filterQuery, setFilterQuery] = useState("");
3358
+ const [runSearchQuery, setRunSearchQuery] = useState("");
3359
+ const [runStatusFilter, setRunStatusFilter] = useState("all");
1407
3360
  const historyBusy = useRef(false);
1408
3361
  const readerBusy = useRef(false);
3362
+ const modalTarget = useRef(null);
1409
3363
  const rowsRef = useRef(rows);
1410
3364
  rowsRef.current = rows;
1411
3365
  const selectionRef = useRef(selection);
@@ -1414,12 +3368,16 @@ function CollectModule() {
1414
3368
  if (runIds.length === 0)
1415
3369
  return;
1416
3370
  try {
1417
- setOverview(await client.storeOverview(runIds));
3371
+ const res = await client.storeOverview(runIds);
3372
+ setOverview(res);
3373
+ setIsReaderOffline(false);
1418
3374
  } catch (error) {
1419
- setDiagnostics((existing) => [
1420
- ...existing,
1421
- { code: "board.store.unavailable", message: messageOf(error) }
1422
- ]);
3375
+ const msg = messageOf(error);
3376
+ if (isOfflineError(msg)) {
3377
+ setIsReaderOffline(true);
3378
+ } else {
3379
+ setDiagnostics((existing) => [...existing, { code: "board.store.unavailable", message: msg }]);
3380
+ }
1423
3381
  }
1424
3382
  }, [client]);
1425
3383
  const loadHistory = useCallback(async () => {
@@ -1463,13 +3421,16 @@ function CollectModule() {
1463
3421
  setResults(nextResults);
1464
3422
  setConfig(nextConfig);
1465
3423
  setReaderRefresh(() => succeeded(Date.now(), ACTIVE_POLL_MS));
3424
+ setIsReaderOffline(false);
1466
3425
  } catch (error) {
1467
3426
  if (selectionRef.current.runId !== runId)
1468
3427
  return;
1469
- setDiagnostics((existing) => [
1470
- ...existing,
1471
- { code: "board.reader.unavailable", message: messageOf(error) }
1472
- ]);
3428
+ const msg = messageOf(error);
3429
+ if (isOfflineError(msg)) {
3430
+ setIsReaderOffline(true);
3431
+ } else {
3432
+ setDiagnostics((existing) => [...existing, { code: "board.reader.unavailable", message: msg }]);
3433
+ }
1473
3434
  setReaderRefresh((state) => failed(state, Date.now()));
1474
3435
  } finally {
1475
3436
  readerBusy.current = false;
@@ -1479,20 +3440,17 @@ function CollectModule() {
1479
3440
  if (selectionRef.current.runId !== runId)
1480
3441
  return;
1481
3442
  setStoreRun(nextStoreRun);
3443
+ setIsReaderOffline(false);
1482
3444
  } catch (error) {
1483
3445
  if (selectionRef.current.runId !== runId)
1484
3446
  return;
1485
- setDiagnostics((existing) => [
1486
- ...existing,
1487
- { code: "board.store.run-unavailable", message: messageOf(error) }
1488
- ]);
3447
+ const msg = messageOf(error);
3448
+ if (isOfflineError(msg)) {
3449
+ setIsReaderOffline(true);
3450
+ } else {
3451
+ setDiagnostics((existing) => [...existing, { code: "board.store.run-unavailable", message: msg }]);
3452
+ }
1489
3453
  }
1490
- try {
1491
- const nextProgress = await client.progress(runId);
1492
- if (selectionRef.current.runId !== runId)
1493
- return;
1494
- setProgress(nextProgress);
1495
- } catch {}
1496
3454
  }, [client]);
1497
3455
  const itemsRequests = useRef(requestSequence());
1498
3456
  const loadItems = useCallback(async (runId) => {
@@ -1502,26 +3460,114 @@ function CollectModule() {
1502
3460
  if (!itemsRequests.current.isCurrent(token))
1503
3461
  return;
1504
3462
  setItems(page);
3463
+ setIsReaderOffline(false);
1505
3464
  } catch (error) {
1506
3465
  if (!itemsRequests.current.isCurrent(token))
1507
3466
  return;
1508
3467
  setItems(null);
1509
- setDiagnostics((existing) => [
1510
- ...existing,
1511
- { code: "board.store.items-unavailable", message: messageOf(error) }
1512
- ]);
3468
+ const msg = messageOf(error);
3469
+ if (isOfflineError(msg)) {
3470
+ setIsReaderOffline(true);
3471
+ } else {
3472
+ setDiagnostics((existing) => [
3473
+ ...existing,
3474
+ { code: "board.store.items-unavailable", message: msg }
3475
+ ]);
3476
+ }
1513
3477
  }
1514
3478
  }, [client, itemFilters]);
3479
+ const selectionsRequests = useRef(requestSequence());
3480
+ const loadSelections = useCallback(async () => {
3481
+ const token = selectionsRequests.current.begin();
3482
+ try {
3483
+ const page = await client.storeSelections(selectionsFilters);
3484
+ if (!selectionsRequests.current.isCurrent(token))
3485
+ return;
3486
+ setSelections(page);
3487
+ setIsReaderOffline(false);
3488
+ } catch (error) {
3489
+ if (!selectionsRequests.current.isCurrent(token))
3490
+ return;
3491
+ setSelections(null);
3492
+ const msg = messageOf(error);
3493
+ if (isOfflineError(msg)) {
3494
+ setIsReaderOffline(true);
3495
+ } else {
3496
+ setDiagnostics((existing) => [
3497
+ ...existing,
3498
+ { code: "board.store.selections-unavailable", message: msg }
3499
+ ]);
3500
+ }
3501
+ }
3502
+ }, [client, selectionsFilters]);
3503
+ useEffect(() => {
3504
+ if (!selectedModalRunId) {
3505
+ modalTarget.current = null;
3506
+ setModalStoreRun(null);
3507
+ setModalResults(null);
3508
+ setModalProgress(null);
3509
+ setModalConfig(null);
3510
+ return;
3511
+ }
3512
+ if (modalTarget.current !== selectedModalRunId) {
3513
+ modalTarget.current = selectedModalRunId;
3514
+ setModalStoreRun(null);
3515
+ setModalResults(null);
3516
+ setModalProgress(null);
3517
+ setModalConfig(null);
3518
+ }
3519
+ if (selectedModalRunId === selection.runId) {
3520
+ if (storeRun?.runId === selectedModalRunId)
3521
+ setModalStoreRun(storeRun);
3522
+ if (results?.runId === selectedModalRunId)
3523
+ setModalResults(results);
3524
+ if (config?.runId === selectedModalRunId)
3525
+ setModalConfig(config);
3526
+ if (modalProgress !== null && storeRun?.runId === selectedModalRunId && results?.runId === selectedModalRunId && config?.runId === selectedModalRunId) {
3527
+ return;
3528
+ }
3529
+ }
3530
+ let cancelled = false;
3531
+ async function fetchModalData(runId) {
3532
+ try {
3533
+ const [rResults, rStore, rProg, rConfig] = await Promise.allSettled([
3534
+ client.results("kk-collect", runId),
3535
+ client.storeRun(runId),
3536
+ client.progress(runId),
3537
+ client.config("kk-collect", runId)
3538
+ ]);
3539
+ if (cancelled)
3540
+ return;
3541
+ if (rResults.status === "fulfilled")
3542
+ setModalResults(rResults.value);
3543
+ if (rStore.status === "fulfilled")
3544
+ setModalStoreRun(rStore.value);
3545
+ if (rProg.status === "fulfilled")
3546
+ setModalProgress(rProg.value);
3547
+ if (rConfig.status === "fulfilled")
3548
+ setModalConfig(rConfig.value);
3549
+ } catch {}
3550
+ }
3551
+ fetchModalData(selectedModalRunId);
3552
+ return () => {
3553
+ cancelled = true;
3554
+ };
3555
+ }, [selectedModalRunId, selection.runId, storeRun, results, config, modalProgress, client]);
1515
3556
  usePolling(loadHistory, HISTORY_POLL_MS, historyRefresh);
1516
3557
  usePolling(loadReader, ACTIVE_POLL_MS, readerRefresh);
1517
3558
  useEffect(() => {
1518
- if (selection.tab !== "items" || selection.runId === null)
3559
+ if (selection.tab !== "items")
1519
3560
  return;
1520
- loadItems(selection.runId);
3561
+ loadItems(selection.runId ?? "");
1521
3562
  }, [selection.tab, selection.runId, loadItems]);
3563
+ useEffect(() => {
3564
+ if (selection.tab !== "selections")
3565
+ return;
3566
+ loadSelections();
3567
+ }, [selection.tab, loadSelections]);
1522
3568
  useEffect(() => {
1523
3569
  if (selection.runId === null && rows.length > 0) {
1524
- setSelection((state) => state.runId === null ? resolveSelection(rows, link, "overview") : state);
3570
+ setSelection((state) => state.runId === null ? resolveSelection(rows, link, "summary") : state);
1525
3571
  }
1526
3572
  }, [rows, selection.runId, link]);
1527
3573
  useEffect(() => {
@@ -1530,17 +3576,7 @@ function CollectModule() {
1530
3576
  const tab = selection.tab;
1531
3577
  const now = Date.now();
1532
3578
  const currentStoreRun = storeRun?.runId === selection.runId ? storeRun : null;
1533
- const facts = results === null ? [] : collectSummaryFacts(results, now);
1534
- const artifacts = results === null ? [] : artifactViews(results, readerOrigin);
1535
- const steps = progressRows(progress);
1536
- const selectedRow = rows.find((row) => row.id === selection.runId);
1537
- const filteredRows = useMemo(() => {
1538
- const q = filterQuery.trim().toLowerCase();
1539
- if (q === "")
1540
- return rows;
1541
- return rows.filter((r) => r.id.toLowerCase().includes(q) || r.status.toLowerCase().includes(q));
1542
- }, [rows, filterQuery]);
1543
- const runHref = useCallback((runId) => selectionPath({ runId, tab: "summary" }, window.location.search), []);
3579
+ const runHref = useCallback((runId) => selectionPath({ runId, tab: "runs" }, window.location.search), []);
1544
3580
  const toggleQuality = useCallback(() => setShowEffective((state) => !state), []);
1545
3581
  const filterChanged = useCallback((event) => {
1546
3582
  const name = event.currentTarget.name;
@@ -1556,94 +3592,54 @@ function CollectModule() {
1556
3592
  const delta = event.currentTarget.name === "previous" ? -1 : 1;
1557
3593
  setItemFilters((state) => ({ ...state, page: Math.max(1, (state.page ?? 1) + delta) }));
1558
3594
  }, []);
3595
+ const handleInspectRun = useCallback((runId) => {
3596
+ setSelectedModalRunId(runId);
3597
+ setSelection((s) => ({ ...s, runId }));
3598
+ }, []);
3599
+ const handleCloseModal = useCallback(() => {
3600
+ setSelectedModalRunId(null);
3601
+ setSelection((s) => ({ ...s, runId: null }));
3602
+ }, []);
3603
+ const modalFacts = modalResults ? collectSummaryFacts(modalResults, now) : [];
3604
+ const modalArtifacts = modalResults ? artifactViews(modalResults, readerOrigin) : [];
3605
+ const modalSteps = progressRows(modalProgress);
3606
+ const displayDiagnostics = dedupeDiagnostics([...diagnostics, ...results?.diagnostics ?? []]).filter((d) => {
3607
+ if (!isReaderOffline)
3608
+ return true;
3609
+ return d.code !== "board.reader.unavailable" && d.code !== "board.store.run-unavailable" && d.code !== "board.store.unavailable" && d.code !== "board.store.items-unavailable" && d.code !== "board.store.selections-unavailable";
3610
+ });
1559
3611
  return /* @__PURE__ */ jsxs2("div", {
1560
3612
  className: "kk-board kk-board-collect",
1561
3613
  children: [
1562
3614
  /* @__PURE__ */ jsxs2("header", {
1563
3615
  className: "kk-board-header",
1564
3616
  children: [
1565
- /* @__PURE__ */ jsxs2("div", {
3617
+ /* @__PURE__ */ jsx2("div", {
1566
3618
  className: "kk-board-header-left",
1567
- children: [
1568
- /* @__PURE__ */ jsxs2("div", {
1569
- className: "kk-board-header-title-row",
1570
- children: [
1571
- /* @__PURE__ */ jsx2("h1", {
1572
- children: "Collect"
1573
- }),
1574
- selectedRow ? /* @__PURE__ */ jsx2("span", {
1575
- className: `kk-badge ${selectedRow.status === "done" ? "kk-badge-success" : selectedRow.status === "failed" ? "kk-badge-failed" : "kk-badge-running"}`,
1576
- children: selectedRow.status
1577
- }) : null
1578
- ]
1579
- }),
1580
- /* @__PURE__ */ jsxs2("p", {
1581
- children: [
1582
- "Run ",
1583
- /* @__PURE__ */ jsx2("code", {
1584
- children: selection.runId ?? "none selected"
1585
- }),
1586
- " · ",
1587
- freshnessLabel(readerRefresh),
1588
- results === null ? "" : ` · Results ${results.availability}`
1589
- ]
3619
+ children: /* @__PURE__ */ jsx2("div", {
3620
+ className: "kk-board-header-title-row",
3621
+ children: /* @__PURE__ */ jsx2("h1", {
3622
+ children: "Collect"
1590
3623
  })
1591
- ]
3624
+ })
1592
3625
  }),
1593
3626
  /* @__PURE__ */ jsxs2("div", {
1594
3627
  className: "kk-board-header-right",
1595
3628
  children: [
1596
- /* @__PURE__ */ jsxs2("div", {
1597
- className: "kk-board-run-filter-group",
1598
- children: [
1599
- /* @__PURE__ */ jsx2("input", {
1600
- type: "text",
1601
- className: "kk-board-run-filter-input",
1602
- placeholder: "Filter runs...",
1603
- value: filterQuery,
1604
- onChange: (e) => setFilterQuery(e.target.value),
1605
- "aria-label": "Filter runs"
1606
- }),
1607
- /* @__PURE__ */ jsxs2("label", {
1608
- className: "kk-board-run-selector",
1609
- children: [
1610
- "Run",
1611
- /* @__PURE__ */ jsxs2("select", {
1612
- value: selection.runId ?? "",
1613
- onChange: (event) => {
1614
- setItemFilters((state) => ({ ...state, page: 1 }));
1615
- setResults(null);
1616
- setConfig(null);
1617
- setProgress(null);
1618
- setStoreRun(null);
1619
- setSelection((state) => ({ ...state, runId: event.target.value || null }));
1620
- },
1621
- children: [
1622
- /* @__PURE__ */ jsx2("option", {
1623
- value: "",
1624
- children: rows.length === 0 ? "No runs recorded" : filteredRows.length === 0 ? "No matching runs" : "Select a run"
1625
- }),
1626
- filteredRows.map((row) => /* @__PURE__ */ jsxs2("option", {
1627
- value: row.id,
1628
- children: [
1629
- row.id,
1630
- " — ",
1631
- row.status
1632
- ]
1633
- }, row.id))
1634
- ]
1635
- })
1636
- ]
1637
- })
1638
- ]
1639
- }),
1640
3629
  /* @__PURE__ */ jsx2("button", {
1641
3630
  type: "button",
1642
- onClick: () => void loadReader(),
1643
- title: "Refresh run data",
3631
+ onClick: () => {
3632
+ loadReader();
3633
+ loadHistory();
3634
+ if (selection.tab === "items")
3635
+ loadItems(selection.runId ?? "");
3636
+ if (selection.tab === "selections")
3637
+ loadSelections();
3638
+ },
3639
+ title: "Refresh data",
1644
3640
  children: "↻ Refresh"
1645
3641
  }),
1646
- selection.runId === null ? null : /* @__PURE__ */ jsx2("a", {
3642
+ /* @__PURE__ */ jsx2("a", {
1647
3643
  href: selectionPath(selection, window.location.search),
1648
3644
  className: "kk-btn-action",
1649
3645
  title: "Permanent link",
@@ -1653,6 +3649,45 @@ function CollectModule() {
1653
3649
  })
1654
3650
  ]
1655
3651
  }),
3652
+ isReaderOffline && readerOrigin !== window.location.origin ? /* @__PURE__ */ jsxs2("div", {
3653
+ className: "kk-board-reader-offline-banner",
3654
+ role: "alert",
3655
+ children: [
3656
+ /* @__PURE__ */ jsx2("div", {
3657
+ className: "kk-board-reader-offline-icon",
3658
+ children: "⚠️"
3659
+ }),
3660
+ /* @__PURE__ */ jsxs2("div", {
3661
+ className: "kk-board-reader-offline-content",
3662
+ children: [
3663
+ /* @__PURE__ */ jsxs2("strong", {
3664
+ children: [
3665
+ "Loopback Board Reader Offline (",
3666
+ readerOrigin,
3667
+ ")"
3668
+ ]
3669
+ }),
3670
+ /* @__PURE__ */ jsx2("p", {
3671
+ children: "Store facts, item inspection, and artifact evidence require the local reader process. Spur run execution history remains available."
3672
+ })
3673
+ ]
3674
+ }),
3675
+ /* @__PURE__ */ jsx2("button", {
3676
+ type: "button",
3677
+ className: "kk-btn-action",
3678
+ onClick: () => {
3679
+ setIsReaderOffline(false);
3680
+ loadReader();
3681
+ loadHistory();
3682
+ if (selection.tab === "items")
3683
+ loadItems(selection.runId ?? "");
3684
+ if (selection.tab === "selections")
3685
+ loadSelections();
3686
+ },
3687
+ children: "↻ Retry Connection"
3688
+ })
3689
+ ]
3690
+ }) : null,
1656
3691
  /* @__PURE__ */ jsx2("nav", {
1657
3692
  className: "kk-board-tabs",
1658
3693
  "aria-label": "Collect tabs",
@@ -1675,54 +3710,71 @@ function CollectModule() {
1675
3710
  "."
1676
3711
  ]
1677
3712
  }) : null,
1678
- tab === "overview" ? /* @__PURE__ */ jsx2(OverviewPanel, {
3713
+ tab === "summary" ? /* @__PURE__ */ jsx2(SummaryPanel, {
1679
3714
  runs: rows,
1680
3715
  overview,
1681
3716
  now,
1682
- runHref
3717
+ timeRange,
3718
+ onTimeRangeChange: setTimeRange,
3719
+ onInspectRun: handleInspectRun
1683
3720
  }) : null,
1684
- tab === "summary" ? /* @__PURE__ */ jsx2(RunDetailPanel, {
1685
- storeRun: currentStoreRun,
1686
- results,
1687
- facts,
1688
- artifacts,
1689
- showEffective,
1690
- onToggleQuality: toggleQuality
3721
+ tab === "runs" ? /* @__PURE__ */ jsx2(RunDetailsPanel, {
3722
+ runs: rows,
3723
+ overview,
3724
+ now,
3725
+ runHref,
3726
+ searchQuery: runSearchQuery,
3727
+ statusFilter: runStatusFilter,
3728
+ onSearchChange: setRunSearchQuery,
3729
+ onStatusFilterChange: setRunStatusFilter,
3730
+ onInspectRun: handleInspectRun
1691
3731
  }) : null,
1692
3732
  tab === "items" ? /* @__PURE__ */ jsx2(ItemsPanel, {
1693
3733
  runId: selection.runId,
1694
3734
  items,
1695
3735
  filters: itemFilters,
1696
3736
  quality: currentStoreRun?.labelQuality ?? [],
3737
+ runs: rows,
3738
+ onRunChange: (rId) => {
3739
+ setItemFilters((s) => ({ ...s, page: 1 }));
3740
+ setSelection((s) => ({ ...s, runId: rId ?? null }));
3741
+ },
1697
3742
  onFilterChange: filterChanged,
1698
- onPageChange: pageChanged
3743
+ onSearchChange: (e) => setItemFilters((s) => ({ ...s, search: e.target.value, page: 1 })),
3744
+ onPageChange: pageChanged,
3745
+ onInspectItem: (item) => setSelectedInspectItem(item)
1699
3746
  }) : null,
1700
- tab === "progress" ? /* @__PURE__ */ jsx2(ProgressPanel, {
1701
- steps
3747
+ tab === "selections" ? /* @__PURE__ */ jsx2(SelectionsPanel, {
3748
+ selections,
3749
+ topics: selections?.topics ?? [],
3750
+ filters: selectionsFilters,
3751
+ onTopicChange: (topic) => setSelectionsFilters((s) => ({ ...s, topic, page: 1 })),
3752
+ onStatusChange: (status) => setSelectionsFilters((s) => ({ ...s, status, page: 1 })),
3753
+ onSearchChange: (search) => setSelectionsFilters((s) => ({ ...s, search, page: 1 })),
3754
+ onPageChange: (delta) => setSelectionsFilters((s) => ({ ...s, page: Math.max(1, (s.page ?? 1) + delta) })),
3755
+ onInspectRun: handleInspectRun
1702
3756
  }) : null,
1703
- tab === "config" ? /* @__PURE__ */ jsx2(ConfigPanel, {
1704
- config
1705
- }) : null,
1706
- dedupeDiagnostics([...diagnostics, ...results?.diagnostics ?? []]).map((diagnostic) => /* @__PURE__ */ jsx2("p", {
3757
+ displayDiagnostics.map((diagnostic) => /* @__PURE__ */ jsx2("p", {
1707
3758
  role: "status",
1708
3759
  className: "kk-board-diagnostic",
1709
3760
  children: diagnosticLine(diagnostic)
1710
3761
  }, diagnostic.code)),
1711
- cursor === null ? null : /* @__PURE__ */ jsx2("button", {
1712
- type: "button",
1713
- onClick: () => void loadHistory(),
1714
- children: "Load more runs"
1715
- }),
1716
- /* @__PURE__ */ jsxs2("p", {
1717
- className: "kk-board-footnote",
1718
- children: [
1719
- "Run history every ",
1720
- HISTORY_POLL_MS / 1000,
1721
- "s; selected run every ",
1722
- ACTIVE_POLL_MS / 1000,
1723
- "s."
1724
- ]
1725
- })
3762
+ selectedModalRunId ? /* @__PURE__ */ jsx2(RunDetailModal, {
3763
+ runId: selectedModalRunId,
3764
+ storeRun: modalStoreRun,
3765
+ results: modalResults,
3766
+ facts: modalFacts,
3767
+ artifacts: modalArtifacts,
3768
+ steps: modalSteps,
3769
+ config: modalConfig,
3770
+ showEffective,
3771
+ onToggleQuality: toggleQuality,
3772
+ onClose: handleCloseModal
3773
+ }) : null,
3774
+ selectedInspectItem ? /* @__PURE__ */ jsx2(ItemDetailModal, {
3775
+ item: selectedInspectItem,
3776
+ onClose: () => setSelectedInspectItem(null)
3777
+ }) : null
1726
3778
  ]
1727
3779
  });
1728
3780
  }