ccqa 1.47.3 → 1.48.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/dist/bin/ccqa.mjs +151 -27
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -9936,6 +9936,12 @@ const HUB_READ_CONCURRENCY = 4;
|
|
|
9936
9936
|
* source that cannot be read warns, and `degraded` flips when the failure
|
|
9937
9937
|
* leaves absence ambiguous (the ledger itself, or the legacy sources while
|
|
9938
9938
|
* no ledger answers).
|
|
9939
|
+
*
|
|
9940
|
+
* The ledger is read first and, once it answers, alone. The legacy sources
|
|
9941
|
+
* resolve every retained run against the whole event stream — minutes each on
|
|
9942
|
+
* a hub of any age — and reading them alongside a ledger that answers in one
|
|
9943
|
+
* request meant waiting for the slow source to confirm what the fast one had
|
|
9944
|
+
* already said (and then discarding it, since the ledger wins).
|
|
9939
9945
|
*/
|
|
9940
9946
|
async function loadCoverageEdges(input) {
|
|
9941
9947
|
if (input == null) return {
|
|
@@ -9947,11 +9953,9 @@ async function loadCoverageEdges(input) {
|
|
|
9947
9953
|
const existing = candidates.get(key);
|
|
9948
9954
|
if (!existing || candidate.measuredAt > existing.measuredAt) candidates.set(key, candidate);
|
|
9949
9955
|
};
|
|
9950
|
-
const [ledger
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
collectReportEdges(input, merge)
|
|
9954
|
-
]);
|
|
9956
|
+
const [ledger] = await Promise.allSettled([collectLedgerEdges(input, merge)]);
|
|
9957
|
+
const ledgerAnswered = ledger.status === "fulfilled" && ledger.value;
|
|
9958
|
+
const legacy = ledgerAnswered ? [] : await Promise.allSettled([collectStreamEdges(input, merge), collectReportEdges(input, merge)]);
|
|
9955
9959
|
let skipped = 0;
|
|
9956
9960
|
let legacyBroken = false;
|
|
9957
9961
|
for (const result of legacy) if (result.status === "rejected") {
|
|
@@ -9963,7 +9967,6 @@ async function loadCoverageEdges(input) {
|
|
|
9963
9967
|
warn(`select-specs: ${skipped} measured run(s) on the hub could not be read; treating the measurements as unreadable rather than absent`);
|
|
9964
9968
|
}
|
|
9965
9969
|
if (ledger.status === "rejected") warn(`select-specs: could not read the hub's coverage-edge ledger (${errMessage(ledger.reason)})`);
|
|
9966
|
-
const ledgerAnswered = ledger.status === "fulfilled" && ledger.value;
|
|
9967
9970
|
const degraded = ledger.status === "rejected" || !ledgerAnswered && legacyBroken;
|
|
9968
9971
|
return {
|
|
9969
9972
|
edges: new Map([...candidates].map(([key, c]) => [key, {
|
|
@@ -22806,6 +22809,9 @@ const HTML_BODY = `
|
|
|
22806
22809
|
<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>
|
|
22807
22810
|
<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>
|
|
22808
22811
|
</div>
|
|
22812
|
+
<!-- Not a chip: the chips pick which cases to show out of what the
|
|
22813
|
+
project verifies, and this changes what that set is. -->
|
|
22814
|
+
<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>
|
|
22809
22815
|
<div class="spacer"></div>
|
|
22810
22816
|
<span class="muted persp-head" id="persp-deploy-head" hidden></span>
|
|
22811
22817
|
<div class="sw-wrap" id="persp-profile-wrap">
|
|
@@ -23278,6 +23284,8 @@ const CSS = `
|
|
|
23278
23284
|
amber as the drift badges (dr-found), not fail-red. */
|
|
23279
23285
|
.spec-card.drift-found { border-left: 3px solid var(--amber); }
|
|
23280
23286
|
.spec-card.passed { border-left: 3px solid var(--pass); }
|
|
23287
|
+
/* Says which card the link landed on; the left rail still carries the verdict. */
|
|
23288
|
+
.spec-card.linked { box-shadow: 0 0 0 2px var(--accent); }
|
|
23281
23289
|
.spec-card-head { display: flex; align-items: center; gap: 12px; padding: 16px 20px; }
|
|
23282
23290
|
.spec-card-head .name { font-weight: 600; font-size: 15px; }
|
|
23283
23291
|
.spec-card-head .slug { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
@@ -23621,6 +23629,10 @@ const CSS = `
|
|
|
23621
23629
|
/* The Perspectives toolbar carries search + two filter-chip groups + the
|
|
23622
23630
|
profile selector, so it wraps instead of overflowing on a narrow window. */
|
|
23623
23631
|
#view-perspectives .toolbar { flex-wrap: wrap; }
|
|
23632
|
+
.ftoggle { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); cursor: pointer; }
|
|
23633
|
+
.ftoggle input { margin: 0; cursor: pointer; }
|
|
23634
|
+
.chip.off { background: var(--surface-2); color: var(--muted-2); border-color: var(--border); }
|
|
23635
|
+
.ftoggle .fcount { margin-left: 2px; font-variant-numeric: tabular-nums; color: var(--muted-2); }
|
|
23624
23636
|
.proj-menu.right { left: auto; right: 0; }
|
|
23625
23637
|
.d-note { margin-top: 12px; max-width: 900px; }
|
|
23626
23638
|
|
|
@@ -23746,7 +23758,7 @@ const CLIENT_JS = `
|
|
|
23746
23758
|
// Every status a run can be in, from the contract that defines them, so the
|
|
23747
23759
|
// runs filter offers exactly what a row's badge can say.
|
|
23748
23760
|
var RUN_STATUSES = ${JSON.stringify(RunStatusSchema.options)};
|
|
23749
|
-
var state = { token: "", project: "", profile: "default", detailRunId: "", jobPollToken: 0, runsLoadToken: 0, spendLoadToken: 0 };
|
|
23761
|
+
var state = { token: "", project: "", profile: "default", detailRunId: "", detailCaseKey: null, detailScrolled: false, jobPollToken: 0, runsLoadToken: 0, spendLoadToken: 0 };
|
|
23750
23762
|
var knownProfiles = [];
|
|
23751
23763
|
var TOKEN_KEY = "ccqa-hub-token";
|
|
23752
23764
|
var LANG_KEY = "ccqa-hub-lang";
|
|
@@ -23859,6 +23871,9 @@ const CLIENT_JS = `
|
|
|
23859
23871
|
"perspectives.filter.all": "All", "perspectives.filter.deterministic": "Deterministic",
|
|
23860
23872
|
"perspectives.filter.live": "Live",
|
|
23861
23873
|
"perspectives.filter.group.mode": "Mode",
|
|
23874
|
+
"perspectives.filter.showDisabled": "Show disabled",
|
|
23875
|
+
"detail.caseNotInRun": "{case} is not in this run.",
|
|
23876
|
+
"perspectives.filter.disabled": "disabled",
|
|
23862
23877
|
"perspectives.col.verdict": "Verdict", "perspectives.col.audit": "Audit",
|
|
23863
23878
|
"perspectives.col.run": "Execution",
|
|
23864
23879
|
"perspectives.audit.state.due": "Audit due",
|
|
@@ -24083,6 +24098,9 @@ const CLIENT_JS = `
|
|
|
24083
24098
|
"perspectives.filter.all": "すべて", "perspectives.filter.deterministic": "決定的",
|
|
24084
24099
|
"perspectives.filter.live": "ライブ",
|
|
24085
24100
|
"perspectives.filter.group.mode": "モード",
|
|
24101
|
+
"perspectives.filter.showDisabled": "無効も表示",
|
|
24102
|
+
"detail.caseNotInRun": "{case} はこの実行に含まれていません。",
|
|
24103
|
+
"perspectives.filter.disabled": "無効",
|
|
24086
24104
|
"perspectives.col.verdict": "判定", "perspectives.col.audit": "監査",
|
|
24087
24105
|
"perspectives.col.run": "実行",
|
|
24088
24106
|
"perspectives.audit.state.due": "監査待ち",
|
|
@@ -24691,6 +24709,32 @@ const CLIENT_JS = `
|
|
|
24691
24709
|
document.querySelector(".nav-jobs").classList.toggle("disabled", gated);
|
|
24692
24710
|
}
|
|
24693
24711
|
|
|
24712
|
+
// --- pure: run hash ------------------------------------------------------
|
|
24713
|
+
// Lifted out and run by perspectives-visibility.test.ts: the pattern lives
|
|
24714
|
+
// inside a template literal, where a lost backslash fails silently.
|
|
24715
|
+
|
|
24716
|
+
/** A run, optionally opened on one case. */
|
|
24717
|
+
function runCaseHref(runId, caseKey) {
|
|
24718
|
+
var base = "#/runs/" + encodeURIComponent(runId);
|
|
24719
|
+
return caseKey ? base + "?case=" + encodeURIComponent(caseKey) : base;
|
|
24720
|
+
}
|
|
24721
|
+
|
|
24722
|
+
/** The run id and case a #/runs/... hash names, or null when it names no run. */
|
|
24723
|
+
function parseRunHash(hash) {
|
|
24724
|
+
var m = String(hash).match(/^#\\/runs\\/([^?]+)(?:\\?case=(.*))?$/);
|
|
24725
|
+
if (!m) return null;
|
|
24726
|
+
// A truncated paste leaves a half-written %XX, which decodeURIComponent
|
|
24727
|
+
// throws on. Thrown out of route() that reads as "you are disconnected"
|
|
24728
|
+
// and shows the login gate, for a link the reader can see is a run.
|
|
24729
|
+
try {
|
|
24730
|
+
return { runId: decodeURIComponent(m[1]), caseKey: m[2] ? decodeURIComponent(m[2]) : null };
|
|
24731
|
+
} catch (e) {
|
|
24732
|
+
return { runId: m[1], caseKey: m[2] || null };
|
|
24733
|
+
}
|
|
24734
|
+
}
|
|
24735
|
+
|
|
24736
|
+
// --- end pure: run hash --------------------------------------------------
|
|
24737
|
+
|
|
24694
24738
|
function route() {
|
|
24695
24739
|
// Disconnected: the full-screen login gate is the only thing to show.
|
|
24696
24740
|
if (!state.token) { showAuthGate(false); return; }
|
|
@@ -24699,8 +24743,8 @@ const CLIENT_JS = `
|
|
|
24699
24743
|
// land there (e.g. right after login) instead of an empty Runs list, and
|
|
24700
24744
|
// gate any deep-linked #/runs or #/secrets to it too.
|
|
24701
24745
|
if (location.hash === "#/projects" || !state.project) { openProjects(); return; }
|
|
24702
|
-
var m = location.hash
|
|
24703
|
-
if (m) { openRunDetail(
|
|
24746
|
+
var m = parseRunHash(location.hash);
|
|
24747
|
+
if (m) { openRunDetail(m.runId, m.caseKey); return; }
|
|
24704
24748
|
if (location.hash === "#/perspectives") { openPerspectives(); return; }
|
|
24705
24749
|
if (location.hash === "#/coverage") { openCoverage(); return; }
|
|
24706
24750
|
if (location.hash === "#/secrets") { openSecrets(); return; }
|
|
@@ -26038,8 +26082,34 @@ const CLIENT_JS = `
|
|
|
26038
26082
|
function renderSpecCards(runId, results, triageState, isDrift) {
|
|
26039
26083
|
var container = document.getElementById("spec-cards");
|
|
26040
26084
|
clear(container);
|
|
26041
|
-
|
|
26085
|
+
// A slower earlier fetch can land after the reader has moved to another
|
|
26086
|
+
// run. Its cards were always wrong; what is new is that it would also
|
|
26087
|
+
// claim their case is missing from a run that has it.
|
|
26088
|
+
var caseKey = runId === state.detailRunId ? state.detailCaseKey : null;
|
|
26089
|
+
var wanted = null;
|
|
26090
|
+
results.forEach(function (r) {
|
|
26091
|
+
var card = renderSpecCard(runId, r, triageState, isDrift);
|
|
26092
|
+
if (caseKey && r.feature + "/" + r.spec === caseKey) wanted = card;
|
|
26093
|
+
container.appendChild(card);
|
|
26094
|
+
});
|
|
26042
26095
|
document.getElementById("detail-spec-count").textContent = "· " + results.length;
|
|
26096
|
+
if (!caseKey) return;
|
|
26097
|
+
// Marked as well as scrolled: a page that jumped has to say where it
|
|
26098
|
+
// jumped to, or the reader re-finds the row by hand. Cards are rebuilt
|
|
26099
|
+
// again when triage lands, so the mark is reapplied but the scroll is
|
|
26100
|
+
// not — the reader has been reading since the first one.
|
|
26101
|
+
if (wanted) {
|
|
26102
|
+
wanted.classList.add("linked");
|
|
26103
|
+
if (!state.detailScrolled) {
|
|
26104
|
+
state.detailScrolled = true;
|
|
26105
|
+
wanted.scrollIntoView({ block: "center" });
|
|
26106
|
+
}
|
|
26107
|
+
} else if (!state.detailScrolled) {
|
|
26108
|
+
// The case is not in this run (renamed, or the run predates it). Silent
|
|
26109
|
+
// would read as "it is here somewhere".
|
|
26110
|
+
state.detailScrolled = true;
|
|
26111
|
+
detailError(t("detail.caseNotInRun").replace("{case}", caseKey));
|
|
26112
|
+
}
|
|
26043
26113
|
}
|
|
26044
26114
|
|
|
26045
26115
|
// ── run detail: triage (confusion matrix) ───────────────────────────
|
|
@@ -26237,9 +26307,11 @@ const CLIENT_JS = `
|
|
|
26237
26307
|
e.textContent = msg;
|
|
26238
26308
|
}
|
|
26239
26309
|
|
|
26240
|
-
function openRunDetail(runId) {
|
|
26310
|
+
function openRunDetail(runId, caseKey) {
|
|
26241
26311
|
showView("detail");
|
|
26242
26312
|
state.detailRunId = runId;
|
|
26313
|
+
state.detailCaseKey = caseKey || null;
|
|
26314
|
+
state.detailScrolled = false;
|
|
26243
26315
|
document.getElementById("detail-title").textContent = runId.slice(0, 8);
|
|
26244
26316
|
document.getElementById("detail-error").hidden = true;
|
|
26245
26317
|
document.getElementById("learn-cta").hidden = true;
|
|
@@ -26697,7 +26769,8 @@ const CLIENT_JS = `
|
|
|
26697
26769
|
var perspState = {
|
|
26698
26770
|
doc: null, q: "", f: "all",
|
|
26699
26771
|
rerun: null, rerunSupported: null, runUrls: {}, rerunProfiles: [],
|
|
26700
|
-
drift: null
|
|
26772
|
+
drift: null,
|
|
26773
|
+
showDisabled: false
|
|
26701
26774
|
};
|
|
26702
26775
|
|
|
26703
26776
|
function perspectivesPath() {
|
|
@@ -26807,6 +26880,20 @@ const CLIENT_JS = `
|
|
|
26807
26880
|
return feature.featureName + "/" + spec.specName;
|
|
26808
26881
|
}
|
|
26809
26882
|
|
|
26883
|
+
// --- pure: perspectives visibility ---------------------------------------
|
|
26884
|
+
// Lifted out and run by perspectives-visibility.test.ts.
|
|
26885
|
+
|
|
26886
|
+
/**
|
|
26887
|
+
* A disabled case is not part of what this project verifies, so it is not in
|
|
26888
|
+
* the answer the page opens on. What the summary counts and what the table
|
|
26889
|
+
* lists both ask here, or the two disagree on how many cases exist.
|
|
26890
|
+
*/
|
|
26891
|
+
function perspHidden(spec, showDisabled) {
|
|
26892
|
+
return spec.disabled === true && !showDisabled;
|
|
26893
|
+
}
|
|
26894
|
+
|
|
26895
|
+
// --- end pure: perspectives visibility -----------------------------------
|
|
26896
|
+
|
|
26810
26897
|
// Shared by rerun and drift, which differ only in which report they read.
|
|
26811
26898
|
function ledgerEntryFor(report, feature, spec) {
|
|
26812
26899
|
return report && report.specs ? (report.specs[perspSpecKey(feature, spec)] || null) : null;
|
|
@@ -27022,10 +27109,10 @@ const CLIENT_JS = `
|
|
|
27022
27109
|
// One ledger entry as "<short sha> · <when>", linking to the hub's run detail
|
|
27023
27110
|
// and, when that run recorded one, to the CI run. Clicks must not bubble: the
|
|
27024
27111
|
// table row itself toggles the detail panel.
|
|
27025
|
-
function ledgerLine(entry) {
|
|
27112
|
+
function ledgerLine(entry, caseKey) {
|
|
27026
27113
|
var wrap = el("span");
|
|
27027
27114
|
var link = el("a", null, shortSha(entry.gitHead) || String(entry.runId).slice(0, 8));
|
|
27028
|
-
link.href =
|
|
27115
|
+
link.href = runCaseHref(entry.runId, caseKey);
|
|
27029
27116
|
link.title = t("perspectives.result.openRun");
|
|
27030
27117
|
link.addEventListener("click", function (e) { e.stopPropagation(); });
|
|
27031
27118
|
wrap.appendChild(link);
|
|
@@ -27153,7 +27240,7 @@ const CLIENT_JS = `
|
|
|
27153
27240
|
due: "rr-none", clean: "passed", drifted: "failed", undecided: "rr-unknown"
|
|
27154
27241
|
};
|
|
27155
27242
|
|
|
27156
|
-
function perspAuditCell(rr, driftEntry) {
|
|
27243
|
+
function perspAuditCell(rr, driftEntry, caseKey) {
|
|
27157
27244
|
var td = el("td");
|
|
27158
27245
|
if (!rr || !rr.audit) {
|
|
27159
27246
|
td.appendChild(el("span", "muted", "\u2014"));
|
|
@@ -27176,7 +27263,7 @@ const CLIENT_JS = `
|
|
|
27176
27263
|
// audit's own coordinate, so it comes from the drift ledger.
|
|
27177
27264
|
if (driftEntry) {
|
|
27178
27265
|
var sub = el("span", "cellsub");
|
|
27179
|
-
sub.appendChild(ledgerLine(driftEntry));
|
|
27266
|
+
sub.appendChild(ledgerLine(driftEntry, caseKey));
|
|
27180
27267
|
if (driftEntry.graded) {
|
|
27181
27268
|
sub.appendChild(document.createTextNode(" · "));
|
|
27182
27269
|
sub.appendChild(el("span", "graded-mark", t("perspectives.drift.graded")));
|
|
@@ -27186,7 +27273,7 @@ const CLIENT_JS = `
|
|
|
27186
27273
|
return td;
|
|
27187
27274
|
}
|
|
27188
27275
|
|
|
27189
|
-
function perspRunCell(rr) {
|
|
27276
|
+
function perspRunCell(rr, caseKey) {
|
|
27190
27277
|
var td = el("td");
|
|
27191
27278
|
var runState = perspRunState(rr);
|
|
27192
27279
|
// No verdict at all: this case is in the document but not in the report
|
|
@@ -27220,7 +27307,7 @@ const CLIENT_JS = `
|
|
|
27220
27307
|
var last = lastResult(rr);
|
|
27221
27308
|
if (last) {
|
|
27222
27309
|
var sub = el("span", "cellsub");
|
|
27223
|
-
sub.appendChild(ledgerLine(last.entry));
|
|
27310
|
+
sub.appendChild(ledgerLine(last.entry, caseKey));
|
|
27224
27311
|
td.appendChild(sub);
|
|
27225
27312
|
}
|
|
27226
27313
|
}
|
|
@@ -27292,16 +27379,29 @@ const CLIENT_JS = `
|
|
|
27292
27379
|
var host = document.getElementById("persp-ov");
|
|
27293
27380
|
clear(host);
|
|
27294
27381
|
var records = [];
|
|
27382
|
+
var features = 0;
|
|
27383
|
+
var listed = 0;
|
|
27295
27384
|
doc.features.forEach(function (feature) {
|
|
27385
|
+
var before = listed;
|
|
27296
27386
|
feature.specs.forEach(function (spec) {
|
|
27297
|
-
|
|
27387
|
+
if (perspHidden(spec, perspState.showDisabled)) return;
|
|
27388
|
+
listed += 1;
|
|
27389
|
+
// A disabled case has no ledger entry — the hub does not ask for one
|
|
27390
|
+
// — and an absent entry reads as "needs re-run". Counting it would
|
|
27391
|
+
// put work in the bar that nobody intends to do.
|
|
27392
|
+
if (spec.disabled !== true) records.push(ledgerEntryFor(perspState.rerun, feature, spec));
|
|
27298
27393
|
});
|
|
27394
|
+
// A feature whose cases are all hidden renders no group below, so
|
|
27395
|
+
// counting it here would put the summary above a table that disagrees.
|
|
27396
|
+
if (listed > before) features += 1;
|
|
27299
27397
|
});
|
|
27300
27398
|
|
|
27301
27399
|
var inv = el("div", "ov-inv");
|
|
27302
|
-
|
|
27400
|
+
// The inventory counts what the table lists; the bars below judge only the
|
|
27401
|
+
// cases the hub keeps a ledger for, so the two numbers can differ.
|
|
27402
|
+
inv.appendChild(el("b", null, String(listed)));
|
|
27303
27403
|
inv.appendChild(document.createTextNode(" " + t("perspectives.ov.cases") + " / "));
|
|
27304
|
-
inv.appendChild(el("b", null, String(
|
|
27404
|
+
inv.appendChild(el("b", null, String(features)));
|
|
27305
27405
|
inv.appendChild(document.createTextNode(" " + t("perspectives.ov.features")));
|
|
27306
27406
|
host.appendChild(inv);
|
|
27307
27407
|
if (!records.length) return;
|
|
@@ -27328,6 +27428,12 @@ const CLIENT_JS = `
|
|
|
27328
27428
|
// search text always applies: a chip's count has to be the number of rows
|
|
27329
27429
|
// clicking it actually leaves.
|
|
27330
27430
|
function perspMatches(feature, spec, f) {
|
|
27431
|
+
if (perspHidden(spec, perspState.showDisabled)) return false;
|
|
27432
|
+
return perspMatchesFilters(feature, spec, f);
|
|
27433
|
+
}
|
|
27434
|
+
|
|
27435
|
+
/** Everything the toolbar filters on except whether the case is disabled. */
|
|
27436
|
+
function perspMatchesFilters(feature, spec, f) {
|
|
27331
27437
|
if (f === "deterministic" && perspMode(spec) !== "deterministic") return false;
|
|
27332
27438
|
if (f === "live" && perspMode(spec) !== "live") return false;
|
|
27333
27439
|
// The verdict chips (same words as RERUN_ORDER/the 判定 column) share
|
|
@@ -27347,12 +27453,12 @@ const CLIENT_JS = `
|
|
|
27347
27453
|
return true;
|
|
27348
27454
|
}
|
|
27349
27455
|
|
|
27350
|
-
function
|
|
27456
|
+
function perspCount(pred) {
|
|
27351
27457
|
var doc = perspState.doc;
|
|
27352
27458
|
if (!doc) return 0;
|
|
27353
27459
|
var n = 0;
|
|
27354
27460
|
doc.features.forEach(function (feature) {
|
|
27355
|
-
feature.specs.forEach(function (spec) { if (
|
|
27461
|
+
feature.specs.forEach(function (spec) { if (pred(feature, spec)) n += 1; });
|
|
27356
27462
|
});
|
|
27357
27463
|
return n;
|
|
27358
27464
|
}
|
|
@@ -27844,9 +27950,13 @@ const CLIENT_JS = `
|
|
|
27844
27950
|
row.tabIndex = 0;
|
|
27845
27951
|
row.setAttribute("aria-expanded", "false");
|
|
27846
27952
|
|
|
27953
|
+
var caseKey = perspSpecKey(feature, spec);
|
|
27847
27954
|
var titleTd = el("td", "c-title");
|
|
27848
27955
|
titleTd.appendChild(document.createTextNode(spec.title));
|
|
27849
|
-
|
|
27956
|
+
// Only reachable with the toggle on, where saying which rows are the
|
|
27957
|
+
// disabled ones is the whole point of having turned it on.
|
|
27958
|
+
if (spec.disabled === true) titleTd.appendChild(el("span", "chip off", t("perspectives.filter.disabled")));
|
|
27959
|
+
titleTd.appendChild(el("span", "c-id", caseKey));
|
|
27850
27960
|
if (spec.summary) titleTd.appendChild(el("span", "csum", spec.summary));
|
|
27851
27961
|
row.appendChild(titleTd);
|
|
27852
27962
|
|
|
@@ -27857,8 +27967,8 @@ const CLIENT_JS = `
|
|
|
27857
27967
|
if (showRerun) {
|
|
27858
27968
|
var rr = ledgerEntryFor(perspState.rerun, feature, spec);
|
|
27859
27969
|
row.appendChild(perspVerdictCell(rr));
|
|
27860
|
-
row.appendChild(perspRunCell(rr));
|
|
27861
|
-
row.appendChild(perspAuditCell(rr, ledgerEntryFor(perspState.drift, feature, spec)));
|
|
27970
|
+
row.appendChild(perspRunCell(rr, caseKey));
|
|
27971
|
+
row.appendChild(perspAuditCell(rr, ledgerEntryFor(perspState.drift, feature, spec), caseKey));
|
|
27862
27972
|
}
|
|
27863
27973
|
|
|
27864
27974
|
var chevTd = el("td", "c-chev");
|
|
@@ -27910,13 +28020,23 @@ const CLIENT_JS = `
|
|
|
27910
28020
|
// Each chip also carries what it would yield — the mode breakdown the
|
|
27911
28021
|
// summary row used to spend four tiles on.
|
|
27912
28022
|
function syncPerspChips() {
|
|
28023
|
+
// What ticking the box would actually add: disabled cases that the filters
|
|
28024
|
+
// in effect would then let through. A flat count of every disabled case
|
|
28025
|
+
// promises rows the active chip or search would drop.
|
|
28026
|
+
var off = perspState.showDisabled ? 0 : perspCount(function (feature, spec) {
|
|
28027
|
+
return spec.disabled === true && perspMatchesFilters(feature, spec, perspState.f);
|
|
28028
|
+
});
|
|
28029
|
+
document.getElementById("persp-disabled-count").textContent = off ? "+" + off : "";
|
|
28030
|
+
document.getElementById("persp-show-disabled").checked = perspState.showDisabled;
|
|
27913
28031
|
var group = document.getElementById("persp-verdict-chips");
|
|
27914
28032
|
group.hidden = perspState.rerunSupported !== true;
|
|
27915
28033
|
if (perspState.rerunSupported !== true && RERUN_ORDER.indexOf(perspState.f) !== -1) perspState.f = "all";
|
|
27916
28034
|
document.querySelectorAll("#view-perspectives .fchip").forEach(function (b) {
|
|
27917
28035
|
var f = b.getAttribute("data-f");
|
|
27918
28036
|
b.setAttribute("aria-pressed", String(f === perspState.f));
|
|
27919
|
-
b.querySelector(".fcount").textContent = perspState.doc
|
|
28037
|
+
b.querySelector(".fcount").textContent = perspState.doc
|
|
28038
|
+
? String(perspCount(function (feature, spec) { return perspMatches(feature, spec, f); }))
|
|
28039
|
+
: "";
|
|
27920
28040
|
});
|
|
27921
28041
|
}
|
|
27922
28042
|
|
|
@@ -28622,6 +28742,10 @@ const CLIENT_JS = `
|
|
|
28622
28742
|
perspState.q = e.target.value.trim().toLowerCase();
|
|
28623
28743
|
renderPerspectives();
|
|
28624
28744
|
});
|
|
28745
|
+
document.getElementById("persp-show-disabled").addEventListener("change", function (e) {
|
|
28746
|
+
perspState.showDisabled = e.target.checked;
|
|
28747
|
+
renderPerspectives();
|
|
28748
|
+
});
|
|
28625
28749
|
|
|
28626
28750
|
// Debounced, unlike persp-q: each keystroke rebuilds the whole tree, and
|
|
28627
28751
|
// the universe can hold thousands of files.
|
package/dist/package.json
CHANGED