@kontourai/survey 0.5.1 → 0.6.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/README.md +34 -7
- package/bin/survey-review-mcp.mjs +7 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +1 -0
- package/dist/src/mcp/review-mcp-bin.d.ts +1 -0
- package/dist/src/mcp/review-mcp-bin.js +2 -0
- package/dist/src/mcp/review-mcp.d.ts +1 -0
- package/dist/src/mcp/review-mcp.js +599 -0
- package/dist/src/review-authorizing.d.ts +39 -0
- package/dist/src/review-authorizing.js +125 -0
- package/dist/src/review-resource.d.ts +5 -1
- package/dist/src/review-workbench/review-workbench-css.generated.d.ts +2 -0
- package/dist/src/review-workbench/review-workbench-css.generated.js +1795 -0
- package/dist/src/review-workbench/review-workbench-element.d.ts +45 -0
- package/dist/src/review-workbench/review-workbench-element.js +215 -0
- package/dist/src/review-workbench/review-workbench.css +196 -14
- package/dist/src/review-workbench/review-workbench.js +141 -19
- package/dist/src/review-workbench/review-workbench.standalone.css +196 -14
- package/dist/src/types.d.ts +23 -0
- package/example-data/mcp-review-session.json +878 -0
- package/package.json +14 -4
|
@@ -3,6 +3,8 @@ import { validateReviewSessionEventsForSnapshot, } from "./review-session-replay
|
|
|
3
3
|
import { buildSurfaceProjectionPreview, formatValue, } from "./review-surface-preview.js";
|
|
4
4
|
import { buildReviewCandidatePresentation, buildReviewItemPresentation, } from "./review-presentation.js";
|
|
5
5
|
import { reviewResourceApiVersion, } from "../review-resource.js";
|
|
6
|
+
import { validateAuthorizing, buildAuthorizedActionAuthorizing } from "../review-authorizing.js";
|
|
7
|
+
import { humanizeIdentifier } from "./review-presentation.js";
|
|
6
8
|
export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionResource, candidateForDecision, currentReviewItem, currentReviewWorkbenchState, defaultReviewSessionName, deriveQueueRowStatus, initialReviewQueueSessionState, initialReviewWorkbenchState, nextUnresolvedItemName, replayReviewSessionEvents, reviewSessionSummary, reviewWorkbenchSessionStorageKey, selectedCandidateRole, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
7
9
|
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-presentation.js";
|
|
8
10
|
export { buildSurfaceProjectionPreview, } from "./review-surface-preview.js";
|
|
@@ -35,6 +37,7 @@ export function buildReviewDecision(state) {
|
|
|
35
37
|
},
|
|
36
38
|
reviewedAt: state.reviewedAt,
|
|
37
39
|
rationale: state.note,
|
|
40
|
+
authorizing: buildDecisionCardAuthorizing(state),
|
|
38
41
|
projection,
|
|
39
42
|
},
|
|
40
43
|
status: {
|
|
@@ -42,6 +45,61 @@ export function buildReviewDecision(state) {
|
|
|
42
45
|
},
|
|
43
46
|
};
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Stable versioned prompt identifier for the workbench decision card control.
|
|
50
|
+
* Derived from the component naming convention: <module>/<component>@<version>.
|
|
51
|
+
*/
|
|
52
|
+
const DECISION_CARD_PROMPT_REF = "review-workbench/decision-card@v1";
|
|
53
|
+
/**
|
|
54
|
+
* Constructs the `authorized-action` authorizing block for a workbench decision.
|
|
55
|
+
* On validation failure, emits a console warning and returns undefined so the
|
|
56
|
+
* outcome is recorded without authorizing (transparency-gap-not-blocker per ADR 0004).
|
|
57
|
+
*/
|
|
58
|
+
function buildDecisionCardAuthorizing(state) {
|
|
59
|
+
if (!state.decision) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const targetLabel = humanizeIdentifier(state.item.spec.target);
|
|
63
|
+
const renderedPrompt = decisionCardRenderedPrompt(state, targetLabel);
|
|
64
|
+
// Reviewer note present means the reviewer also typed a rationale — "typed".
|
|
65
|
+
// Control-only affirmation (no note) is "affirmed-control".
|
|
66
|
+
const action = state.note?.trim() ? "typed" : "affirmed-control";
|
|
67
|
+
const authorityRef = `actor:${state.actorId}`;
|
|
68
|
+
try {
|
|
69
|
+
const block = buildAuthorizedActionAuthorizing({
|
|
70
|
+
promptRef: DECISION_CARD_PROMPT_REF,
|
|
71
|
+
renderedPrompt,
|
|
72
|
+
action,
|
|
73
|
+
authorityRef,
|
|
74
|
+
});
|
|
75
|
+
const issues = validateAuthorizing(block);
|
|
76
|
+
if (issues.length > 0) {
|
|
77
|
+
console.warn("[survey] buildDecisionCardAuthorizing: authorizing block failed validation — recording outcome without authorizing.", issues);
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return block;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.warn("[survey] buildDecisionCardAuthorizing: failed to construct authorizing block — recording outcome without authorizing.", err);
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Derives the exact decision prompt rendered on the workbench decision card
|
|
89
|
+
* for a given review item and decision.
|
|
90
|
+
*
|
|
91
|
+
* Format mirrors the review question shown in the workbench header:
|
|
92
|
+
* "For {target}, decide whether {proposed} should replace {current}."
|
|
93
|
+
* followed by the selected decision label so the block is self-contained.
|
|
94
|
+
*/
|
|
95
|
+
function decisionCardRenderedPrompt(state, targetLabel) {
|
|
96
|
+
const currentCandidate = state.item.spec.candidates.find((c) => c.role === "current");
|
|
97
|
+
const proposedCandidate = state.item.spec.candidates.find((c) => c.role === "proposed");
|
|
98
|
+
const currentValue = formatValue(currentCandidate?.value ?? "");
|
|
99
|
+
const proposedValue = formatValue(proposedCandidate?.value ?? "");
|
|
100
|
+
const decisionLabel = state.decision ? workbenchDecisionDefinitions[state.decision].label : "";
|
|
101
|
+
return `For ${targetLabel}, decide whether ${proposedValue} should replace ${currentValue}. Selected decision: ${decisionLabel}.`;
|
|
102
|
+
}
|
|
45
103
|
export class ReviewApplyActionMappingError extends Error {
|
|
46
104
|
name = "ReviewApplyActionMappingError";
|
|
47
105
|
issues;
|
|
@@ -391,9 +449,12 @@ function renderReviewQueueSessionHtml(session, events, presentationAdapter) {
|
|
|
391
449
|
return `
|
|
392
450
|
<section class="workbench-shell" aria-label="Survey review workbench">
|
|
393
451
|
${renderWorkbenchHeader(state, presentationAdapter)}
|
|
452
|
+
${renderMobileQueueBar(session, state, presentationAdapter)}
|
|
394
453
|
${renderActiveReviewStrip(session, state, presentationAdapter)}
|
|
395
454
|
<div class="queue-layout">
|
|
396
|
-
<
|
|
455
|
+
<div class="queue-drawer-backdrop" data-testid="queue-drawer-backdrop" aria-hidden="true"></div>
|
|
456
|
+
<aside class="queue-panel" aria-label="Review queue" role="dialog" aria-modal="false" id="queue-panel">
|
|
457
|
+
<button class="queue-drawer-close" type="button" data-testid="queue-drawer-close" aria-label="Close queue panel">✕</button>
|
|
397
458
|
${renderQueueRows(session, presentationAdapter)}
|
|
398
459
|
${renderSessionSummary(session)}
|
|
399
460
|
${renderSessionAudit(session, sessionExport)}
|
|
@@ -407,6 +468,28 @@ function renderReviewQueueSessionHtml(session, events, presentationAdapter) {
|
|
|
407
468
|
</section>
|
|
408
469
|
`;
|
|
409
470
|
}
|
|
471
|
+
function renderMobileQueueBar(session, state, presentationAdapter) {
|
|
472
|
+
const activeIndex = session.items.findIndex((item) => item.metadata.name === session.activeItemName);
|
|
473
|
+
const position = activeIndex >= 0 ? activeIndex + 1 : 1;
|
|
474
|
+
const resolved = Object.keys(session.decisionsByItemName).length;
|
|
475
|
+
const presentation = buildReviewItemPresentation(state.item, presentationAdapter);
|
|
476
|
+
return `
|
|
477
|
+
<div class="mobile-queue-bar" data-testid="mobile-queue-bar" aria-label="Queue progress">
|
|
478
|
+
<div class="mobile-queue-progress">
|
|
479
|
+
<span class="field-label">Queue · ${resolved} of ${session.items.length} resolved</span>
|
|
480
|
+
<span class="mobile-queue-item-label">${escapeHtml(presentation.targetLabel)}</span>
|
|
481
|
+
</div>
|
|
482
|
+
<button
|
|
483
|
+
class="mobile-queue-open"
|
|
484
|
+
type="button"
|
|
485
|
+
data-testid="mobile-queue-open"
|
|
486
|
+
aria-controls="queue-panel"
|
|
487
|
+
aria-expanded="false"
|
|
488
|
+
aria-label="Open review queue"
|
|
489
|
+
>Queue</button>
|
|
490
|
+
</div>
|
|
491
|
+
`;
|
|
492
|
+
}
|
|
410
493
|
function renderSessionAudit(session, sessionExport) {
|
|
411
494
|
const lastEvents = sessionExport.events.slice(-6);
|
|
412
495
|
const replayed = replayReviewSessionEvents({
|
|
@@ -527,17 +610,13 @@ function renderActiveReviewStrip(session, state, presentationAdapter) {
|
|
|
527
610
|
`;
|
|
528
611
|
}
|
|
529
612
|
function renderWorkbenchHeader(state, presentationAdapter) {
|
|
530
|
-
const current = candidateByRole(state.item, "current");
|
|
531
|
-
const proposed = candidateByRole(state.item, "proposed");
|
|
532
613
|
const presentation = buildReviewItemPresentation(state.item, presentationAdapter);
|
|
533
|
-
const currentPresentation = buildReviewCandidatePresentation(state.item, current, presentationAdapter, presentation.targetLabel);
|
|
534
|
-
const proposedPresentation = buildReviewCandidatePresentation(state.item, proposed, presentationAdapter, presentation.targetLabel);
|
|
535
614
|
return `
|
|
536
615
|
<header class="topbar">
|
|
537
616
|
<div>
|
|
538
617
|
<p class="eyebrow">${escapeHtml(String(state.item.metadata.producer?.displayName ?? "Survey"))}</p>
|
|
539
618
|
<h1>Review candidate update</h1>
|
|
540
|
-
<p class="review-
|
|
619
|
+
<p class="review-target-label" data-testid="review-target-label"><strong>${escapeHtml(presentation.targetLabel)}</strong></p>
|
|
541
620
|
</div>
|
|
542
621
|
<dl class="meta-grid">
|
|
543
622
|
${metaItem("Item", state.item.metadata.name)}
|
|
@@ -661,32 +740,31 @@ function renderSurfacePreview(state, presentationAdapter) {
|
|
|
661
740
|
}
|
|
662
741
|
function renderPendingSurfacePreview() {
|
|
663
742
|
return `
|
|
664
|
-
<
|
|
665
|
-
<
|
|
666
|
-
<
|
|
743
|
+
<details class="surface-preview" data-testid="surface-preview" aria-label="Surface preview">
|
|
744
|
+
<summary class="surface-summary">
|
|
745
|
+
<span class="surface-summary-label">Surface preview</span>
|
|
667
746
|
<span class="state-label">pending</span>
|
|
668
|
-
</
|
|
669
|
-
<p class="preview-disclaimer">Choose a review decision to preview the source and review posture that would project toward Surface
|
|
670
|
-
</
|
|
747
|
+
</summary>
|
|
748
|
+
<p class="preview-disclaimer">Choose a review decision to preview the source and review posture that would project toward Surface.</p>
|
|
749
|
+
</details>
|
|
671
750
|
`;
|
|
672
751
|
}
|
|
673
752
|
function renderPopulatedSurfacePreview(preview) {
|
|
674
753
|
return `
|
|
675
|
-
<
|
|
676
|
-
<
|
|
677
|
-
<
|
|
754
|
+
<details class="surface-preview" data-testid="surface-preview" aria-label="Surface preview">
|
|
755
|
+
<summary class="surface-summary">
|
|
756
|
+
<span class="surface-summary-label">Surface preview</span>
|
|
678
757
|
<span class="state-label">local preview</span>
|
|
679
|
-
</
|
|
680
|
-
<p class="preview-disclaimer" data-testid="surface-preview-disclaimer">${escapeHtml(preview.postureDisclaimer)}</p>
|
|
758
|
+
</summary>
|
|
681
759
|
<div class="preview-section-grid">
|
|
682
760
|
${renderSurfacePreviewSections(preview)}
|
|
683
761
|
</div>
|
|
684
|
-
|
|
762
|
+
<p class="preview-disclaimer preview-disclaimer-footer" data-testid="surface-preview-disclaimer">${escapeHtml(preview.postureDisclaimer)}</p>
|
|
763
|
+
</details>
|
|
685
764
|
`;
|
|
686
765
|
}
|
|
687
766
|
function renderSurfacePreviewSections(preview) {
|
|
688
767
|
return [
|
|
689
|
-
renderCanonicalClaim(preview),
|
|
690
768
|
renderCandidateHistory(preview),
|
|
691
769
|
renderSourceEvidence(preview),
|
|
692
770
|
renderReviewEvent(preview),
|
|
@@ -884,11 +962,13 @@ function renderCurrentState(root, session, controller, presentationAdapter) {
|
|
|
884
962
|
bindQueueRows(root, (itemName) => {
|
|
885
963
|
controller.selectQueueItem(itemName);
|
|
886
964
|
controller.renderCurrentState();
|
|
965
|
+
closeQueueDrawer(root);
|
|
887
966
|
});
|
|
888
967
|
bindNextUnresolved(root, () => {
|
|
889
968
|
controller.goToNextUnresolved();
|
|
890
969
|
controller.renderCurrentState();
|
|
891
970
|
});
|
|
971
|
+
bindQueueDrawer(root);
|
|
892
972
|
}
|
|
893
973
|
function queueSessionFromStartState(startState) {
|
|
894
974
|
if ("items" in startState) {
|
|
@@ -935,6 +1015,48 @@ function bindNextUnresolved(root, goToNextUnresolved) {
|
|
|
935
1015
|
});
|
|
936
1016
|
});
|
|
937
1017
|
}
|
|
1018
|
+
function bindQueueDrawer(root) {
|
|
1019
|
+
const openButton = root.querySelector("[data-testid='mobile-queue-open']");
|
|
1020
|
+
const closeButton = root.querySelector("[data-testid='queue-drawer-close']");
|
|
1021
|
+
const backdrop = root.querySelector("[data-testid='queue-drawer-backdrop']");
|
|
1022
|
+
const panel = root.querySelector("#queue-panel");
|
|
1023
|
+
openButton?.addEventListener("click", () => openQueueDrawer(root));
|
|
1024
|
+
closeButton?.addEventListener("click", () => closeQueueDrawer(root));
|
|
1025
|
+
backdrop?.addEventListener("click", () => closeQueueDrawer(root));
|
|
1026
|
+
if (typeof root.addEventListener === "function") {
|
|
1027
|
+
root.addEventListener("keydown", (event) => {
|
|
1028
|
+
if (event.key === "Escape" && panel?.classList.contains("is-open")) {
|
|
1029
|
+
closeQueueDrawer(root);
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function openQueueDrawer(root) {
|
|
1035
|
+
const panel = root.querySelector("#queue-panel");
|
|
1036
|
+
const backdrop = root.querySelector("[data-testid='queue-drawer-backdrop']");
|
|
1037
|
+
const openButton = root.querySelector("[data-testid='mobile-queue-open']");
|
|
1038
|
+
if (!panel) {
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
panel.classList.add("is-open");
|
|
1042
|
+
panel.setAttribute("aria-modal", "true");
|
|
1043
|
+
backdrop?.classList.add("is-visible");
|
|
1044
|
+
openButton?.setAttribute("aria-expanded", "true");
|
|
1045
|
+
panel.querySelector("button, [tabindex]")?.focus();
|
|
1046
|
+
}
|
|
1047
|
+
function closeQueueDrawer(root) {
|
|
1048
|
+
const panel = root.querySelector("#queue-panel");
|
|
1049
|
+
const backdrop = root.querySelector("[data-testid='queue-drawer-backdrop']");
|
|
1050
|
+
const openButton = root.querySelector("[data-testid='mobile-queue-open']");
|
|
1051
|
+
if (!panel) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
panel.classList.remove("is-open");
|
|
1055
|
+
panel.setAttribute("aria-modal", "false");
|
|
1056
|
+
backdrop?.classList.remove("is-visible");
|
|
1057
|
+
openButton?.setAttribute("aria-expanded", "false");
|
|
1058
|
+
openButton?.focus();
|
|
1059
|
+
}
|
|
938
1060
|
function scrollActiveReviewIntoView(root) {
|
|
939
1061
|
if (typeof window === "undefined" || !window.matchMedia("(max-width: 980px)").matches) {
|
|
940
1062
|
return;
|
|
@@ -169,6 +169,17 @@ h1 {
|
|
|
169
169
|
font-size: 0.95em;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
.review-target-label {
|
|
173
|
+
margin-top: 4px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.review-target-label strong {
|
|
177
|
+
color: var(--paper);
|
|
178
|
+
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
|
179
|
+
font-size: 13px;
|
|
180
|
+
font-weight: 600;
|
|
181
|
+
}
|
|
182
|
+
|
|
172
183
|
.meta-grid {
|
|
173
184
|
display: none;
|
|
174
185
|
grid-template-columns: repeat(4, minmax(110px, 1fr));
|
|
@@ -223,6 +234,52 @@ h1 {
|
|
|
223
234
|
display: none;
|
|
224
235
|
}
|
|
225
236
|
|
|
237
|
+
/* ---- Mobile queue progress bar (mobile only) -------------------------- */
|
|
238
|
+
|
|
239
|
+
.mobile-queue-bar {
|
|
240
|
+
display: none;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* ---- Queue drawer backdrop -------------------------------------------- */
|
|
244
|
+
|
|
245
|
+
.queue-drawer-backdrop {
|
|
246
|
+
display: none;
|
|
247
|
+
position: fixed;
|
|
248
|
+
inset: 0;
|
|
249
|
+
z-index: 40;
|
|
250
|
+
background: rgba(0, 0, 0, 0.55);
|
|
251
|
+
backdrop-filter: blur(2px);
|
|
252
|
+
opacity: 0;
|
|
253
|
+
transition: opacity 0.22s ease;
|
|
254
|
+
pointer-events: none;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.queue-drawer-backdrop.is-visible {
|
|
258
|
+
opacity: 1;
|
|
259
|
+
pointer-events: auto;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* ---- Queue drawer close button (mobile only) -------------------------- */
|
|
263
|
+
|
|
264
|
+
.queue-drawer-close {
|
|
265
|
+
display: none;
|
|
266
|
+
position: absolute;
|
|
267
|
+
top: 12px;
|
|
268
|
+
right: 12px;
|
|
269
|
+
width: 30px;
|
|
270
|
+
height: 30px;
|
|
271
|
+
border: 1px solid var(--line-strong);
|
|
272
|
+
border-radius: 999px;
|
|
273
|
+
background: rgba(8, 12, 18, 0.8);
|
|
274
|
+
color: var(--paper-faint);
|
|
275
|
+
cursor: pointer;
|
|
276
|
+
font-size: 14px;
|
|
277
|
+
line-height: 1;
|
|
278
|
+
align-items: center;
|
|
279
|
+
justify-content: center;
|
|
280
|
+
z-index: 2;
|
|
281
|
+
}
|
|
282
|
+
|
|
226
283
|
.queue-list,
|
|
227
284
|
.session-summary,
|
|
228
285
|
.session-audit {
|
|
@@ -894,6 +951,53 @@ textarea:focus {
|
|
|
894
951
|
gap: 11px;
|
|
895
952
|
}
|
|
896
953
|
|
|
954
|
+
/* When surface-preview is a <details> element */
|
|
955
|
+
details.surface-preview {
|
|
956
|
+
display: block;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
details.surface-preview[open] {
|
|
960
|
+
display: grid;
|
|
961
|
+
gap: 11px;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.surface-summary {
|
|
965
|
+
display: flex;
|
|
966
|
+
gap: 12px;
|
|
967
|
+
justify-content: space-between;
|
|
968
|
+
align-items: center;
|
|
969
|
+
cursor: pointer;
|
|
970
|
+
list-style: none;
|
|
971
|
+
padding: 0;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.surface-summary::-webkit-details-marker {
|
|
975
|
+
display: none;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.surface-summary::before {
|
|
979
|
+
content: none;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
.surface-summary-label {
|
|
983
|
+
font-family: "Fraunces", Georgia, serif;
|
|
984
|
+
font-weight: 600;
|
|
985
|
+
font-size: 16px;
|
|
986
|
+
letter-spacing: -0.01em;
|
|
987
|
+
color: var(--paper);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.surface-summary-label::before {
|
|
991
|
+
content: "› ";
|
|
992
|
+
color: var(--accent);
|
|
993
|
+
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
|
994
|
+
font-size: 13px;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
details.surface-preview[open] .surface-summary-label::before {
|
|
998
|
+
content: "⌄ ";
|
|
999
|
+
}
|
|
1000
|
+
|
|
897
1001
|
.surface-head {
|
|
898
1002
|
display: flex;
|
|
899
1003
|
gap: 12px;
|
|
@@ -916,6 +1020,13 @@ textarea:focus {
|
|
|
916
1020
|
line-height: 1.5;
|
|
917
1021
|
}
|
|
918
1022
|
|
|
1023
|
+
.preview-disclaimer-footer {
|
|
1024
|
+
margin-top: 4px;
|
|
1025
|
+
border-left-color: var(--line);
|
|
1026
|
+
font-size: 11.5px;
|
|
1027
|
+
color: var(--paper-faint);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
919
1030
|
.preview-section-grid {
|
|
920
1031
|
display: grid;
|
|
921
1032
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -1058,7 +1169,6 @@ pre {
|
|
|
1058
1169
|
|
|
1059
1170
|
@media (max-width: 980px) {
|
|
1060
1171
|
.topbar,
|
|
1061
|
-
.queue-layout,
|
|
1062
1172
|
.focus-evidence,
|
|
1063
1173
|
.candidate-grid {
|
|
1064
1174
|
grid-template-columns: 1fr;
|
|
@@ -1073,9 +1183,8 @@ pre {
|
|
|
1073
1183
|
}
|
|
1074
1184
|
|
|
1075
1185
|
.queue-layout {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
"queue";
|
|
1186
|
+
display: block;
|
|
1187
|
+
position: relative;
|
|
1079
1188
|
}
|
|
1080
1189
|
|
|
1081
1190
|
.active-review-strip {
|
|
@@ -1136,8 +1245,85 @@ pre {
|
|
|
1136
1245
|
font-size: 11px;
|
|
1137
1246
|
}
|
|
1138
1247
|
|
|
1139
|
-
.queue-
|
|
1140
|
-
|
|
1248
|
+
.queue-drawer-backdrop {
|
|
1249
|
+
display: block;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
.queue-drawer-close {
|
|
1253
|
+
display: flex;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.mobile-queue-bar {
|
|
1257
|
+
display: grid;
|
|
1258
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
1259
|
+
gap: 12px;
|
|
1260
|
+
align-items: center;
|
|
1261
|
+
border: 1px solid var(--line-strong);
|
|
1262
|
+
border-left: 3px solid var(--accent);
|
|
1263
|
+
border-radius: var(--radius);
|
|
1264
|
+
padding: 11px 14px;
|
|
1265
|
+
margin-bottom: 14px;
|
|
1266
|
+
background:
|
|
1267
|
+
linear-gradient(90deg, rgba(92, 224, 198, 0.08), transparent 46%),
|
|
1268
|
+
linear-gradient(180deg, var(--ink-800), var(--ink-850));
|
|
1269
|
+
animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.mobile-queue-progress {
|
|
1273
|
+
display: grid;
|
|
1274
|
+
gap: 3px;
|
|
1275
|
+
min-width: 0;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.mobile-queue-item-label {
|
|
1279
|
+
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
|
1280
|
+
font-size: 13px;
|
|
1281
|
+
font-weight: 600;
|
|
1282
|
+
color: var(--paper);
|
|
1283
|
+
overflow-wrap: anywhere;
|
|
1284
|
+
word-break: break-word;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
.mobile-queue-open {
|
|
1288
|
+
flex-shrink: 0;
|
|
1289
|
+
min-height: 34px;
|
|
1290
|
+
padding: 0 14px;
|
|
1291
|
+
border: 1px solid var(--line-strong);
|
|
1292
|
+
border-radius: 999px;
|
|
1293
|
+
background: rgba(92, 224, 198, 0.08);
|
|
1294
|
+
color: var(--accent);
|
|
1295
|
+
cursor: pointer;
|
|
1296
|
+
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
|
1297
|
+
font-size: 11px;
|
|
1298
|
+
font-weight: 600;
|
|
1299
|
+
letter-spacing: 0.06em;
|
|
1300
|
+
transition: background 0.16s ease, border-color 0.16s ease;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
.mobile-queue-open:hover {
|
|
1304
|
+
background: rgba(92, 224, 198, 0.16);
|
|
1305
|
+
border-color: var(--accent);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/* Higher specificity than the @container stacked-layout fallback below:
|
|
1309
|
+
on a narrow viewport the drawer must win even inside a narrow container. */
|
|
1310
|
+
.queue-layout .queue-panel {
|
|
1311
|
+
position: fixed;
|
|
1312
|
+
top: 0;
|
|
1313
|
+
left: 0;
|
|
1314
|
+
bottom: 0;
|
|
1315
|
+
width: min(320px, 90vw);
|
|
1316
|
+
z-index: 50;
|
|
1317
|
+
overflow-y: auto;
|
|
1318
|
+
overflow-x: hidden;
|
|
1319
|
+
transform: translateX(-105%);
|
|
1320
|
+
transition: transform 0.26s cubic-bezier(0.22, 1, 0.36, 1);
|
|
1321
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
1322
|
+
padding-top: 50px;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
.queue-layout .queue-panel.is-open {
|
|
1326
|
+
transform: translateX(0);
|
|
1141
1327
|
}
|
|
1142
1328
|
|
|
1143
1329
|
.content-grid {
|
|
@@ -1148,10 +1334,6 @@ pre {
|
|
|
1148
1334
|
display: none;
|
|
1149
1335
|
}
|
|
1150
1336
|
|
|
1151
|
-
.queue-panel {
|
|
1152
|
-
position: static;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
1337
|
.content-grid,
|
|
1156
1338
|
.review-main,
|
|
1157
1339
|
.candidate-grid,
|
|
@@ -1331,11 +1513,11 @@ pre {
|
|
|
1331
1513
|
}
|
|
1332
1514
|
|
|
1333
1515
|
.focus-value strong {
|
|
1334
|
-
font-size: clamp(
|
|
1516
|
+
font-size: clamp(16px, 5cqi, 22px);
|
|
1335
1517
|
}
|
|
1336
1518
|
|
|
1337
1519
|
.candidate-value .field-value {
|
|
1338
|
-
font-size: clamp(
|
|
1520
|
+
font-size: clamp(15px, 4.5cqi, 20px);
|
|
1339
1521
|
line-height: 1.12;
|
|
1340
1522
|
}
|
|
1341
1523
|
|
|
@@ -1440,7 +1622,7 @@ pre {
|
|
|
1440
1622
|
|
|
1441
1623
|
.focus-value strong {
|
|
1442
1624
|
margin-top: 6px;
|
|
1443
|
-
font-size:
|
|
1625
|
+
font-size: 18px;
|
|
1444
1626
|
line-height: 1.05;
|
|
1445
1627
|
}
|
|
1446
1628
|
|
|
@@ -1449,6 +1631,6 @@ pre {
|
|
|
1449
1631
|
}
|
|
1450
1632
|
|
|
1451
1633
|
.candidate-value .field-value {
|
|
1452
|
-
font-size:
|
|
1634
|
+
font-size: 17px;
|
|
1453
1635
|
}
|
|
1454
1636
|
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -47,6 +47,26 @@ export interface CandidateSet {
|
|
|
47
47
|
metadata?: Record<string, unknown>;
|
|
48
48
|
}
|
|
49
49
|
export type ReviewStatus = Extract<TrustStatus, "verified" | "assumed" | "rejected" | "proposed">;
|
|
50
|
+
export type ReviewAuthorizingKind = "explicit-statement" | "exchange" | "authorized-action";
|
|
51
|
+
export interface ReviewAuthorizingExplicitStatement {
|
|
52
|
+
kind: "explicit-statement";
|
|
53
|
+
statement: string;
|
|
54
|
+
source?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ReviewAuthorizingExchange {
|
|
57
|
+
kind: "exchange";
|
|
58
|
+
prompt: string;
|
|
59
|
+
response: string;
|
|
60
|
+
source?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface ReviewAuthorizingAuthorizedAction {
|
|
63
|
+
kind: "authorized-action";
|
|
64
|
+
promptRef: string;
|
|
65
|
+
renderedPrompt: string;
|
|
66
|
+
action: "affirmed-control" | "typed";
|
|
67
|
+
authorityRef: string;
|
|
68
|
+
}
|
|
69
|
+
export type ReviewAuthorizing = ReviewAuthorizingExplicitStatement | ReviewAuthorizingExchange | ReviewAuthorizingAuthorizedAction;
|
|
50
70
|
export interface ReviewOutcome {
|
|
51
71
|
id: string;
|
|
52
72
|
candidateSetId: string;
|
|
@@ -58,6 +78,9 @@ export interface ReviewOutcome {
|
|
|
58
78
|
evidenceIds?: string[];
|
|
59
79
|
withinComfortZone?: boolean;
|
|
60
80
|
comfortZoneNote?: string;
|
|
81
|
+
/** Optional testimony provenance. When present, records how this decision
|
|
82
|
+
* was authorized — interpreted by downstream verifiers for admissibility. */
|
|
83
|
+
authorizing?: ReviewAuthorizing;
|
|
61
84
|
metadata?: Record<string, unknown>;
|
|
62
85
|
}
|
|
63
86
|
export interface EscalationRecord {
|