ccqa 1.47.2 → 1.48.0
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/dist/bin/ccqa.mjs +144 -21
- package/dist/hub-client/index.d.mts +2 -2
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -16600,6 +16600,8 @@ Selectors in AB_ACTION must follow Selector Rules. \`find_*\` lines use the loca
|
|
|
16600
16600
|
- A non-zero exit from agent-browser (selector not found, element not interactable, timeout) → **do NOT emit AB_ACTION** for that attempt. Switch selector and only emit the AB_ACTION for the call that finally succeeded.
|
|
16601
16601
|
- If you tried several selectors / \`find_*\` locators for the same logical action, emit AB_ACTION for the **last working one only**. Multiple failed attempts in a row will all fail at replay validation and silently delete the step from the generated test.
|
|
16602
16602
|
- \`AB_ACTION|assert|...\` follows the same rule: only emit assertions you actually verified on the current page in the current snapshot.
|
|
16603
|
+
- **Open only a URL the step names.** A step's \`open\` takes the address its own instruction gives — a \`\${VAR}\` plus the literal tail after it. Never open an address the run produced: the id in \`/items/01H8XZ...\` belongs to the record this run created, and opening it next time visits a record that run does not have (or worse, one it does, silently testing the wrong thing). Reach such a page the way a person does — click through from where the run already is.
|
|
16604
|
+
|
|
16603
16605
|
- **Environment-failure recovery is not part of the test.** If a session times out, a network blip drops you to login, or the app crashes and you re-login / re-navigate / re-fill to recover, do NOT emit AB_ACTION for the recovery operations.
|
|
16604
16606
|
- If a step ultimately fails after retries: emit \`ASSERTION_FAILED\` and STOP. Do not leave half-recorded actions in the stream.
|
|
16605
16607
|
|
|
@@ -22804,6 +22806,9 @@ const HTML_BODY = `
|
|
|
22804
22806
|
<button class="fchip" id="persp-chip-manuallyverified" data-f="manuallyVerified" aria-pressed="false" type="button"><span data-i18n="perspectives.rerun.state.manuallyVerified">Manually verified</span><span class="fcount"></span></button>
|
|
22805
22807
|
<button class="fchip" id="persp-chip-verified" data-f="verified" aria-pressed="false" type="button"><span data-i18n="perspectives.rerun.state.verified">Verified</span><span class="fcount"></span></button>
|
|
22806
22808
|
</div>
|
|
22809
|
+
<!-- Not a chip: the chips pick which cases to show out of what the
|
|
22810
|
+
project verifies, and this changes what that set is. -->
|
|
22811
|
+
<label class="ftoggle"><input type="checkbox" id="persp-show-disabled"><span data-i18n="perspectives.filter.showDisabled">Show disabled</span><span class="fcount" id="persp-disabled-count"></span></label>
|
|
22807
22812
|
<div class="spacer"></div>
|
|
22808
22813
|
<span class="muted persp-head" id="persp-deploy-head" hidden></span>
|
|
22809
22814
|
<div class="sw-wrap" id="persp-profile-wrap">
|
|
@@ -23276,6 +23281,8 @@ const CSS = `
|
|
|
23276
23281
|
amber as the drift badges (dr-found), not fail-red. */
|
|
23277
23282
|
.spec-card.drift-found { border-left: 3px solid var(--amber); }
|
|
23278
23283
|
.spec-card.passed { border-left: 3px solid var(--pass); }
|
|
23284
|
+
/* Says which card the link landed on; the left rail still carries the verdict. */
|
|
23285
|
+
.spec-card.linked { box-shadow: 0 0 0 2px var(--accent); }
|
|
23279
23286
|
.spec-card-head { display: flex; align-items: center; gap: 12px; padding: 16px 20px; }
|
|
23280
23287
|
.spec-card-head .name { font-weight: 600; font-size: 15px; }
|
|
23281
23288
|
.spec-card-head .slug { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
@@ -23619,6 +23626,10 @@ const CSS = `
|
|
|
23619
23626
|
/* The Perspectives toolbar carries search + two filter-chip groups + the
|
|
23620
23627
|
profile selector, so it wraps instead of overflowing on a narrow window. */
|
|
23621
23628
|
#view-perspectives .toolbar { flex-wrap: wrap; }
|
|
23629
|
+
.ftoggle { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); cursor: pointer; }
|
|
23630
|
+
.ftoggle input { margin: 0; cursor: pointer; }
|
|
23631
|
+
.chip.off { background: var(--surface-2); color: var(--muted-2); border-color: var(--border); }
|
|
23632
|
+
.ftoggle .fcount { margin-left: 2px; font-variant-numeric: tabular-nums; color: var(--muted-2); }
|
|
23622
23633
|
.proj-menu.right { left: auto; right: 0; }
|
|
23623
23634
|
.d-note { margin-top: 12px; max-width: 900px; }
|
|
23624
23635
|
|
|
@@ -23744,7 +23755,7 @@ const CLIENT_JS = `
|
|
|
23744
23755
|
// Every status a run can be in, from the contract that defines them, so the
|
|
23745
23756
|
// runs filter offers exactly what a row's badge can say.
|
|
23746
23757
|
var RUN_STATUSES = ${JSON.stringify(RunStatusSchema.options)};
|
|
23747
|
-
var state = { token: "", project: "", profile: "default", detailRunId: "", jobPollToken: 0, runsLoadToken: 0, spendLoadToken: 0 };
|
|
23758
|
+
var state = { token: "", project: "", profile: "default", detailRunId: "", detailCaseKey: null, detailScrolled: false, jobPollToken: 0, runsLoadToken: 0, spendLoadToken: 0 };
|
|
23748
23759
|
var knownProfiles = [];
|
|
23749
23760
|
var TOKEN_KEY = "ccqa-hub-token";
|
|
23750
23761
|
var LANG_KEY = "ccqa-hub-lang";
|
|
@@ -23857,6 +23868,9 @@ const CLIENT_JS = `
|
|
|
23857
23868
|
"perspectives.filter.all": "All", "perspectives.filter.deterministic": "Deterministic",
|
|
23858
23869
|
"perspectives.filter.live": "Live",
|
|
23859
23870
|
"perspectives.filter.group.mode": "Mode",
|
|
23871
|
+
"perspectives.filter.showDisabled": "Show disabled",
|
|
23872
|
+
"detail.caseNotInRun": "{case} is not in this run.",
|
|
23873
|
+
"perspectives.filter.disabled": "disabled",
|
|
23860
23874
|
"perspectives.col.verdict": "Verdict", "perspectives.col.audit": "Audit",
|
|
23861
23875
|
"perspectives.col.run": "Execution",
|
|
23862
23876
|
"perspectives.audit.state.due": "Audit due",
|
|
@@ -24081,6 +24095,9 @@ const CLIENT_JS = `
|
|
|
24081
24095
|
"perspectives.filter.all": "すべて", "perspectives.filter.deterministic": "決定的",
|
|
24082
24096
|
"perspectives.filter.live": "ライブ",
|
|
24083
24097
|
"perspectives.filter.group.mode": "モード",
|
|
24098
|
+
"perspectives.filter.showDisabled": "無効も表示",
|
|
24099
|
+
"detail.caseNotInRun": "{case} はこの実行に含まれていません。",
|
|
24100
|
+
"perspectives.filter.disabled": "無効",
|
|
24084
24101
|
"perspectives.col.verdict": "判定", "perspectives.col.audit": "監査",
|
|
24085
24102
|
"perspectives.col.run": "実行",
|
|
24086
24103
|
"perspectives.audit.state.due": "監査待ち",
|
|
@@ -24689,6 +24706,32 @@ const CLIENT_JS = `
|
|
|
24689
24706
|
document.querySelector(".nav-jobs").classList.toggle("disabled", gated);
|
|
24690
24707
|
}
|
|
24691
24708
|
|
|
24709
|
+
// --- pure: run hash ------------------------------------------------------
|
|
24710
|
+
// Lifted out and run by perspectives-visibility.test.ts: the pattern lives
|
|
24711
|
+
// inside a template literal, where a lost backslash fails silently.
|
|
24712
|
+
|
|
24713
|
+
/** A run, optionally opened on one case. */
|
|
24714
|
+
function runCaseHref(runId, caseKey) {
|
|
24715
|
+
var base = "#/runs/" + encodeURIComponent(runId);
|
|
24716
|
+
return caseKey ? base + "?case=" + encodeURIComponent(caseKey) : base;
|
|
24717
|
+
}
|
|
24718
|
+
|
|
24719
|
+
/** The run id and case a #/runs/... hash names, or null when it names no run. */
|
|
24720
|
+
function parseRunHash(hash) {
|
|
24721
|
+
var m = String(hash).match(/^#\\/runs\\/([^?]+)(?:\\?case=(.*))?$/);
|
|
24722
|
+
if (!m) return null;
|
|
24723
|
+
// A truncated paste leaves a half-written %XX, which decodeURIComponent
|
|
24724
|
+
// throws on. Thrown out of route() that reads as "you are disconnected"
|
|
24725
|
+
// and shows the login gate, for a link the reader can see is a run.
|
|
24726
|
+
try {
|
|
24727
|
+
return { runId: decodeURIComponent(m[1]), caseKey: m[2] ? decodeURIComponent(m[2]) : null };
|
|
24728
|
+
} catch (e) {
|
|
24729
|
+
return { runId: m[1], caseKey: m[2] || null };
|
|
24730
|
+
}
|
|
24731
|
+
}
|
|
24732
|
+
|
|
24733
|
+
// --- end pure: run hash --------------------------------------------------
|
|
24734
|
+
|
|
24692
24735
|
function route() {
|
|
24693
24736
|
// Disconnected: the full-screen login gate is the only thing to show.
|
|
24694
24737
|
if (!state.token) { showAuthGate(false); return; }
|
|
@@ -24697,8 +24740,8 @@ const CLIENT_JS = `
|
|
|
24697
24740
|
// land there (e.g. right after login) instead of an empty Runs list, and
|
|
24698
24741
|
// gate any deep-linked #/runs or #/secrets to it too.
|
|
24699
24742
|
if (location.hash === "#/projects" || !state.project) { openProjects(); return; }
|
|
24700
|
-
var m = location.hash
|
|
24701
|
-
if (m) { openRunDetail(
|
|
24743
|
+
var m = parseRunHash(location.hash);
|
|
24744
|
+
if (m) { openRunDetail(m.runId, m.caseKey); return; }
|
|
24702
24745
|
if (location.hash === "#/perspectives") { openPerspectives(); return; }
|
|
24703
24746
|
if (location.hash === "#/coverage") { openCoverage(); return; }
|
|
24704
24747
|
if (location.hash === "#/secrets") { openSecrets(); return; }
|
|
@@ -26036,8 +26079,34 @@ const CLIENT_JS = `
|
|
|
26036
26079
|
function renderSpecCards(runId, results, triageState, isDrift) {
|
|
26037
26080
|
var container = document.getElementById("spec-cards");
|
|
26038
26081
|
clear(container);
|
|
26039
|
-
|
|
26082
|
+
// A slower earlier fetch can land after the reader has moved to another
|
|
26083
|
+
// run. Its cards were always wrong; what is new is that it would also
|
|
26084
|
+
// claim their case is missing from a run that has it.
|
|
26085
|
+
var caseKey = runId === state.detailRunId ? state.detailCaseKey : null;
|
|
26086
|
+
var wanted = null;
|
|
26087
|
+
results.forEach(function (r) {
|
|
26088
|
+
var card = renderSpecCard(runId, r, triageState, isDrift);
|
|
26089
|
+
if (caseKey && r.feature + "/" + r.spec === caseKey) wanted = card;
|
|
26090
|
+
container.appendChild(card);
|
|
26091
|
+
});
|
|
26040
26092
|
document.getElementById("detail-spec-count").textContent = "· " + results.length;
|
|
26093
|
+
if (!caseKey) return;
|
|
26094
|
+
// Marked as well as scrolled: a page that jumped has to say where it
|
|
26095
|
+
// jumped to, or the reader re-finds the row by hand. Cards are rebuilt
|
|
26096
|
+
// again when triage lands, so the mark is reapplied but the scroll is
|
|
26097
|
+
// not — the reader has been reading since the first one.
|
|
26098
|
+
if (wanted) {
|
|
26099
|
+
wanted.classList.add("linked");
|
|
26100
|
+
if (!state.detailScrolled) {
|
|
26101
|
+
state.detailScrolled = true;
|
|
26102
|
+
wanted.scrollIntoView({ block: "center" });
|
|
26103
|
+
}
|
|
26104
|
+
} else if (!state.detailScrolled) {
|
|
26105
|
+
// The case is not in this run (renamed, or the run predates it). Silent
|
|
26106
|
+
// would read as "it is here somewhere".
|
|
26107
|
+
state.detailScrolled = true;
|
|
26108
|
+
detailError(t("detail.caseNotInRun").replace("{case}", caseKey));
|
|
26109
|
+
}
|
|
26041
26110
|
}
|
|
26042
26111
|
|
|
26043
26112
|
// ── run detail: triage (confusion matrix) ───────────────────────────
|
|
@@ -26235,9 +26304,11 @@ const CLIENT_JS = `
|
|
|
26235
26304
|
e.textContent = msg;
|
|
26236
26305
|
}
|
|
26237
26306
|
|
|
26238
|
-
function openRunDetail(runId) {
|
|
26307
|
+
function openRunDetail(runId, caseKey) {
|
|
26239
26308
|
showView("detail");
|
|
26240
26309
|
state.detailRunId = runId;
|
|
26310
|
+
state.detailCaseKey = caseKey || null;
|
|
26311
|
+
state.detailScrolled = false;
|
|
26241
26312
|
document.getElementById("detail-title").textContent = runId.slice(0, 8);
|
|
26242
26313
|
document.getElementById("detail-error").hidden = true;
|
|
26243
26314
|
document.getElementById("learn-cta").hidden = true;
|
|
@@ -26695,7 +26766,8 @@ const CLIENT_JS = `
|
|
|
26695
26766
|
var perspState = {
|
|
26696
26767
|
doc: null, q: "", f: "all",
|
|
26697
26768
|
rerun: null, rerunSupported: null, runUrls: {}, rerunProfiles: [],
|
|
26698
|
-
drift: null
|
|
26769
|
+
drift: null,
|
|
26770
|
+
showDisabled: false
|
|
26699
26771
|
};
|
|
26700
26772
|
|
|
26701
26773
|
function perspectivesPath() {
|
|
@@ -26805,6 +26877,20 @@ const CLIENT_JS = `
|
|
|
26805
26877
|
return feature.featureName + "/" + spec.specName;
|
|
26806
26878
|
}
|
|
26807
26879
|
|
|
26880
|
+
// --- pure: perspectives visibility ---------------------------------------
|
|
26881
|
+
// Lifted out and run by perspectives-visibility.test.ts.
|
|
26882
|
+
|
|
26883
|
+
/**
|
|
26884
|
+
* A disabled case is not part of what this project verifies, so it is not in
|
|
26885
|
+
* the answer the page opens on. What the summary counts and what the table
|
|
26886
|
+
* lists both ask here, or the two disagree on how many cases exist.
|
|
26887
|
+
*/
|
|
26888
|
+
function perspHidden(spec, showDisabled) {
|
|
26889
|
+
return spec.disabled === true && !showDisabled;
|
|
26890
|
+
}
|
|
26891
|
+
|
|
26892
|
+
// --- end pure: perspectives visibility -----------------------------------
|
|
26893
|
+
|
|
26808
26894
|
// Shared by rerun and drift, which differ only in which report they read.
|
|
26809
26895
|
function ledgerEntryFor(report, feature, spec) {
|
|
26810
26896
|
return report && report.specs ? (report.specs[perspSpecKey(feature, spec)] || null) : null;
|
|
@@ -27020,10 +27106,10 @@ const CLIENT_JS = `
|
|
|
27020
27106
|
// One ledger entry as "<short sha> · <when>", linking to the hub's run detail
|
|
27021
27107
|
// and, when that run recorded one, to the CI run. Clicks must not bubble: the
|
|
27022
27108
|
// table row itself toggles the detail panel.
|
|
27023
|
-
function ledgerLine(entry) {
|
|
27109
|
+
function ledgerLine(entry, caseKey) {
|
|
27024
27110
|
var wrap = el("span");
|
|
27025
27111
|
var link = el("a", null, shortSha(entry.gitHead) || String(entry.runId).slice(0, 8));
|
|
27026
|
-
link.href =
|
|
27112
|
+
link.href = runCaseHref(entry.runId, caseKey);
|
|
27027
27113
|
link.title = t("perspectives.result.openRun");
|
|
27028
27114
|
link.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
27029
27115
|
wrap.appendChild(link);
|
|
@@ -27151,7 +27237,7 @@ const CLIENT_JS = `
|
|
|
27151
27237
|
due: "rr-none", clean: "passed", drifted: "failed", undecided: "rr-unknown"
|
|
27152
27238
|
};
|
|
27153
27239
|
|
|
27154
|
-
function perspAuditCell(rr, driftEntry) {
|
|
27240
|
+
function perspAuditCell(rr, driftEntry, caseKey) {
|
|
27155
27241
|
var td = el("td");
|
|
27156
27242
|
if (!rr || !rr.audit) {
|
|
27157
27243
|
td.appendChild(el("span", "muted", "\u2014"));
|
|
@@ -27174,7 +27260,7 @@ const CLIENT_JS = `
|
|
|
27174
27260
|
// audit's own coordinate, so it comes from the drift ledger.
|
|
27175
27261
|
if (driftEntry) {
|
|
27176
27262
|
var sub = el("span", "cellsub");
|
|
27177
|
-
sub.appendChild(ledgerLine(driftEntry));
|
|
27263
|
+
sub.appendChild(ledgerLine(driftEntry, caseKey));
|
|
27178
27264
|
if (driftEntry.graded) {
|
|
27179
27265
|
sub.appendChild(document.createTextNode(" · "));
|
|
27180
27266
|
sub.appendChild(el("span", "graded-mark", t("perspectives.drift.graded")));
|
|
@@ -27184,7 +27270,7 @@ const CLIENT_JS = `
|
|
|
27184
27270
|
return td;
|
|
27185
27271
|
}
|
|
27186
27272
|
|
|
27187
|
-
function perspRunCell(rr) {
|
|
27273
|
+
function perspRunCell(rr, caseKey) {
|
|
27188
27274
|
var td = el("td");
|
|
27189
27275
|
var runState = perspRunState(rr);
|
|
27190
27276
|
// No verdict at all: this case is in the document but not in the report
|
|
@@ -27218,7 +27304,7 @@ const CLIENT_JS = `
|
|
|
27218
27304
|
var last = lastResult(rr);
|
|
27219
27305
|
if (last) {
|
|
27220
27306
|
var sub = el("span", "cellsub");
|
|
27221
|
-
sub.appendChild(ledgerLine(last.entry));
|
|
27307
|
+
sub.appendChild(ledgerLine(last.entry, caseKey));
|
|
27222
27308
|
td.appendChild(sub);
|
|
27223
27309
|
}
|
|
27224
27310
|
}
|
|
@@ -27290,16 +27376,29 @@ const CLIENT_JS = `
|
|
|
27290
27376
|
var host = document.getElementById("persp-ov");
|
|
27291
27377
|
clear(host);
|
|
27292
27378
|
var records = [];
|
|
27379
|
+
var features = 0;
|
|
27380
|
+
var listed = 0;
|
|
27293
27381
|
doc.features.forEach(function (feature) {
|
|
27382
|
+
var before = listed;
|
|
27294
27383
|
feature.specs.forEach(function (spec) {
|
|
27295
|
-
|
|
27384
|
+
if (perspHidden(spec, perspState.showDisabled)) return;
|
|
27385
|
+
listed += 1;
|
|
27386
|
+
// A disabled case has no ledger entry — the hub does not ask for one
|
|
27387
|
+
// — and an absent entry reads as "needs re-run". Counting it would
|
|
27388
|
+
// put work in the bar that nobody intends to do.
|
|
27389
|
+
if (spec.disabled !== true) records.push(ledgerEntryFor(perspState.rerun, feature, spec));
|
|
27296
27390
|
});
|
|
27391
|
+
// A feature whose cases are all hidden renders no group below, so
|
|
27392
|
+
// counting it here would put the summary above a table that disagrees.
|
|
27393
|
+
if (listed > before) features += 1;
|
|
27297
27394
|
});
|
|
27298
27395
|
|
|
27299
27396
|
var inv = el("div", "ov-inv");
|
|
27300
|
-
|
|
27397
|
+
// The inventory counts what the table lists; the bars below judge only the
|
|
27398
|
+
// cases the hub keeps a ledger for, so the two numbers can differ.
|
|
27399
|
+
inv.appendChild(el("b", null, String(listed)));
|
|
27301
27400
|
inv.appendChild(document.createTextNode(" " + t("perspectives.ov.cases") + " / "));
|
|
27302
|
-
inv.appendChild(el("b", null, String(
|
|
27401
|
+
inv.appendChild(el("b", null, String(features)));
|
|
27303
27402
|
inv.appendChild(document.createTextNode(" " + t("perspectives.ov.features")));
|
|
27304
27403
|
host.appendChild(inv);
|
|
27305
27404
|
if (!records.length) return;
|
|
@@ -27326,6 +27425,12 @@ const CLIENT_JS = `
|
|
|
27326
27425
|
// search text always applies: a chip's count has to be the number of rows
|
|
27327
27426
|
// clicking it actually leaves.
|
|
27328
27427
|
function perspMatches(feature, spec, f) {
|
|
27428
|
+
if (perspHidden(spec, perspState.showDisabled)) return false;
|
|
27429
|
+
return perspMatchesFilters(feature, spec, f);
|
|
27430
|
+
}
|
|
27431
|
+
|
|
27432
|
+
/** Everything the toolbar filters on except whether the case is disabled. */
|
|
27433
|
+
function perspMatchesFilters(feature, spec, f) {
|
|
27329
27434
|
if (f === "deterministic" && perspMode(spec) !== "deterministic") return false;
|
|
27330
27435
|
if (f === "live" && perspMode(spec) !== "live") return false;
|
|
27331
27436
|
// The verdict chips (same words as RERUN_ORDER/the 判定 column) share
|
|
@@ -27345,12 +27450,12 @@ const CLIENT_JS = `
|
|
|
27345
27450
|
return true;
|
|
27346
27451
|
}
|
|
27347
27452
|
|
|
27348
|
-
function
|
|
27453
|
+
function perspCount(pred) {
|
|
27349
27454
|
var doc = perspState.doc;
|
|
27350
27455
|
if (!doc) return 0;
|
|
27351
27456
|
var n = 0;
|
|
27352
27457
|
doc.features.forEach(function (feature) {
|
|
27353
|
-
feature.specs.forEach(function (spec) { if (
|
|
27458
|
+
feature.specs.forEach(function (spec) { if (pred(feature, spec)) n += 1; });
|
|
27354
27459
|
});
|
|
27355
27460
|
return n;
|
|
27356
27461
|
}
|
|
@@ -27842,9 +27947,13 @@ const CLIENT_JS = `
|
|
|
27842
27947
|
row.tabIndex = 0;
|
|
27843
27948
|
row.setAttribute("aria-expanded", "false");
|
|
27844
27949
|
|
|
27950
|
+
var caseKey = perspSpecKey(feature, spec);
|
|
27845
27951
|
var titleTd = el("td", "c-title");
|
|
27846
27952
|
titleTd.appendChild(document.createTextNode(spec.title));
|
|
27847
|
-
|
|
27953
|
+
// Only reachable with the toggle on, where saying which rows are the
|
|
27954
|
+
// disabled ones is the whole point of having turned it on.
|
|
27955
|
+
if (spec.disabled === true) titleTd.appendChild(el("span", "chip off", t("perspectives.filter.disabled")));
|
|
27956
|
+
titleTd.appendChild(el("span", "c-id", caseKey));
|
|
27848
27957
|
if (spec.summary) titleTd.appendChild(el("span", "csum", spec.summary));
|
|
27849
27958
|
row.appendChild(titleTd);
|
|
27850
27959
|
|
|
@@ -27855,8 +27964,8 @@ const CLIENT_JS = `
|
|
|
27855
27964
|
if (showRerun) {
|
|
27856
27965
|
var rr = ledgerEntryFor(perspState.rerun, feature, spec);
|
|
27857
27966
|
row.appendChild(perspVerdictCell(rr));
|
|
27858
|
-
row.appendChild(perspRunCell(rr));
|
|
27859
|
-
row.appendChild(perspAuditCell(rr, ledgerEntryFor(perspState.drift, feature, spec)));
|
|
27967
|
+
row.appendChild(perspRunCell(rr, caseKey));
|
|
27968
|
+
row.appendChild(perspAuditCell(rr, ledgerEntryFor(perspState.drift, feature, spec), caseKey));
|
|
27860
27969
|
}
|
|
27861
27970
|
|
|
27862
27971
|
var chevTd = el("td", "c-chev");
|
|
@@ -27908,13 +28017,23 @@ const CLIENT_JS = `
|
|
|
27908
28017
|
// Each chip also carries what it would yield — the mode breakdown the
|
|
27909
28018
|
// summary row used to spend four tiles on.
|
|
27910
28019
|
function syncPerspChips() {
|
|
28020
|
+
// What ticking the box would actually add: disabled cases that the filters
|
|
28021
|
+
// in effect would then let through. A flat count of every disabled case
|
|
28022
|
+
// promises rows the active chip or search would drop.
|
|
28023
|
+
var off = perspState.showDisabled ? 0 : perspCount(function (feature, spec) {
|
|
28024
|
+
return spec.disabled === true && perspMatchesFilters(feature, spec, perspState.f);
|
|
28025
|
+
});
|
|
28026
|
+
document.getElementById("persp-disabled-count").textContent = off ? "+" + off : "";
|
|
28027
|
+
document.getElementById("persp-show-disabled").checked = perspState.showDisabled;
|
|
27911
28028
|
var group = document.getElementById("persp-verdict-chips");
|
|
27912
28029
|
group.hidden = perspState.rerunSupported !== true;
|
|
27913
28030
|
if (perspState.rerunSupported !== true && RERUN_ORDER.indexOf(perspState.f) !== -1) perspState.f = "all";
|
|
27914
28031
|
document.querySelectorAll("#view-perspectives .fchip").forEach(function (b) {
|
|
27915
28032
|
var f = b.getAttribute("data-f");
|
|
27916
28033
|
b.setAttribute("aria-pressed", String(f === perspState.f));
|
|
27917
|
-
b.querySelector(".fcount").textContent = perspState.doc
|
|
28034
|
+
b.querySelector(".fcount").textContent = perspState.doc
|
|
28035
|
+
? String(perspCount(function (feature, spec) { return perspMatches(feature, spec, f); }))
|
|
28036
|
+
: "";
|
|
27918
28037
|
});
|
|
27919
28038
|
}
|
|
27920
28039
|
|
|
@@ -28620,6 +28739,10 @@ const CLIENT_JS = `
|
|
|
28620
28739
|
perspState.q = e.target.value.trim().toLowerCase();
|
|
28621
28740
|
renderPerspectives();
|
|
28622
28741
|
});
|
|
28742
|
+
document.getElementById("persp-show-disabled").addEventListener("change", function (e) {
|
|
28743
|
+
perspState.showDisabled = e.target.checked;
|
|
28744
|
+
renderPerspectives();
|
|
28745
|
+
});
|
|
28623
28746
|
|
|
28624
28747
|
// Debounced, unlike persp-q: each keystroke rebuilds the whole tree, and
|
|
28625
28748
|
// the universe can hold thousands of files.
|
|
@@ -35,9 +35,9 @@ declare const RunSchema: z.ZodObject<{
|
|
|
35
35
|
running: "running";
|
|
36
36
|
}>;
|
|
37
37
|
kind: z.ZodDefault<z.ZodEnum<{
|
|
38
|
-
record: "record";
|
|
39
38
|
run: "run";
|
|
40
39
|
drift: "drift";
|
|
40
|
+
record: "record";
|
|
41
41
|
}>>;
|
|
42
42
|
drift: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
43
43
|
specs: z.ZodNumber;
|
|
@@ -811,9 +811,9 @@ type ReportSpecResult = z.infer<typeof ReportSpecResultSchema>;
|
|
|
811
811
|
declare const RunReportDataSchema: z.ZodObject<{
|
|
812
812
|
schemaVersion: z.ZodLiteral<1>;
|
|
813
813
|
kind: z.ZodDefault<z.ZodEnum<{
|
|
814
|
-
record: "record";
|
|
815
814
|
run: "run";
|
|
816
815
|
drift: "drift";
|
|
816
|
+
record: "record";
|
|
817
817
|
}>>;
|
|
818
818
|
createdAt: z.ZodString;
|
|
819
819
|
runId: z.ZodNullable<z.ZodString>;
|
package/dist/package.json
CHANGED