diff --git a/docs/dispatch-matrix.html b/docs/dispatch-matrix.html
index d58ad9cd..d13fa9af 100644
--- a/docs/dispatch-matrix.html
+++ b/docs/dispatch-matrix.html
@@ -18,6 +18,7 @@
.method{border-top:1px solid var(--line);padding:35px 0 60px;display:grid;grid-template-columns:.8fr 1.2fr;gap:30px}.method h2{margin:0;font-size:clamp(1.9rem,3vw,2.7rem);letter-spacing:-.04em}.method p{color:var(--muted);margin:0 0 14px}.method code{color:var(--mint);overflow-wrap:anywhere}.method a{font-weight:700}.foot{border-top:1px solid var(--line);padding:25px 0 55px;color:var(--muted);font-size:.78rem}
@media(max-width:850px){.hero{grid-template-columns:1fr;gap:25px;padding:50px 0}.totals{grid-template-columns:repeat(2,1fr)}.lenses{grid-template-columns:1fr}.lens{min-height:0}.path{grid-template-columns:repeat(2,1fr)}.method{grid-template-columns:1fr}}
@media(max-width:520px){.wrap{width:min(100% - 28px,1300px)}.top{align-items:flex-start;flex-direction:column}.top nav{gap:11px}.hero{padding-top:43px}.hero h1{font-size:clamp(3.55rem,15vw,5.8rem)}.hero-aside{padding-left:16px}.totals{gap:7px;margin-bottom:55px}.total{padding:16px;min-height:127px}.total strong{font-size:2.7rem}.section-heading{display:block}.section-heading p{margin-top:13px}.board{padding:17px}.filters label,.filters select{flex:1;min-width:135px}.filters output{width:100%;margin-left:0}.path,.bindings{grid-template-columns:1fr}.path-item{min-height:0}.case{margin-bottom:55px}}
+ .cell-placeholder{height:26px;border:1px dashed #426170;border-radius:6px;opacity:.55}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}.lens{transition:none}}
diff --git a/docs/dispatch-matrix.js b/docs/dispatch-matrix.js
index c7b5861c..8f950242 100644
--- a/docs/dispatch-matrix.js
+++ b/docs/dispatch-matrix.js
@@ -50,6 +50,14 @@ export function visibleMatrixIndices(report, policy = "all", status = "all") {
(status === "all" || row.receipt.execution_status === status) ? [index] : []);
}
+export function matrixGroupSlots(policy, approval, visibleIndices) {
+ const visible = new Set(visibleIndices);
+ return MATRIX_AXES.claim.flatMap(claim => MATRIX_AXES.callback.map(callback => {
+ const index = matrixIndex(policy, approval, claim, callback);
+ return visible.has(index) ? index : null;
+ }));
+}
+
export function matrixInsights(report) {
return {
spent_and_blocked: report.rows.filter(row => row.receipt.execution_status === "blocked" &&
@@ -115,7 +123,9 @@ if (typeof document !== "undefined" && document.getElementById("matrix-grid")) {
"The tool callback was not entered in this local vector.";
const values = [
["01 / HOST POLICY", `${label(receipt.policy_decision)} · exact-match decision`],
- ["02 / APPROVAL HOOK", `${row.approval_requests} request · ${label(row.approval)} fixture`],
+ ["02 / APPROVAL HOOK", row.approval_requests ?
+ `${row.approval_requests} request · ${label(row.approval)} fixture` :
+ `0 requests · configured ${label(row.approval)} hook not called`],
["03 / ONE-USE CLAIM", `${row.claim_attempts} attempt · ${row.claims_consumed} consumed`],
["04 / TOOL CALLBACK", `${row.callback_count} entry · ${row.fixture_writes} local marker write`]
];
@@ -144,21 +154,27 @@ if (typeof document !== "undefined" && document.getElementById("matrix-grid")) {
const group = element("div", "approval-group", "");
group.append(element("h4", "", label(approvalName)));
const cells = element("div", "cells", "");
- for (const claimName of MATRIX_AXES.claim) for (const callbackName of MATRIX_AXES.callback) {
- const index = matrixIndex(policyName, approvalName, claimName, callbackName);
- if (!visibleSet.has(index)) continue;
+ let matches = 0;
+ for (const index of matrixGroupSlots(policyName, approvalName, visible)) {
+ if (index === null) {
+ const placeholder = element("span", "cell-placeholder", "");
+ placeholder.setAttribute("aria-hidden", "true");
+ cells.append(placeholder);
+ continue;
+ }
+ matches++;
const row = report.rows[index];
- const button = element("button", "vector", callbackName === "return" ? "↗" : "!");
+ const button = element("button", "vector", row.callback === "return" ? "↗" : "!");
button.type = "button";
button.dataset.index = String(index);
button.dataset.status = row.receipt.execution_status;
- button.setAttribute("aria-label", `Vector ${index + 1}: ${[policyName, approvalName, claimName, callbackName].map(label).join(", ")}; ${row.receipt.execution_status}`);
+ button.setAttribute("aria-label", `Vector ${index + 1}: ${[policyName, approvalName, row.claim, row.callback].map(label).join(", ")}; ${row.receipt.execution_status}`);
button.setAttribute("aria-pressed", "false");
- button.title = `${label(claimName)} / ${label(callbackName)} → ${row.receipt.execution_status}`;
+ button.title = `${label(row.claim)} / ${label(row.callback)} → ${row.receipt.execution_status}`;
button.addEventListener("click", () => showCase(index));
cells.append(button);
}
- if (!cells.children.length) cells.append(element("span", "empty", "No matches"));
+ if (!matches) cells.replaceChildren(element("span", "empty", "No matches"));
group.append(cells);
col.append(group);
}
diff --git a/test/dispatch-matrix-view.test.js b/test/dispatch-matrix-view.test.js
index 53242c88..d057e434 100644
--- a/test/dispatch-matrix-view.test.js
+++ b/test/dispatch-matrix-view.test.js
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
-import { inspectMatrix, MATRIX_AXES, matrixIndex, matrixInsights, matrixSelection, matrixUrl,
+import { inspectMatrix, MATRIX_AXES, matrixGroupSlots, matrixIndex, matrixInsights, matrixSelection, matrixUrl,
visibleMatrixIndices } from "../docs/dispatch-matrix.js";
const report = JSON.parse(readFileSync(new URL("../docs/action-dispatch-matrix.json", import.meta.url), "utf8"));
@@ -16,6 +16,9 @@ test("the atlas indexes every vector and filters the actual checked-in outcomes"
assert.equal(visibleMatrixIndices(report, "deny", "blocked").length, 48);
assert.equal(visibleMatrixIndices(report, "deny", "unknown").length, 0);
assert.equal(visibleMatrixIndices(report, "requires_approval", "unknown").length, 2);
+ assert.deepEqual(matrixGroupSlots("requires_approval", "match",
+ visibleMatrixIndices(report, "requires_approval", "unknown")),
+ [null, 97, null, null, null, null, null, null], "filtering keeps callback and claim positions");
assert.throws(() => visibleMatrixIndices(report, "unknown_policy"), RangeError);
});