iterate-plugin 2.12.2 → 2.12.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/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/dist/git-scope.js +61 -7
- package/lib/client.js +356 -100
- package/lib/parse.js +93 -0
- package/package.json +6 -5
- package/src/client/index.ts +265 -44
- package/src/git-scope.ts +48 -7
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/config.ts +1 -1
- package/src/tools/decision-log.ts +1 -1
- package/src/tools/fix.ts +1 -1
- package/src/tools/history.ts +1 -1
- package/src/tools/prune.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/transcript.ts +1 -1
- package/src/tools/triage.ts +1 -1
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,9 @@ 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); }
|
|
1158
1210
|
|
|
1159
1211
|
/* Accessibility-switch toggle */
|
|
1160
1212
|
.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 +1345,24 @@ function copyText(text) {
|
|
|
1293
1345
|
}
|
|
1294
1346
|
return Promise.resolve(false);
|
|
1295
1347
|
}
|
|
1348
|
+
function downloadTextFile(filename, text) {
|
|
1349
|
+
if (typeof document === "undefined" || typeof Blob === "undefined" || typeof URL === "undefined") return false;
|
|
1350
|
+
try {
|
|
1351
|
+
const blob = new Blob([text], { type: "application/json;charset=utf-8" });
|
|
1352
|
+
const url = URL.createObjectURL(blob);
|
|
1353
|
+
const a = document.createElement("a");
|
|
1354
|
+
a.href = url;
|
|
1355
|
+
a.download = filename;
|
|
1356
|
+
a.rel = "noopener";
|
|
1357
|
+
document.body.appendChild(a);
|
|
1358
|
+
a.click();
|
|
1359
|
+
document.body.removeChild(a);
|
|
1360
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
1361
|
+
return true;
|
|
1362
|
+
} catch {
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1296
1366
|
var SEVERITY_KEYS = ["critical", "high", "medium", "low"];
|
|
1297
1367
|
function coerceSeverity(severity) {
|
|
1298
1368
|
return SEVERITY_KEYS.includes(severity ?? "") ? severity : "low";
|
|
@@ -1415,6 +1485,14 @@ function ConvergenceDashboard(props) {
|
|
|
1415
1485
|
key: "images",
|
|
1416
1486
|
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
1487
|
}, `\u9644\u4EF6\u56FE\u7247 ${String(imageCount)}`) : null;
|
|
1488
|
+
const transcript = latestTranscript(session);
|
|
1489
|
+
const phase = transcript ? latestPhase(transcript.phases) : "";
|
|
1490
|
+
const liveChip = transcript && phase ? React.createElement("span", {
|
|
1491
|
+
className: "iterate-chip-phase",
|
|
1492
|
+
key: "phase",
|
|
1493
|
+
"data-live": transcript.active === true ? "" : void 0,
|
|
1494
|
+
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`
|
|
1495
|
+
}, `${phase} \xB7 ${transcript.active === true ? "\u8FD0\u884C\u4E2D" : "\u5DF2\u7ED3\u675F"}`) : null;
|
|
1418
1496
|
const dimNames = Object.keys(dims);
|
|
1419
1497
|
const dimBadges = dimNames.slice(0, 6).map(
|
|
1420
1498
|
(dim) => React.createElement(
|
|
@@ -1478,6 +1556,7 @@ function ConvergenceDashboard(props) {
|
|
|
1478
1556
|
fixBadge,
|
|
1479
1557
|
resumeChip,
|
|
1480
1558
|
imageChip,
|
|
1559
|
+
liveChip,
|
|
1481
1560
|
React.createElement(TrendChart, { points: trend.points }),
|
|
1482
1561
|
...dimBadges
|
|
1483
1562
|
);
|
|
@@ -2150,11 +2229,17 @@ function ObservatoryPanel(props) {
|
|
|
2150
2229
|
const [open, setOpen] = React.useState(false);
|
|
2151
2230
|
const [tab, setTab] = React.useState("live");
|
|
2152
2231
|
const [expandedThreads, setExpandedThreads] = React.useState(/* @__PURE__ */ new Set());
|
|
2232
|
+
const [threadMode, setThreadMode] = React.useState("auto");
|
|
2153
2233
|
const [copiedKey, setCopiedKey] = React.useState(null);
|
|
2154
2234
|
const [copyFailText, setCopyFailText] = React.useState(null);
|
|
2155
2235
|
const [nudgeText, setNudgeText] = React.useState("");
|
|
2156
2236
|
const [timelineType, setTimelineType] = React.useState("");
|
|
2237
|
+
const [timelineRound, setTimelineRound] = React.useState("");
|
|
2157
2238
|
const [timelineSearch, setTimelineSearch] = React.useState("");
|
|
2239
|
+
const [liveType, setLiveType] = React.useState("");
|
|
2240
|
+
const [f3Filter, setF3Filter] = React.useState(
|
|
2241
|
+
{ severities: [], dimensions: [], search: "" }
|
|
2242
|
+
);
|
|
2158
2243
|
const copyTimer = React.useRef(null);
|
|
2159
2244
|
React.useEffect(() => () => {
|
|
2160
2245
|
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
@@ -2172,6 +2257,7 @@ function ObservatoryPanel(props) {
|
|
|
2172
2257
|
});
|
|
2173
2258
|
};
|
|
2174
2259
|
const toggleThread = (key) => {
|
|
2260
|
+
setThreadMode("auto");
|
|
2175
2261
|
setExpandedThreads((prev) => {
|
|
2176
2262
|
const next = new Set(prev);
|
|
2177
2263
|
if (next.has(key)) next.delete(key);
|
|
@@ -2179,6 +2265,35 @@ function ObservatoryPanel(props) {
|
|
|
2179
2265
|
return next;
|
|
2180
2266
|
});
|
|
2181
2267
|
};
|
|
2268
|
+
const exportObservatory = () => {
|
|
2269
|
+
const json = serializeObservatoryExport(manifest, manifest.live || []);
|
|
2270
|
+
const filename = `iterate-observatory-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`;
|
|
2271
|
+
const flash = () => {
|
|
2272
|
+
setCopiedKey("export");
|
|
2273
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
2274
|
+
copyTimer.current = setTimeout(() => setCopiedKey((cur) => cur === "export" ? null : cur), 1600);
|
|
2275
|
+
};
|
|
2276
|
+
if (downloadTextFile(filename, json)) {
|
|
2277
|
+
flash();
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
copyText(json).then((ok) => {
|
|
2281
|
+
if (!ok) {
|
|
2282
|
+
setCopyFailText(json);
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
flash();
|
|
2286
|
+
});
|
|
2287
|
+
};
|
|
2288
|
+
const setAllThreads = (mode) => {
|
|
2289
|
+
setThreadMode(mode);
|
|
2290
|
+
setExpandedThreads(/* @__PURE__ */ new Set());
|
|
2291
|
+
};
|
|
2292
|
+
const isThreadExpanded = (key) => {
|
|
2293
|
+
if (threadMode === "all") return true;
|
|
2294
|
+
if (threadMode === "none") return false;
|
|
2295
|
+
return expandedThreads.has(key);
|
|
2296
|
+
};
|
|
2182
2297
|
if (!manifest) {
|
|
2183
2298
|
return React.createElement(
|
|
2184
2299
|
"div",
|
|
@@ -2211,69 +2326,85 @@ function ObservatoryPanel(props) {
|
|
|
2211
2326
|
if (rounds.length === 0) {
|
|
2212
2327
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5BA1\u67E5\u7EBF\u7A0B");
|
|
2213
2328
|
}
|
|
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
|
-
|
|
2329
|
+
return React.createElement(
|
|
2330
|
+
"div",
|
|
2331
|
+
{},
|
|
2332
|
+
React.createElement(
|
|
2333
|
+
"div",
|
|
2334
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
|
|
2335
|
+
React.createElement("b", {}, "\u5BA1\u67E5\u7EBF\u7A0B"),
|
|
2336
|
+
React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("all"), title: "\u5C55\u5F00\u5168\u90E8\u7EBF\u7A0B" }, "\u5168\u90E8\u5C55\u5F00"),
|
|
2337
|
+
React.createElement("button", { className: "iterate-btn", onClick: () => setAllThreads("none"), title: "\u6536\u8D77\u5168\u90E8\u7EBF\u7A0B" }, "\u5168\u90E8\u6536\u8D77"),
|
|
2338
|
+
React.createElement(
|
|
2339
|
+
"span",
|
|
2340
|
+
{ className: "iterate-filter-count" },
|
|
2341
|
+
`${threadMode === "all" ? "\u5168\u90E8\u5C55\u5F00" : threadMode === "none" ? "\u5168\u90E8\u6536\u8D77" : "\u81EA\u7531\u5207\u6362"} \xB7 ${rounds.length} \u8F6E`
|
|
2342
|
+
)
|
|
2343
|
+
),
|
|
2344
|
+
...rounds.map((r, ri) => {
|
|
2345
|
+
const threads = r.threads || [];
|
|
2346
|
+
const fCount = threads.reduce((sum, t) => sum + (t.findings ? t.findings.length : 0), 0);
|
|
2347
|
+
const threadBlocks = threads.length === 0 ? [React.createElement("div", { key: "none", className: "iterate-obs-empty" }, "\u672C\u8F6E\u65E0\u7EBF\u7A0B")] : threads.map((t, ti) => {
|
|
2348
|
+
const key = `${ri}-${ti}`;
|
|
2349
|
+
const expanded = isThreadExpanded(key);
|
|
2350
|
+
const dim = t.dimension || "\u672A\u547D\u540D\u7EF4\u5EA6";
|
|
2351
|
+
const tFindings = t.findings || [];
|
|
2352
|
+
const files = t.readFiles || [];
|
|
2353
|
+
const msgs = t.messages || [];
|
|
2354
|
+
return React.createElement(
|
|
2236
2355
|
"div",
|
|
2237
|
-
{ className: "iterate-obs-block
|
|
2356
|
+
{ key, className: "iterate-obs-block" },
|
|
2238
2357
|
React.createElement(
|
|
2239
2358
|
"div",
|
|
2240
|
-
{ className: "iterate-obs-
|
|
2241
|
-
React.createElement("
|
|
2242
|
-
React.createElement("
|
|
2359
|
+
{ className: "iterate-obs-block-head", "data-click": "", onClick: () => toggleThread(key), title: expanded ? "\u6536\u8D77\u7EBF\u7A0B" : "\u5C55\u5F00\u7EBF\u7A0B" },
|
|
2360
|
+
React.createElement("span", {}, expanded ? "\u2212" : "+"),
|
|
2361
|
+
React.createElement("b", {}, dim),
|
|
2362
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `attempt ${typeof t.attempt === "number" ? t.attempt : "?"}`),
|
|
2363
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `${tFindings.length} findings`)
|
|
2243
2364
|
),
|
|
2244
|
-
|
|
2245
|
-
|
|
2365
|
+
expanded ? React.createElement(
|
|
2366
|
+
"div",
|
|
2367
|
+
{ className: "iterate-obs-block-body" },
|
|
2246
2368
|
React.createElement(
|
|
2247
2369
|
"div",
|
|
2248
|
-
{
|
|
2249
|
-
React.createElement("b", {}, "\
|
|
2370
|
+
{ className: "iterate-obs-bar" },
|
|
2371
|
+
React.createElement("b", {}, "\u53D9\u8FF0"),
|
|
2372
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `${msgs.length} \u6761`)
|
|
2250
2373
|
),
|
|
2251
|
-
...
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2374
|
+
...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))),
|
|
2375
|
+
...files.length > 0 ? [
|
|
2376
|
+
React.createElement(
|
|
2377
|
+
"div",
|
|
2378
|
+
{ key: "files-h", className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2379
|
+
React.createElement("b", {}, "\u8BFB\u53D6\u6587\u4EF6")
|
|
2380
|
+
),
|
|
2381
|
+
...files.map(
|
|
2382
|
+
(f, fi) => React.createElement("div", { key: `f${fi}`, className: "iterate-obs-file" }, String(f))
|
|
2383
|
+
)
|
|
2384
|
+
] : [],
|
|
2385
|
+
React.createElement(
|
|
2386
|
+
"div",
|
|
2387
|
+
{ className: "iterate-obs-bar", style: { marginTop: 6 } },
|
|
2388
|
+
React.createElement("b", {}, "\u53D1\u73B0")
|
|
2389
|
+
),
|
|
2390
|
+
...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}`))
|
|
2391
|
+
) : null
|
|
2392
|
+
);
|
|
2393
|
+
});
|
|
2394
|
+
return React.createElement(
|
|
2268
2395
|
"div",
|
|
2269
|
-
{ className: "iterate-obs-block
|
|
2270
|
-
React.createElement(
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2396
|
+
{ key: ri, className: "iterate-obs-block" },
|
|
2397
|
+
React.createElement(
|
|
2398
|
+
"div",
|
|
2399
|
+
{ className: "iterate-obs-block-head" },
|
|
2400
|
+
React.createElement("b", {}, `Round ${typeof r.round === "number" ? r.round : ri + 1}`),
|
|
2401
|
+
React.createElement("span", { className: "iterate-obs-chip" }, `${threads.length} \u7EBF\u7A0B`),
|
|
2402
|
+
React.createElement("span", { className: "iterate-obs-head-meta" }, `${fCount} findings`)
|
|
2403
|
+
),
|
|
2404
|
+
React.createElement("div", { className: "iterate-obs-block-body" }, ...threadBlocks)
|
|
2405
|
+
);
|
|
2406
|
+
})
|
|
2407
|
+
);
|
|
2277
2408
|
};
|
|
2278
2409
|
const renderTrend = () => {
|
|
2279
2410
|
const conv = manifest.convergence || [];
|
|
@@ -2297,7 +2428,60 @@ function ObservatoryPanel(props) {
|
|
|
2297
2428
|
if (findings.length === 0) {
|
|
2298
2429
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u53D1\u73B0");
|
|
2299
2430
|
}
|
|
2300
|
-
|
|
2431
|
+
const opts = buildFilterOptions(findings);
|
|
2432
|
+
const { filtered } = filterFindingsWithIndices(findings, f3Filter);
|
|
2433
|
+
const filterActive = f3Filter.severities.length > 0 || f3Filter.dimensions.length > 0 || f3Filter.search.trim() !== "";
|
|
2434
|
+
const toggleSeverity = (sev) => {
|
|
2435
|
+
setF3Filter((prev) => ({
|
|
2436
|
+
...prev,
|
|
2437
|
+
severities: prev.severities.includes(sev) ? prev.severities.filter((s) => s !== sev) : [...prev.severities, sev]
|
|
2438
|
+
}));
|
|
2439
|
+
};
|
|
2440
|
+
const filterBar = React.createElement(
|
|
2441
|
+
"div",
|
|
2442
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
|
|
2443
|
+
React.createElement("b", {}, "\u53D1\u73B0"),
|
|
2444
|
+
...opts.severities.map(
|
|
2445
|
+
(s) => React.createElement("button", {
|
|
2446
|
+
key: `sev-${s.value}`,
|
|
2447
|
+
className: "iterate-filter-chip",
|
|
2448
|
+
"data-active": f3Filter.severities.includes(s.value) ? "" : void 0,
|
|
2449
|
+
title: `${s.value}\uFF08${s.count} \u9879\uFF09`,
|
|
2450
|
+
onClick: () => toggleSeverity(s.value)
|
|
2451
|
+
}, `${s.value} ${s.count}`)
|
|
2452
|
+
),
|
|
2453
|
+
React.createElement(
|
|
2454
|
+
"select",
|
|
2455
|
+
{
|
|
2456
|
+
className: "iterate-filter-select",
|
|
2457
|
+
value: f3Filter.dimensions[0] || "",
|
|
2458
|
+
"aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009",
|
|
2459
|
+
onChange: (e) => {
|
|
2460
|
+
const v = e.target.value;
|
|
2461
|
+
setF3Filter((prev) => ({ ...prev, dimensions: v ? [v] : [] }));
|
|
2462
|
+
}
|
|
2463
|
+
},
|
|
2464
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
|
|
2465
|
+
...opts.dimensions.map(
|
|
2466
|
+
(d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
|
|
2467
|
+
)
|
|
2468
|
+
),
|
|
2469
|
+
React.createElement("input", {
|
|
2470
|
+
className: "iterate-filter-search",
|
|
2471
|
+
type: "search",
|
|
2472
|
+
placeholder: "\u641C\u7D22\u53D1\u73B0\u2026",
|
|
2473
|
+
"aria-label": "\u641C\u7D22\u53D1\u73B0",
|
|
2474
|
+
value: f3Filter.search,
|
|
2475
|
+
onChange: (e) => setF3Filter((prev) => ({ ...prev, search: e.target.value }))
|
|
2476
|
+
}),
|
|
2477
|
+
filterActive ? React.createElement("button", {
|
|
2478
|
+
className: "iterate-filter-clear",
|
|
2479
|
+
onClick: () => setF3Filter({ severities: [], dimensions: [], search: "" }),
|
|
2480
|
+
title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
|
|
2481
|
+
}, "\u6E05\u9664\u7B5B\u9009") : null,
|
|
2482
|
+
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${findings.length}`)
|
|
2483
|
+
);
|
|
2484
|
+
const findingBlocks = filtered.map((f, i) => {
|
|
2301
2485
|
const k = `f3-${i}`;
|
|
2302
2486
|
const loc = `${String(f.file || "?")}${typeof f.line === "number" && f.line > 0 ? `:${f.line}` : ""}`;
|
|
2303
2487
|
const entry = {
|
|
@@ -2349,7 +2533,16 @@ function ObservatoryPanel(props) {
|
|
|
2349
2533
|
)
|
|
2350
2534
|
)
|
|
2351
2535
|
);
|
|
2352
|
-
})
|
|
2536
|
+
});
|
|
2537
|
+
if (filtered.length === 0) {
|
|
2538
|
+
return React.createElement(
|
|
2539
|
+
"div",
|
|
2540
|
+
{},
|
|
2541
|
+
filterBar,
|
|
2542
|
+
React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u53D1\u73B0")
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
return React.createElement("div", {}, filterBar, ...findingBlocks);
|
|
2353
2546
|
};
|
|
2354
2547
|
const renderFixes = () => {
|
|
2355
2548
|
const fixes = manifest.fixes || [];
|
|
@@ -2515,14 +2708,21 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2515
2708
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u65F6\u95F4\u7EBF\u6761\u76EE");
|
|
2516
2709
|
}
|
|
2517
2710
|
const types = Array.from(new Set(entries.map((t) => String(t.type || "unknown")).filter(Boolean))).sort();
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2711
|
+
const rounds = Array.from(
|
|
2712
|
+
new Set(entries.map((t) => typeof t.round === "number" ? String(t.round) : "").filter(Boolean))
|
|
2713
|
+
).sort((a, b) => Number(a) - Number(b));
|
|
2714
|
+
const filtered = filterTimelineEntries(entries, {
|
|
2715
|
+
type: timelineType,
|
|
2716
|
+
round: timelineRound,
|
|
2717
|
+
search: timelineSearch
|
|
2524
2718
|
});
|
|
2525
|
-
const
|
|
2719
|
+
const filterActive = Boolean(timelineType || timelineRound || timelineSearch.trim());
|
|
2720
|
+
const clearTimelineFilter = () => {
|
|
2721
|
+
setTimelineType("");
|
|
2722
|
+
setTimelineRound("");
|
|
2723
|
+
setTimelineSearch("");
|
|
2724
|
+
};
|
|
2725
|
+
const sorted = filtered.slice();
|
|
2526
2726
|
const rows = sorted.map((t, i) => {
|
|
2527
2727
|
const k = `f7-${i}`;
|
|
2528
2728
|
const dataText = t.data && typeof t.data === "object" ? JSON.stringify(t.data) : "";
|
|
@@ -2542,38 +2742,56 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2542
2742
|
dataText ? React.createElement("div", { className: "iterate-obs-code" }, dataText) : null
|
|
2543
2743
|
);
|
|
2544
2744
|
});
|
|
2545
|
-
|
|
2546
|
-
return React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE");
|
|
2547
|
-
}
|
|
2548
|
-
return React.createElement(
|
|
2745
|
+
const filterBar = React.createElement(
|
|
2549
2746
|
"div",
|
|
2550
|
-
{},
|
|
2747
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2748
|
+
React.createElement("b", {}, "\u65F6\u95F4\u7EBF"),
|
|
2551
2749
|
React.createElement(
|
|
2552
|
-
"
|
|
2553
|
-
{
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
},
|
|
2562
|
-
React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
|
|
2563
|
-
...types.map((t) => React.createElement("option", { key: t, value: t }, t))
|
|
2564
|
-
),
|
|
2565
|
-
React.createElement("input", {
|
|
2566
|
-
className: "iterate-filter-search",
|
|
2567
|
-
type: "search",
|
|
2568
|
-
placeholder: "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026",
|
|
2569
|
-
"aria-label": "\u641C\u7D22\u65F6\u95F4\u7EBF",
|
|
2570
|
-
value: timelineSearch,
|
|
2571
|
-
onChange: (e) => setTimelineSearch(e.target.value)
|
|
2572
|
-
}),
|
|
2573
|
-
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${entries.length}`)
|
|
2750
|
+
"select",
|
|
2751
|
+
{
|
|
2752
|
+
className: "iterate-filter-select",
|
|
2753
|
+
value: timelineType,
|
|
2754
|
+
"aria-label": "\u6309\u65F6\u95F4\u7EBF\u7C7B\u578B\u7B5B\u9009",
|
|
2755
|
+
onChange: (e) => setTimelineType(e.target.value)
|
|
2756
|
+
},
|
|
2757
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u7C7B\u578B"),
|
|
2758
|
+
...types.map((t) => React.createElement("option", { key: t, value: t }, t))
|
|
2574
2759
|
),
|
|
2575
|
-
|
|
2760
|
+
React.createElement(
|
|
2761
|
+
"select",
|
|
2762
|
+
{
|
|
2763
|
+
className: "iterate-filter-select",
|
|
2764
|
+
value: timelineRound,
|
|
2765
|
+
"aria-label": "\u6309\u8F6E\u6B21\u7B5B\u9009",
|
|
2766
|
+
onChange: (e) => setTimelineRound(e.target.value)
|
|
2767
|
+
},
|
|
2768
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u8F6E\u6B21"),
|
|
2769
|
+
...rounds.map((r) => React.createElement("option", { key: r, value: r }, `Round ${r}`))
|
|
2770
|
+
),
|
|
2771
|
+
React.createElement("input", {
|
|
2772
|
+
className: "iterate-filter-search",
|
|
2773
|
+
type: "search",
|
|
2774
|
+
placeholder: "\u641C\u7D22\u65F6\u95F4\u7EBF\u2026",
|
|
2775
|
+
"aria-label": "\u641C\u7D22\u65F6\u95F4\u7EBF",
|
|
2776
|
+
value: timelineSearch,
|
|
2777
|
+
onChange: (e) => setTimelineSearch(e.target.value)
|
|
2778
|
+
}),
|
|
2779
|
+
filterActive ? React.createElement("button", {
|
|
2780
|
+
className: "iterate-filter-clear",
|
|
2781
|
+
onClick: clearTimelineFilter,
|
|
2782
|
+
title: "\u6E05\u9664\u5168\u90E8\u7B5B\u9009"
|
|
2783
|
+
}, "\u6E05\u9664") : null,
|
|
2784
|
+
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${entries.length}`)
|
|
2576
2785
|
);
|
|
2786
|
+
if (rows.length === 0) {
|
|
2787
|
+
return React.createElement(
|
|
2788
|
+
"div",
|
|
2789
|
+
{},
|
|
2790
|
+
filterBar,
|
|
2791
|
+
React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u65F6\u95F4\u7EBF\u6761\u76EE")
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
return React.createElement("div", {}, filterBar, ...rows);
|
|
2577
2795
|
};
|
|
2578
2796
|
const renderLive = () => {
|
|
2579
2797
|
const liveEntries = manifest.live || [];
|
|
@@ -2595,7 +2813,9 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2595
2813
|
}
|
|
2596
2814
|
return React.createElement("div", { className: "iterate-obs-empty" }, "\u6682\u65E0\u5B9E\u65F6\u6D3B\u52A8");
|
|
2597
2815
|
}
|
|
2598
|
-
const
|
|
2816
|
+
const types = Array.from(new Set(liveEntries.map((e) => String(e.type || "unknown")).filter(Boolean))).sort();
|
|
2817
|
+
const filtered = filterLiveEntries(liveEntries, liveType);
|
|
2818
|
+
const rows = filtered.map((e, i) => {
|
|
2599
2819
|
const type = String(e.type || "unknown");
|
|
2600
2820
|
const meta = OBS_LIVE_META[type] || { label: String(e.type || "?"), color: OBS_LIVE_FALLBACK_COLOR };
|
|
2601
2821
|
const tool = String(e.tool || "");
|
|
@@ -2610,17 +2830,37 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2610
2830
|
React.createElement("span", { className: "iterate-obs-head-meta" }, time)
|
|
2611
2831
|
);
|
|
2612
2832
|
});
|
|
2613
|
-
|
|
2833
|
+
const filterBar = React.createElement(
|
|
2614
2834
|
"div",
|
|
2615
|
-
{},
|
|
2835
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
2836
|
+
React.createElement("b", {}, "\u5B9E\u65F6\u6D3B\u52A8"),
|
|
2616
2837
|
React.createElement(
|
|
2617
|
-
"
|
|
2618
|
-
{
|
|
2619
|
-
|
|
2620
|
-
|
|
2838
|
+
"select",
|
|
2839
|
+
{
|
|
2840
|
+
className: "iterate-filter-select",
|
|
2841
|
+
value: liveType,
|
|
2842
|
+
"aria-label": "\u6309\u6D3B\u52A8\u7C7B\u578B\u7B5B\u9009",
|
|
2843
|
+
onChange: (e) => setLiveType(e.target.value)
|
|
2844
|
+
},
|
|
2845
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u6D3B\u52A8"),
|
|
2846
|
+
...types.map((t) => React.createElement("option", { key: t, value: t }, t))
|
|
2621
2847
|
),
|
|
2622
|
-
|
|
2848
|
+
liveType ? React.createElement("button", {
|
|
2849
|
+
className: "iterate-filter-clear",
|
|
2850
|
+
onClick: () => setLiveType(""),
|
|
2851
|
+
title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
|
|
2852
|
+
}, "\u6E05\u9664") : null,
|
|
2853
|
+
React.createElement("span", { className: "iterate-filter-count" }, `${filtered.length}/${liveEntries.length}`)
|
|
2623
2854
|
);
|
|
2855
|
+
if (rows.length === 0) {
|
|
2856
|
+
return React.createElement(
|
|
2857
|
+
"div",
|
|
2858
|
+
{},
|
|
2859
|
+
filterBar,
|
|
2860
|
+
React.createElement("div", { className: "iterate-obs-empty" }, "\u65E0\u5339\u914D\u7684\u5B9E\u65F6\u6D3B\u52A8")
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
return React.createElement("div", {}, filterBar, ...rows);
|
|
2624
2864
|
};
|
|
2625
2865
|
const renderBody = () => {
|
|
2626
2866
|
switch (tab) {
|
|
@@ -2682,6 +2922,22 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
|
|
|
2682
2922
|
),
|
|
2683
2923
|
React.createElement("div", { className: "iterate-obs-code" }, copyFailText)
|
|
2684
2924
|
) : null,
|
|
2925
|
+
React.createElement(
|
|
2926
|
+
"div",
|
|
2927
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8, flexWrap: "wrap" } },
|
|
2928
|
+
React.createElement(
|
|
2929
|
+
"span",
|
|
2930
|
+
{ className: "iterate-obs-head-meta" },
|
|
2931
|
+
`${manifest.mode || "runtime"} \xB7 ${String(manifest.updatedAt || "")}`
|
|
2932
|
+
),
|
|
2933
|
+
React.createElement("button", {
|
|
2934
|
+
className: "iterate-btn",
|
|
2935
|
+
"data-primary": "",
|
|
2936
|
+
"data-copied": copiedKey === "export" ? "" : void 0,
|
|
2937
|
+
onClick: exportObservatory,
|
|
2938
|
+
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"
|
|
2939
|
+
}, copiedKey === "export" ? "\u5DF2\u5BFC\u51FA" : "\u5BFC\u51FA JSON")
|
|
2940
|
+
),
|
|
2685
2941
|
React.createElement(
|
|
2686
2942
|
"div",
|
|
2687
2943
|
{ className: "iterate-obs-tabs" },
|