iterate-plugin 2.12.2 → 3.2.1
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/README.md +28 -12
- package/README.zh-CN.md +2 -1
- package/dist/approval-gate.js +16 -2
- package/dist/config-loader.js +5 -0
- package/dist/git-scope.js +61 -7
- package/dist/index.js +16 -6
- package/dist/session-hooks.js +36 -11
- package/dist/skill-prompt.js +3 -0
- package/dist/tools/decision-log.js +10 -1
- package/dist/tools/defense-events.js +260 -0
- package/dist/tools/defense-store.js +97 -0
- package/dist/tools/experience-bank.js +248 -0
- package/dist/tools/experience-store.js +132 -0
- package/dist/tools/quality-gate.js +180 -0
- package/dist/tools/quality-store.js +174 -0
- package/lib/client.js +662 -103
- package/lib/parse.js +93 -0
- package/package.json +7 -6
- package/src/approval-gate.ts +14 -2
- package/src/client/index.ts +542 -49
- package/src/config-loader.ts +5 -0
- package/src/git-scope.ts +48 -7
- package/src/index.ts +16 -6
- package/src/session-hooks.ts +33 -11
- package/src/skill-prompt.ts +3 -0
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/config.ts +1 -1
- package/src/tools/decision-log.ts +11 -2
- package/src/tools/defense-events.ts +295 -0
- package/src/tools/defense-store.ts +113 -0
- package/src/tools/experience-bank.ts +264 -0
- package/src/tools/experience-store.ts +160 -0
- package/src/tools/fix.ts +1 -1
- package/src/tools/history.ts +1 -1
- package/src/tools/prune.ts +1 -1
- package/src/tools/quality-gate.ts +193 -0
- package/src/tools/quality-store.ts +199 -0
- package/src/tools/review.ts +1 -1
- package/src/tools/transcript.ts +1 -1
- package/src/tools/triage.ts +1 -1
- package/src/types.ts +118 -0
package/lib/client.js
CHANGED
|
@@ -994,6 +994,50 @@ function buildRuntimeStatusGuide() {
|
|
|
994
994
|
];
|
|
995
995
|
return lines.join("\n");
|
|
996
996
|
}
|
|
997
|
+
function filterLiveEntries(entries, type) {
|
|
998
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
999
|
+
const t = typeof type === "string" ? type.trim() : "";
|
|
1000
|
+
if (!t) return list.slice();
|
|
1001
|
+
return list.filter((e) => e && typeof e === "object" && String(e.type ?? "") === t);
|
|
1002
|
+
}
|
|
1003
|
+
function filterTimelineEntries(entries, opts) {
|
|
1004
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
1005
|
+
const o = opts && typeof opts === "object" ? opts : {};
|
|
1006
|
+
const type = typeof o.type === "string" ? o.type : "";
|
|
1007
|
+
const round = typeof o.round === "string" ? o.round : "";
|
|
1008
|
+
const q = typeof o.search === "string" ? o.search.trim().toLowerCase() : "";
|
|
1009
|
+
const filtered = list.filter((t) => {
|
|
1010
|
+
if (!t || typeof t !== "object") return false;
|
|
1011
|
+
if (type && String(t.type ?? "") !== type) return false;
|
|
1012
|
+
if (round && String(t.round ?? "") !== round) return false;
|
|
1013
|
+
if (q) {
|
|
1014
|
+
const hay = [String(t.type ?? ""), String(t.round ?? ""), JSON.stringify(t.data ?? {})].join(" ").toLowerCase();
|
|
1015
|
+
if (hay.indexOf(q) < 0) return false;
|
|
1016
|
+
}
|
|
1017
|
+
return true;
|
|
1018
|
+
});
|
|
1019
|
+
return filtered.slice().sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
|
|
1020
|
+
}
|
|
1021
|
+
function serializeObservatoryExport(manifest, live) {
|
|
1022
|
+
const payload = {
|
|
1023
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1024
|
+
manifest: manifest && typeof manifest === "object" ? manifest : null,
|
|
1025
|
+
live: Array.isArray(live) ? live : []
|
|
1026
|
+
};
|
|
1027
|
+
try {
|
|
1028
|
+
return JSON.stringify(payload, null, 2);
|
|
1029
|
+
} catch {
|
|
1030
|
+
return JSON.stringify({ exportedAt: payload.exportedAt, manifest: null, live: [] }, null, 2);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function latestPhase(phases) {
|
|
1034
|
+
const list = Array.isArray(phases) ? phases : [];
|
|
1035
|
+
let latest = "";
|
|
1036
|
+
for (const p of list) {
|
|
1037
|
+
if (typeof p === "string" && p.trim()) latest = p.trim();
|
|
1038
|
+
}
|
|
1039
|
+
return latest;
|
|
1040
|
+
}
|
|
997
1041
|
|
|
998
1042
|
// src/client/index.ts
|
|
999
1043
|
var React = require("react");
|
|
@@ -1103,6 +1147,11 @@ var ITERATE_CSS = `
|
|
|
1103
1147
|
.iterate-filter-search { padding: 4px 8px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-size: 11px; min-width: 140px; }
|
|
1104
1148
|
.iterate-filter-search::placeholder { color: var(--dsw-alias-label-secondary); }
|
|
1105
1149
|
.iterate-filter-count { margin-left: auto; white-space: nowrap; }
|
|
1150
|
+
.iterate-filter-chip { padding: 3px 9px; border-radius: 999px; 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; }
|
|
1151
|
+
.iterate-filter-chip:hover { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
|
|
1152
|
+
.iterate-filter-chip[data-active] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); font-weight: 600; }
|
|
1153
|
+
.iterate-filter-clear { padding: 3px 9px; border-radius: 999px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 45%, transparent); color: var(--dsw-alias-state-warn-primary); background: transparent; font-size: 11px; cursor: pointer; }
|
|
1154
|
+
.iterate-filter-clear:hover { background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent); }
|
|
1106
1155
|
.iterate-batch { display: flex; align-items: center; gap: 6px; padding: 8px 14px; border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-secondary); }
|
|
1107
1156
|
.iterate-batch-label { margin-right: 2px; }
|
|
1108
1157
|
.iterate-batch-btn { 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; }
|
|
@@ -1155,6 +1204,46 @@ var ITERATE_CSS = `
|
|
|
1155
1204
|
/* Interruption / resume + attachment chips (dashboard) */
|
|
1156
1205
|
.iterate-chip-resume { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 28%, transparent); }
|
|
1157
1206
|
.iterate-chip-images { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 28%, transparent); }
|
|
1207
|
+
/* Live workflow-phase chip (dashboard) */
|
|
1208
|
+
.iterate-chip-phase { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; color: var(--dsw-alias-label-primary); background: color-mix(in srgb, var(--dsw-alias-label-secondary) 14%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-label-secondary) 26%, transparent); }
|
|
1209
|
+
.iterate-chip-phase[data-live] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
|
|
1210
|
+
|
|
1211
|
+
/* v3.0: task_mode indicator chip */
|
|
1212
|
+
.iterate-chip-taskmode { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
1213
|
+
.iterate-chip-taskmode[data-mode="code"] { color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 28%, transparent); }
|
|
1214
|
+
.iterate-chip-taskmode[data-mode="iterate"] { color: #B45309; background: color-mix(in srgb, #B45309 12%, transparent); border: 1px solid color-mix(in srgb, #B45309 28%, transparent); }
|
|
1215
|
+
|
|
1216
|
+
/* v3.0: Quality gate status */
|
|
1217
|
+
.iterate-gate { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
1218
|
+
.iterate-gate[data-status="pass"] { 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); }
|
|
1219
|
+
.iterate-gate[data-status="fail"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
|
|
1220
|
+
.iterate-gate[data-status="pending"] { color: var(--dsw-alias-label-secondary); background: color-mix(in srgb, var(--dsw-alias-label-secondary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-label-secondary) 28%, transparent); }
|
|
1221
|
+
|
|
1222
|
+
/* v3.0: Experience bank hit highlight */
|
|
1223
|
+
.iterate-exp-hit { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; 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); font-size: 11px; color: var(--dsw-alias-state-success-primary); }
|
|
1224
|
+
.iterate-exp-hit b { font-weight: 600; }
|
|
1225
|
+
|
|
1226
|
+
/* v3.0: Defense event type badges */
|
|
1227
|
+
.iterate-defense-badge { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
1228
|
+
.iterate-defense-badge[data-type="precondition_failed"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
|
|
1229
|
+
.iterate-defense-badge[data-type="rollback"] { color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 28%, transparent); }
|
|
1230
|
+
.iterate-defense-badge[data-type="invariant_violated"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
|
|
1231
|
+
.iterate-defense-badge[data-type="assumption_falsified"] { color: var(--dsw-alias-brand-primary); background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 28%, transparent); }
|
|
1232
|
+
|
|
1233
|
+
/* v3.0: Command buttons */
|
|
1234
|
+
.iterate-cmd { padding: 4px 10px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-size: 11px; cursor: pointer; font-weight: 500; }
|
|
1235
|
+
.iterate-cmd:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
1236
|
+
.iterate-cmd[data-primary] { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }
|
|
1237
|
+
.iterate-cmd[data-danger] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent); color: var(--dsw-alias-state-error-primary); }
|
|
1238
|
+
.iterate-cmd:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
1239
|
+
|
|
1240
|
+
/* v3.0: Dimension score bar */
|
|
1241
|
+
.iterate-dim-score { display: flex; align-items: center; gap: 6px; font-size: 11px; }
|
|
1242
|
+
.iterate-dim-score-bar { width: 60px; height: 6px; border-radius: 3px; background: var(--dsw-alias-bg-layer-2); overflow: hidden; }
|
|
1243
|
+
.iterate-dim-score-fill { height: 100%; border-radius: 3px; transition: width 300ms ease; }
|
|
1244
|
+
.iterate-dim-score-fill[data-status="pass"] { background: var(--dsw-alias-state-success-primary); }
|
|
1245
|
+
.iterate-dim-score-fill[data-status="warn"] { background: var(--dsw-alias-state-warn-primary); }
|
|
1246
|
+
.iterate-dim-score-fill[data-status="fail"] { background: var(--dsw-alias-state-error-primary); }
|
|
1158
1247
|
|
|
1159
1248
|
/* Accessibility-switch toggle */
|
|
1160
1249
|
.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; }
|
|
@@ -1293,6 +1382,24 @@ function copyText(text) {
|
|
|
1293
1382
|
}
|
|
1294
1383
|
return Promise.resolve(false);
|
|
1295
1384
|
}
|
|
1385
|
+
function downloadTextFile(filename, text) {
|
|
1386
|
+
if (typeof document === "undefined" || typeof Blob === "undefined" || typeof URL === "undefined") return false;
|
|
1387
|
+
try {
|
|
1388
|
+
const blob = new Blob([text], { type: "application/json;charset=utf-8" });
|
|
1389
|
+
const url = URL.createObjectURL(blob);
|
|
1390
|
+
const a = document.createElement("a");
|
|
1391
|
+
a.href = url;
|
|
1392
|
+
a.download = filename;
|
|
1393
|
+
a.rel = "noopener";
|
|
1394
|
+
document.body.appendChild(a);
|
|
1395
|
+
a.click();
|
|
1396
|
+
document.body.removeChild(a);
|
|
1397
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
1398
|
+
return true;
|
|
1399
|
+
} catch {
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1296
1403
|
var SEVERITY_KEYS = ["critical", "high", "medium", "low"];
|
|
1297
1404
|
function coerceSeverity(severity) {
|
|
1298
1405
|
return SEVERITY_KEYS.includes(severity ?? "") ? severity : "low";
|
|
@@ -1415,6 +1522,22 @@ function ConvergenceDashboard(props) {
|
|
|
1415
1522
|
key: "images",
|
|
1416
1523
|
title: "\u4F1A\u8BDD\u4E2D\u68C0\u6D4B\u5230\u7528\u6237\u9644\u5E26\u7684\u56FE\u7247\uFF0C\u8BC4\u5BA1\u5C06\u4F5C\u4E3A\u89C6\u89C9\u8BC1\u636E\u53C2\u8003"
|
|
1417
1524
|
}, `\u9644\u4EF6\u56FE\u7247 ${String(imageCount)}`) : null;
|
|
1525
|
+
const transcript = latestTranscript(session);
|
|
1526
|
+
const phase = transcript ? latestPhase(transcript.phases) : "";
|
|
1527
|
+
const liveChip = transcript && phase ? React.createElement("span", {
|
|
1528
|
+
className: "iterate-chip-phase",
|
|
1529
|
+
key: "phase",
|
|
1530
|
+
"data-live": transcript.active === true ? "" : void 0,
|
|
1531
|
+
title: transcript.active === true ? `\u5F53\u524D\u5904\u4E8E\u300C${phase}\u300D\u9636\u6BB5\uFF08\u8FD0\u884C\u4E2D\uFF09` : `\u6700\u8FD1\u4E00\u6B21\u6267\u884C\u505C\u7559\u5728\u300C${phase}\u300D\u9636\u6BB5\uFF08\u5DF2\u7ED3\u675F\uFF09`
|
|
1532
|
+
}, `${phase} \xB7 ${transcript.active === true ? "\u8FD0\u884C\u4E2D" : "\u5DF2\u7ED3\u675F"}`) : null;
|
|
1533
|
+
const taskModeTranscript = latestTranscript(session);
|
|
1534
|
+
const taskMode = taskModeTranscript?.taskMode;
|
|
1535
|
+
const taskModeChip = taskMode ? React.createElement("span", {
|
|
1536
|
+
className: "iterate-chip-taskmode",
|
|
1537
|
+
key: "taskmode",
|
|
1538
|
+
"data-mode": taskMode,
|
|
1539
|
+
title: taskMode === "code" ? "\u4EE3\u7801\u6A21\u5F0F\uFF1A\u76F4\u63A5\u4FEE\u6539\u6587\u4EF6" : "\u8FED\u4EE3\u6A21\u5F0F\uFF1A\u5BA1\u67E5-\u4FEE\u590D\u5FAA\u73AF"
|
|
1540
|
+
}, taskMode === "code" ? "\u25CF code" : "\u25CF iterate") : null;
|
|
1418
1541
|
const dimNames = Object.keys(dims);
|
|
1419
1542
|
const dimBadges = dimNames.slice(0, 6).map(
|
|
1420
1543
|
(dim) => React.createElement(
|
|
@@ -1478,6 +1601,8 @@ function ConvergenceDashboard(props) {
|
|
|
1478
1601
|
fixBadge,
|
|
1479
1602
|
resumeChip,
|
|
1480
1603
|
imageChip,
|
|
1604
|
+
liveChip,
|
|
1605
|
+
taskModeChip,
|
|
1481
1606
|
React.createElement(TrendChart, { points: trend.points }),
|
|
1482
1607
|
...dimBadges
|
|
1483
1608
|
);
|
|
@@ -1598,6 +1723,7 @@ function TriagePanel(props) {
|
|
|
1598
1723
|
const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: "" });
|
|
1599
1724
|
const [selected, setSelected] = React.useState(null);
|
|
1600
1725
|
const [selectAll, setSelectAll] = React.useState(false);
|
|
1726
|
+
const [cmdCopied, setCmdCopied] = React.useState(null);
|
|
1601
1727
|
React.useEffect(() => {
|
|
1602
1728
|
if (storage) storage.set(storageKey, JSON.stringify(verdicts));
|
|
1603
1729
|
}, [storageKey, verdicts]);
|
|
@@ -1661,6 +1787,33 @@ function TriagePanel(props) {
|
|
|
1661
1787
|
const ignored = collectIgnoredEntries(verdicts, findings);
|
|
1662
1788
|
const ignoredCount = ignored.length;
|
|
1663
1789
|
const counts = countVerdicts(verdicts);
|
|
1790
|
+
const doApproveArchitecturalFix = () => {
|
|
1791
|
+
const cmd = "\u8BF7\u8C03\u7528 `iterate_fix` \u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u8BBE\u7F6E is_architectural: true\uFF09";
|
|
1792
|
+
copyText(cmd).then((ok) => {
|
|
1793
|
+
if (ok) {
|
|
1794
|
+
setCmdCopied("approve-arch");
|
|
1795
|
+
setTimeout(() => setCmdCopied(null), 1600);
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
};
|
|
1799
|
+
const doTriggerNewRound = () => {
|
|
1800
|
+
const cmd = "\u8BF7\u7EE7\u7EED\u6267\u884C\u4E0B\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5";
|
|
1801
|
+
copyText(cmd).then((ok) => {
|
|
1802
|
+
if (ok) {
|
|
1803
|
+
setCmdCopied("new-round");
|
|
1804
|
+
setTimeout(() => setCmdCopied(null), 1600);
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
};
|
|
1808
|
+
const doRollbackToCheckpoint = () => {
|
|
1809
|
+
const cmd = "\u8BF7\u8C03\u7528 `iterate_checkpoint` \u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9";
|
|
1810
|
+
copyText(cmd).then((ok) => {
|
|
1811
|
+
if (ok) {
|
|
1812
|
+
setCmdCopied("rollback");
|
|
1813
|
+
setTimeout(() => setCmdCopied(null), 1600);
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
};
|
|
1664
1817
|
const doCopyYaml = () => {
|
|
1665
1818
|
const yaml = toKnownIntentionalYaml(ignored);
|
|
1666
1819
|
if (!yaml) return;
|
|
@@ -1816,6 +1969,36 @@ function TriagePanel(props) {
|
|
|
1816
1969
|
}, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
|
|
1817
1970
|
)
|
|
1818
1971
|
),
|
|
1972
|
+
// v3.0: Command buttons for native actions (§8)
|
|
1973
|
+
React.createElement(
|
|
1974
|
+
"div",
|
|
1975
|
+
{ className: "iterate-triage-foot", style: { borderTop: "1px solid var(--dsw-alias-border-l1)" } },
|
|
1976
|
+
React.createElement("span", { style: { fontSize: 11, fontWeight: 600, color: "var(--dsw-alias-label-secondary)" } }, "\u6307\u6325\u64CD\u4F5C"),
|
|
1977
|
+
React.createElement(
|
|
1978
|
+
"span",
|
|
1979
|
+
{ style: { display: "flex", gap: 6 } },
|
|
1980
|
+
React.createElement("button", {
|
|
1981
|
+
className: "iterate-cmd",
|
|
1982
|
+
"data-primary": "",
|
|
1983
|
+
"data-copied": cmdCopied === "approve-arch" ? "" : void 0,
|
|
1984
|
+
onClick: doApproveArchitecturalFix,
|
|
1985
|
+
title: "\u6279\u51C6\u67B6\u6784\u4FEE\u590D\uFF08\u590D\u5236\u6307\u4EE4\u6587\u672C\uFF09"
|
|
1986
|
+
}, cmdCopied === "approve-arch" ? "\u5DF2\u590D\u5236" : "\u6279\u51C6\u67B6\u6784\u4FEE\u590D"),
|
|
1987
|
+
React.createElement("button", {
|
|
1988
|
+
className: "iterate-cmd",
|
|
1989
|
+
"data-copied": cmdCopied === "new-round" ? "" : void 0,
|
|
1990
|
+
onClick: doTriggerNewRound,
|
|
1991
|
+
title: "\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5"
|
|
1992
|
+
}, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : "\u89E6\u53D1\u65B0\u4E00\u8F6E"),
|
|
1993
|
+
React.createElement("button", {
|
|
1994
|
+
className: "iterate-cmd",
|
|
1995
|
+
"data-danger": "",
|
|
1996
|
+
"data-copied": cmdCopied === "rollback" ? "" : void 0,
|
|
1997
|
+
onClick: doRollbackToCheckpoint,
|
|
1998
|
+
title: "\u56DE\u6EDA\u5230\u4E0A\u4E00\u4E2A\u68C0\u67E5\u70B9"
|
|
1999
|
+
}, cmdCopied === "rollback" ? "\u5DF2\u590D\u5236" : "\u56DE\u6EDA\u68C0\u67E5\u70B9")
|
|
2000
|
+
)
|
|
2001
|
+
),
|
|
1819
2002
|
payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
|
|
1820
2003
|
);
|
|
1821
2004
|
}
|
|
@@ -1899,7 +2082,13 @@ function ProgressCapsule() {
|
|
|
1899
2082
|
};
|
|
1900
2083
|
}, []);
|
|
1901
2084
|
if (!info) return null;
|
|
1902
|
-
return React.createElement("div", {
|
|
2085
|
+
return React.createElement("div", {
|
|
2086
|
+
"data-iterate-root": "",
|
|
2087
|
+
className: "iterate-capsule",
|
|
2088
|
+
"data-ok": info.ok ? "" : void 0,
|
|
2089
|
+
role: "status",
|
|
2090
|
+
"aria-live": "polite"
|
|
2091
|
+
}, info.text);
|
|
1903
2092
|
}
|
|
1904
2093
|
function SettingsPanel(_props) {
|
|
1905
2094
|
const [enabled, setEnabled] = React.useState(themeEnabled);
|
|
@@ -2092,7 +2281,11 @@ var OBS_TABS = [
|
|
|
2092
2281
|
{ key: "f4", label: "\u4FEE\u590D\u4E0E\u56DE\u6EDA" },
|
|
2093
2282
|
{ key: "f5", label: "\u65AD\u70B9\u6062\u590D" },
|
|
2094
2283
|
{ key: "f6", label: "\u8FD0\u884C\u63A7\u5236\u53F0" },
|
|
2095
|
-
{ key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" }
|
|
2284
|
+
{ key: "f7", label: "\u51B3\u7B56\u65F6\u95F4\u7EBF" },
|
|
2285
|
+
// v3.0: Quality Command Center tabs
|
|
2286
|
+
{ key: "f8", label: "\u8D28\u91CF\u95E8\u7981" },
|
|
2287
|
+
{ key: "f9", label: "\u7ECF\u9A8C\u94F6\u884C" },
|
|
2288
|
+
{ key: "f10", label: "\u9632\u5FA1\u4E8B\u4EF6" }
|
|
2096
2289
|
];
|
|
2097
2290
|
var OBS_LIVE_META = {
|
|
2098
2291
|
read: { label: "\u9605\u8BFB", color: "var(--dsw-alias-brand-primary)" },
|
|
@@ -2150,11 +2343,20 @@ function ObservatoryPanel(props) {
|
|
|
2150
2343
|
const [open, setOpen] = React.useState(false);
|
|
2151
2344
|
const [tab, setTab] = React.useState("live");
|
|
2152
2345
|
const [expandedThreads, setExpandedThreads] = React.useState(/* @__PURE__ */ new Set());
|
|
2346
|
+
const [threadMode, setThreadMode] = React.useState("auto");
|
|
2153
2347
|
const [copiedKey, setCopiedKey] = React.useState(null);
|
|
2154
2348
|
const [copyFailText, setCopyFailText] = React.useState(null);
|
|
2155
2349
|
const [nudgeText, setNudgeText] = React.useState("");
|
|
2156
2350
|
const [timelineType, setTimelineType] = React.useState("");
|
|
2351
|
+
const [timelineRound, setTimelineRound] = React.useState("");
|
|
2157
2352
|
const [timelineSearch, setTimelineSearch] = React.useState("");
|
|
2353
|
+
const [liveType, setLiveType] = React.useState("");
|
|
2354
|
+
const [f3Filter, setF3Filter] = React.useState(
|
|
2355
|
+
{ severities: [], dimensions: [], search: "" }
|
|
2356
|
+
);
|
|
2357
|
+
const [expSearch, setExpSearch] = React.useState("");
|
|
2358
|
+
const [expResults, setExpResults] = React.useState([]);
|
|
2359
|
+
const [defenseFilter, setDefenseFilter] = React.useState("");
|
|
2158
2360
|
const copyTimer = React.useRef(null);
|
|
2159
2361
|
React.useEffect(() => () => {
|
|
2160
2362
|
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
@@ -2172,6 +2374,7 @@ function ObservatoryPanel(props) {
|
|
|
2172
2374
|
});
|
|
2173
2375
|
};
|
|
2174
2376
|
const toggleThread = (key) => {
|
|
2377
|
+
setThreadMode("auto");
|
|
2175
2378
|
setExpandedThreads((prev) => {
|
|
2176
2379
|
const next = new Set(prev);
|
|
2177
2380
|
if (next.has(key)) next.delete(key);
|
|
@@ -2179,6 +2382,36 @@ function ObservatoryPanel(props) {
|
|
|
2179
2382
|
return next;
|
|
2180
2383
|
});
|
|
2181
2384
|
};
|
|
2385
|
+
const exportObservatory = () => {
|
|
2386
|
+
if (!manifest) return;
|
|
2387
|
+
const json = serializeObservatoryExport(manifest, manifest.live || []);
|
|
2388
|
+
const filename = `iterate-observatory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`;
|
|
2389
|
+
const flash = () => {
|
|
2390
|
+
setCopiedKey("export");
|
|
2391
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
2392
|
+
copyTimer.current = setTimeout(() => setCopiedKey((cur) => cur === "export" ? null : cur), 1600);
|
|
2393
|
+
};
|
|
2394
|
+
if (downloadTextFile(filename, json)) {
|
|
2395
|
+
flash();
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
copyText(json).then((ok) => {
|
|
2399
|
+
if (!ok) {
|
|
2400
|
+
setCopyFailText(json);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
flash();
|
|
2404
|
+
});
|
|
2405
|
+
};
|
|
2406
|
+
const setAllThreads = (mode) => {
|
|
2407
|
+
setThreadMode(mode);
|
|
2408
|
+
setExpandedThreads(/* @__PURE__ */ new Set());
|
|
2409
|
+
};
|
|
2410
|
+
const isThreadExpanded = (key) => {
|
|
2411
|
+
if (threadMode === "all") return true;
|
|
2412
|
+
if (threadMode === "none") return false;
|
|
2413
|
+
return expandedThreads.has(key);
|
|
2414
|
+
};
|
|
2182
2415
|
if (!manifest) {
|
|
2183
2416
|
return React.createElement(
|
|
2184
2417
|
"div",
|
|
@@ -2211,69 +2444,85 @@ function ObservatoryPanel(props) {
|
|
|
2211
2444
|
if (rounds.length === 0) {
|
|
2212
2445
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B");
|
|
2213
2446
|
}
|
|
2214
|
-
return React.createElement(
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
"
|
|
2226
|
-
{
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
)
|
|
2235
|
-
|
|
2447
|
+
return React.createElement(
|
|
2448
|
+
"div",
|
|
2449
|
+
{},
|
|
2450
|
+
React.createElement(
|
|
2451
|
+
"div",
|
|
2452
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
|
|
2453
|
+
React.createElement("b", {}, "\u5BA1\u67E5\u7EBF\u7A0B"),
|
|
2454
|
+
React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("all"), title: "\u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B" }, "\u5168\u90E8\u5C55\u5F00"),
|
|
2455
|
+
React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("none"), title: "\u6536\u8D77\u5168\u90E8\u7EBF\u7A0B" }, "\u5168\u90E8\u6536\u8D77"),
|
|
2456
|
+
React.createElement(
|
|
2457
|
+
"span",
|
|
2458
|
+
{ className: "iterate-filter-count" },
|
|
2459
|
+
`${threadMode === "all" ? "\u5168\u90E8\u5C55\u5F00" : threadMode === "none" ? "\u5168\u90E8\u6536\u8D77" : "\u81EA\u7531\u5207\u6362"} \xB7 ${rounds.length} \u8F6E`
|
|
2460
|
+
)
|
|
2461
|
+
),
|
|
2462
|
+
...rounds.map((r, ri) => {
|
|
2463
|
+
const threads = r.threads || [];
|
|
2464
|
+
const fCount = threads.reduce((sum, t) => sum + (t.findings ? t.findings.length : 0), 0);
|
|
2465
|
+
const threadBlocks = threads.length === 0 ? [React.createElement("div", { key: "none", className: "iterate-obs-empty" }, "\u672C\u8F6E\u65E0\u7EBF\u7A0B")] : threads.map((t, ti) => {
|
|
2466
|
+
const key = `${ri}-${ti}`;
|
|
2467
|
+
const expanded = isThreadExpanded(key);
|
|
2468
|
+
const dim = t.dimension || "\u672A\u547D\u540D\u7EF4\u5EA6";
|
|
2469
|
+
const tFindings = t.findings || [];
|
|
2470
|
+
const files = t.readFiles || [];
|
|
2471
|
+
const msgs = t.messages || [];
|
|
2472
|
+
return React.createElement(
|
|
2236
2473
|
"div",
|
|
2237
|
-
{ className: "iterate-obs-block
|
|
2474
|
+
{ key, className: "iterate-obs-block" },
|
|
2238
2475
|
React.createElement(
|
|
2239
2476
|
"div",
|
|
2240
|
-
{ className: "iterate-obs-
|
|
2241
|
-
React.createElement("
|
|
2242
|
-
React.createElement("
|
|
2477
|
+
{ className: "iterate-obs-block-head", "data-click": "", onClick: () => toggleThread(key), title: expanded ? "\u6536\u8D77\u7EBF\u7A0B" : "\u5C55\u5F00\u7EBF\u7A0B" },
|
|
2478
|
+
React.createElement("span", {}, expanded ? "\u2212" : "+"),
|
|
2479
|
+
React.createElement("b", {}, dim),
|
|
2480
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `attempt ${typeof t.attempt === "number" ? t.attempt : "?"}`),
|
|
2481
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `${tFindings.length} findings`)
|
|
2243
2482
|
),
|
|
2244
|
-
|
|
2245
|
-
|
|
2483
|
+
expanded ? React.createElement(
|
|
2484
|
+
"div",
|
|
2485
|
+
{ className: "iterate-obs-block-body" },
|
|
2246
2486
|
React.createElement(
|
|
2247
2487
|
"div",
|
|
2248
|
-
{
|
|
2249
|
-
React.createElement("b", {}, "\
|
|
2488
|
+
{ className: "iterate-obs-bar" },
|
|
2489
|
+
React.createElement("b", {}, "\u53D9\u8FF0"),
|
|
2490
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `${msgs.length} \u6761`)
|
|
2250
2491
|
),
|
|
2251
|
-
...
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2492
|
+
...msgs.length === 0 ? [React.createElement("div", { key: "msg-none", className: "iterate-obs-empty" }, "\u65E0\u7EBF\u7D22\u53D9\u8FF0")] : msgs.map((m, mi) => React.createElement("div", { key: mi, className: "iterate-obs-msg" }, String(m))),
|
|
2493
|
+
...files.length > 0 ? [
|
|
2494
|
+
React.createElement(
|
|
2495
|
+
"div",
|
|
2496
|
+
{ key: "files-h", className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2497
|
+
React.createElement("b", {}, "\u8BFB\u53D6\u6587\u4EF6")
|
|
2498
|
+
),
|
|
2499
|
+
...files.map(
|
|
2500
|
+
(f, fi) => React.createElement("div", { key: `f${fi}`, className: "iterate-obs-file" }, String(f))
|
|
2501
|
+
)
|
|
2502
|
+
] : [],
|
|
2503
|
+
React.createElement(
|
|
2504
|
+
"div",
|
|
2505
|
+
{ className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2506
|
+
React.createElement("b", {}, "\u53D1\u73B0")
|
|
2507
|
+
),
|
|
2508
|
+
...tFindings.length === 0 ? [React.createElement("div", { key: "finding-none", className: "iterate-obs-empty" }, "\u672C\u7EBF\u7A0B\u65E0\u53D1\u73B0")] : tFindings.map((f, fi) => threadFindingPill(f, `tf${fi}`))
|
|
2509
|
+
) : null
|
|
2510
|
+
);
|
|
2511
|
+
});
|
|
2512
|
+
return React.createElement(
|
|
2268
2513
|
"div",
|
|
2269
|
-
{ className: "iterate-obs-block
|
|
2270
|
-
React.createElement(
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2514
|
+
{ key: ri, className: "iterate-obs-block" },
|
|
2515
|
+
React.createElement(
|
|
2516
|
+
"div",
|
|
2517
|
+
{ className: "iterate-obs-block-head" },
|
|
2518
|
+
React.createElement("b", {}, `Round ${typeof r.round === "number" ? r.round : ri + 1}`),
|
|
2519
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `${threads.length} \u7EBF\u7A0B`),
|
|
2520
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `${fCount} findings`)
|
|
2521
|
+
),
|
|
2522
|
+
React.createElement("div", { className: "iterate-obs-block-body" }, ...threadBlocks)
|
|
2523
|
+
);
|
|
2524
|
+
})
|
|
2525
|
+
);
|
|
2277
2526
|
};
|
|
2278
2527
|
const renderTrend = () => {
|
|
2279
2528
|
const conv = manifest.convergence || [];
|
|
@@ -2297,40 +2546,94 @@ function ObservatoryPanel(props) {
|
|
|
2297
2546
|
if (findings.length === 0) {
|
|
2298
2547
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u53D1\u73B0");
|
|
2299
2548
|
}
|
|
2300
|
-
|
|
2549
|
+
const opts = buildFilterOptions(findings);
|
|
2550
|
+
const { filtered } = filterFindingsWithIndices(findings, f3Filter);
|
|
2551
|
+
const filterActive = f3Filter.severities.length > 0 || f3Filter.dimensions.length > 0 || f3Filter.search.trim() !== "";
|
|
2552
|
+
const toggleSeverity = (sev) => {
|
|
2553
|
+
setF3Filter((prev) => ({
|
|
2554
|
+
...prev,
|
|
2555
|
+
severities: prev.severities.includes(sev) ? prev.severities.filter((s) => s !== sev) : [...prev.severities, sev]
|
|
2556
|
+
}));
|
|
2557
|
+
};
|
|
2558
|
+
const filterBar = React.createElement(
|
|
2559
|
+
"div",
|
|
2560
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
|
|
2561
|
+
React.createElement("b", {}, "\u53D1\u73B0"),
|
|
2562
|
+
...opts.severities.map(
|
|
2563
|
+
(s) => React.createElement("button", {
|
|
2564
|
+
key: `sev-${s.value}`,
|
|
2565
|
+
className: "iterate-filter-chip",
|
|
2566
|
+
"data-active": f3Filter.severities.includes(s.value) ? "" : void 0,
|
|
2567
|
+
title: `${s.value}\uFF08${s.count} \u9879\uFF09`,
|
|
2568
|
+
onClick: () => toggleSeverity(s.value)
|
|
2569
|
+
}, `${s.value} ${s.count}`)
|
|
2570
|
+
),
|
|
2571
|
+
React.createElement(
|
|
2572
|
+
"select",
|
|
2573
|
+
{
|
|
2574
|
+
className: "iterate-filter-select",
|
|
2575
|
+
value: f3Filter.dimensions[0] || "",
|
|
2576
|
+
"aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009",
|
|
2577
|
+
onChange: (e) => {
|
|
2578
|
+
const v = e.target.value;
|
|
2579
|
+
setF3Filter((prev) => ({ ...prev, dimensions: v ? [v] : [] }));
|
|
2580
|
+
}
|
|
2581
|
+
},
|
|
2582
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
|
|
2583
|
+
...opts.dimensions.map(
|
|
2584
|
+
(d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
|
|
2585
|
+
)
|
|
2586
|
+
),
|
|
2587
|
+
React.createElement("input", {
|
|
2588
|
+
className: "iterate-filter-search",
|
|
2589
|
+
type: "search",
|
|
2590
|
+
placeholder: "\u641C\u7D22\u53D1\u73B0\u2026",
|
|
2591
|
+
"aria-label": "\u641C\u7D22\u53D1\u73B0",
|
|
2592
|
+
value: f3Filter.search,
|
|
2593
|
+
onChange: (e) => setF3Filter((prev) => ({ ...prev, search: e.target.value }))
|
|
2594
|
+
}),
|
|
2595
|
+
filterActive ? React.createElement("button", {
|
|
2596
|
+
className: "iterate-filter-clear",
|
|
2597
|
+
onClick: () => setF3Filter({ severities: [], dimensions: [], search: "" }),
|
|
2598
|
+
title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
|
|
2599
|
+
}, "\u6E05\u9664\u7B5B\u9009") : null,
|
|
2600
|
+
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${findings.length}`)
|
|
2601
|
+
);
|
|
2602
|
+
const findingBlocks = filtered.map((f, i) => {
|
|
2301
2603
|
const k = `f3-${i}`;
|
|
2302
|
-
const
|
|
2604
|
+
const finding = f;
|
|
2605
|
+
const loc = `${String(finding.file || "?")}${typeof finding.line === "number" && finding.line > 0 ? `:${finding.line}` : ""}`;
|
|
2303
2606
|
const entry = {
|
|
2304
|
-
file: String(
|
|
2305
|
-
...typeof
|
|
2306
|
-
dimension: String(
|
|
2307
|
-
reason: String(
|
|
2607
|
+
file: String(finding.file || ""),
|
|
2608
|
+
...typeof finding.line === "number" && finding.line > 0 ? { line: finding.line } : {},
|
|
2609
|
+
dimension: String(finding.dimension || ""),
|
|
2610
|
+
reason: String(finding.summary || "")
|
|
2308
2611
|
};
|
|
2309
2612
|
const knownInstruction = buildApplyInstruction([entry]);
|
|
2310
|
-
const fixInstruction = buildObsFixInstruction(
|
|
2613
|
+
const fixInstruction = buildObsFixInstruction(finding);
|
|
2311
2614
|
return React.createElement(
|
|
2312
2615
|
"div",
|
|
2313
2616
|
{ key: k, className: "iterate-obs-block" },
|
|
2314
2617
|
React.createElement(
|
|
2315
2618
|
"div",
|
|
2316
2619
|
{ className: "iterate-obs-block-head" },
|
|
2317
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(
|
|
2318
|
-
React.createElement("span", {}, severityLabel(
|
|
2620
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(finding.severity) } }),
|
|
2621
|
+
React.createElement("span", {}, severityLabel(finding.severity)),
|
|
2319
2622
|
React.createElement("span", { className: "iterate-obs-file" }, loc),
|
|
2320
|
-
React.createElement("span", {}, String(
|
|
2321
|
-
|
|
2623
|
+
React.createElement("span", {}, String(finding.dimension || "")),
|
|
2624
|
+
finding.acknowledged === true ? React.createElement("span", { className: "iterate-obs-chip" }, "\u5DF2\u786E\u8BA4") : null
|
|
2322
2625
|
),
|
|
2323
2626
|
React.createElement(
|
|
2324
2627
|
"div",
|
|
2325
2628
|
{ className: "iterate-obs-block-body" },
|
|
2326
|
-
React.createElement("div", { className: "iterate-obs-msg" }, String(
|
|
2629
|
+
React.createElement("div", { className: "iterate-obs-msg" }, String(finding.summary || "")),
|
|
2327
2630
|
React.createElement(
|
|
2328
2631
|
"div",
|
|
2329
2632
|
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
2330
2633
|
React.createElement("button", {
|
|
2331
2634
|
className: "iterate-btn",
|
|
2332
2635
|
"data-copied": copiedKey === `${k}-fix` ? "" : void 0,
|
|
2333
|
-
onClick: () => copyInstruction(`${k}-fix`, String(
|
|
2636
|
+
onClick: () => copyInstruction(`${k}-fix`, String(finding.suggested_fix || "")),
|
|
2334
2637
|
title: "\u590D\u5236\u8BE5 finding \u7684\u5EFA\u8BAE\u4FEE\u590D\u6587\u672C"
|
|
2335
2638
|
}, copiedKey === `${k}-fix` ? "\u5DF2\u590D\u5236" : "\u590D\u5236 suggested_fix"),
|
|
2336
2639
|
React.createElement("button", {
|
|
@@ -2349,7 +2652,16 @@ function ObservatoryPanel(props) {
|
|
|
2349
2652
|
)
|
|
2350
2653
|
)
|
|
2351
2654
|
);
|
|
2352
|
-
})
|
|
2655
|
+
});
|
|
2656
|
+
if (filtered.length === 0) {
|
|
2657
|
+
return React.createElement(
|
|
2658
|
+
"div",
|
|
2659
|
+
{},
|
|
2660
|
+
filterBar,
|
|
2661
|
+
React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u53D1\u73B0")
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2664
|
+
return React.createElement("div", {}, filterBar, ...findingBlocks);
|
|
2353
2665
|
};
|
|
2354
2666
|
const renderFixes = () => {
|
|
2355
2667
|
const fixes = manifest.fixes || [];
|
|
@@ -2515,14 +2827,21 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2515
2827
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE");
|
|
2516
2828
|
}
|
|
2517
2829
|
const types = Array.from(new Set(entries.map((t) => String(t.type || "unknown")).filter(Boolean))).sort();
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2830
|
+
const rounds = Array.from(
|
|
2831
|
+
new Set(entries.map((t) => typeof t.round === "number" ? String(t.round) : "").filter(Boolean))
|
|
2832
|
+
).sort((a, b) => Number(a) - Number(b));
|
|
2833
|
+
const filtered = filterTimelineEntries(entries, {
|
|
2834
|
+
type: timelineType,
|
|
2835
|
+
round: timelineRound,
|
|
2836
|
+
search: timelineSearch
|
|
2524
2837
|
});
|
|
2525
|
-
const
|
|
2838
|
+
const filterActive = Boolean(timelineType || timelineRound || timelineSearch.trim());
|
|
2839
|
+
const clearTimelineFilter = () => {
|
|
2840
|
+
setTimelineType("");
|
|
2841
|
+
setTimelineRound("");
|
|
2842
|
+
setTimelineSearch("");
|
|
2843
|
+
};
|
|
2844
|
+
const sorted = filtered.slice();
|
|
2526
2845
|
const rows = sorted.map((t, i) => {
|
|
2527
2846
|
const k = `f7-${i}`;
|
|
2528
2847
|
const dataText = t.data && typeof t.data === "object" ? JSON.stringify(t.data) : "";
|
|
@@ -2542,37 +2861,232 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2542
2861
|
dataText ? React.createElement("div", { className: "iterate-obs-code" }, dataText) : null
|
|
2543
2862
|
);
|
|
2544
2863
|
});
|
|
2864
|
+
const filterBar = React.createElement(
|
|
2865
|
+
"div",
|
|
2866
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2867
|
+
React.createElement("b", {}, "\u65F6\u95F4\u7EBF"),
|
|
2868
|
+
React.createElement(
|
|
2869
|
+
"select",
|
|
2870
|
+
{
|
|
2871
|
+
className: "iterate-filter-select",
|
|
2872
|
+
value: timelineType,
|
|
2873
|
+
"aria-label": "\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009",
|
|
2874
|
+
onChange: (e) => setTimelineType(e.target.value)
|
|
2875
|
+
},
|
|
2876
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
|
|
2877
|
+
...types.map((t) => React.createElement("option", { key: t, value: t }, t))
|
|
2878
|
+
),
|
|
2879
|
+
React.createElement(
|
|
2880
|
+
"select",
|
|
2881
|
+
{
|
|
2882
|
+
className: "iterate-filter-select",
|
|
2883
|
+
value: timelineRound,
|
|
2884
|
+
"aria-label": "\u6309\u8F6E\u6B21\u7B5B\u9009",
|
|
2885
|
+
onChange: (e) => setTimelineRound(e.target.value)
|
|
2886
|
+
},
|
|
2887
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u8F6E\u6B21"),
|
|
2888
|
+
...rounds.map((r) => React.createElement("option", { key: r, value: r }, `Round ${r}`))
|
|
2889
|
+
),
|
|
2890
|
+
React.createElement("input", {
|
|
2891
|
+
className: "iterate-filter-search",
|
|
2892
|
+
type: "search",
|
|
2893
|
+
placeholder: "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026",
|
|
2894
|
+
"aria-label": "\u641C\u7D22\u65F6\u95F4\u7EBF",
|
|
2895
|
+
value: timelineSearch,
|
|
2896
|
+
onChange: (e) => setTimelineSearch(e.target.value)
|
|
2897
|
+
}),
|
|
2898
|
+
filterActive ? React.createElement("button", {
|
|
2899
|
+
className: "iterate-filter-clear",
|
|
2900
|
+
onClick: clearTimelineFilter,
|
|
2901
|
+
title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
|
|
2902
|
+
}, "\u6E05\u9664") : null,
|
|
2903
|
+
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${entries.length}`)
|
|
2904
|
+
);
|
|
2545
2905
|
if (rows.length === 0) {
|
|
2546
|
-
return React.createElement(
|
|
2906
|
+
return React.createElement(
|
|
2907
|
+
"div",
|
|
2908
|
+
{},
|
|
2909
|
+
filterBar,
|
|
2910
|
+
React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE")
|
|
2911
|
+
);
|
|
2547
2912
|
}
|
|
2913
|
+
return React.createElement("div", {}, filterBar, ...rows);
|
|
2914
|
+
};
|
|
2915
|
+
const renderQualityGate = () => {
|
|
2916
|
+
const qualityGateInstruction = "\u8BF7\u8C03\u7528 `iterate_quality_gate` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001";
|
|
2548
2917
|
return React.createElement(
|
|
2549
2918
|
"div",
|
|
2550
2919
|
{},
|
|
2551
2920
|
React.createElement(
|
|
2552
2921
|
"div",
|
|
2553
2922
|
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2923
|
+
React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
|
|
2924
|
+
React.createElement("button", {
|
|
2925
|
+
className: "iterate-btn",
|
|
2926
|
+
"data-primary": "",
|
|
2927
|
+
"data-copied": copiedKey === "qgate" ? "" : void 0,
|
|
2928
|
+
onClick: () => copyInstruction("qgate", qualityGateInstruction),
|
|
2929
|
+
title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4"
|
|
2930
|
+
}, copiedKey === "qgate" ? "\u5DF2\u590D\u5236" : "\u67E5\u8BE2\u95E8\u7981")
|
|
2931
|
+
),
|
|
2932
|
+
React.createElement(
|
|
2933
|
+
"div",
|
|
2934
|
+
{ className: "iterate-obs-block" },
|
|
2554
2935
|
React.createElement(
|
|
2555
|
-
"
|
|
2556
|
-
{
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
"aria-label": "\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009",
|
|
2560
|
-
onChange: (e) => setTimelineType(e.target.value)
|
|
2561
|
-
},
|
|
2562
|
-
React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
|
|
2563
|
-
...types.map((t) => React.createElement("option", { key: t, value: t }, t))
|
|
2936
|
+
"div",
|
|
2937
|
+
{ className: "iterate-obs-block-head" },
|
|
2938
|
+
React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
|
|
2939
|
+
React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
|
|
2564
2940
|
),
|
|
2941
|
+
React.createElement(
|
|
2942
|
+
"div",
|
|
2943
|
+
{ className: "iterate-obs-block-body" },
|
|
2944
|
+
React.createElement("div", { className: "iterate-obs-msg" }, "\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002"),
|
|
2945
|
+
React.createElement(
|
|
2946
|
+
"div",
|
|
2947
|
+
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
2948
|
+
React.createElement("button", {
|
|
2949
|
+
className: "iterate-btn",
|
|
2950
|
+
"data-copied": copiedKey === "qgate-instr" ? "" : void 0,
|
|
2951
|
+
onClick: () => copyInstruction("qgate-instr", qualityGateInstruction),
|
|
2952
|
+
title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
|
|
2953
|
+
}, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
|
|
2954
|
+
)
|
|
2955
|
+
)
|
|
2956
|
+
)
|
|
2957
|
+
);
|
|
2958
|
+
};
|
|
2959
|
+
const renderExperienceBank = () => {
|
|
2960
|
+
const expInstruction = expSearch ? `\u8BF7\u8C03\u7528 \`iterate_experience\` \u641C\u7D22\u7ECF\u9A8C\uFF1A
|
|
2961
|
+
|
|
2962
|
+
\`\`\`json
|
|
2963
|
+
${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
|
|
2964
|
+
\`\`\`` : "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C";
|
|
2965
|
+
const resultRows = expResults.map((entry, i) => {
|
|
2966
|
+
const e = entry;
|
|
2967
|
+
return React.createElement(
|
|
2968
|
+
"div",
|
|
2969
|
+
{ key: `exp-${i}`, className: "iterate-obs-row" },
|
|
2970
|
+
React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
|
|
2971
|
+
React.createElement("span", { className: "iterate-obs-msg" }, String(e.pattern || "")),
|
|
2972
|
+
React.createElement("span", { className: "iterate-exp-hit" }, `\u547D\u4E2D ${e.hitCount ?? 0} \u6B21`)
|
|
2973
|
+
);
|
|
2974
|
+
});
|
|
2975
|
+
return React.createElement(
|
|
2976
|
+
"div",
|
|
2977
|
+
{},
|
|
2978
|
+
React.createElement(
|
|
2979
|
+
"div",
|
|
2980
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2981
|
+
React.createElement("b", {}, "\u7ECF\u9A8C\u94F6\u884C"),
|
|
2982
|
+
React.createElement("button", {
|
|
2983
|
+
className: "iterate-btn",
|
|
2984
|
+
"data-primary": "",
|
|
2985
|
+
"data-copied": copiedKey === "exp-list" ? "" : void 0,
|
|
2986
|
+
onClick: () => copyInstruction("exp-list", "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C"),
|
|
2987
|
+
title: "\u590D\u5236\u5217\u51FA\u7ECF\u9A8C\u6307\u4EE4"
|
|
2988
|
+
}, copiedKey === "exp-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u7ECF\u9A8C")
|
|
2989
|
+
),
|
|
2990
|
+
React.createElement(
|
|
2991
|
+
"div",
|
|
2992
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2565
2993
|
React.createElement("input", {
|
|
2566
2994
|
className: "iterate-filter-search",
|
|
2567
2995
|
type: "search",
|
|
2568
|
-
placeholder: "\u641C\u7D22\
|
|
2569
|
-
"aria-label": "\u641C\u7D22\
|
|
2570
|
-
value:
|
|
2571
|
-
onChange: (e) =>
|
|
2996
|
+
placeholder: "\u641C\u7D22\u7ECF\u9A8C\u6A21\u5F0F\u2026",
|
|
2997
|
+
"aria-label": "\u641C\u7D22\u7ECF\u9A8C",
|
|
2998
|
+
value: expSearch,
|
|
2999
|
+
onChange: (e) => setExpSearch(e.target.value)
|
|
2572
3000
|
}),
|
|
2573
|
-
React.createElement("
|
|
3001
|
+
React.createElement("button", {
|
|
3002
|
+
className: "iterate-btn",
|
|
3003
|
+
"data-copied": copiedKey === "exp-search" ? "" : void 0,
|
|
3004
|
+
disabled: !expSearch,
|
|
3005
|
+
onClick: () => copyInstruction("exp-search", expInstruction),
|
|
3006
|
+
title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
|
|
3007
|
+
}, copiedKey === "exp-search" ? "\u5DF2\u590D\u5236" : "\u641C\u7D22")
|
|
3008
|
+
),
|
|
3009
|
+
resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : React.createElement("div", { className: "iterate-obs-empty" }, "\u8F93\u5165\u5173\u952E\u8BCD\u641C\u7D22\u5386\u53F2\u7ECF\u9A8C\uFF0C\u6216\u70B9\u51FB\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u770B\u5168\u90E8")
|
|
3010
|
+
);
|
|
3011
|
+
};
|
|
3012
|
+
const renderDefenseEvents = () => {
|
|
3013
|
+
const defenseInstruction = defenseFilter ? `\u8BF7\u8C03\u7528 \`iterate_defense_events\` \u67E5\u8BE2\u9632\u5FA1\u4E8B\u4EF6\uFF1A
|
|
3014
|
+
|
|
3015
|
+
\`\`\`json
|
|
3016
|
+
${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
|
|
3017
|
+
\`\`\`` : "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6";
|
|
3018
|
+
const typeOptions = [
|
|
3019
|
+
{ value: "", label: "\u5168\u90E8\u7C7B\u578B" },
|
|
3020
|
+
{ value: "precondition_failed", label: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25" },
|
|
3021
|
+
{ value: "rollback", label: "\u56DE\u6EDA" },
|
|
3022
|
+
{ value: "invariant_violated", label: "\u4E0D\u53D8\u91CF\u8FDD\u53CD" },
|
|
3023
|
+
{ value: "assumption_falsified", label: "\u5047\u8BBE\u88AB\u8BC1\u4F2A" }
|
|
3024
|
+
];
|
|
3025
|
+
return React.createElement(
|
|
3026
|
+
"div",
|
|
3027
|
+
{},
|
|
3028
|
+
React.createElement(
|
|
3029
|
+
"div",
|
|
3030
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
3031
|
+
React.createElement("b", {}, "\u9632\u5FA1\u4E8B\u4EF6"),
|
|
3032
|
+
React.createElement("button", {
|
|
3033
|
+
className: "iterate-btn",
|
|
3034
|
+
"data-primary": "",
|
|
3035
|
+
"data-copied": copiedKey === "defense-list" ? "" : void 0,
|
|
3036
|
+
onClick: () => copyInstruction("defense-list", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6"),
|
|
3037
|
+
title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4"
|
|
3038
|
+
}, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
|
|
3039
|
+
React.createElement("button", {
|
|
3040
|
+
className: "iterate-btn",
|
|
3041
|
+
"data-copied": copiedKey === "defense-counts" ? "" : void 0,
|
|
3042
|
+
onClick: () => copyInstruction("defense-counts", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1"),
|
|
3043
|
+
title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4"
|
|
3044
|
+
}, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1")
|
|
2574
3045
|
),
|
|
2575
|
-
|
|
3046
|
+
React.createElement(
|
|
3047
|
+
"div",
|
|
3048
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
3049
|
+
React.createElement(
|
|
3050
|
+
"select",
|
|
3051
|
+
{
|
|
3052
|
+
className: "iterate-filter-select",
|
|
3053
|
+
value: defenseFilter,
|
|
3054
|
+
"aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
|
|
3055
|
+
onChange: (e) => setDefenseFilter(e.target.value)
|
|
3056
|
+
},
|
|
3057
|
+
...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
|
|
3058
|
+
),
|
|
3059
|
+
defenseFilter ? React.createElement("button", {
|
|
3060
|
+
className: "iterate-filter-clear",
|
|
3061
|
+
onClick: () => setDefenseFilter(""),
|
|
3062
|
+
title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
|
|
3063
|
+
}, "\u6E05\u9664") : null
|
|
3064
|
+
),
|
|
3065
|
+
React.createElement(
|
|
3066
|
+
"div",
|
|
3067
|
+
{ className: "iterate-obs-block" },
|
|
3068
|
+
React.createElement(
|
|
3069
|
+
"div",
|
|
3070
|
+
{ className: "iterate-obs-block-head" },
|
|
3071
|
+
React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
|
|
3072
|
+
React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
|
|
3073
|
+
),
|
|
3074
|
+
React.createElement(
|
|
3075
|
+
"div",
|
|
3076
|
+
{ className: "iterate-obs-block-body" },
|
|
3077
|
+
React.createElement("div", { className: "iterate-obs-msg" }, "\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002"),
|
|
3078
|
+
React.createElement(
|
|
3079
|
+
"div",
|
|
3080
|
+
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
3081
|
+
React.createElement("button", {
|
|
3082
|
+
className: "iterate-btn",
|
|
3083
|
+
"data-copied": copiedKey === "defense-instr" ? "" : void 0,
|
|
3084
|
+
onClick: () => copyInstruction("defense-instr", defenseInstruction),
|
|
3085
|
+
title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
|
|
3086
|
+
}, copiedKey === "defense-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
|
|
3087
|
+
)
|
|
3088
|
+
)
|
|
3089
|
+
)
|
|
2576
3090
|
);
|
|
2577
3091
|
};
|
|
2578
3092
|
const renderLive = () => {
|
|
@@ -2595,12 +3109,14 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2595
3109
|
}
|
|
2596
3110
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8");
|
|
2597
3111
|
}
|
|
2598
|
-
const
|
|
3112
|
+
const types = Array.from(new Set(liveEntries.map((e) => String(e.type || "unknown")).filter(Boolean))).sort();
|
|
3113
|
+
const filtered = filterLiveEntries(liveEntries, liveType);
|
|
3114
|
+
const rows = filtered.map((e, i) => {
|
|
2599
3115
|
const type = String(e.type || "unknown");
|
|
2600
3116
|
const meta = OBS_LIVE_META[type] || { label: String(e.type || "?"), color: OBS_LIVE_FALLBACK_COLOR };
|
|
2601
3117
|
const tool = String(e.tool || "");
|
|
2602
3118
|
const target = String(e.target || "");
|
|
2603
|
-
const time = formatObsLiveTime(e.ts);
|
|
3119
|
+
const time = formatObsLiveTime(String(e.ts || ""));
|
|
2604
3120
|
return React.createElement(
|
|
2605
3121
|
"div",
|
|
2606
3122
|
{ key: `live-${i}`, className: "iterate-obs-row" },
|
|
@@ -2610,17 +3126,37 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2610
3126
|
React.createElement("span", { className: "iterate-obs-head-meta" }, time)
|
|
2611
3127
|
);
|
|
2612
3128
|
});
|
|
2613
|
-
|
|
3129
|
+
const filterBar = React.createElement(
|
|
2614
3130
|
"div",
|
|
2615
|
-
{},
|
|
3131
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
3132
|
+
React.createElement("b", {}, "\u5B9E\u65F6\u6D3B\u52A8"),
|
|
2616
3133
|
React.createElement(
|
|
2617
|
-
"
|
|
2618
|
-
{
|
|
2619
|
-
|
|
2620
|
-
|
|
3134
|
+
"select",
|
|
3135
|
+
{
|
|
3136
|
+
className: "iterate-filter-select",
|
|
3137
|
+
value: liveType,
|
|
3138
|
+
"aria-label": "\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009",
|
|
3139
|
+
onChange: (e) => setLiveType(e.target.value)
|
|
3140
|
+
},
|
|
3141
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u6D3B\u52A8"),
|
|
3142
|
+
...types.map((t) => React.createElement("option", { key: t, value: t }, t))
|
|
2621
3143
|
),
|
|
2622
|
-
|
|
3144
|
+
liveType ? React.createElement("button", {
|
|
3145
|
+
className: "iterate-filter-clear",
|
|
3146
|
+
onClick: () => setLiveType(""),
|
|
3147
|
+
title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
|
|
3148
|
+
}, "\u6E05\u9664") : null,
|
|
3149
|
+
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${liveEntries.length}`)
|
|
2623
3150
|
);
|
|
3151
|
+
if (rows.length === 0) {
|
|
3152
|
+
return React.createElement(
|
|
3153
|
+
"div",
|
|
3154
|
+
{},
|
|
3155
|
+
filterBar,
|
|
3156
|
+
React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8")
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3159
|
+
return React.createElement("div", {}, filterBar, ...rows);
|
|
2624
3160
|
};
|
|
2625
3161
|
const renderBody = () => {
|
|
2626
3162
|
switch (tab) {
|
|
@@ -2640,6 +3176,13 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2640
3176
|
return renderConsole();
|
|
2641
3177
|
case "f7":
|
|
2642
3178
|
return renderTimeline();
|
|
3179
|
+
// v3.0: Quality Command Center tabs
|
|
3180
|
+
case "f8":
|
|
3181
|
+
return renderQualityGate();
|
|
3182
|
+
case "f9":
|
|
3183
|
+
return renderExperienceBank();
|
|
3184
|
+
case "f10":
|
|
3185
|
+
return renderDefenseEvents();
|
|
2643
3186
|
default:
|
|
2644
3187
|
return null;
|
|
2645
3188
|
}
|
|
@@ -2682,6 +3225,22 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2682
3225
|
),
|
|
2683
3226
|
React.createElement("div", { className: "iterate-obs-code" }, copyFailText)
|
|
2684
3227
|
) : null,
|
|
3228
|
+
React.createElement(
|
|
3229
|
+
"div",
|
|
3230
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
|
|
3231
|
+
React.createElement(
|
|
3232
|
+
"span",
|
|
3233
|
+
{ className: "iterate-obs-head-meta" },
|
|
3234
|
+
`${manifest.mode || "runtime"} \xB7 ${String(manifest.updatedAt || "")}`
|
|
3235
|
+
),
|
|
3236
|
+
React.createElement("button", {
|
|
3237
|
+
className: "iterate-btn",
|
|
3238
|
+
"data-primary": "",
|
|
3239
|
+
"data-copied": copiedKey === "export" ? "" : void 0,
|
|
3240
|
+
onClick: exportObservatory,
|
|
3241
|
+
title: "\u5C06\u89C2\u6D4B\u53F0\u5168\u90E8\u6570\u636E\u5BFC\u51FA\u4E3A JSON\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09"
|
|
3242
|
+
}, copiedKey === "export" ? "\u5DF2\u5BFC\u51FA" : "\u5BFC\u51FA JSON")
|
|
3243
|
+
),
|
|
2685
3244
|
React.createElement(
|
|
2686
3245
|
"div",
|
|
2687
3246
|
{ className: "iterate-obs-tabs" },
|