iterate-plugin 2.7.2 → 2.7.3

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.
package/lib/client.js CHANGED
@@ -1,92 +1,709 @@
1
- /**
2
- * lib/client.js iterate-plugin client entry.
3
- *
4
- * Loaded by the dsh web front-end via `package.json.dsh.client` (the
5
- * `exports["./client"]` subpath). Module contract matches dsh-gui-customization:
6
- * the file exports an `apply(ctx)` function that is called with the client
7
- * Cordis context.
8
- *
9
- * Implements the §14.4 / §16 six-part UI layer:
10
- * 1. Convergence dashboard -> conversation.input.dock
11
- * 2. Findings triage panel -> conversation.chat.turnTail
12
- * 3. Iteration stats card -> conversation.chat.turnTail (same chain, empty-findings case)
13
- * 4. iterate theme skin -> theme.overrideTokens
14
- * 5. Progress event linkage -> ctx.on('slots/changed') + round-pulse + shell.overlay capsule
15
- * 6. Settings page section -> settings.section
16
- *
17
- * Design notes:
18
- * - Build-free: no bundler. All styles are injected via one <style> tag and
19
- * colors use the dsh token variables (`--dsw-*`).
20
- * - Defensive by design: every service lookup is optional and guarded, so a
21
- * missing slot/theme/React degrades gracefully instead of crashing the UI.
22
- */
23
-
24
- import {
25
- findReportInObject,
26
- scanSessionForReport,
27
- scanSessionForRunSummary,
28
- extractVerdict,
29
- normalizeReport,
30
- computeConvergenceProgress,
31
- getCurrentRound,
32
- getTotalRounds,
33
- severityStats,
34
- groupByDimension,
35
- buildTriageState,
36
- hashReport,
37
- toKnownIntentionalYaml,
38
- buildApplyInstruction,
39
- collectIgnoredEntries,
40
- filterFindingsWithIndices,
41
- buildFilterOptions,
42
- countVerdicts,
43
- batchSetVerdict,
44
- setAllVerdicts,
45
- buildRoundHistory,
46
- buildFindingTrend,
47
- computeTrendMetrics,
48
- trendMax,
49
- buildCompletionSummary,
50
- buildConfigEditGuide,
51
- keyToVerdict,
52
- allVerdictKeys,
53
- buildRuntimeStatusGuide,
54
- SEVERITY_LABEL,
55
- SEVERITY_COLOR,
56
- } from './parse.js'
57
-
58
- // ─── Constants ───────────────────────────────────────────────────────────────
59
-
60
- const PLUGIN_TAG = 'iterate-ui'
61
-
62
- /** localStorage key prefix for triage verdicts. */
63
- const TRIAGE_STORAGE_PREFIX = 'iterate.triage.'
64
-
65
- /** localStorage key for the theme skin toggle. */
66
- const THEME_STORAGE_KEY = 'iterate.theme.enabled'
67
-
68
- /** Theme override source name (matches the doc §16.1). */
69
- const THEME_SOURCE = 'iterate'
70
-
71
- /** Theme tokens: 13 dsw tokens, each with light + dark values (warm amber accent). */
72
- const ITERATE_TOKENS = {
73
- '--dsw-alias-bg-base': { light: '#FAF8F5', dark: '#171412' },
74
- '--dsw-alias-bg-layer-1': { light: '#FFFFFF', dark: '#1F1B17' },
75
- '--dsw-alias-bg-layer-2': { light: '#F4F1EA', dark: '#27221C' },
76
- '--dsw-alias-bg-overlay': { light: 'rgba(255,255,255,0.96)', dark: 'rgba(23,20,18,0.96)' },
77
- '--dsw-alias-border-l1': { light: '#E8E2D8', dark: '#332C25' },
78
- '--dsw-alias-border-l2': { light: '#DDD5C9', dark: '#3C342B' },
79
- '--dsw-alias-brand-primary': { light: '#B45309', dark: '#F59E0B' },
80
- '--dsw-alias-label-primary': { light: '#1C1917', dark: '#F5F0EA' },
81
- '--dsw-alias-label-secondary': { light: '#57534E', dark: '#A9A29B' },
82
- '--dsw-alias-state-error-primary': { light: '#DC2626', dark: '#F87171' },
83
- '--dsw-alias-state-success-primary': { light: '#15803D', dark: '#4ADE80' },
84
- '--dsw-alias-state-warn-primary': { light: '#D97706', dark: '#FBBF24' },
85
- '--dsw-specific-sidebar-fill': { light: '#F5F2EC', dark: '#14110E' },
1
+ window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
2
+ var module = { exports: {} };
3
+ var exports = module.exports;
4
+ "use strict";
5
+ var __create = Object.create;
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __export = (target, all) => {
12
+ for (var name2 in all)
13
+ __defProp(target, name2, { get: all[name2], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/client/index.ts
34
+ var index_exports = {};
35
+ __export(index_exports, {
36
+ apply: () => apply,
37
+ inject: () => inject,
38
+ name: () => name
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+ var React = __toESM(require("react"), 1);
42
+
43
+ // lib/parse.js
44
+ var SEVERITY_ORDER = ["critical", "high", "medium", "low"];
45
+ var SEVERITY_LABEL = {
46
+ critical: "CRIT",
47
+ high: "HIGH",
48
+ medium: "MED",
49
+ low: "LOW"
50
+ };
51
+ var SEVERITY_COLOR = {
52
+ critical: "#ef4444",
53
+ high: "#f97316",
54
+ medium: "#eab308",
55
+ low: "#6b7280"
56
+ };
57
+ function isReviewReport(obj) {
58
+ if (!obj || typeof obj !== "object") return false;
59
+ const o = (
60
+ /** @type {Record<string, unknown>} */
61
+ obj
62
+ );
63
+ return typeof o.convergence === "object" && o.convergence !== null && Array.isArray(o.findings) && Array.isArray(o.rounds);
64
+ }
65
+ function findReportInObject(obj, seen, maxDepth = 20) {
66
+ if (maxDepth <= 0) return null;
67
+ if (!obj || typeof obj !== "object") return null;
68
+ const s = seen || /* @__PURE__ */ new Set();
69
+ if (s.has(obj)) return null;
70
+ s.add(obj);
71
+ if (isReviewReport(obj)) return (
72
+ /** @type {Record<string, unknown>} */
73
+ obj
74
+ );
75
+ if (Array.isArray(obj)) {
76
+ for (const item of obj) {
77
+ const found = findReportInObject(item, s, maxDepth - 1);
78
+ if (found) return found;
79
+ }
80
+ return null;
81
+ }
82
+ const o = (
83
+ /** @type {Record<string, unknown>} */
84
+ obj
85
+ );
86
+ for (const key of Object.keys(o)) {
87
+ const val = o[key];
88
+ if (val && typeof val === "object") {
89
+ const found = findReportInObject(val, s, maxDepth - 1);
90
+ if (found) return found;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ function scanSessionForReport(session) {
96
+ if (!session || typeof session !== "object") return null;
97
+ const direct = findReportInObject(session);
98
+ if (direct) return direct;
99
+ const s = (
100
+ /** @type {Record<string, unknown>} */
101
+ session
102
+ );
103
+ if (Array.isArray(s.toolCalls)) {
104
+ const calls = (
105
+ /** @type {Array<Record<string, unknown>>} */
106
+ s.toolCalls
107
+ );
108
+ for (let i = calls.length - 1; i >= 0; i--) {
109
+ const call = calls[i];
110
+ if (!call) continue;
111
+ if (call.tool === "iterate_review" || String(call.tool ?? "").endsWith("iterate_review")) {
112
+ const result = call.result;
113
+ if (result && typeof result === "object") {
114
+ const r = (
115
+ /** @type {Record<string, unknown>} */
116
+ result
117
+ );
118
+ if (r.report && typeof r.report === "object") {
119
+ return (
120
+ /** @type {Record<string, unknown>} */
121
+ r.report
122
+ );
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ if (Array.isArray(s.messages)) {
129
+ const msgs = (
130
+ /** @type {Array<Record<string, unknown>>} */
131
+ s.messages
132
+ );
133
+ for (let i = msgs.length - 1; i >= 0; i--) {
134
+ const msg = msgs[i];
135
+ if (!msg || !Array.isArray(msg.tool_calls)) continue;
136
+ const calls = (
137
+ /** @type {Array<Record<string, unknown>>} */
138
+ msg.tool_calls
139
+ );
140
+ for (const call of calls) {
141
+ if (!call) continue;
142
+ const fn = call.function;
143
+ if (fn && typeof fn === "object") {
144
+ const f = (
145
+ /** @type {Record<string, unknown>} */
146
+ fn
147
+ );
148
+ if (String(f.name ?? "").endsWith("iterate_review")) {
149
+ try {
150
+ const args = JSON.parse(String(f.arguments ?? "{}"));
151
+ const found = findReportInObject(args);
152
+ if (found) return found;
153
+ } catch {
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ return null;
161
+ }
162
+ function isRunSummary(obj) {
163
+ if (!obj || typeof obj !== "object") return false;
164
+ const o = (
165
+ /** @type {Record<string, unknown>} */
166
+ obj
167
+ );
168
+ const final = o.finalReport;
169
+ return !!final && typeof final === "object" && (final.verdict === "approved" || final.verdict === "needs_revision");
170
+ }
171
+ function findRunSummaryInObject(obj, seen, maxDepth = 20) {
172
+ if (maxDepth <= 0) return null;
173
+ if (!obj || typeof obj !== "object") return null;
174
+ const s = seen || /* @__PURE__ */ new Set();
175
+ if (s.has(obj)) return null;
176
+ s.add(obj);
177
+ if (isRunSummary(obj)) return (
178
+ /** @type {Record<string, unknown>} */
179
+ obj
180
+ );
181
+ if (Array.isArray(obj)) {
182
+ for (const item of obj) {
183
+ const found = findRunSummaryInObject(item, s, maxDepth - 1);
184
+ if (found) return found;
185
+ }
186
+ return null;
187
+ }
188
+ const o = (
189
+ /** @type {Record<string, unknown>} */
190
+ obj
191
+ );
192
+ for (const key of Object.keys(o)) {
193
+ const val = o[key];
194
+ if (val && typeof val === "object") {
195
+ const found = findRunSummaryInObject(val, s, maxDepth - 1);
196
+ if (found) return found;
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ function scanSessionForRunSummary(session) {
202
+ if (!session || typeof session !== "object") return null;
203
+ const direct = findRunSummaryInObject(session);
204
+ if (direct) return direct;
205
+ const s = (
206
+ /** @type {Record<string, unknown>} */
207
+ session
208
+ );
209
+ if (Array.isArray(s.toolCalls)) {
210
+ const calls = (
211
+ /** @type {Array<Record<string, unknown>>} */
212
+ s.toolCalls
213
+ );
214
+ for (let i = calls.length - 1; i >= 0; i--) {
215
+ const call = calls[i];
216
+ if (!call) continue;
217
+ if (call.tool === "workflow" || String(call.tool ?? "").endsWith("workflow")) {
218
+ const found = findRunSummaryInObject(call.result, void 0, 24);
219
+ if (found) return found;
220
+ }
221
+ }
222
+ }
223
+ if (Array.isArray(s.messages)) {
224
+ const msgs = (
225
+ /** @type {Array<Record<string, unknown>>} */
226
+ s.messages
227
+ );
228
+ for (let i = msgs.length - 1; i >= 0; i--) {
229
+ const msg = msgs[i];
230
+ if (!msg) continue;
231
+ const found = findRunSummaryInObject(msg.content);
232
+ if (found) return found;
233
+ }
234
+ }
235
+ return null;
236
+ }
237
+ function extractVerdict(runSummary) {
238
+ if (!isRunSummary(runSummary)) return null;
239
+ const o = (
240
+ /** @type {Record<string, unknown>} */
241
+ runSummary
242
+ );
243
+ const final = (
244
+ /** @type {Record<string, unknown>} */
245
+ o.finalReport
246
+ );
247
+ const meta = final.metaReview && typeof final.metaReview === "object" ? (
248
+ /** @type {Record<string, unknown>} */
249
+ final.metaReview
250
+ ) : {};
251
+ const issues = Array.isArray(meta.issues) ? meta.issues : [];
252
+ const roundsVal = o.rounds;
253
+ const totalRounds = typeof roundsVal === "number" ? roundsVal : Array.isArray(roundsVal) ? roundsVal.length : 0;
254
+ return {
255
+ verdict: final.verdict === "needs_revision" ? "needs_revision" : "approved",
256
+ reportIssues: issues.length,
257
+ checksRun: typeof meta.checksRun === "number" ? meta.checksRun : 0,
258
+ converged: o.converged === true,
259
+ totalRounds,
260
+ totalFindings: typeof o.totalFindings === "number" ? o.totalFindings : 0
261
+ };
262
+ }
263
+ function normalizeReport(report) {
264
+ const convergence = (
265
+ /** @type {Record<string, unknown>} */
266
+ report.convergence ?? {}
267
+ );
268
+ const rounds = (
269
+ /** @type {Array<unknown>} */
270
+ report.rounds ?? []
271
+ );
272
+ const findings = (
273
+ /** @type {Array<Record<string, unknown>>} */
274
+ report.findings ?? []
275
+ );
276
+ const totalRounds = typeof convergence.totalRounds === "number" ? convergence.totalRounds : rounds.length;
277
+ const normalizedConvergence = {
278
+ totalRounds,
279
+ findingsByRound: Array.isArray(convergence.findingsByRound) ? convergence.findingsByRound : rounds.map((r) => {
280
+ const rr = (
281
+ /** @type {Record<string, unknown>} */
282
+ r
283
+ );
284
+ return Array.isArray(rr?.findings) ? rr.findings.length : 0;
285
+ }),
286
+ converged: convergence.converged === true,
287
+ stoppedReason: convergence.stoppedReason ?? (rounds.length < totalRounds ? "converged" : "max_rounds_reached")
288
+ };
289
+ let summary = report.summary;
290
+ if (!summary || typeof summary !== "object") {
291
+ summary = computeSummaryFromFindings(findings);
292
+ } else {
293
+ const s = (
294
+ /** @type {Record<string, unknown>} */
295
+ summary
296
+ );
297
+ const computed = computeSummaryFromFindings(findings);
298
+ summary = {
299
+ totalFindings: typeof s.totalFindings === "number" ? s.totalFindings : findings.length,
300
+ critical: typeof s.critical === "number" ? s.critical : computed.critical,
301
+ high: typeof s.high === "number" ? s.high : computed.high,
302
+ medium: typeof s.medium === "number" ? s.medium : computed.medium,
303
+ low: typeof s.low === "number" ? s.low : computed.low,
304
+ byDimension: s.byDimension && typeof s.byDimension === "object" ? s.byDimension : computed.byDimension,
305
+ ...typeof s.fixedCount === "number" ? { fixedCount: s.fixedCount } : {}
306
+ };
307
+ }
308
+ return {
309
+ mode: report.mode ?? "dry-run",
310
+ goal: report.goal ?? "",
311
+ dimensions: Array.isArray(report.dimensions) ? report.dimensions : [],
312
+ maxReviewRounds: report.maxReviewRounds ?? totalRounds,
313
+ rounds,
314
+ findings,
315
+ convergence: normalizedConvergence,
316
+ summary
317
+ };
318
+ }
319
+ function computeSummaryFromFindings(findings) {
320
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 };
321
+ const byDimension = {};
322
+ for (const f of findings) {
323
+ const sev = String(f.severity ?? "low");
324
+ if (sev in counts) counts[sev]++;
325
+ const dim = String(f.dimension ?? "unknown");
326
+ byDimension[dim] = (byDimension[dim] ?? 0) + 1;
327
+ }
328
+ return {
329
+ totalFindings: findings.length,
330
+ critical: counts.critical,
331
+ high: counts.high,
332
+ medium: counts.medium,
333
+ low: counts.low,
334
+ byDimension
335
+ };
336
+ }
337
+ function computeConvergenceProgress(report) {
338
+ const convergence = (
339
+ /** @type {Record<string, unknown>} */
340
+ report.convergence ?? {}
341
+ );
342
+ const totalRounds = typeof convergence.totalRounds === "number" ? convergence.totalRounds : 1;
343
+ const currentRounds = (
344
+ /** @type {Array<unknown>} */
345
+ (report.rounds ?? []).length
346
+ );
347
+ if (!(totalRounds > 0)) return 0;
348
+ return Math.min(100, Math.round(currentRounds / totalRounds * 100));
349
+ }
350
+ function getCurrentRound(report) {
351
+ return (
352
+ /** @type {Array<unknown>} */
353
+ (report.rounds ?? []).length
354
+ );
355
+ }
356
+ function getTotalRounds(report) {
357
+ const convergence = (
358
+ /** @type {Record<string, unknown>} */
359
+ report.convergence ?? {}
360
+ );
361
+ return typeof convergence.totalRounds === "number" ? convergence.totalRounds : 1;
362
+ }
363
+ function severityStats(report) {
364
+ const findings = (
365
+ /** @type {Array<Record<string, unknown>>} */
366
+ report.findings ?? []
367
+ );
368
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 };
369
+ for (const f of findings) {
370
+ const sev = String(f.severity ?? "low");
371
+ if (sev in counts) counts[sev]++;
372
+ }
373
+ return counts;
374
+ }
375
+ function groupByDimension(report) {
376
+ const findings = (
377
+ /** @type {Array<Record<string, unknown>>} */
378
+ report.findings ?? []
379
+ );
380
+ const groups = {};
381
+ for (const f of findings) {
382
+ const dim = String(f.dimension ?? "unknown");
383
+ if (!groups[dim]) groups[dim] = [];
384
+ groups[dim].push(f);
385
+ }
386
+ return groups;
387
+ }
388
+ function buildTriageState(report) {
389
+ const findings = (
390
+ /** @type {Array<unknown>} */
391
+ report.findings ?? []
392
+ );
393
+ const state = {};
394
+ for (let i = 0; i < findings.length; i++) {
395
+ state[String(i)] = "keep";
396
+ }
397
+ return state;
398
+ }
399
+ function hashReport(report) {
400
+ const convergence = (
401
+ /** @type {Record<string, unknown>} */
402
+ report.convergence ?? {}
403
+ );
404
+ const totalRounds = String(convergence.totalRounds ?? "");
405
+ const findingsCount = String(
406
+ /** @type {Array<unknown>} */
407
+ (report.findings ?? []).length
408
+ );
409
+ const firstFinding = (
410
+ /** @type {Array<Record<string, unknown>>} */
411
+ (report.findings ?? [])[0]
412
+ );
413
+ const firstSummary = firstFinding ? String(firstFinding.summary ?? "") : "";
414
+ const mode = String(report.mode ?? "");
415
+ return `iterate-triage-${mode}-${totalRounds}-${findingsCount}-${firstSummary.slice(0, 20)}`;
416
+ }
417
+ function toKnownIntentionalYaml(entries) {
418
+ if (!entries || entries.length === 0) return "";
419
+ const lines = ["known_intentional:"];
420
+ for (const e of entries) {
421
+ lines.push(` - file: ${JSON.stringify(e.file)}`);
422
+ if (e.line !== void 0 && e.line > 0) {
423
+ lines.push(` line: ${e.line}`);
424
+ }
425
+ lines.push(` dimension: ${JSON.stringify(e.dimension)}`);
426
+ lines.push(` reason: ${JSON.stringify(e.reason)}`);
427
+ }
428
+ return lines.join("\n");
429
+ }
430
+ function buildApplyInstruction(entries) {
431
+ if (!entries || entries.length === 0) return "";
432
+ const payload = JSON.stringify(
433
+ {
434
+ operation: "apply",
435
+ entries: entries.map((e) => ({
436
+ file: e.file,
437
+ ...e.line !== void 0 ? { line: e.line } : {},
438
+ dimension: e.dimension,
439
+ reason: e.reason
440
+ }))
441
+ },
442
+ null,
443
+ 2
444
+ );
445
+ return `Please call \`iterate_triage\` with the following payload to apply the triage verdicts:
446
+
447
+ \`\`\`json
448
+ ${payload}
449
+ \`\`\``;
450
+ }
451
+ function collectIgnoredEntries(triageState, findings) {
452
+ const entries = [];
453
+ for (const [idx, verdict] of Object.entries(triageState)) {
454
+ if (verdict !== "ignore") continue;
455
+ const finding = findings[Number(idx)];
456
+ if (!finding) continue;
457
+ entries.push({
458
+ file: String(finding.file ?? ""),
459
+ ...typeof finding.line === "number" && finding.line > 0 ? { line: finding.line } : {},
460
+ dimension: String(finding.dimension ?? ""),
461
+ reason: String(finding.summary ?? "")
462
+ });
463
+ }
464
+ return entries;
465
+ }
466
+ function normalizeFindingFilter(filter) {
467
+ const f = filter && typeof filter === "object" ? filter : {};
468
+ const severities = Array.isArray(f.severities) ? f.severities.filter((s) => SEVERITY_ORDER.includes(String(s))) : [];
469
+ const dimensions = Array.isArray(f.dimensions) ? f.dimensions.filter((d) => typeof d === "string" && d.length > 0) : [];
470
+ const search = typeof f.search === "string" ? f.search.trim().toLowerCase() : "";
471
+ return { severities, dimensions, search };
472
+ }
473
+ function findingMatches(finding, filter) {
474
+ const f = normalizeFindingFilter(filter);
475
+ const sev = String(finding.severity ?? "low");
476
+ if (f.severities.length > 0 && !f.severities.includes(sev)) return false;
477
+ const dim = String(finding.dimension ?? "");
478
+ if (f.dimensions.length > 0 && !f.dimensions.includes(dim)) return false;
479
+ if (f.search) {
480
+ const haystack = [
481
+ String(finding.file ?? ""),
482
+ String(finding.summary ?? ""),
483
+ String(finding.dimension ?? ""),
484
+ String(finding.suggested_fix ?? "")
485
+ ].join(" ").toLowerCase();
486
+ if (haystack.indexOf(f.search) < 0) return false;
487
+ }
488
+ return true;
489
+ }
490
+ function filterFindingsWithIndices(findings, filter) {
491
+ const f = normalizeFindingFilter(filter);
492
+ const list = Array.isArray(findings) ? findings : [];
493
+ const filtered = [];
494
+ const indices = [];
495
+ for (let i = 0; i < list.length; i++) {
496
+ if (findingMatches(list[i], f)) {
497
+ filtered.push(list[i]);
498
+ indices.push(i);
499
+ }
500
+ }
501
+ return { filtered, indices };
502
+ }
503
+ function buildFilterOptions(findings) {
504
+ const list = Array.isArray(findings) ? findings : [];
505
+ const severities = SEVERITY_ORDER.map((value) => ({ value, count: 0 }));
506
+ const dimCounts = {};
507
+ for (const f of list) {
508
+ const sev = String(f.severity ?? "low");
509
+ const sv = severities.find((s) => s.value === sev);
510
+ if (sv) sv.count++;
511
+ const dim = String(f.dimension ?? "unknown");
512
+ dimCounts[dim] = (dimCounts[dim] ?? 0) + 1;
513
+ }
514
+ return {
515
+ severities: severities.map((s) => ({ ...s })),
516
+ dimensions: Object.keys(dimCounts).map((value) => ({ value, count: dimCounts[value] }))
517
+ };
518
+ }
519
+ function countVerdicts(triageState) {
520
+ const counts = { keep: 0, skip: 0, ignore: 0 };
521
+ for (const v of Object.values(triageState ?? {})) {
522
+ if (v === "keep" || v === "skip" || v === "ignore") counts[v]++;
523
+ }
524
+ return counts;
525
+ }
526
+ function batchSetVerdict(triageState, indices, verdict) {
527
+ if (verdict !== "keep" && verdict !== "skip" && verdict !== "ignore") return triageState;
528
+ if (!Array.isArray(indices) || indices.length === 0) return triageState;
529
+ const next = { ...triageState };
530
+ for (const idx of indices) {
531
+ if (typeof idx === "number" && Number.isInteger(idx) && idx >= 0) {
532
+ next[String(idx)] = verdict;
533
+ }
534
+ }
535
+ return next;
536
+ }
537
+ function setAllVerdicts(triageState, verdict, indices) {
538
+ const targets = Array.isArray(indices) ? indices : Object.keys(triageState ?? {}).map(Number);
539
+ return batchSetVerdict(triageState, targets, verdict);
540
+ }
541
+ function buildRoundHistory(report) {
542
+ const rounds = Array.isArray(report.rounds) ? report.rounds : [];
543
+ return rounds.map((r) => {
544
+ const rr = (
545
+ /** @type {Record<string, unknown>} */
546
+ r
547
+ );
548
+ const findings = Array.isArray(rr.findings) ? rr.findings : [];
549
+ const sev = severityStats({ findings });
550
+ return {
551
+ round: typeof rr.round === "number" ? rr.round : 0,
552
+ count: findings.length,
553
+ critical: sev.critical,
554
+ high: sev.high,
555
+ medium: sev.medium,
556
+ low: sev.low
557
+ };
558
+ });
559
+ }
560
+ function buildFindingTrend(report) {
561
+ const conv = (
562
+ /** @type {Record<string, unknown>} */
563
+ report.convergence ?? {}
564
+ );
565
+ if (Array.isArray(conv.findingsByRound)) {
566
+ return conv.findingsByRound.map((n, i) => ({ round: i + 1, count: typeof n === "number" ? n : 0 }));
567
+ }
568
+ return buildRoundHistory(report).map((h) => ({ round: h.round, count: h.count }));
569
+ }
570
+ function computeTrendMetrics(report) {
571
+ const conv = (
572
+ /** @type {Record<string, unknown>} */
573
+ report.convergence ?? {}
574
+ );
575
+ const points = buildFindingTrend(report);
576
+ const total = points.reduce((sum, p) => sum + p.count, 0);
577
+ const firstRound = points.length > 0 ? points[0].count : 0;
578
+ const lastRound = points.length > 0 ? points[points.length - 1].count : 0;
579
+ const reductionPercent = firstRound > 0 ? Math.round((firstRound - lastRound) / firstRound * 100) : 0;
580
+ return {
581
+ points,
582
+ total,
583
+ firstRound,
584
+ lastRound,
585
+ reductionPercent,
586
+ converged: conv.converged === true
587
+ };
588
+ }
589
+ function trendMax(points) {
590
+ let max = 1;
591
+ for (const p of Array.isArray(points) ? points : []) {
592
+ if (typeof p.count === "number" && p.count > max) max = p.count;
593
+ }
594
+ return max;
595
+ }
596
+ function buildCompletionSummary(report) {
597
+ const conv = (
598
+ /** @type {Record<string, unknown>} */
599
+ report.convergence ?? {}
600
+ );
601
+ const rounds = getCurrentRound(report);
602
+ const total = getTotalRounds(report);
603
+ const stats = severityStats(report);
604
+ const converged = conv.converged === true;
605
+ const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
606
+ const totalFindings = stats.critical + stats.high + stats.medium + stats.low;
607
+ return `iterate \u8BC4\u5BA1\u5B8C\u6210 \xB7 ${rounds}/${total} \u8F6E \xB7 ${totalFindings} \u9879\u53D1\u73B0 \xB7 ${reason}`;
608
+ }
609
+ var CONFIG_EDIT_FIELDS = [
610
+ { key: "goal", label: "\u76EE\u6807", hint: "\u4E00\u53E5\u8BDD\u63CF\u8FF0\u672C\u6B21\u8FED\u4EE3\u76EE\u6807\uFF08\u5B57\u7B26\u4E32\uFF09" },
611
+ { key: "dimensions", label: "\u5BA1\u67E5\u7EF4\u5EA6", hint: '\u6570\u7EC4\uFF0C\u5982 ["correctness","security"]' },
612
+ { key: "max_rounds", label: "\u6700\u5927\u8F6E\u6570", hint: "\u6B63\u6574\u6570" },
613
+ { key: "review.scope", label: "\u5BA1\u67E5\u8303\u56F4", hint: '"full" \u6216 "changed-only"' },
614
+ { key: "atomic.max_lines", label: "\u539F\u5B50\u4FEE\u590D\u4E0A\u9650\u884C\u6570", hint: "\u6B63\u6574\u6570" },
615
+ { key: "git.push_per_round", label: "\u6BCF\u8F6E\u63A8\u9001", hint: "true / false" }
616
+ ];
617
+ function buildConfigEditGuide() {
618
+ const lines = [
619
+ "iterate \u914D\u7F6E\u7F16\u8F91\u6307\u5F15",
620
+ "---------------------",
621
+ "\u914D\u7F6E\u6587\u4EF6\uFF1A\u9879\u76EE\u6839\u76EE\u5F55 iterate.config.yaml\u3002",
622
+ "",
623
+ "\u53EF\u7F16\u8F91\u5B57\u6BB5\uFF1A",
624
+ ...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}\uFF08${f.label}\uFF09\uFF1A${f.hint}`),
625
+ "",
626
+ "\u8BA9\u6A21\u578B\u5E2E\u4F60\u6539\uFF1A",
627
+ '1. \u8C03\u7528 iterate_config({ operation: "read" }) \u67E5\u770B\u5F53\u524D\u914D\u7F6E\uFF1B',
628
+ "2. \u8BF4\u660E\u60F3\u6539\u7684\u5B57\u6BB5\uFF0C\u4F8B\u5982\u300C\u628A max_rounds \u6539\u6210 5\uFF0Cdimensions \u53EA\u4FDD\u7559 correctness \u548C security\u300D\uFF1B",
629
+ '3. \u6A21\u578B\u4F1A\u8C03\u7528 iterate_config({ operation: "write", updates: {...} }) \u5199\u5165\uFF0C\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002'
630
+ ];
631
+ return lines.join("\n");
632
+ }
633
+ var VERDICT_SHORTCUTS = {
634
+ y: "keep",
635
+ Y: "keep",
636
+ n: "skip",
637
+ N: "skip",
638
+ a: "ignore",
639
+ A: "ignore"
640
+ };
641
+ function keyToVerdict(key) {
642
+ return VERDICT_SHORTCUTS[key] ?? null;
643
+ }
644
+ function allVerdictKeys(triageState) {
645
+ const state = triageState && typeof triageState === "object" ? triageState : {};
646
+ return Object.keys(state).map(Number).filter((n) => Number.isInteger(n) && n >= 0).sort((a, b) => a - b);
647
+ }
648
+ var RUNTIME_ARTIFACTS = [
649
+ {
650
+ key: "decision-log.jsonl",
651
+ label: "\u51B3\u7B56\u65E5\u5FD7",
652
+ hint: "\u8FFD\u52A0\u5F0F JSONL\uFF0C\u8BB0\u5F55\u6BCF\u8F6E plan / review / fix / revert / validation \u51B3\u7B56"
653
+ },
654
+ {
655
+ key: "checkpoint.json",
656
+ label: "\u8FED\u4EE3\u65AD\u70B9",
657
+ hint: "\u957F\u8FED\u4EE3\u7684\u8FDB\u5EA6\u5FEB\u7167\uFF0C\u4E2D\u65AD\u540E\u53EF\u6062\u590D\uFF08iterate_checkpoint\uFF09"
658
+ },
659
+ {
660
+ key: "fixes/registry.json",
661
+ label: "\u4FEE\u590D\u6CE8\u518C\u8868",
662
+ hint: "\u6BCF\u4E2A\u539F\u5B50\u4FEE\u590D\u7684 id / diff / \u5907\u4EFD\u8DEF\u5F84\uFF08iterate_fix / iterate_diff\uFF09"
663
+ },
664
+ {
665
+ key: "fixes/*.bak",
666
+ label: "\u4FEE\u590D\u5907\u4EFD",
667
+ hint: "\u6BCF\u6B21\u4FEE\u590D\u524D\u7684\u539F\u6587\u4EF6\u5907\u4EFD\uFF0C\u56DE\u6EDA\u4F9D\u8D56\uFF08iterate_rollback\uFF09"
668
+ }
669
+ ];
670
+ function buildRuntimeStatusGuide() {
671
+ const lines = [
672
+ "iterate \u8FD0\u884C\u65F6\u72B6\u6001\u6982\u89C8",
673
+ "----------------------",
674
+ "\u6240\u6709\u8FD0\u884C\u65F6\u4EA7\u7269\u4F4D\u4E8E\u9879\u76EE\u6839\u76EE\u5F55 .iterate/ \u4E0B\uFF1A",
675
+ "",
676
+ ...RUNTIME_ARTIFACTS.map((a) => `- ${a.key}\uFF08${a.label}\uFF09\uFF1A${a.hint}`),
677
+ "",
678
+ "\u67E5\u770B\u72B6\u6001\uFF1A\u8BA9\u6A21\u578B\u8C03\u7528 iterate_status\uFF08\u6C47\u603B\uFF09\u6216 iterate_history\uFF08\u660E\u7EC6\uFF09\u3002",
679
+ "\u6E05\u7406\u72B6\u6001\uFF1A\u8BA9\u6A21\u578B\u8C03\u7528 iterate_prune\uFF08\u9ED8\u8BA4 dry-run\uFF0C\u53EA\u62A5\u544A\u4E0D\u5220\u9664\uFF0C\u663E\u5F0F dryRun:false \u624D\u771F\u6B63\u6E05\u7406\uFF09\u3002"
680
+ ];
681
+ return lines.join("\n");
86
682
  }
87
683
 
88
- /** Injected stylesheet. Every selector is prefixed to avoid clobbering dsh. */
89
- const ITERATE_CSS = `
684
+ // src/client/index.ts
685
+ var name = "iterate-plugin";
686
+ var inject = ["slots", "theme"];
687
+ var PLUGIN_TAG = "iterate-ui";
688
+ var TRIAGE_STORAGE_PREFIX = "iterate.triage.";
689
+ var THEME_STORAGE_KEY = "iterate.theme.enabled";
690
+ var THEME_SOURCE = "iterate";
691
+ var ITERATE_TOKENS = {
692
+ "--dsw-alias-bg-base": { light: "#FAF8F5", dark: "#171412" },
693
+ "--dsw-alias-bg-layer-1": { light: "#FFFFFF", dark: "#1F1B17" },
694
+ "--dsw-alias-bg-layer-2": { light: "#F4F1EA", dark: "#27221C" },
695
+ "--dsw-alias-bg-overlay": { light: "rgba(255,255,255,0.96)", dark: "rgba(23,20,18,0.96)" },
696
+ "--dsw-alias-border-l1": { light: "#E8E2D8", dark: "#332C25" },
697
+ "--dsw-alias-border-l2": { light: "#DDD5C9", dark: "#3C342B" },
698
+ "--dsw-alias-brand-primary": { light: "#B45309", dark: "#F59E0B" },
699
+ "--dsw-alias-label-primary": { light: "#1C1917", dark: "#F5F0EA" },
700
+ "--dsw-alias-label-secondary": { light: "#57534E", dark: "#A9A29B" },
701
+ "--dsw-alias-state-error-primary": { light: "#DC2626", dark: "#F87171" },
702
+ "--dsw-alias-state-success-primary": { light: "#15803D", dark: "#4ADE80" },
703
+ "--dsw-alias-state-warn-primary": { light: "#D97706", dark: "#FBBF24" },
704
+ "--dsw-specific-sidebar-fill": { light: "#F5F2EC", dark: "#14110E" }
705
+ };
706
+ var ITERATE_CSS = `
90
707
  [data-iterate-root] { box-sizing: border-box; font-family: var(--dsw-font-sans, system-ui, sans-serif); }
91
708
  [data-iterate-root] * , [data-iterate-root] *::before, [data-iterate-root] *::after { box-sizing: border-box; }
92
709
 
@@ -161,8 +778,6 @@ const ITERATE_CSS = `
161
778
  @keyframes iterate-fadein { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
162
779
 
163
780
  .iterate-settings { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 12px; }
164
- .iterate-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--dsw-alias-border-l1); }
165
- .iterate-settings-row:last-child { border-bottom: none; }
166
781
  .iterate-settings-title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }
167
782
  .iterate-settings-desc { font-size: 12px; color: var(--dsw-alias-label-secondary); margin-top: 3px; line-height: 1.5; }
168
783
  .iterate-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-secondary); }
@@ -197,7 +812,6 @@ const ITERATE_CSS = `
197
812
  .iterate-completion[data-ok] { border: 1px solid var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent); }
198
813
  .iterate-completion[data-warn] { border: 1px solid var(--dsw-alias-state-warn-primary); color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent); }
199
814
  .iterate-capsule[data-ok] { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }
200
- .iterate-settings-guide { white-space: pre-wrap; padding: 8px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 11px; max-height: 180px; overflow: auto; }
201
815
  .iterate-chip[data-ok] { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent); }
202
816
  .iterate-batch-check { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary); font-size: 11px; cursor: pointer; }
203
817
  .iterate-batch-check input { margin: 0; cursor: pointer; }
@@ -210,778 +824,904 @@ const ITERATE_CSS = `
210
824
  .iterate-verdict-detail { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; color: var(--dsw-alias-label-secondary); }
211
825
  .iterate-verdict-item { white-space: nowrap; }
212
826
  .iterate-verdict-item b { color: var(--dsw-alias-label-primary); font-weight: 600; }
213
- `
214
-
215
- // ─── Small helpers ───────────────────────────────────────────────────────────
216
827
 
217
- /** Namespaced logger (kept minimal; only errors/warnings, no debug spam). */
828
+ /* Settings page redesign: grouped cards + switch + code blocks. */
829
+ .iterate-settings { display: flex; flex-direction: column; gap: 14px; padding: 6px 2px 16px; }
830
+ .iterate-scard { border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-1); padding: 14px 16px; }
831
+ .iterate-scard-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
832
+ .iterate-scard-title { font-size: 14px; font-weight: 650; color: var(--dsw-alias-label-primary); letter-spacing: -0.01em; }
833
+ .iterate-scard-desc { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); margin-top: 4px; }
834
+ .iterate-scard-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
835
+
836
+ /* Status pill */
837
+ .iterate-pill { display: inline-flex; align-items: center; gap: 7px; padding: 3px 11px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
838
+ .iterate-pill-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
839
+
840
+ /* Accessibility-switch toggle */
841
+ .iterate-switch { position: relative; width: 42px; height: 24px; border-radius: 999px; padding: 0; cursor: pointer; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); transition: background-color 160ms ease, border-color 160ms ease; }
842
+ .iterate-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
843
+ .iterate-switch-knob { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: var(--dsw-alias-label-secondary); transition: transform 160ms ease, background-color 160ms ease; }
844
+ .iterate-switch[data-on] { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
845
+ .iterate-switch[data-on] .iterate-switch-knob { transform: translateX(18px); background: #FFFFFF; }
846
+
847
+ /* Button variants */
848
+ .iterate-btn[data-ghost] { background: transparent; }
849
+ .iterate-btn[data-danger] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent); color: var(--dsw-alias-state-error-primary); background: transparent; }
850
+ .iterate-btn[data-danger]:hover { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent); }
851
+ .iterate-btn[data-confirm] { border-color: var(--dsw-alias-state-error-primary); color: #FFFFFF; background: var(--dsw-alias-state-error-primary); }
852
+
853
+ /* Collapsible guide / status code blocks */
854
+ .iterate-guide { margin-top: 4px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: hidden; background: var(--dsw-alias-bg-layer-2); }
855
+ .iterate-guide-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; background: var(--dsw-alias-bg-layer-2); border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 12px; color: var(--dsw-alias-label-secondary); }
856
+ .iterate-guide-body { padding: 12px 14px; font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 11.5px; line-height: 1.75; white-space: pre-wrap; color: var(--dsw-alias-label-primary); max-height: 260px; overflow: auto; }
857
+ `;
218
858
  function log(...args) {
219
- if (typeof console !== 'undefined' && typeof console.error === 'function') {
220
- console.error(`[${PLUGIN_TAG}]`, ...args)
859
+ if (typeof console !== "undefined" && typeof console.error === "function") {
860
+ console.error(`[${PLUGIN_TAG}]`, ...args);
221
861
  }
222
862
  }
223
-
224
- /** Resolve the React runtime (ctx.React first, then window.React). */
225
- function resolveReact(ctx) {
226
- if (ctx && ctx.React && typeof ctx.React.createElement === 'function') return ctx.React
227
- const globalScope = typeof window !== 'undefined' ? window : typeof globalThis !== 'undefined' ? globalThis : null
228
- if (globalScope && globalScope.React && typeof globalScope.React.createElement === 'function') {
229
- return globalScope.React
230
- }
231
- return null
232
- }
233
-
234
- /** Safe localStorage wrapper (never throws in private mode / SSR). */
235
863
  function createStorage() {
236
864
  try {
237
- const testKey = '__iterate_storage_test__'
238
- window.localStorage.setItem(testKey, '1')
239
- window.localStorage.removeItem(testKey)
865
+ const testKey = "__iterate_storage_test__";
866
+ window.localStorage.setItem(testKey, "1");
867
+ window.localStorage.removeItem(testKey);
240
868
  return {
241
- get(key) { return window.localStorage.getItem(key) },
242
- set(key, value) { window.localStorage.setItem(key, value) },
243
- remove(key) { window.localStorage.removeItem(key) },
244
- keys() { return Object.keys(window.localStorage) },
245
- }
869
+ get(key) {
870
+ return window.localStorage.getItem(key);
871
+ },
872
+ set(key, value) {
873
+ window.localStorage.setItem(key, value);
874
+ },
875
+ remove(key) {
876
+ window.localStorage.removeItem(key);
877
+ },
878
+ keys() {
879
+ return Object.keys(window.localStorage);
880
+ }
881
+ };
246
882
  } catch {
247
- const mem = new Map()
883
+ const mem = /* @__PURE__ */ new Map();
248
884
  return {
249
- get(key) { return mem.has(key) ? mem.get(key) : null },
250
- set(key, value) { mem.set(key, value) },
251
- remove(key) { mem.delete(key) },
252
- keys() { return [...mem.keys()] },
253
- }
885
+ get(key) {
886
+ return mem.has(key) ? mem.get(key) : null;
887
+ },
888
+ set(key, value) {
889
+ mem.set(key, value);
890
+ },
891
+ remove(key) {
892
+ mem.delete(key);
893
+ },
894
+ keys() {
895
+ return [...mem.keys()];
896
+ }
897
+ };
254
898
  }
255
899
  }
256
-
257
- /**
258
- * Remove every stored key with the given prefix (e.g. all triage verdicts).
259
- * Returns how many keys were removed.
260
- */
261
900
  function removeStorageByPrefix(prefix) {
262
- if (!storage) return 0
263
- let removed = 0
901
+ if (!storage) return 0;
902
+ let removed = 0;
264
903
  try {
265
904
  for (const key of storage.keys()) {
266
905
  if (key.startsWith(prefix)) {
267
- storage.remove(key)
268
- removed++
906
+ storage.remove(key);
907
+ removed++;
269
908
  }
270
909
  }
271
910
  } catch (err) {
272
- log('failed to clear storage prefix', prefix, err)
911
+ log("failed to clear storage prefix", prefix, err);
273
912
  }
274
- return removed
913
+ return removed;
275
914
  }
276
-
277
- /** Copy text to the clipboard, returning whether it succeeded. */
278
915
  function copyText(text) {
279
- if (typeof navigator !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
916
+ if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
280
917
  navigator.clipboard.writeText(text).then(
281
918
  () => true,
282
- () => false,
283
- )
284
- return true
919
+ () => false
920
+ );
921
+ return true;
285
922
  }
286
- return false
923
+ return false;
287
924
  }
288
-
289
- // ─── Module-level runtime state (shared across slots) ────────────────────────
290
-
291
- let React = null
292
- let slotsSvc = null
293
- let themeSvc = null
294
- let storage = null
295
- let themeDisposer = null
296
- let themeEnabled = true
297
- const roundPulseListeners = []
298
-
299
- /**
300
- * Emit a "round changed" pulse to subscribed components.
301
- * The payload is `{ round, converged }` so the overlay capsule can surface
302
- * completion AND convergence in one notification.
303
- */
925
+ var SEVERITY_KEYS = ["critical", "high", "medium", "low"];
926
+ function coerceSeverity(severity) {
927
+ return SEVERITY_KEYS.includes(severity ?? "") ? severity : "low";
928
+ }
929
+ function severityColor(severity) {
930
+ return SEVERITY_COLOR[coerceSeverity(severity)];
931
+ }
932
+ function severityLabel(severity) {
933
+ return SEVERITY_LABEL[coerceSeverity(severity)];
934
+ }
935
+ function readSlots(ctx) {
936
+ if (ctx && ctx.slots && typeof ctx.slots.inject === "function" && typeof ctx.slots.register === "function") {
937
+ return ctx.slots;
938
+ }
939
+ const raw = ctx && typeof ctx.get === "function" ? ctx.get("slots", false) : void 0;
940
+ if (raw && typeof raw.inject === "function" && typeof raw.register === "function") {
941
+ return raw;
942
+ }
943
+ return void 0;
944
+ }
945
+ function readTheme(ctx) {
946
+ if (ctx && ctx.theme && typeof ctx.theme.overrideTokens === "function") {
947
+ return ctx.theme;
948
+ }
949
+ const raw = ctx && typeof ctx.get === "function" ? ctx.get("theme", false) : void 0;
950
+ if (raw && typeof raw.overrideTokens === "function") {
951
+ return raw;
952
+ }
953
+ return void 0;
954
+ }
955
+ var slotsSvc = void 0;
956
+ var themeSvc = void 0;
957
+ var storage = null;
958
+ var themeDisposer = null;
959
+ var themeEnabled = true;
960
+ var roundPulseListeners = [];
304
961
  function emitRoundPulse(round, converged) {
305
- const payload = { round, converged: converged === true }
306
- for (const fn of roundPulseListeners.slice()) fn(payload)
962
+ const payload = { round, converged: converged === true };
963
+ for (const fn of roundPulseListeners.slice()) fn(payload);
307
964
  }
308
-
309
- /** Apply the iterate theme skin (idempotent). */
310
965
  function applyThemeSkin() {
311
- if (!themeSvc || typeof themeSvc.overrideTokens !== 'function') return
312
- // Never stack disposers: drop any previously applied override first.
313
- clearThemeSkin()
314
- themeDisposer = themeSvc.overrideTokens(THEME_SOURCE, ITERATE_TOKENS)
966
+ if (!themeSvc || typeof themeSvc.overrideTokens !== "function") return;
967
+ clearThemeSkin();
968
+ themeDisposer = themeSvc.overrideTokens(THEME_SOURCE, ITERATE_TOKENS);
315
969
  }
316
-
317
- /** Remove the iterate theme skin (idempotent). */
318
970
  function clearThemeSkin() {
319
971
  if (themeDisposer) {
320
- try { themeDisposer() } catch (err) { log('theme disposer failed', err) }
321
- themeDisposer = null
972
+ try {
973
+ themeDisposer();
974
+ } catch (err) {
975
+ log("theme disposer failed", err);
976
+ }
977
+ themeDisposer = null;
322
978
  }
323
979
  }
324
-
325
- /** Persist + apply the theme preference. */
326
980
  function setThemeEnabled(enabled) {
327
- themeEnabled = enabled
328
- if (storage) storage.set(THEME_STORAGE_KEY, enabled ? '1' : '0')
329
- if (enabled) applyThemeSkin()
330
- else clearThemeSkin()
981
+ themeEnabled = enabled;
982
+ if (storage) storage.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
983
+ if (enabled) applyThemeSkin();
984
+ else clearThemeSkin();
331
985
  }
332
-
333
- // ─── Components (React.createElement trees) ──────────────────────────────────
334
-
335
- /** Obtain a session snapshot defensively from the slot props. */
336
986
  function sessionSnapshot(props) {
337
- let session = null
338
- if (props && typeof props.useSession === 'function') {
339
- try { session = props.useSession() } catch (err) { log('useSession failed', err) }
987
+ let session = null;
988
+ const useSession = props && typeof props.useSession === "function" ? props.useSession : null;
989
+ if (useSession) {
990
+ try {
991
+ session = useSession();
992
+ } catch (err) {
993
+ log("useSession failed", err);
994
+ }
340
995
  }
341
- if (!session && props && props.session) session = props.session
342
- return session
996
+ if (!session && props && props.session) session = props.session;
997
+ return session;
343
998
  }
344
-
345
- /** Find the latest report inside a session snapshot (normalized). */
346
999
  function latestReport(session) {
347
- if (!session) return null
348
- const raw = scanSessionForReport(session) || findReportInObject(session, undefined, 24)
349
- return raw ? normalizeReport(raw) : null
1000
+ if (!session) return null;
1001
+ const raw = scanSessionForReport(session) || findReportInObject(session, void 0, 24);
1002
+ return raw ? normalizeReport(raw) : null;
350
1003
  }
351
-
352
- /** Inline bar chart of findings-by-round (pure divs, no SVG needed). */
353
1004
  function TrendChart({ points }) {
354
- if (!points || points.length === 0) return null
355
- const max = trendMax(points)
356
- const bars = points.map((p) =>
357
- React.createElement('div', {
1005
+ if (!points || points.length === 0) return null;
1006
+ const max = trendMax(points);
1007
+ const bars = points.map(
1008
+ (p) => React.createElement("div", {
358
1009
  key: p.round,
359
- className: 'iterate-trend-bar',
360
- 'data-hot': p.count > 0 ? '' : undefined,
361
- title: `Round ${p.round}:${p.count} 项`,
362
- style: { height: `${Math.max(4, Math.round((p.count / max) * 24))}px` },
363
- }),
364
- )
365
- return React.createElement('div', { className: 'iterate-trend', title: '各轮发现数量趋势' }, ...bars)
1010
+ className: "iterate-trend-bar",
1011
+ "data-hot": p.count > 0 ? "" : void 0,
1012
+ title: `Round ${p.round}\uFF1A${p.count} \u9879`,
1013
+ style: { height: `${Math.max(4, Math.round(p.count / max * 24))}px` }
1014
+ })
1015
+ );
1016
+ return React.createElement("div", { className: "iterate-trend", title: "\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF" }, ...bars);
366
1017
  }
367
-
368
- /** Dashboard: live convergence strip above the composer. */
369
1018
  function ConvergenceDashboard(props) {
370
- const [pulseKey, setPulseKey] = React.useState(0)
371
- const report = latestReport(sessionSnapshot(props))
372
-
1019
+ const [pulseKey, setPulseKey] = React.useState(0);
1020
+ const report = latestReport(sessionSnapshot(props));
373
1021
  React.useEffect(() => {
374
- if (!report) return
375
- const cur = getCurrentRound(report)
376
- const conv = report.convergence
377
- emitRoundPulse(cur, conv && conv.converged === true)
378
- setPulseKey((k) => k + 1)
379
- }, [report && hashReport(report) + ':' + getCurrentRound(report)])
380
-
381
- if (!report) return null
382
-
383
- const round = getCurrentRound(report)
384
- const total = getTotalRounds(report)
385
- const progress = computeConvergenceProgress(report)
386
- const stats = severityStats(report)
387
- const dims = groupByDimension(report)
388
- const trend = computeTrendMetrics(report)
389
-
390
- const dimBadges = Object.keys(dims).slice(0, 6).map((dim) =>
391
- React.createElement(
392
- 'span',
393
- { key: dim, className: 'iterate-dim-badge' },
394
- `${dim} · ${dims[dim].length}`,
395
- ),
396
- )
397
-
398
- // Fix-count badge: show a running "fixes applied" metric when the report
399
- // carries a number (normal mode only — threaded through `fixedCount`).
400
- const mode = report.mode
401
- const summary = report.summary
402
- const isNormal = mode === 'normal'
403
- const fixCount = isNormal && summary && typeof summary.fixedCount === 'number' ? summary.fixedCount : null
404
- const fixBadge = fixCount !== null
405
- ? React.createElement('span', {
406
- className: 'iterate-metric',
407
- key: 'fixes',
408
- title: '本轮已应用的原子修复数(正常模式)',
409
- }, `${String(fixCount)} fixes`)
410
- : null
411
-
1022
+ if (!report) return;
1023
+ const cur = getCurrentRound(report);
1024
+ const conv = report.convergence;
1025
+ emitRoundPulse(cur, conv?.converged === true);
1026
+ setPulseKey((k) => k + 1);
1027
+ }, [report && hashReport(report) + ":" + getCurrentRound(report)]);
1028
+ if (!report) return null;
1029
+ const round = getCurrentRound(report);
1030
+ const total = getTotalRounds(report);
1031
+ const progress = computeConvergenceProgress(report);
1032
+ const stats = severityStats(report);
1033
+ const dims = groupByDimension(report);
1034
+ const trend = computeTrendMetrics(report);
1035
+ const dimBadges = Object.keys(dims).slice(0, 6).map(
1036
+ (dim) => React.createElement(
1037
+ "span",
1038
+ { key: dim, className: "iterate-dim-badge" },
1039
+ `${dim} \xB7 ${dims[dim]?.length ?? 0}`
1040
+ )
1041
+ );
1042
+ const mode = report.mode;
1043
+ const summary = report.summary;
1044
+ const isNormal = mode === "normal";
1045
+ const fixCount = isNormal && summary && typeof summary.fixedCount === "number" ? summary.fixedCount : null;
1046
+ const fixBadge = fixCount !== null ? React.createElement("span", {
1047
+ className: "iterate-metric",
1048
+ key: "fixes",
1049
+ title: "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09"
1050
+ }, `${String(fixCount)} fixes`) : null;
412
1051
  return React.createElement(
413
- 'div',
414
- { 'data-iterate-root': '', 'data-iterate': 'dashboard', className: 'iterate-dashboard' },
1052
+ "div",
1053
+ { "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard" },
415
1054
  React.createElement(
416
- 'span',
417
- { className: 'iterate-round-badge', 'data-pulse': pulseKey > 0 ? '' : undefined, key: `round-${round}` },
418
- `Round ${round} / ${total}`,
1055
+ "span",
1056
+ { className: "iterate-round-badge", "data-pulse": pulseKey > 0 ? "" : void 0, key: `round-${round}` },
1057
+ `Round ${round} / ${total}`
419
1058
  ),
420
- React.createElement('div', { className: 'iterate-progress' },
421
- React.createElement('div', { className: 'iterate-progress-fill', style: { width: `${progress}%` } }),
1059
+ React.createElement(
1060
+ "div",
1061
+ { className: "iterate-progress" },
1062
+ React.createElement("div", { className: "iterate-progress-fill", style: { width: `${progress}%` } })
422
1063
  ),
423
- React.createElement('span', { className: 'iterate-metric' },
424
- React.createElement('span', { className: 'iterate-sev-dot', style: { background: SEVERITY_COLOR.critical } }),
425
- stats.critical,
1064
+ React.createElement(
1065
+ "span",
1066
+ { className: "iterate-metric" },
1067
+ React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.critical } }),
1068
+ stats.critical
426
1069
  ),
427
- React.createElement('span', { className: 'iterate-metric' },
428
- React.createElement('span', { className: 'iterate-sev-dot', style: { background: SEVERITY_COLOR.high } }),
429
- stats.high,
1070
+ React.createElement(
1071
+ "span",
1072
+ { className: "iterate-metric" },
1073
+ React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.high } }),
1074
+ stats.high
430
1075
  ),
431
- React.createElement('span', { className: 'iterate-metric' },
432
- React.createElement('span', { className: 'iterate-sev-dot', style: { background: SEVERITY_COLOR.medium } }),
433
- stats.medium,
1076
+ React.createElement(
1077
+ "span",
1078
+ { className: "iterate-metric" },
1079
+ React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.medium } }),
1080
+ stats.medium
434
1081
  ),
435
1082
  fixBadge,
436
1083
  React.createElement(TrendChart, { points: trend.points }),
437
- ...dimBadges,
438
- )
1084
+ ...dimBadges
1085
+ );
439
1086
  }
440
-
441
- /** Stats card (empty-findings case of the turn-tail chain). */
442
1087
  function StatsCard(props) {
443
- const report = props.report
444
- const [showHistory, setShowHistory] = React.useState(false)
445
- const stats = severityStats(report)
446
- const total = stats.critical + stats.high + stats.medium + stats.low
1088
+ const report = props.report;
1089
+ const [showHistory, setShowHistory] = React.useState(false);
1090
+ const stats = severityStats(report);
1091
+ const total = stats.critical + stats.high + stats.medium + stats.low;
447
1092
  const rows = [
448
- { label: 'Critical', value: stats.critical, color: SEVERITY_COLOR.critical },
449
- { label: 'High', value: stats.high, color: SEVERITY_COLOR.high },
450
- { label: 'Medium', value: stats.medium, color: SEVERITY_COLOR.medium },
451
- { label: 'Low', value: stats.low, color: SEVERITY_COLOR.low },
452
- ].map((r) =>
453
- React.createElement('div', { key: r.label, className: 'iterate-stat' },
454
- React.createElement('div', { className: 'iterate-stat-num', style: { color: r.color } }, String(r.value)),
455
- React.createElement('div', { className: 'iterate-stat-label' }, r.label),
1093
+ { label: "Critical", value: stats.critical, color: SEVERITY_COLOR.critical },
1094
+ { label: "High", value: stats.high, color: SEVERITY_COLOR.high },
1095
+ { label: "Medium", value: stats.medium, color: SEVERITY_COLOR.medium },
1096
+ { label: "Low", value: stats.low, color: SEVERITY_COLOR.low }
1097
+ ].map(
1098
+ (r) => React.createElement(
1099
+ "div",
1100
+ { key: r.label, className: "iterate-stat" },
1101
+ React.createElement("div", { className: "iterate-stat-num", style: { color: r.color } }, String(r.value)),
1102
+ React.createElement("div", { className: "iterate-stat-label" }, r.label)
1103
+ )
1104
+ );
1105
+ const history = buildRoundHistory(report);
1106
+ const trend = computeTrendMetrics(report);
1107
+ const historyRows = history.map(
1108
+ (h) => React.createElement(
1109
+ "tr",
1110
+ { key: h.round },
1111
+ React.createElement("td", { className: "iterate-history-num" }, `Round ${h.round}`),
1112
+ React.createElement("td", {}, String(h.count)),
1113
+ React.createElement("td", { style: { color: SEVERITY_COLOR.critical } }, String(h.critical)),
1114
+ React.createElement("td", { style: { color: SEVERITY_COLOR.high } }, String(h.high)),
1115
+ React.createElement("td", { style: { color: SEVERITY_COLOR.medium } }, String(h.medium)),
1116
+ React.createElement("td", { style: { color: SEVERITY_COLOR.low } }, String(h.low))
1117
+ )
1118
+ );
1119
+ const trendLine = `\u9996\u8F6E ${trend.firstRound} \u2192 \u672B\u8F6E ${trend.lastRound} \u9879\uFF0C\u964D\u5E45 ${trend.reductionPercent}%${trend.converged ? "\uFF0C\u5DF2\u6536\u655B" : ""}`;
1120
+ const completion = buildCompletionSummary(report);
1121
+ return React.createElement(
1122
+ "div",
1123
+ { "data-iterate-root": "", "data-iterate": "stats", className: "iterate-stats" },
1124
+ React.createElement(
1125
+ "div",
1126
+ { className: "iterate-triage-head" },
1127
+ React.createElement("span", {}, "Iterate \xB7 \u6536\u655B\u7EDF\u8BA1"),
1128
+ React.createElement(
1129
+ "span",
1130
+ { className: "iterate-triage-hint" },
1131
+ `Round ${getCurrentRound(report)}/${getTotalRounds(report)} \xB7 ${total} findings \xB7 ${report.convergence && report.convergence.converged ? "\u5DF2\u6536\u655B" : "\u672A\u6536\u655B"}`
1132
+ )
456
1133
  ),
457
- )
458
-
459
- const history = buildRoundHistory(report)
460
- const trend = computeTrendMetrics(report)
461
- const historyRows = history.map((h) =>
462
- React.createElement('tr', { key: h.round },
463
- React.createElement('td', { className: 'iterate-history-num' }, `Round ${h.round}`),
464
- React.createElement('td', {}, String(h.count)),
465
- React.createElement('td', { style: { color: SEVERITY_COLOR.critical } }, String(h.critical)),
466
- React.createElement('td', { style: { color: SEVERITY_COLOR.high } }, String(h.high)),
467
- React.createElement('td', { style: { color: SEVERITY_COLOR.medium } }, String(h.medium)),
468
- React.createElement('td', { style: { color: SEVERITY_COLOR.low } }, String(h.low)),
1134
+ React.createElement("div", { className: "iterate-stats-grid" }, ...rows),
1135
+ React.createElement("div", { className: "iterate-completion", "data-ok": trend.converged ? "" : void 0, "data-warn": trend.converged ? void 0 : "" }, completion),
1136
+ React.createElement(
1137
+ "div",
1138
+ { className: "iterate-triage-foot", style: { marginTop: 8, borderRadius: 8 } },
1139
+ React.createElement("span", {}, trendLine),
1140
+ React.createElement("button", {
1141
+ className: "iterate-btn",
1142
+ "data-primary": showHistory ? "" : void 0,
1143
+ onClick: () => setShowHistory((v) => !v)
1144
+ }, showHistory ? "\u6536\u8D77\u5386\u53F2" : "\u5386\u53F2 / \u8D8B\u52BF")
469
1145
  ),
470
- )
471
- const trendLine = `首轮 ${trend.firstRound} → 末轮 ${trend.lastRound} 项,降幅 ${trend.reductionPercent}%${trend.converged ? ',已收敛' : ''}`
472
- const completion = buildCompletionSummary(report)
473
-
474
- return React.createElement('div', { 'data-iterate-root': '', 'data-iterate': 'stats', className: 'iterate-stats' },
475
- React.createElement('div', { className: 'iterate-triage-head' },
476
- React.createElement('span', {}, 'Iterate · 收敛统计'),
477
- React.createElement('span', { className: 'iterate-triage-hint' },
478
- `Round ${getCurrentRound(report)}/${getTotalRounds(report)} · ${total} findings · ${report.convergence && report.convergence.converged ? '已收敛' : '未收敛'}`,
1146
+ showHistory ? React.createElement(
1147
+ "div",
1148
+ { className: "iterate-history-body" },
1149
+ React.createElement(
1150
+ "table",
1151
+ { className: "iterate-history-table" },
1152
+ React.createElement(
1153
+ "thead",
1154
+ {},
1155
+ React.createElement(
1156
+ "tr",
1157
+ {},
1158
+ React.createElement("th", {}, "\u8F6E\u6B21"),
1159
+ React.createElement("th", {}, "\u53D1\u73B0"),
1160
+ React.createElement("th", { style: { color: SEVERITY_COLOR.critical } }, "CRIT"),
1161
+ React.createElement("th", { style: { color: SEVERITY_COLOR.high } }, "HIGH"),
1162
+ React.createElement("th", { style: { color: SEVERITY_COLOR.medium } }, "MED"),
1163
+ React.createElement("th", { style: { color: SEVERITY_COLOR.low } }, "LOW")
1164
+ )
1165
+ ),
1166
+ React.createElement("tbody", {}, ...historyRows)
479
1167
  ),
480
- ),
481
- React.createElement('div', { className: 'iterate-stats-grid' }, ...rows),
482
- React.createElement('div', { className: 'iterate-completion', 'data-ok': trend.converged ? '' : undefined, 'data-warn': trend.converged ? undefined : '' }, completion),
483
- React.createElement('div', { className: 'iterate-triage-foot', style: { marginTop: 8, borderRadius: 8 } },
484
- React.createElement('span', {}, trendLine),
485
- React.createElement('button', {
486
- className: 'iterate-btn',
487
- 'data-primary': showHistory ? '' : undefined,
488
- onClick: () => setShowHistory((v) => !v),
489
- }, showHistory ? '收起历史' : '历史 / 趋势'),
490
- ),
491
- showHistory
492
- ? React.createElement('div', { className: 'iterate-history-body' },
493
- React.createElement('table', { className: 'iterate-history-table' },
494
- React.createElement('thead', {},
495
- React.createElement('tr', {},
496
- React.createElement('th', {}, '轮次'),
497
- React.createElement('th', {}, '发现'),
498
- React.createElement('th', { style: { color: SEVERITY_COLOR.critical } }, 'CRIT'),
499
- React.createElement('th', { style: { color: SEVERITY_COLOR.high } }, 'HIGH'),
500
- React.createElement('th', { style: { color: SEVERITY_COLOR.medium } }, 'MED'),
501
- React.createElement('th', { style: { color: SEVERITY_COLOR.low } }, 'LOW'),
502
- ),
503
- ),
504
- React.createElement('tbody', {}, ...historyRows),
505
- ),
506
- React.createElement('div', { className: 'iterate-history-trendline' },
507
- React.createElement(TrendChart, { points: trend.points }),
508
- ),
509
- )
510
- : null,
511
- )
1168
+ React.createElement(
1169
+ "div",
1170
+ { className: "iterate-history-trendline" },
1171
+ React.createElement(TrendChart, { points: trend.points })
1172
+ )
1173
+ ) : null
1174
+ );
512
1175
  }
513
-
514
- /** Triage panel: per-finding y / n / a with filters, batch ops, keyboard shortcuts. */
515
1176
  function TriagePanel(props) {
516
- const report = props.report
517
- const findings = report.findings
518
- const storageKey = TRIAGE_STORAGE_PREFIX + hashReport(report)
1177
+ const report = props.report;
1178
+ const findings = report.findings || [];
1179
+ const storageKey = TRIAGE_STORAGE_PREFIX + hashReport(report);
519
1180
  const [verdicts, setVerdicts] = React.useState(() => {
520
- const initial = buildTriageState(report)
521
- const saved = storage ? storage.get(storageKey) : null
1181
+ const initial = buildTriageState(report);
1182
+ const saved = storage ? storage.get(storageKey) : null;
522
1183
  if (saved) {
523
1184
  try {
524
- const parsed = JSON.parse(saved)
525
- if (parsed && typeof parsed === 'object') {
1185
+ const parsed = JSON.parse(saved);
1186
+ if (parsed && typeof parsed === "object") {
526
1187
  for (const key of Object.keys(initial)) {
527
- if (parsed[key] === 'keep' || parsed[key] === 'skip' || parsed[key] === 'ignore') {
528
- initial[key] = parsed[key]
1188
+ if (parsed[key] === "keep" || parsed[key] === "skip" || parsed[key] === "ignore") {
1189
+ initial[key] = parsed[key];
529
1190
  }
530
1191
  }
531
1192
  }
532
- } catch { /* corrupt storage, fall back to defaults */ }
1193
+ } catch {
1194
+ }
533
1195
  }
534
- return initial
535
- })
536
- const [payload, setPayload] = React.useState(null)
537
- const [copied, setCopied] = React.useState(false)
538
- const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: '' })
539
- const [selected, setSelected] = React.useState(null)
540
- const [selectAll, setSelectAll] = React.useState(false)
541
-
542
- /** Persist + return the next verdicts state. */
1196
+ return initial;
1197
+ });
1198
+ const [payload, setPayload] = React.useState(null);
1199
+ const [copied, setCopied] = React.useState(false);
1200
+ const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: "" });
1201
+ const [selected, setSelected] = React.useState(null);
1202
+ const [selectAll, setSelectAll] = React.useState(false);
543
1203
  const persistVerdicts = (next) => {
544
- if (storage) storage.set(storageKey, JSON.stringify(next))
545
- return next
546
- }
547
-
1204
+ if (storage) storage.set(storageKey, JSON.stringify(next));
1205
+ return next;
1206
+ };
548
1207
  const setVerdict = (index, verdict) => {
549
- setVerdicts((prev) => persistVerdicts({ ...prev, [String(index)]: verdict }))
550
- }
551
-
552
- // ── Filtering (visible findings + their original indices) ───────────────
553
- const { filtered, indices } = filterFindingsWithIndices(findings, filter)
554
- const indicesKey = indices.join(',')
555
- const options = buildFilterOptions(findings)
556
- const filterActive = filter.severities.length > 0 || filter.dimensions.length > 0 || filter.search !== ''
557
-
558
- const setSeverityFilter = (value) => setFilter((f) => ({ ...f, severities: value ? [value] : [] }))
559
- const setDimensionFilter = (value) => setFilter((f) => ({ ...f, dimensions: value ? [value] : [] }))
560
- const setSearchFilter = (value) => setFilter((f) => ({ ...f, search: value }))
561
- const clearFilter = () => setFilter({ severities: [], dimensions: [], search: '' })
562
-
563
- // ── Batch operations (apply to the currently VISIBLE findings, or to ALL
564
- // findings when the select-all toggle is on) ──────────────────────────
565
- const allIndices = allVerdictKeys(verdicts)
566
- const batchTarget = selectAll ? allIndices : indices
1208
+ setVerdicts((prev) => persistVerdicts({ ...prev, [String(index)]: verdict }));
1209
+ };
1210
+ const { filtered, indices } = filterFindingsWithIndices(findings, filter);
1211
+ const indicesKey = indices.join(",");
1212
+ const options = buildFilterOptions(findings);
1213
+ const isFilterActive = filter.severities.length > 0 || filter.dimensions.length > 0 || filter.search !== "";
1214
+ const setSeverityFilter = (value) => setFilter((f) => ({ ...f, severities: value ? [value] : [] }));
1215
+ const setDimensionFilter = (value) => setFilter((f) => ({ ...f, dimensions: value ? [value] : [] }));
1216
+ const setSearchFilter = (value) => setFilter((f) => ({ ...f, search: value }));
1217
+ const clearFilter = () => setFilter({ severities: [], dimensions: [], search: "" });
1218
+ const allIndices = allVerdictKeys(verdicts);
1219
+ const batchTarget = selectAll ? allIndices : indices;
567
1220
  const applyBatch = (verdict) => {
568
- setVerdicts((prev) => persistVerdicts(batchSetVerdict(prev, batchTarget, verdict)))
569
- }
1221
+ setVerdicts((prev) => persistVerdicts(batchSetVerdict(prev, batchTarget, verdict)));
1222
+ };
570
1223
  const applyBatchAll = (verdict) => {
571
- setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)))
572
- }
1224
+ setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)));
1225
+ };
573
1226
  const doResetVerdicts = () => {
574
- setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, 'keep')))
575
- setSelectAll(false)
576
- }
577
-
578
- // ── Keyboard shortcuts (y / n / a on the selected finding, ↑/↓ to move) ──
1227
+ setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, "keep")));
1228
+ setSelectAll(false);
1229
+ };
579
1230
  React.useEffect(() => {
580
- const doc = typeof document !== 'undefined' ? document : null
581
- if (!doc) return
1231
+ const doc = typeof document !== "undefined" ? document : null;
1232
+ if (!doc) return;
582
1233
  const onKeyDown = (ev) => {
583
- const t = ev.target
584
- if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT')) return
585
- const verdict = keyToVerdict(ev.key)
1234
+ const t = ev.target;
1235
+ if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
1236
+ const verdict = keyToVerdict(ev.key);
586
1237
  if (verdict && selected !== null && indices.includes(selected)) {
587
- ev.preventDefault()
588
- setVerdict(selected, verdict)
589
- const pos = indices.indexOf(selected)
590
- const nextIdx = indices[pos + 1]
591
- if (nextIdx !== undefined) setSelected(nextIdx)
592
- return
1238
+ ev.preventDefault();
1239
+ setVerdict(selected, verdict);
1240
+ const pos = indices.indexOf(selected);
1241
+ const nextIdx = indices[pos + 1];
1242
+ if (nextIdx !== void 0) setSelected(nextIdx);
1243
+ return;
593
1244
  }
594
- if (ev.key === 'ArrowDown' && indices.length > 0) {
595
- ev.preventDefault()
596
- const pos = selected === null ? -1 : indices.indexOf(selected)
597
- setSelected(indices[Math.min(pos + 1, indices.length - 1)])
598
- return
1245
+ if (ev.key === "ArrowDown" && indices.length > 0) {
1246
+ ev.preventDefault();
1247
+ const pos = selected === null ? -1 : indices.indexOf(selected);
1248
+ setSelected(indices[Math.min(pos + 1, indices.length - 1)] ?? null);
1249
+ return;
599
1250
  }
600
- if (ev.key === 'ArrowUp' && indices.length > 0) {
601
- ev.preventDefault()
602
- const pos = selected === null ? 0 : indices.indexOf(selected)
603
- setSelected(indices[Math.max(pos - 1, 0)])
1251
+ if (ev.key === "ArrowUp" && indices.length > 0) {
1252
+ ev.preventDefault();
1253
+ const pos = selected === null ? 0 : indices.indexOf(selected);
1254
+ setSelected(indices[Math.max(pos - 1, 0)] ?? null);
604
1255
  }
605
- }
606
- doc.addEventListener('keydown', onKeyDown)
607
- return () => doc.removeEventListener('keydown', onKeyDown)
608
- }, [selected, indicesKey])
609
-
610
- const ignored = collectIgnoredEntries(verdicts, findings)
611
- const ignoredCount = ignored.length
612
- const counts = countVerdicts(verdicts)
613
-
1256
+ };
1257
+ doc.addEventListener("keydown", onKeyDown);
1258
+ return () => doc.removeEventListener("keydown", onKeyDown);
1259
+ }, [selected, indicesKey]);
1260
+ const ignored = collectIgnoredEntries(verdicts, findings);
1261
+ const ignoredCount = ignored.length;
1262
+ const counts = countVerdicts(verdicts);
614
1263
  const doCopyYaml = () => {
615
- const yaml = toKnownIntentionalYaml(ignored)
616
- if (!yaml) return
617
- const ok = copyText(yaml)
618
- setCopied(true)
619
- setTimeout(() => setCopied(false), 1600)
620
- if (!ok) setPayload(yaml)
621
- }
622
-
1264
+ const yaml = toKnownIntentionalYaml(ignored);
1265
+ if (!yaml) return;
1266
+ const ok = copyText(yaml);
1267
+ setCopied(true);
1268
+ setTimeout(() => setCopied(false), 1600);
1269
+ if (!ok) setPayload(yaml);
1270
+ };
623
1271
  const doBuildInstruction = () => {
624
- const text = buildApplyInstruction(ignored)
625
- setPayload(text)
626
- if (text) copyText(text)
627
- }
628
-
1272
+ const text = buildApplyInstruction(ignored);
1273
+ setPayload(text);
1274
+ if (text) copyText(text);
1275
+ };
629
1276
  const rows = filtered.map((finding, i) => {
630
- const index = indices[i]
631
- const severity = finding.severity || 'low'
632
- const verdict = verdicts[String(index)] || 'keep'
633
- const isSelected = selected === index
634
- const btn = (label, value, title) =>
1277
+ const index = indices[i];
1278
+ const severity = finding.severity || "low";
1279
+ const verdict = verdicts[String(index)] || "keep";
1280
+ const isSelected = selected === index;
1281
+ const btn = (label, value, title) => React.createElement(
1282
+ "button",
1283
+ {
1284
+ key: value,
1285
+ className: "iterate-vbtn",
1286
+ "data-active": verdict === value ? value : void 0,
1287
+ title,
1288
+ onClick: () => setVerdict(index, value)
1289
+ },
1290
+ label
1291
+ );
1292
+ return React.createElement(
1293
+ "div",
1294
+ {
1295
+ key: String(index),
1296
+ className: "iterate-finding",
1297
+ "data-selected": isSelected ? "" : void 0,
1298
+ onClick: () => setSelected(index)
1299
+ },
635
1300
  React.createElement(
636
- 'button',
637
- {
638
- key: value,
639
- className: 'iterate-vbtn',
640
- 'data-active': verdict === value ? value : undefined,
641
- title,
642
- onClick: () => setVerdict(index, value),
643
- },
644
- label,
645
- )
646
-
647
- return React.createElement('div', {
648
- key: String(index),
649
- className: 'iterate-finding',
650
- 'data-selected': isSelected ? '' : undefined,
651
- onClick: () => setSelected(index),
652
- },
653
- React.createElement('div', { className: 'iterate-finding-meta' },
654
- React.createElement('span', { className: 'iterate-sev-dot', style: { background: SEVERITY_COLOR[severity] || SEVERITY_COLOR.low } }),
655
- React.createElement('span', {}, String(SEVERITY_LABEL[severity] || 'LOW')),
656
- React.createElement('span', { className: 'iterate-finding-file' }, String(finding.file || '?')),
657
- finding.line ? React.createElement('span', {}, `:${finding.line}`) : null,
658
- React.createElement('span', {}, String(finding.dimension || '')),
659
- ),
660
- React.createElement('div', { className: 'iterate-finding-summary' }, String(finding.summary || '')),
661
- React.createElement('div', { className: 'iterate-finding-actions' },
662
- btn('y 修复', 'keep', '保留该 finding,进入修复'),
663
- btn('n 跳过', 'skip', '跳过该 finding'),
664
- btn('a 已知有意', 'ignore', '标记为已知有意并写入 known_intentional'),
1301
+ "div",
1302
+ { className: "iterate-finding-meta" },
1303
+ React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(severity) } }),
1304
+ React.createElement("span", {}, severityLabel(severity)),
1305
+ React.createElement("span", { className: "iterate-finding-file" }, String(finding.file || "?")),
1306
+ finding.line ? React.createElement("span", {}, `:${finding.line}`) : null,
1307
+ React.createElement("span", {}, String(finding.dimension || ""))
665
1308
  ),
666
- )
667
- })
668
-
669
- return React.createElement('div', { 'data-iterate-root': '', 'data-iterate': 'triage', className: 'iterate-triage' },
670
- React.createElement('div', { className: 'iterate-triage-head' },
671
- React.createElement('span', {}, `Iterate · Findings 分诊 (${filtered.length}/${findings.length})`),
672
- React.createElement('span', { className: 'iterate-triage-hint' }, 'y=修复 · n=跳过 · a=已知有意 · ↑/↓ 选择'),
1309
+ React.createElement("div", { className: "iterate-finding-summary" }, String(finding.summary || "")),
1310
+ React.createElement(
1311
+ "div",
1312
+ { className: "iterate-finding-actions" },
1313
+ btn("y \u4FEE\u590D", "keep", "\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D"),
1314
+ btn("n \u8DF3\u8FC7", "skip", "\u8DF3\u8FC7\u8BE5 finding"),
1315
+ btn("a \u5DF2\u77E5\u6709\u610F", "ignore", "\u6807\u8BB0\u4E3A\u5DF2\u77E5\u6709\u610F\u5E76\u5199\u5165 known_intentional")
1316
+ )
1317
+ );
1318
+ });
1319
+ return React.createElement(
1320
+ "div",
1321
+ { "data-iterate-root": "", "data-iterate": "triage", className: "iterate-triage" },
1322
+ React.createElement(
1323
+ "div",
1324
+ { className: "iterate-triage-head" },
1325
+ React.createElement("span", {}, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
1326
+ React.createElement("span", { className: "iterate-triage-hint" }, "y=\u4FEE\u590D \xB7 n=\u8DF3\u8FC7 \xB7 a=\u5DF2\u77E5\u6709\u610F \xB7 \u2191/\u2193 \u9009\u62E9")
673
1327
  ),
674
- React.createElement('div', { className: 'iterate-filter' },
675
- React.createElement('select', { className: 'iterate-filter-select', value: filter.severities[0] || '', onChange: (e) => setSeverityFilter(e.target.value), title: '按严重度筛选' },
676
- React.createElement('option', { value: '' }, '全部严重度'),
677
- ...options.severities.map((s) =>
678
- React.createElement('option', { key: s.value, value: s.value }, `${SEVERITY_LABEL[s.value] || s.value} (${s.count})`),
679
- ),
1328
+ React.createElement(
1329
+ "div",
1330
+ { className: "iterate-filter" },
1331
+ React.createElement(
1332
+ "select",
1333
+ { className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), title: "\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009" },
1334
+ React.createElement("option", { value: "" }, "\u5168\u90E8\u4E25\u91CD\u5EA6"),
1335
+ ...options.severities.map(
1336
+ (s) => React.createElement("option", { key: s.value, value: s.value }, `${severityLabel(s.value)} (${s.count})`)
1337
+ )
680
1338
  ),
681
- React.createElement('select', { className: 'iterate-filter-select', value: filter.dimensions[0] || '', onChange: (e) => setDimensionFilter(e.target.value), title: '按维度筛选' },
682
- React.createElement('option', { value: '' }, '全部维度'),
683
- ...options.dimensions.map((d) =>
684
- React.createElement('option', { key: d.value, value: d.value }, `${d.value} (${d.count})`),
685
- ),
1339
+ React.createElement(
1340
+ "select",
1341
+ { className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), title: "\u6309\u7EF4\u5EA6\u7B5B\u9009" },
1342
+ React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
1343
+ ...options.dimensions.map(
1344
+ (d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
1345
+ )
686
1346
  ),
687
- React.createElement('input', {
688
- className: 'iterate-filter-search',
689
- type: 'search',
690
- placeholder: '搜索文件 / 摘要…',
1347
+ React.createElement("input", {
1348
+ className: "iterate-filter-search",
1349
+ type: "search",
1350
+ placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
691
1351
  value: filter.search,
692
- onChange: (e) => setSearchFilter(e.target.value),
1352
+ onChange: (e) => setSearchFilter(e.target.value)
693
1353
  }),
694
- React.createElement('span', { className: 'iterate-filter-count' },
695
- filterActive
696
- ? React.createElement('button', { className: 'iterate-batch-btn', onClick: clearFilter }, `清除筛选(显示 ${filtered.length})`)
697
- : `共 ${findings.length} 项`,
698
- ),
1354
+ React.createElement(
1355
+ "span",
1356
+ { className: "iterate-filter-count" },
1357
+ isFilterActive ? React.createElement("button", { className: "iterate-batch-btn", onClick: clearFilter }, `\u6E05\u9664\u7B5B\u9009\uFF08\u663E\u793A ${filtered.length}\uFF09`) : `\u5171 ${findings.length} \u9879`
1358
+ )
699
1359
  ),
700
- React.createElement('div', { className: 'iterate-batch' },
701
- React.createElement('span', { className: 'iterate-batch-label' }, '批量:'),
702
- React.createElement('label', { className: 'iterate-batch-check', title: '勾选后批量按钮作用于全部 findings,否则仅当前可见' },
703
- React.createElement('input', { type: 'checkbox', checked: selectAll, onChange: (e) => setSelectAll(e.target.checked) }),
704
- selectAll ? `全部 ${allIndices.length}` : '全选',
1360
+ React.createElement(
1361
+ "div",
1362
+ { className: "iterate-batch" },
1363
+ React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\uFF1A"),
1364
+ React.createElement(
1365
+ "label",
1366
+ { className: "iterate-batch-check", title: "\u52FE\u9009\u540E\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings\uFF0C\u5426\u5219\u4EC5\u5F53\u524D\u53EF\u89C1" },
1367
+ React.createElement("input", { type: "checkbox", checked: selectAll, onChange: (e) => setSelectAll(e.target.checked) }),
1368
+ selectAll ? `\u5168\u90E8 ${allIndices.length}` : "\u5168\u9009"
705
1369
  ),
706
- React.createElement('button', { className: 'iterate-batch-btn', onClick: () => applyBatch('keep') }, '全部 y'),
707
- React.createElement('button', { className: 'iterate-batch-btn', onClick: () => applyBatch('skip') }, '全部 n'),
708
- React.createElement('button', { className: 'iterate-batch-btn', onClick: () => applyBatch('ignore') }, '全部 a'),
709
- React.createElement('span', { className: 'iterate-batch-label', style: { marginLeft: 8 } }, '全部:'),
710
- React.createElement('button', { className: 'iterate-batch-btn', onClick: () => applyBatchAll('keep') }, 'y'),
711
- React.createElement('button', { className: 'iterate-batch-btn', onClick: () => applyBatchAll('skip') }, 'n'),
712
- React.createElement('button', { className: 'iterate-batch-btn', onClick: () => applyBatchAll('ignore') }, 'a'),
713
- React.createElement('button', { className: 'iterate-batch-btn', onClick: doResetVerdicts, title: '把所有判定恢复为默认 y(修复)' }, '重置'),
1370
+ React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "\u5168\u90E8 y"),
1371
+ React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "\u5168\u90E8 n"),
1372
+ React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("ignore") }, "\u5168\u90E8 a"),
1373
+ React.createElement("span", { className: "iterate-batch-label", style: { marginLeft: 8 } }, "\u5168\u90E8\uFF1A"),
1374
+ React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("keep") }, "y"),
1375
+ React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("skip") }, "n"),
1376
+ React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("ignore") }, "a"),
1377
+ React.createElement("button", { className: "iterate-batch-btn", onClick: doResetVerdicts, title: "\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09" }, "\u91CD\u7F6E")
714
1378
  ),
715
1379
  ...rows,
716
- React.createElement('div', { className: 'iterate-triage-foot' },
717
- React.createElement('span', {}, `y ${counts.keep} · n ${counts.skip} · a ${counts.ignore} · 待写回 known_intentional:${ignoredCount} 条`),
718
- React.createElement('span', { style: { display: 'flex', gap: 6 } },
719
- React.createElement('button', { className: 'iterate-btn', 'data-primary': '', 'data-copied': copied ? '' : undefined, onClick: doCopyYaml }, copied ? '已复制' : '复制 known_intentional'),
720
- React.createElement('button', { className: 'iterate-btn', onClick: doBuildInstruction }, '生成应用指令'),
721
- ),
1380
+ React.createElement(
1381
+ "div",
1382
+ { className: "iterate-triage-foot" },
1383
+ React.createElement("span", {}, `y ${counts.keep} \xB7 n ${counts.skip} \xB7 a ${counts.ignore} \xB7 \u5F85\u5199\u56DE known_intentional\uFF1A${ignoredCount} \u6761`),
1384
+ React.createElement(
1385
+ "span",
1386
+ { style: { display: "flex", gap: 6 } },
1387
+ React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": copied ? "" : void 0, onClick: doCopyYaml }, copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236 known_intentional"),
1388
+ React.createElement("button", { className: "iterate-btn", onClick: doBuildInstruction }, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
1389
+ )
722
1390
  ),
723
- payload
724
- ? React.createElement('div', { className: 'iterate-payload' }, payload)
725
- : null,
726
- )
1391
+ payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
1392
+ );
727
1393
  }
728
-
729
- /** Meta-review verdict banner: surfaces the closing dry-run audit result. */
730
1394
  function VerdictBanner(props) {
731
- const verdict = props.verdict
732
- if (!verdict) return null
733
- const ok = verdict.verdict === 'approved'
734
- const item = (num, unit) =>
735
- React.createElement('span', { className: 'iterate-verdict-item' },
736
- React.createElement('b', {}, String(num)), ` ${unit}`)
737
- const phrase = (text) =>
738
- React.createElement('span', { className: 'iterate-verdict-item' }, text)
1395
+ const verdict = props.verdict;
1396
+ if (!verdict) return null;
1397
+ const ok = verdict.verdict === "approved";
1398
+ const item = (num, unit) => React.createElement(
1399
+ "span",
1400
+ { className: "iterate-verdict-item" },
1401
+ React.createElement("b", {}, String(num)),
1402
+ ` ${unit}`
1403
+ );
1404
+ const phrase = (text) => React.createElement("span", { className: "iterate-verdict-item" }, text);
739
1405
  return React.createElement(
740
- 'div',
741
- { 'data-iterate-root': '', 'data-iterate': 'verdict', className: 'iterate-verdict' },
742
- React.createElement('span', { className: 'iterate-verdict-tag', 'data-ok': ok ? '' : undefined, 'data-warn': ok ? undefined : '' },
743
- ok ? '报告已批准' : '报告需修订'),
744
- React.createElement('span', { className: 'iterate-verdict-detail' },
745
- item(verdict.totalFindings, '项发现'),
746
- item(verdict.totalRounds, '轮'),
747
- item(verdict.checksRun, '项审查'),
748
- ok
749
- ? phrase('报告通过全部一致性检查')
750
- : item(verdict.reportIssues, '处报告缺陷'),
751
- phrase(verdict.converged ? '已收敛' : '未收敛'),
1406
+ "div",
1407
+ { "data-iterate-root": "", "data-iterate": "verdict", className: "iterate-verdict" },
1408
+ React.createElement(
1409
+ "span",
1410
+ { className: "iterate-verdict-tag", "data-ok": ok ? "" : void 0, "data-warn": ok ? void 0 : "" },
1411
+ ok ? "\u62A5\u544A\u5DF2\u6279\u51C6" : "\u62A5\u544A\u9700\u4FEE\u8BA2"
752
1412
  ),
753
- )
1413
+ React.createElement(
1414
+ "span",
1415
+ { className: "iterate-verdict-detail" },
1416
+ item(verdict.totalFindings, "\u9879\u53D1\u73B0"),
1417
+ item(verdict.totalRounds, "\u8F6E"),
1418
+ item(verdict.checksRun, "\u9879\u5BA1\u67E5"),
1419
+ ok ? phrase("\u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5") : item(verdict.reportIssues, "\u5904\u62A5\u544A\u7F3A\u9677"),
1420
+ phrase(verdict.converged ? "\u5DF2\u6536\u655B" : "\u672A\u6536\u655B")
1421
+ )
1422
+ );
754
1423
  }
755
-
756
- /** Turn-tail chain entry: triage when findings exist, stats card otherwise. */
757
1424
  function TurnTailEntry(props) {
758
- const candidates = []
759
- if (props && props.turn) candidates.push(props.turn)
760
- if (props && props.matched) candidates.push(props.matched)
761
- if (props && props.data) candidates.push(props.data)
762
- candidates.push(props)
763
-
764
- // Meta-review verdict (dry-run closing result), found before the report.
765
- let verdict = null
1425
+ const candidates = [];
1426
+ if (props && props.turn) candidates.push(props.turn);
1427
+ if (props && props.matched) candidates.push(props.matched);
1428
+ if (props && props.data) candidates.push(props.data);
1429
+ candidates.push(props);
1430
+ let verdict = null;
766
1431
  for (const c of candidates) {
767
- const run = scanSessionForRunSummary(c)
768
- if (run) { verdict = extractVerdict(run); break }
1432
+ const run = scanSessionForRunSummary(c);
1433
+ if (run) {
1434
+ verdict = extractVerdict(run);
1435
+ break;
1436
+ }
769
1437
  }
770
-
771
- let report = null
1438
+ let report = null;
772
1439
  for (const c of candidates) {
773
- const raw = findReportInObject(c, undefined, 24) || scanSessionForReport(c)
774
- if (raw) { report = normalizeReport(raw); break }
1440
+ const raw = findReportInObject(c, void 0, 24) || scanSessionForReport(c);
1441
+ if (raw) {
1442
+ report = normalizeReport(raw);
1443
+ break;
1444
+ }
775
1445
  }
776
-
777
- const blocks = []
778
- if (verdict) blocks.push(React.createElement(VerdictBanner, { key: 'verdict', verdict }))
1446
+ const blocks = [];
1447
+ if (verdict) blocks.push(React.createElement(VerdictBanner, { key: "verdict", verdict }));
779
1448
  if (report) {
780
- // Render through React.createElement so each panel is a real component with
781
- // its own hook identity (calling them as functions would violate the Rules
782
- // of Hooks and crash when the findings/empty branch flips between renders).
783
- const panel = !report.findings || report.findings.length === 0
784
- ? React.createElement(StatsCard, { report })
785
- : React.createElement(TriagePanel, { report })
786
- blocks.push(React.createElement('div', { key: 'report' }, panel))
1449
+ const panel = !report.findings || report.findings.length === 0 ? React.createElement(StatsCard, { report }) : React.createElement(TriagePanel, { report });
1450
+ blocks.push(React.createElement("div", { key: "report" }, panel));
787
1451
  }
788
- if (blocks.length === 0) return null
789
- return React.createElement('div', { 'data-iterate-root': '', className: 'iterate-turn-tail-root' }, ...blocks)
1452
+ if (blocks.length === 0) return null;
1453
+ return React.createElement("div", { "data-iterate-root": "", className: "iterate-turn-tail-root" }, ...blocks);
790
1454
  }
791
-
792
- /** Progress capsule: briefly surfaces round-completion (incl. convergence). */
793
1455
  function ProgressCapsule() {
794
- const [info, setInfo] = React.useState(null)
1456
+ const [info, setInfo] = React.useState(null);
795
1457
  React.useEffect(() => {
796
- let timer = null
1458
+ let timer = null;
797
1459
  const listener = (payload) => {
798
- const converged = payload && payload.converged === true
799
- const round = payload && typeof payload.round === 'number' ? payload.round : '?'
800
- setInfo({ text: converged ? `Round ${round} 完成 · 已收敛` : `Round ${round} 完成`, ok: converged })
801
- if (timer) clearTimeout(timer)
802
- // Converged notifications stay a bit longer.
803
- timer = setTimeout(() => setInfo(null), converged ? 3600 : 2400)
804
- }
805
- roundPulseListeners.push(listener)
1460
+ const converged = payload && payload.converged === true;
1461
+ const round = payload && typeof payload.round === "number" ? payload.round : "?";
1462
+ setInfo({ text: converged ? `Round ${round} \u5B8C\u6210 \xB7 \u5DF2\u6536\u655B` : `Round ${round} \u5B8C\u6210`, ok: converged });
1463
+ if (timer) clearTimeout(timer);
1464
+ timer = setTimeout(() => setInfo(null), converged ? 3600 : 2400);
1465
+ };
1466
+ roundPulseListeners.push(listener);
806
1467
  return () => {
807
- const i = roundPulseListeners.indexOf(listener)
808
- if (i >= 0) roundPulseListeners.splice(i, 1)
809
- if (timer) clearTimeout(timer)
810
- }
811
- }, [])
812
- if (!info) return null
813
- return React.createElement('div', { 'data-iterate-root': '', className: 'iterate-capsule', 'data-ok': info.ok ? '' : undefined }, info.text)
1468
+ const i = roundPulseListeners.indexOf(listener);
1469
+ if (i >= 0) roundPulseListeners.splice(i, 1);
1470
+ if (timer) clearTimeout(timer);
1471
+ };
1472
+ }, []);
1473
+ if (!info) return null;
1474
+ return React.createElement("div", { "data-iterate-root": "", className: "iterate-capsule", "data-ok": info.ok ? "" : void 0 }, info.text);
814
1475
  }
815
-
816
- /** Settings page section (root scope; reads localStorage + latest known data). */
817
- function SettingsPanel() {
818
- const [enabled, setEnabled] = React.useState(themeEnabled)
819
- const [copied, setCopied] = React.useState(false)
820
- const [showGuide, setShowGuide] = React.useState(false)
821
- const [clearedCount, setClearedCount] = React.useState(null)
822
- const [showStatus, setShowStatus] = React.useState(false)
823
- const guide = buildConfigEditGuide()
824
- const statusGuide = buildRuntimeStatusGuide()
825
-
1476
+ function SettingsPanel(_props) {
1477
+ const [enabled, setEnabled] = React.useState(themeEnabled);
1478
+ const [guideCopied, setGuideCopied] = React.useState(false);
1479
+ const [statusCopied, setStatusCopied] = React.useState(false);
1480
+ const [showGuide, setShowGuide] = React.useState(false);
1481
+ const [showStatus, setShowStatus] = React.useState(false);
1482
+ const [confirming, setConfirming] = React.useState(false);
1483
+ const [clearedInfo, setClearedInfo] = React.useState(null);
1484
+ const guide = buildConfigEditGuide();
1485
+ const statusGuide = buildRuntimeStatusGuide();
826
1486
  const toggleTheme = () => {
827
- const next = !enabled
828
- setEnabled(next)
829
- setThemeEnabled(next)
830
- }
831
-
832
- const doCopyGuide = () => {
833
- copyText(guide)
834
- setCopied(true)
835
- setTimeout(() => setCopied(false), 1600)
836
- }
837
-
838
- const doClearTriage = () => {
839
- const count = removeStorageByPrefix(TRIAGE_STORAGE_PREFIX)
840
- setClearedCount(count)
841
- setTimeout(() => setClearedCount(null), 3000)
842
- }
843
-
844
- return React.createElement('div', { 'data-iterate-root': '', 'data-iterate': 'settings', className: 'iterate-settings' },
845
- React.createElement('div', { className: 'iterate-settings-title' }, 'iterate 设置'),
846
- React.createElement('div', { className: 'iterate-settings-row' },
847
- React.createElement('div', {},
848
- React.createElement('div', { className: 'iterate-settings-title' }, 'iterate 主题'),
849
- React.createElement('div', { className: 'iterate-settings-desc' }, '启用暖琥珀配色的 iterate 专属皮肤(覆盖 dsh 默认主题令牌)。'),
1487
+ const next = !enabled;
1488
+ setEnabled(next);
1489
+ setThemeEnabled(next);
1490
+ };
1491
+ const flashCopied = (slot) => {
1492
+ const setter = slot === "guide" ? setGuideCopied : setStatusCopied;
1493
+ setter(true);
1494
+ setTimeout(() => setter(false), 1600);
1495
+ };
1496
+ const doCopy = (text, slot) => {
1497
+ copyText(text);
1498
+ flashCopied(slot);
1499
+ };
1500
+ const requestClear = () => {
1501
+ if (confirming) {
1502
+ const count = removeStorageByPrefix(TRIAGE_STORAGE_PREFIX);
1503
+ setClearedInfo(count);
1504
+ setConfirming(false);
1505
+ setTimeout(() => setClearedInfo(null), 3e3);
1506
+ return;
1507
+ }
1508
+ setConfirming(true);
1509
+ setTimeout(() => setConfirming(false), 3e3);
1510
+ };
1511
+ const clearButton = clearedInfo !== null ? React.createElement("button", { className: "iterate-btn", "data-copied": "", disabled: true }, `\u5DF2\u6E05\u9664 ${clearedInfo} \u6761`) : React.createElement("button", {
1512
+ className: "iterate-btn",
1513
+ "data-danger": confirming ? void 0 : "",
1514
+ "data-confirm": confirming ? "" : void 0,
1515
+ onClick: requestClear,
1516
+ title: confirming ? "\u518D\u6B21\u70B9\u51FB\u4EE5\u786E\u8BA4\u6E05\u9664\u5168\u90E8\u5224\u5B9A" : "\u6E05\u9664\u6240\u6709\u5206\u8BCA\u5224\u5B9A\u8BB0\u5F55\uFF08\u9700\u8981\u4E8C\u6B21\u786E\u8BA4\uFF09"
1517
+ }, confirming ? "\u786E\u8BA4\u6E05\u9664\uFF1F" : "\u6E05\u9664\u5206\u8BCA");
1518
+ const themeCard = React.createElement(
1519
+ "div",
1520
+ { key: "theme", className: "iterate-scard" },
1521
+ React.createElement(
1522
+ "div",
1523
+ { className: "iterate-scard-head" },
1524
+ React.createElement(
1525
+ "div",
1526
+ {},
1527
+ React.createElement("div", { className: "iterate-settings-title" }, "\u8FED\u4EE3\u4E3B\u9898"),
1528
+ React.createElement("div", { className: "iterate-settings-desc" }, "\u542F\u7528\u6696\u7425\u73C0\u914D\u8272\u7684 iterate \u4E13\u5C5E\u76AE\u80A4\uFF0C\u8986\u76D6 dsh \u9ED8\u8BA4\u4E3B\u9898\u4EE4\u724C\u3002")
850
1529
  ),
851
- React.createElement('button', {
852
- className: 'iterate-btn',
853
- 'data-primary': enabled ? '' : undefined,
854
- onClick: toggleTheme,
855
- }, enabled ? '已启用' : '已关闭'),
856
- ),
857
- React.createElement('div', { className: 'iterate-settings-row' },
858
- React.createElement('div', {},
859
- React.createElement('div', { className: 'iterate-settings-title' }, '分诊持久化'),
860
- React.createElement('div', { className: 'iterate-settings-desc' }, '分诊面板的 y/n/a 判定保存在本地浏览器(localStorage),刷新会话后仍保留。'),
1530
+ React.createElement(
1531
+ "div",
1532
+ { className: "iterate-scard-actions" },
1533
+ React.createElement("span", { className: "iterate-chip", "data-ok": enabled ? "" : void 0 }, enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u5173\u95ED"),
1534
+ React.createElement("button", {
1535
+ role: "switch",
1536
+ "aria-checked": enabled,
1537
+ "aria-label": "\u5F00\u5173 iterate \u4E3B\u9898",
1538
+ className: "iterate-switch",
1539
+ "data-on": enabled ? "" : void 0,
1540
+ onClick: toggleTheme
1541
+ }, React.createElement("span", { className: "iterate-switch-knob" }))
1542
+ )
1543
+ )
1544
+ );
1545
+ const dataCard = React.createElement(
1546
+ "div",
1547
+ { key: "data", className: "iterate-scard" },
1548
+ React.createElement(
1549
+ "div",
1550
+ { className: "iterate-scard-head" },
1551
+ React.createElement(
1552
+ "div",
1553
+ {},
1554
+ React.createElement("div", { className: "iterate-settings-title" }, "\u5206\u8BCA\u6301\u4E45\u5316"),
1555
+ React.createElement("div", { className: "iterate-settings-desc" }, "y / n / a \u5224\u5B9A\u4FDD\u5B58\u5728\u672C\u5730\u6D4F\u89C8\u5668\uFF08localStorage\uFF09\uFF0C\u5237\u65B0\u4F1A\u8BDD\u540E\u4ECD\u4FDD\u7559\u3002")
861
1556
  ),
862
- React.createElement('span', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
863
- React.createElement('span', { className: 'iterate-chip' }, '本地保存'),
864
- React.createElement('button', {
865
- className: 'iterate-btn',
866
- onClick: doClearTriage,
867
- title: '清除所有分诊判定记录',
868
- 'data-primary': clearedCount !== null ? '' : undefined,
869
- 'data-copied': clearedCount !== null ? '' : undefined,
870
- }, clearedCount !== null ? `已清除 ${clearedCount} 条` : '清除分诊'),
1557
+ React.createElement(
1558
+ "div",
1559
+ { className: "iterate-scard-actions" },
1560
+ React.createElement("span", { className: "iterate-chip" }, "\u672C\u5730\u4FDD\u5B58"),
1561
+ clearButton
1562
+ )
1563
+ )
1564
+ );
1565
+ const guideCard = React.createElement(
1566
+ "div",
1567
+ { key: "guide", className: "iterate-scard" },
1568
+ React.createElement(
1569
+ "div",
1570
+ { className: "iterate-scard-head" },
1571
+ React.createElement(
1572
+ "div",
1573
+ {},
1574
+ React.createElement("div", { className: "iterate-settings-title" }, "\u914D\u7F6E\u7BA1\u7406"),
1575
+ React.createElement("div", { className: "iterate-settings-desc" }, "\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570\u5199\u5728\u9879\u76EE\u7684 iterate.config.yaml\uFF0C\u590D\u5236\u6307\u5F15\u53EF\u8BA9\u6A21\u578B\u6309\u9700\u8C03\u6574\u3002")
871
1576
  ),
1577
+ React.createElement(
1578
+ "div",
1579
+ { className: "iterate-scard-actions" },
1580
+ React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": guideCopied ? "" : void 0, onClick: () => doCopy(guide, "guide") }, guideCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6307\u5F15"),
1581
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowGuide((v) => !v) }, showGuide ? "\u6536\u8D77" : "\u5C55\u5F00")
1582
+ )
872
1583
  ),
873
- React.createElement('div', { className: 'iterate-settings-row' },
874
- React.createElement('div', {},
875
- React.createElement('div', { className: 'iterate-settings-title' }, '配置管理'),
876
- React.createElement('div', { className: 'iterate-settings-desc' }, '目标 / 维度 / 最大轮数写在项目的 iterate.config.yaml。复制指引让模型为你调整,或展开查看可编辑字段。'),
1584
+ showGuide ? React.createElement(
1585
+ "div",
1586
+ { className: "iterate-guide" },
1587
+ React.createElement(
1588
+ "div",
1589
+ { className: "iterate-guide-bar" },
1590
+ React.createElement("span", {}, "iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5"),
1591
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(guide, "guide") }, "\u590D\u5236")
877
1592
  ),
878
- React.createElement('span', { style: { display: 'flex', gap: 6 } },
879
- React.createElement('button', { className: 'iterate-btn', 'data-primary': '', 'data-copied': copied ? '' : undefined, onClick: doCopyGuide }, copied ? '已复制' : '复制指引'),
880
- React.createElement('button', { className: 'iterate-btn', onClick: () => setShowGuide((v) => !v) }, showGuide ? '收起' : '查看'),
1593
+ React.createElement("div", { className: "iterate-guide-body" }, guide)
1594
+ ) : null
1595
+ );
1596
+ const statusCard = React.createElement(
1597
+ "div",
1598
+ { key: "status", className: "iterate-scard" },
1599
+ React.createElement(
1600
+ "div",
1601
+ { className: "iterate-scard-head" },
1602
+ React.createElement(
1603
+ "div",
1604
+ {},
1605
+ React.createElement("div", { className: "iterate-settings-title" }, "\u72B6\u6001\u6982\u89C8"),
1606
+ React.createElement("div", { className: "iterate-settings-desc" }, "\u67E5\u770B\u8FD0\u884C\u65F6\u4EA7\u7269\u5E03\u5C40\u4E0E\u6E05\u7406\u6307\u5F15\u3002iterate_status / iterate_history / iterate_prune \u5DE5\u5177\u7528\u4E8E\u67E5\u770B\u548C\u7BA1\u7406\u3002")
881
1607
  ),
1608
+ React.createElement(
1609
+ "div",
1610
+ { className: "iterate-scard-actions" },
1611
+ React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": statusCopied ? "" : void 0, onClick: () => doCopy(statusGuide, "status") }, statusCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6307\u5F15"),
1612
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowStatus((v) => !v) }, showStatus ? "\u6536\u8D77" : "\u5C55\u5F00")
1613
+ )
882
1614
  ),
883
- showGuide
884
- ? React.createElement('div', { className: 'iterate-settings-guide' }, guide)
885
- : null,
886
- React.createElement('div', { className: 'iterate-settings-row' },
887
- React.createElement('div', {},
888
- React.createElement('div', { className: 'iterate-settings-title' }, '状态概览'),
889
- React.createElement('div', { className: 'iterate-settings-desc' }, '运行时产物布局与清理指引。iterate_status / iterate_history / iterate_prune 工具用于查看和管理。'),
1615
+ showStatus ? React.createElement(
1616
+ "div",
1617
+ { className: "iterate-guide" },
1618
+ React.createElement(
1619
+ "div",
1620
+ { className: "iterate-guide-bar" },
1621
+ React.createElement("span", {}, "\u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406"),
1622
+ React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(statusGuide, "status") }, "\u590D\u5236")
890
1623
  ),
891
- React.createElement('button', { className: 'iterate-btn', onClick: () => setShowStatus((v) => !v) }, showStatus ? '收起' : '查看'),
892
- ),
893
- showStatus
894
- ? React.createElement('div', { className: 'iterate-settings-guide' }, statusGuide)
895
- : null,
896
- )
1624
+ React.createElement("div", { className: "iterate-guide-body" }, statusGuide)
1625
+ ) : null
1626
+ );
1627
+ const banner = React.createElement(
1628
+ "div",
1629
+ { key: "banner", className: "iterate-scard" },
1630
+ React.createElement(
1631
+ "div",
1632
+ { className: "iterate-scard-head" },
1633
+ React.createElement(
1634
+ "div",
1635
+ {},
1636
+ React.createElement("div", { className: "iterate-settings-title" }, "iterate"),
1637
+ React.createElement("div", { className: "iterate-settings-desc" }, "\u4E3A\u6BCF\u6B21\u4EE3\u7801\u8BC4\u5BA1\u751F\u6210 Review \u62A5\u544A\u4E0E\u4EA4\u4E92\u5F0F\u5206\u8BCA\u89C6\u56FE\uFF0C\u4E13\u6CE8 AI \u81EA\u6211\u5BA1\u67E5\u4E0E\u4FEE\u6B63\u3002")
1638
+ ),
1639
+ React.createElement(
1640
+ "span",
1641
+ { className: "iterate-pill" },
1642
+ React.createElement("span", { className: "iterate-pill-dot" }),
1643
+ "\u5C31\u7EEA"
1644
+ )
1645
+ )
1646
+ );
1647
+ return React.createElement(
1648
+ "div",
1649
+ { "data-iterate-root": "", "data-iterate": "settings", className: "iterate-settings" },
1650
+ React.createElement("div", { className: "iterate-settings-title", style: { fontSize: 15, fontWeight: 700 } }, "iterate \u8BBE\u7F6E"),
1651
+ banner,
1652
+ themeCard,
1653
+ dataCard,
1654
+ guideCard,
1655
+ statusCard
1656
+ );
897
1657
  }
898
-
899
- // ─── Registration ────────────────────────────────────────────────────────────
900
-
901
- /**
902
- * Chain selector for conversation.chat.turnTail.
903
- * Returns a marker when the turn contains an iterate review report, null otherwise.
904
- */
905
1658
  function selectTurnTail(owner) {
906
- if (!owner) return null
907
- if (findReportInObject(owner.turn, undefined, 24) || scanSessionForReport(owner.turn)) return { matched: true }
908
- if (scanSessionForRunSummary(owner.turn)) return { matched: true }
909
- return null
1659
+ if (!owner) return null;
1660
+ if (findReportInObject(owner.turn, void 0, 24) || scanSessionForReport(owner.turn)) return { matched: true };
1661
+ if (scanSessionForRunSummary(owner.turn)) return { matched: true };
1662
+ return null;
910
1663
  }
911
-
912
- /**
913
- * Plugin client entry. Called by dsh web with the client Cordis context.
914
- */
915
- export function apply(ctx) {
916
- // 1. Resolve optional services (degrade, never throw).
917
- const get = ctx && typeof ctx.get === 'function' ? ctx.get.bind(ctx) : null
918
- slotsSvc = get ? get('slots') : undefined
919
- themeSvc = get ? get('theme') : undefined
920
- React = resolveReact(ctx)
921
- storage = createStorage()
922
-
923
- // 2. Theme preference bootstrap.
924
- const savedTheme = storage.get(THEME_STORAGE_KEY)
925
- themeEnabled = savedTheme === null ? true : savedTheme === '1'
926
- if (themeEnabled) applyThemeSkin()
927
-
928
- // 3. Inject styles (independent of slots / React availability).
929
- if (typeof document !== 'undefined' && document.createElement && document.head) {
930
- const style = document.createElement('style')
931
- style.dataset.plugin = PLUGIN_TAG
932
- style.dataset.pluginCss = 'iterate-main'
933
- style.textContent = ITERATE_CSS
934
- document.head.appendChild(style)
935
- if (typeof ctx.effect === 'function') {
936
- ctx.effect(() => { try { style.remove() } catch { /* noop */ } })
1664
+ function apply(ctx) {
1665
+ slotsSvc = readSlots(ctx);
1666
+ themeSvc = readTheme(ctx);
1667
+ storage = createStorage();
1668
+ const savedTheme = storage.get(THEME_STORAGE_KEY);
1669
+ themeEnabled = savedTheme === null ? true : savedTheme === "1";
1670
+ if (themeEnabled) applyThemeSkin();
1671
+ if (typeof document !== "undefined" && document.createElement && document.head) {
1672
+ const style = document.createElement("style");
1673
+ style.dataset.plugin = PLUGIN_TAG;
1674
+ style.dataset.pluginCss = "iterate-main";
1675
+ style.textContent = ITERATE_CSS;
1676
+ document.head.appendChild(style);
1677
+ if (typeof ctx.effect === "function") {
1678
+ ctx.effect(() => {
1679
+ try {
1680
+ style.remove();
1681
+ } catch {
1682
+ }
1683
+ });
937
1684
  }
938
1685
  }
939
-
940
- // 4. Slot UI requires React + slots service.
941
- if (React === null || slotsSvc === undefined) {
942
- log('React or slots service unavailable — slot UI disabled')
943
- return
1686
+ if (slotsSvc === void 0) {
1687
+ log("slots service unavailable \u2014 slot UI disabled");
1688
+ return;
944
1689
  }
945
-
946
- // 5. Register the six-part UI.
947
- if (typeof slotsSvc.inject === 'function') {
948
- // 1 + 5: convergence dashboard + round pulse (session scope).
949
- slotsSvc.inject('conversation.input.dock', () =>
950
- slotsSvc.register(
951
- { name: 'conversation.input.dock', id: 'iterate-dashboard', order: 90 },
952
- (props) => React.createElement(ConvergenceDashboard, props),
953
- ),
954
- )
955
-
956
- // 2 + 3: triage panel / stats card (turn-tail chain, session scope).
957
- slotsSvc.inject('conversation.chat.turnTail', () =>
958
- slotsSvc.register(
959
- { name: 'conversation.chat.turnTail', id: 'iterate-turn-tail', select: selectTurnTail },
960
- (props) => React.createElement(TurnTailEntry, props),
961
- ),
962
- )
963
-
964
- // 5: progress capsule (frame overlay, root scope).
965
- slotsSvc.inject('shell.overlay', () =>
966
- slotsSvc.register(
967
- { name: 'shell.overlay', id: 'iterate-progress', order: 0 },
968
- (props) => React.createElement(ProgressCapsule, props),
969
- ),
970
- )
971
-
972
- // 6: settings page section (root scope).
973
- slotsSvc.inject('settings.section', () =>
974
- slotsSvc.register(
975
- { name: 'settings.section', id: 'iterate-settings', order: 30, label: () => 'iterate' },
976
- (props) => React.createElement(SettingsPanel, props),
977
- ),
978
- )
1690
+ if (typeof slotsSvc.inject === "function") {
1691
+ slotsSvc.inject(
1692
+ "conversation.input.dock",
1693
+ () => slotsSvc?.register(
1694
+ { name: "conversation.input.dock", id: "iterate-dashboard", order: 90 },
1695
+ (props) => React.createElement(ConvergenceDashboard, props)
1696
+ )
1697
+ );
1698
+ slotsSvc.inject(
1699
+ "conversation.chat.turnTail",
1700
+ () => slotsSvc?.register(
1701
+ { name: "conversation.chat.turnTail", id: "iterate-turn-tail", select: selectTurnTail },
1702
+ (props) => React.createElement(TurnTailEntry, props)
1703
+ )
1704
+ );
1705
+ slotsSvc.inject(
1706
+ "shell.overlay",
1707
+ () => slotsSvc?.register(
1708
+ { name: "shell.overlay", id: "iterate-progress", order: 0 },
1709
+ (props) => React.createElement(ProgressCapsule, props)
1710
+ )
1711
+ );
1712
+ slotsSvc.inject(
1713
+ "settings.section",
1714
+ () => slotsSvc?.register(
1715
+ { name: "settings.section", id: "iterate-settings", order: 30, label: () => "iterate" },
1716
+ (props) => React.createElement(SettingsPanel, props)
1717
+ )
1718
+ );
979
1719
  }
980
-
981
- // 6: keep theme toggle state consistent across theme/change events.
982
- if (typeof ctx.on === 'function') {
983
- ctx.on('theme/change', () => {
984
- if (themeEnabled) applyThemeSkin()
985
- })
1720
+ if (typeof ctx.on === "function") {
1721
+ ctx.on("theme/change", () => {
1722
+ if (themeEnabled) applyThemeSkin();
1723
+ });
986
1724
  }
987
- }
1725
+ }
1726
+ return module.exports; } });
1727
+