@kontourai/survey 7.0.0 → 8.1.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/LICENSE +201 -0
- package/README.md +2 -1
- package/dist/src/calibration.d.ts +15 -0
- package/dist/src/calibration.js +11 -0
- package/dist/src/canonical-reviewed-trust-input.d.ts +10 -0
- package/dist/src/canonical-reviewed-trust-input.js +15 -0
- package/dist/src/console/review-console-server.d.ts +20 -0
- package/dist/src/console/review-console-server.js +75 -4
- package/dist/src/decision-carry-forward.d.ts +157 -0
- package/dist/src/decision-carry-forward.js +295 -0
- package/dist/src/extraction-envelope.d.ts +4 -3
- package/dist/src/extraction-envelope.js +3 -3
- package/dist/src/field-states.d.ts +100 -0
- package/dist/src/field-states.js +169 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +2 -0
- package/dist/src/mcp/review-mcp.js +44 -13
- package/dist/src/review-resource.d.ts +29 -0
- package/dist/src/review-session-file.d.ts +6 -0
- package/dist/src/review-session-file.js +2 -0
- package/dist/src/review-workbench/audit-sample.d.ts +32 -0
- package/dist/src/review-workbench/audit-sample.js +53 -0
- package/dist/src/review-workbench/extraction-inspector.d.ts +1 -1
- package/dist/src/review-workbench/extraction-inspector.js +1 -1
- package/dist/src/review-workbench/field-state-view.d.ts +15 -0
- package/dist/src/review-workbench/field-state-view.js +84 -0
- package/dist/src/review-workbench/review-queue-session.d.ts +46 -1
- package/dist/src/review-workbench/review-queue-session.js +74 -3
- package/dist/src/review-workbench/review-session-replay.d.ts +1 -1
- package/dist/src/review-workbench/review-session-replay.js +15 -1
- package/dist/src/review-workbench/review-workbench-css.generated.d.ts +9 -0
- package/dist/src/review-workbench/review-workbench-css.generated.js +453 -43
- package/dist/src/review-workbench/review-workbench-element.js +68 -212
- package/dist/src/review-workbench/review-workbench.css +386 -42
- package/dist/src/review-workbench/review-workbench.d.ts +13 -1
- package/dist/src/review-workbench/review-workbench.js +41 -15
- package/dist/src/review-workbench/review-workbench.standalone.css +37 -23
- package/dist/src/review-workbench/vendor/kontourai-ui/tokens/themes.css +189 -10
- package/dist/src/review-workbench/vendor/kontourai-ui/tokens/tokens.css +156 -5
- package/package.json +13 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalJson } from "./canonical.js";
|
|
2
2
|
import { assertReviewResolutionConsistency } from "../producer-discipline.js";
|
|
3
|
-
import { candidateForDecision, conflictSelectionIssue, decisionCandidateId, decisionSelectsNoCandidate, keepActionDecision, unchosenProposedCandidates, buildReviewSessionEvent, buildReviewSessionEvents, buildReviewSessionResource, currentReviewWorkbenchState, defaultReviewSessionName, effectiveValueForDecision, initialReviewQueueSessionState, replayReviewSessionEvents, reviewWorkbenchSessionStorageKey, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
3
|
+
import { candidateForDecision, conflictSelectionIssue, decisionCandidateId, decisionSelectsNoCandidate, keepActionDecision, unchosenProposedCandidates, buildReviewSessionEvent, buildReviewSessionEvents, buildReviewSessionResource, currentReviewWorkbenchState, defaultReviewSessionName, effectiveValueForDecision, initialReviewQueueSessionState, isScoreBlind, assertReviewSessionConditions, sessionConditionsStamp, replayReviewSessionEvents, reviewWorkbenchSessionStorageKey, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
4
4
|
import { reviewSessionReplayWarningsForSnapshot, validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
5
5
|
import { buildSurfaceProjectionPreview, formatValue, } from "./review-surface-preview.js";
|
|
6
6
|
import { buildReviewCandidatePresentation, buildReviewItemPresentation, } from "./review-presentation.js";
|
|
@@ -9,11 +9,14 @@ import { validateAuthorizing, buildAuthorizedActionAuthorizing } from "../review
|
|
|
9
9
|
import { attestReviewQueueExtraction } from "./queue-binding.js";
|
|
10
10
|
import { candidateVerificationNotes, excludedProposalsSentence, excludedProposalsUnreadableSentence } from "./review-presentation.js";
|
|
11
11
|
import { editedValueFromEditorText, isIsoCalendarDate, parsePlainDecimal } from "./edited-value.js";
|
|
12
|
+
import { renderFieldStatesHtml } from "./field-state-view.js";
|
|
12
13
|
import { createAuditFactTrace, } from "./audit-rows.js";
|
|
13
14
|
export { reviewAuditRowKeys } from "./audit-rows.js";
|
|
14
15
|
export { assertReviewQueueAgainstExtractionImport, assertReviewQueueBinding, attestReviewQueueExtraction, bindReviewQueue, reviewItemCarriesExtractionBinding, unverifiedExtractionQueueSentence, hashReviewQueueSnapshot, UnattestedExtractionQueueError, UnattestedReviewQueueError, validateReviewQueueAgainstExtractionImport, validateReviewQueueBinding, } from "./queue-binding.js";
|
|
15
16
|
export { buildExtractionInspectorModel, exportExtractionInspector, filterExtractionInspectorCandidates, mountExtractionInspector, } from "./extraction-inspector.js";
|
|
16
|
-
export {
|
|
17
|
+
export { renderFieldStatesHtml } from "./field-state-view.js";
|
|
18
|
+
export { drawRandomAuditSample, openRandomAuditSession, } from "./audit-sample.js";
|
|
19
|
+
export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionResource, candidateForDecision, conflictSelectionIssue, unchosenProposedCandidates, decisionCandidateId, decisionSelectsNoCandidate, keepActionDecision, currentReviewItem, currentReviewWorkbenchState, defaultReviewSessionName, deriveQueueRowStatus, initialReviewQueueSessionState, initialReviewWorkbenchState, isScoreBlind, assertReviewSessionConditions, nextUnresolvedItemName, replayReviewSessionEvents, reviewSessionSummary, reviewWorkbenchSessionStorageKey, selectedCandidateRole, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
17
20
|
export { buildReviewCandidatePresentation, buildReviewItemPresentation, buildReviewResultPresentation, humanizeIdentifier, } from "./review-presentation.js";
|
|
18
21
|
export { buildSurfaceProjectionPreview, } from "./review-surface-preview.js";
|
|
19
22
|
export { reviewSessionReplayWarningsForSnapshot, validateReviewSessionEventsForSnapshot, } from "./review-session-replay.js";
|
|
@@ -24,6 +27,7 @@ export function buildReviewDecision(state, options = {}) {
|
|
|
24
27
|
return undefined;
|
|
25
28
|
}
|
|
26
29
|
const definition = workbenchDecisionDefinitions[state.decision];
|
|
30
|
+
assertReviewSessionConditions(state);
|
|
27
31
|
if (state.decision === "could-not-confirm" && !state.note.trim()) {
|
|
28
32
|
throw new Error("Could not confirm requires a non-empty reason.");
|
|
29
33
|
}
|
|
@@ -70,6 +74,10 @@ export function buildReviewDecision(state, options = {}) {
|
|
|
70
74
|
: {}),
|
|
71
75
|
// A choice between conflicting values records the rivals it passed over.
|
|
72
76
|
...(unchosen.length ? { unselectedCandidateIds: unchosen.map((entry) => entry.id) } : {}),
|
|
77
|
+
// The session's conditions, never the reviewer's: a score-blind session
|
|
78
|
+
// records that the decision was made without scores on screen.
|
|
79
|
+
...(state.presentation !== undefined ? { presentation: { scoreBlind: state.presentation.scoreBlind } } : {}),
|
|
80
|
+
...(state.sampling !== undefined ? { sampling: { ...state.sampling } } : {}),
|
|
73
81
|
},
|
|
74
82
|
status: {
|
|
75
83
|
...(candidateProjection?.claimId ? { appliedToClaimIds: [candidateProjection.claimId] } : {}),
|
|
@@ -140,7 +148,8 @@ function decisionCardRenderedPrompt(state, presentation, adapter) {
|
|
|
140
148
|
const excluded = excludedProposalsSentence(presentation.excludedProposals);
|
|
141
149
|
const unreadable = excludedProposalsUnreadableSentence(presentation.excludedProposalsUnreadable);
|
|
142
150
|
// Likewise the verifier records the card shows, read against the same value.
|
|
143
|
-
|
|
151
|
+
// A score-blind card shows no verifier result, so the recorded prompt names none.
|
|
152
|
+
const verification = isScoreBlind(state) ? [] : candidateVerificationNotes(state.item, state.decision === "accept-proposed" ? state.editedValue : undefined).map((note) => note.sentence);
|
|
144
153
|
return [decisionCardBasePrompt(state, presentation.targetLabel, adapter), ...(excluded ? [excluded] : []), ...(unreadable ? [unreadable] : []), ...verification].join(" ");
|
|
145
154
|
}
|
|
146
155
|
function decisionCardBasePrompt(state, targetLabel, adapter) {
|
|
@@ -514,7 +523,7 @@ export function createPersistentReviewSessionEventStore(options) {
|
|
|
514
523
|
}
|
|
515
524
|
export function renderReviewWorkbenchHtml(state, _events, options = {}) {
|
|
516
525
|
const session = queueSessionFromStartState(state);
|
|
517
|
-
return renderReviewQueueSessionHtml(session, options.presentationAdapter, normalizeReviewQueueView(options.queueView), attestReviewQueueExtraction(session.items, options.extractionImport));
|
|
526
|
+
return renderReviewQueueSessionHtml(session, options.presentationAdapter, normalizeReviewQueueView(options.queueView), attestReviewQueueExtraction(session.items, options.extractionImport), options.fieldStates);
|
|
518
527
|
}
|
|
519
528
|
/**
|
|
520
529
|
* The visual/decision state of a field card, derived from the reviewer's local
|
|
@@ -602,7 +611,7 @@ function isEmptyValue(value) {
|
|
|
602
611
|
}
|
|
603
612
|
const ARROW_SVG = "<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M5 12h14M13 6l6 6-6 6\"/></svg>";
|
|
604
613
|
const WARNING_SVG = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" aria-hidden=\"true\"><path d=\"M12 9v4M12 17h.01\"/><path d=\"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z\"/></svg>";
|
|
605
|
-
function renderReviewQueueSessionHtml(session, presentationAdapter, queueView, attestation) {
|
|
614
|
+
function renderReviewQueueSessionHtml(session, presentationAdapter, queueView, attestation, fieldStates) {
|
|
606
615
|
// A queue that does not match its stored extraction import is not reviewed:
|
|
607
616
|
// its items could hide or alter what the source said.
|
|
608
617
|
if (attestation.state === "diverges") {
|
|
@@ -625,6 +634,9 @@ function renderReviewQueueSessionHtml(session, presentationAdapter, queueView, a
|
|
|
625
634
|
${attestation.state === "unverified"
|
|
626
635
|
? `<div class="queue-attestation unverified" data-testid="queue-attestation" data-state="unverified" role="note">${WARNING_SVG}<span>${escapeHtml(attestation.message)}</span></div>`
|
|
627
636
|
: ""}
|
|
637
|
+
${isScoreBlind(session)
|
|
638
|
+
? `<div class="queue-attestation score-blind" data-testid="score-blind-notice" data-sampling="${escapeHtml(session.sampling?.kind ?? "")}" role="note"><span>${escapeHtml(scoreBlindNotice(session))}</span></div>`
|
|
639
|
+
: ""}
|
|
628
640
|
<header class="rhead">
|
|
629
641
|
<div class="top">
|
|
630
642
|
<div class="subj">
|
|
@@ -641,6 +653,7 @@ function renderReviewQueueSessionHtml(session, presentationAdapter, queueView, a
|
|
|
641
653
|
<button class="apply" type="button" data-testid="apply-button"${decidedCount === 0 ? " disabled" : ""}>Apply ${decidedCount} decision${decidedCount === 1 ? "" : "s"}</button>
|
|
642
654
|
</div>
|
|
643
655
|
</header>
|
|
656
|
+
${fieldStates ? renderFieldStatesHtml(fieldStates, session) : ""}
|
|
644
657
|
<nav class="queue-controls" aria-label="Review queue navigation">
|
|
645
658
|
<label>
|
|
646
659
|
<span>Find fields</span>
|
|
@@ -730,9 +743,9 @@ function renderFieldCard(item, session, presentationAdapter) {
|
|
|
730
743
|
: proposedCandidates.length > 1
|
|
731
744
|
? renderConflictingProposals(item, proposedCandidates, presentationAdapter, presentation.targetLabel, decision, session.selectedCandidateIdsByItemName?.[item.metadata.name])
|
|
732
745
|
: "<p class=\"field-value\">No proposed value is available for this field.</p>"}
|
|
733
|
-
${proposed ? renderProvenanceRow(item, proposed, presentationAdapter) : ""}
|
|
746
|
+
${proposed ? renderProvenanceRow(item, proposed, presentationAdapter, isScoreBlind(session)) : ""}
|
|
734
747
|
${renderExtractionImportNotes(presentation)}
|
|
735
|
-
${renderVerificationNotes(candidateVerificationNotes(item, decision === "accept-proposed" ? editedValue : undefined))}
|
|
748
|
+
${isScoreBlind(session) ? "" : renderVerificationNotes(candidateVerificationNotes(item, decision === "accept-proposed" ? editedValue : undefined))}
|
|
736
749
|
<div class="decide">
|
|
737
750
|
${keepDecision === undefined ? "" : `<button class="btn keep" type="button" data-testid="keep-current" data-item-name="${escapeHtml(item.metadata.name)}">${keepLabel}</button>`}
|
|
738
751
|
${proposed ? `<button class="btn use" type="button" data-testid="use-proposed" data-item-name="${escapeHtml(item.metadata.name)}">Use proposed</button>
|
|
@@ -955,7 +968,7 @@ export function validateProposedValue(descriptor, rawValue) {
|
|
|
955
968
|
return undefined;
|
|
956
969
|
}
|
|
957
970
|
}
|
|
958
|
-
function renderProvenanceRow(item, proposed, presentationAdapter) {
|
|
971
|
+
function renderProvenanceRow(item, proposed, presentationAdapter, scoreBlind) {
|
|
959
972
|
const excerpt = proposed.locator?.excerpt;
|
|
960
973
|
if (!excerpt) {
|
|
961
974
|
return `
|
|
@@ -968,7 +981,8 @@ function renderProvenanceRow(item, proposed, presentationAdapter) {
|
|
|
968
981
|
</div>
|
|
969
982
|
`;
|
|
970
983
|
}
|
|
971
|
-
|
|
984
|
+
// A score-blind session never renders the proposer's confidence.
|
|
985
|
+
const confidence = scoreBlind ? undefined : proposed.extraction.confidence ?? proposed.confidence;
|
|
972
986
|
const presentation = buildReviewCandidatePresentation(item, proposed, presentationAdapter);
|
|
973
987
|
const sourceLinkHtml = presentation.sourceLink
|
|
974
988
|
? `<a href="${escapeHtml(presentation.sourceLink.href)}">${escapeHtml(presentation.sourceLink.label ?? presentation.sourceText)}</a>`
|
|
@@ -1064,6 +1078,8 @@ function renderAuditDetails(item, current, proposed, session, presentationAdapte
|
|
|
1064
1078
|
selectedCandidateId: session.selectedCandidateIdsByItemName?.[item.metadata.name],
|
|
1065
1079
|
reviewedAt: session.reviewedAt,
|
|
1066
1080
|
actorId: session.actorId,
|
|
1081
|
+
...(session.presentation !== undefined ? { presentation: session.presentation } : {}),
|
|
1082
|
+
...(session.sampling !== undefined ? { sampling: session.sampling } : {}),
|
|
1067
1083
|
};
|
|
1068
1084
|
const reviewDecisionPayload = buildReviewDecision(state, { presentationAdapter });
|
|
1069
1085
|
const preview = buildSurfaceProjectionPreview(item, reviewDecisionPayload, presentationAdapter);
|
|
@@ -1348,10 +1364,14 @@ function createReviewWorkbenchController(root, startState, options) {
|
|
|
1348
1364
|
const attemptEvidenceIds = itemName && decision === "could-not-confirm"
|
|
1349
1365
|
? session.attemptEvidenceIdsByItemName?.[itemName]
|
|
1350
1366
|
: undefined;
|
|
1367
|
+
const conditions = sessionConditionsStamp(session);
|
|
1351
1368
|
const defaultData = decision
|
|
1352
|
-
?
|
|
1353
|
-
|
|
1354
|
-
|
|
1369
|
+
? {
|
|
1370
|
+
...(editedValue !== undefined
|
|
1371
|
+
? { workbenchDecision: decision, workbenchEditedValue: editedValue }
|
|
1372
|
+
: { workbenchDecision: decision, ...(attemptEvidenceIds?.length ? { attemptEvidenceIds } : {}) }),
|
|
1373
|
+
...(conditions ? { sessionConditions: conditions } : {}),
|
|
1374
|
+
}
|
|
1355
1375
|
: undefined;
|
|
1356
1376
|
return buildReviewSessionEvent(session, {
|
|
1357
1377
|
sessionName: defaultReviewSessionName,
|
|
@@ -1439,7 +1459,7 @@ function createReviewWorkbenchController(root, startState, options) {
|
|
|
1439
1459
|
currentSession: () => session,
|
|
1440
1460
|
currentSessionExport: () => buildReviewWorkbenchSessionExport(session, events, { presentationAdapter: options.presentationAdapter }),
|
|
1441
1461
|
presentationAdapter: options.presentationAdapter,
|
|
1442
|
-
renderCurrentState: () => renderCurrentState(root, session, controller, options.presentationAdapter, queueView, attestation),
|
|
1462
|
+
renderCurrentState: () => renderCurrentState(root, session, controller, options.presentationAdapter, queueView, attestation, options.fieldStates),
|
|
1443
1463
|
setDecision,
|
|
1444
1464
|
clearDecision,
|
|
1445
1465
|
updateReviewerNote,
|
|
@@ -1459,8 +1479,8 @@ function reviewSessionStorageKey(session, keyPrefix) {
|
|
|
1459
1479
|
const itemNames = buildReviewSessionResource(session).spec.reviewItemNames.join(",");
|
|
1460
1480
|
return `${keyPrefix}:${itemNames}`;
|
|
1461
1481
|
}
|
|
1462
|
-
function renderCurrentState(root, session, controller, presentationAdapter, queueView, attestation) {
|
|
1463
|
-
root.innerHTML = renderReviewQueueSessionHtml(session, presentationAdapter, queueView ?? normalizeReviewQueueView(undefined), attestation ?? attestReviewQueueExtraction(session.items, undefined));
|
|
1482
|
+
function renderCurrentState(root, session, controller, presentationAdapter, queueView, attestation, fieldStates) {
|
|
1483
|
+
root.innerHTML = renderReviewQueueSessionHtml(session, presentationAdapter, queueView ?? normalizeReviewQueueView(undefined), attestation ?? attestReviewQueueExtraction(session.items, undefined), fieldStates);
|
|
1464
1484
|
bindFieldCardInteractions(root, controller);
|
|
1465
1485
|
bindApplyButton(root, controller);
|
|
1466
1486
|
bindClampToggles(root);
|
|
@@ -1513,8 +1533,14 @@ function queueSessionFromStartState(startState) {
|
|
|
1513
1533
|
: {}),
|
|
1514
1534
|
reviewedAt: startState.reviewedAt,
|
|
1515
1535
|
actorId: startState.actorId,
|
|
1536
|
+
...(startState.presentation !== undefined ? { presentation: startState.presentation } : {}),
|
|
1537
|
+
...(startState.sampling !== undefined ? { sampling: startState.sampling } : {}),
|
|
1516
1538
|
};
|
|
1517
1539
|
}
|
|
1540
|
+
function scoreBlindNotice(session) {
|
|
1541
|
+
const audit = session.sampling?.kind === "random-audit" ? "Random audit sample. " : "";
|
|
1542
|
+
return `${audit}Score-blind review: confidence and verifier results are hidden for this whole session. Decide from the source and its excerpt.`;
|
|
1543
|
+
}
|
|
1518
1544
|
const COULD_NOT_CONFIRM_REASON_REQUIRED = "A reason is required when you could not confirm.";
|
|
1519
1545
|
/**
|
|
1520
1546
|
* Opens every collapsed region between `control` and the card before anything
|
|
@@ -91,31 +91,13 @@
|
|
|
91
91
|
.queue-result-count { margin-left: auto; color: var(--k-muted); }
|
|
92
92
|
.queue-empty { padding: 2rem; color: var(--k-muted); text-align: center; }
|
|
93
93
|
|
|
94
|
-
/*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
the light values here (loaded after themes.css) wins that tie-break. Presets
|
|
100
|
-
still control --k-brand (and theme-console's fuller palette) via their own,
|
|
101
|
-
more specific `[data-theme="light"].theme-*` rules in themes.css. */
|
|
94
|
+
/* Light-mode token values come from @kontourai/ui (tokens.css plus each
|
|
95
|
+
theme's light block, which since 1.17 re-sets every mode-dependent value
|
|
96
|
+
its dark preset sets). Only color-scheme is re-asserted here: the :root rule
|
|
97
|
+
above sets it to dark and would otherwise win the source-order tie with
|
|
98
|
+
tokens.css's [data-theme="light"], leaving native controls dark. */
|
|
102
99
|
[data-theme="light"] {
|
|
103
100
|
color-scheme: light;
|
|
104
|
-
--k-bg: #f5f4ef;
|
|
105
|
-
--k-panel: #ffffff;
|
|
106
|
-
--k-panel-raised: #fbfaf7;
|
|
107
|
-
--k-line: rgba(36, 40, 46, 0.12);
|
|
108
|
-
--k-line-strong: rgba(36, 40, 46, 0.20);
|
|
109
|
-
--k-shadow: 0 18px 50px -30px rgba(0, 0, 0, 0.25);
|
|
110
|
-
--k-text: #202124;
|
|
111
|
-
--k-text-muted: #5b626b;
|
|
112
|
-
--k-text-faint: #707782;
|
|
113
|
-
--k-brand-contrast: #ffffff;
|
|
114
|
-
--k-positive: #168257;
|
|
115
|
-
--k-caution: #8a5a00;
|
|
116
|
-
--k-negative: #c83b3b;
|
|
117
|
-
--k-neutral: #5f6975;
|
|
118
|
-
--k-active: #3f6fd6;
|
|
119
101
|
}
|
|
120
102
|
|
|
121
103
|
* {
|
|
@@ -409,6 +391,30 @@ h1, h2, h3, p {
|
|
|
409
391
|
.chip.rejected { color: var(--k-negative); background: var(--k-negative-wash); }
|
|
410
392
|
.chip.could-not-confirm { color: var(--k-caution); background: var(--k-caution-wash); }
|
|
411
393
|
|
|
394
|
+
/* Field-state panel: one row per field, content and lifecycle derived from
|
|
395
|
+
the records, never stored labels. A field that was not read is listed. */
|
|
396
|
+
.field-states { padding: var(--k-space-3, 12px) 1rem; border-bottom: 1px solid var(--k-line); background: var(--k-panel); }
|
|
397
|
+
.field-states h2 { margin: 0 0 8px; font-size: 13px; font-weight: 700; color: var(--k-text); }
|
|
398
|
+
.field-states ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
|
|
399
|
+
.field-states-lead { margin: 0 0 8px; font-size: 13px; line-height: 1.45; color: var(--k-caution); }
|
|
400
|
+
.field-state-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px; align-items: center; padding: 6px 0; border-top: 1px solid var(--k-line); }
|
|
401
|
+
.field-state-row:first-child { border-top: 0; }
|
|
402
|
+
.field-state-name { font-family: var(--k-font-mono, ui-monospace, monospace); font-size: 12px; color: var(--k-text); overflow-wrap: anywhere; }
|
|
403
|
+
.field-state-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
|
|
404
|
+
.field-state-note { grid-column: 1 / -1; font-size: 12px; color: var(--k-muted); }
|
|
405
|
+
.field-state-note.signal { color: var(--k-caution); }
|
|
406
|
+
.chip.state-content.value { color: var(--k-positive); background: var(--k-positive-wash); }
|
|
407
|
+
.chip.state-content.conflicting,
|
|
408
|
+
.chip.state-content.excluded,
|
|
409
|
+
.chip.state-content.not_covered { color: var(--k-caution); background: var(--k-caution-wash); }
|
|
410
|
+
.chip.state-content.unsupported { color: var(--k-negative); background: var(--k-negative-wash); }
|
|
411
|
+
.chip.state-content.none,
|
|
412
|
+
.chip.state-lifecycle.superseded { color: var(--k-muted); background: var(--k-sunken); }
|
|
413
|
+
.chip.state-lifecycle.pending { color: var(--k-brand); background: var(--k-brand-wash); }
|
|
414
|
+
.chip.state-lifecycle.accepted { color: var(--k-positive); background: var(--k-positive-wash); }
|
|
415
|
+
.chip.state-lifecycle.rejected { color: var(--k-negative); background: var(--k-negative-wash); }
|
|
416
|
+
.chip.state-lifecycle.could_not_confirm { color: var(--k-caution); background: var(--k-caution-wash); }
|
|
417
|
+
|
|
412
418
|
.frow1 .push {
|
|
413
419
|
margin-left: auto;
|
|
414
420
|
}
|
|
@@ -653,6 +659,12 @@ h1, h2, h3, p {
|
|
|
653
659
|
background: var(--k-negative-wash);
|
|
654
660
|
}
|
|
655
661
|
|
|
662
|
+
/* A score-blind session: an informational note, not a warning. */
|
|
663
|
+
.queue-attestation.score-blind {
|
|
664
|
+
color: var(--k-active);
|
|
665
|
+
background: var(--k-active-soft);
|
|
666
|
+
}
|
|
667
|
+
|
|
656
668
|
/* Conflicting proposed values: each value is listed with its own choice
|
|
657
669
|
control until the field is decided, then marked chosen or not chosen. */
|
|
658
670
|
.conflict-values ul {
|
|
@@ -1213,6 +1225,8 @@ pre[data-testid="decision-payload"] {
|
|
|
1213
1225
|
/* The diff-collapse must also hold in host contexts without a query container
|
|
1214
1226
|
(e.g. an embed that doesn't establish `container-type`): fall back to viewport. */
|
|
1215
1227
|
@media (max-width: 620px) {
|
|
1228
|
+
.field-state-row { grid-template-columns: minmax(0, 1fr); }
|
|
1229
|
+
.field-state-chips { justify-content: flex-start; }
|
|
1216
1230
|
.diff {
|
|
1217
1231
|
grid-template-columns: 1fr;
|
|
1218
1232
|
}
|
|
@@ -1,7 +1,39 @@
|
|
|
1
|
+
/* Product themes. Each theme sets its brand (identity) and, beside it, the
|
|
2
|
+
action and focus roles. The roles hold their own values rather than aliasing
|
|
3
|
+
the brand, so a later brand override cannot break primary actions or focus.
|
|
4
|
+
Every scope that sets --k-action also sets --k-action-contrast: a theme
|
|
5
|
+
nested under another theme's scope would otherwise inherit that scope's text
|
|
6
|
+
color onto its own fill.
|
|
7
|
+
|
|
8
|
+
Every scope that sets --k-focus also redeclares --k-focus-ring, and every
|
|
9
|
+
scope that sets a status hue redeclares its soft fill: a var()-derived token
|
|
10
|
+
resolves where it is declared, so without this a scope below the root would
|
|
11
|
+
inherit the root's value (issue 78). check:tokens enforces both.
|
|
12
|
+
|
|
13
|
+
Each light block matches three placements of the theme class and
|
|
14
|
+
data-theme="light": on one element, the theme class below the light
|
|
15
|
+
attribute, and the light attribute below the theme class. The last is
|
|
16
|
+
wrapped in :where() so it weighs the same as the bare [data-theme="light"]
|
|
17
|
+
block in tokens.css (which it follows, so it wins there) and less than a
|
|
18
|
+
theme class sitting on the light element itself: a nested theme keeps its
|
|
19
|
+
own identity.
|
|
20
|
+
|
|
21
|
+
Scoping rule (ui#80, ui#84; DESIGN.md "Theme and mode scoping"): the
|
|
22
|
+
nearest data-theme picks the mode and the nearest theme class picks the
|
|
23
|
+
product. The :where(:not(...)) tails enforce "nearest" without changing a
|
|
24
|
+
selector's weight: the class-below-attribute form stops at a nearer
|
|
25
|
+
data-theme="dark", and the attribute-below-class form stops at a nearer
|
|
26
|
+
(or its own) other theme class. Weights are unchanged, so an override
|
|
27
|
+
declared later with these selectors still wins. */
|
|
28
|
+
|
|
1
29
|
.theme-survey {
|
|
2
30
|
--k-bg: #06080b;
|
|
3
31
|
--k-brand: #5ce0c6;
|
|
4
|
-
--k-
|
|
32
|
+
--k-action: #5ce0c6;
|
|
33
|
+
--k-action-contrast: #06080b;
|
|
34
|
+
--k-focus: #5ce0c6;
|
|
35
|
+
--k-text-faint: #78889b;
|
|
36
|
+
--k-focus-ring: var(--k-focus);
|
|
5
37
|
}
|
|
6
38
|
|
|
7
39
|
.theme-console {
|
|
@@ -13,6 +45,9 @@
|
|
|
13
45
|
--k-radius-overlay: 0;
|
|
14
46
|
--k-brand: #c9ff4a;
|
|
15
47
|
--k-brand-contrast: #11120f;
|
|
48
|
+
--k-action: #c9ff4a;
|
|
49
|
+
--k-action-contrast: #11120f;
|
|
50
|
+
--k-focus: #c9ff4a;
|
|
16
51
|
--k-bg: #11120f;
|
|
17
52
|
--k-panel: #191b16;
|
|
18
53
|
--k-panel-raised: #20231e;
|
|
@@ -20,30 +55,81 @@
|
|
|
20
55
|
--k-line-strong: #8ea36e;
|
|
21
56
|
--k-text: #f2f0e8;
|
|
22
57
|
--k-text-muted: #a6ab9c;
|
|
23
|
-
--k-text-faint: #
|
|
58
|
+
--k-text-faint: #858b7e;
|
|
24
59
|
--k-positive: #a6d37b;
|
|
25
60
|
--k-caution: #e8c15f;
|
|
26
61
|
--k-negative: #ee776f;
|
|
27
62
|
--k-active: #84d8c8;
|
|
63
|
+
--k-status-contrast: #11120f;
|
|
64
|
+
--k-focus-ring: var(--k-focus);
|
|
65
|
+
--k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
|
|
66
|
+
--k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
|
|
67
|
+
--k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
|
|
68
|
+
--k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
|
|
28
69
|
}
|
|
29
70
|
|
|
71
|
+
/* Flow dark: the brand is the accent lightened (OKLCH hue within 0.3°, same chroma)
|
|
72
|
+
just enough to read as text at 4.5:1 on bg, panel and raised panel; the
|
|
73
|
+
action and focus keep the accent, which clears their 3:1 and dark-text
|
|
74
|
+
pairs. */
|
|
30
75
|
.theme-flow {
|
|
31
|
-
--k-brand: #
|
|
76
|
+
--k-brand: #3890ae;
|
|
77
|
+
--k-action: #2f88a6;
|
|
78
|
+
--k-action-contrast: #06080b;
|
|
79
|
+
--k-focus: #2f88a6;
|
|
80
|
+
--k-focus-ring: var(--k-focus);
|
|
32
81
|
}
|
|
33
82
|
|
|
34
83
|
.theme-surface {
|
|
35
84
|
--k-brand: #14a37a;
|
|
85
|
+
--k-action: #14a37a;
|
|
86
|
+
--k-action-contrast: #06080b;
|
|
87
|
+
--k-focus: #14a37a;
|
|
88
|
+
--k-focus-ring: var(--k-focus);
|
|
36
89
|
}
|
|
37
90
|
|
|
91
|
+
/* Station: the draft accent, adjusted per mode only where WCAG AA needs it.
|
|
92
|
+
Dark: the accent carries white text as a fill but is too dark to read as
|
|
93
|
+
text on the dark panel, so the focus ring is the accent lightened to clear
|
|
94
|
+
contrast, and the brand is lightened a little further (OKLCH hue within 0.1°) to
|
|
95
|
+
read as text at 4.5:1 on bg, panel and raised panel; the action keeps the
|
|
96
|
+
draft accent. */
|
|
97
|
+
.theme-station {
|
|
98
|
+
--k-brand: #966aff;
|
|
99
|
+
--k-action: #7c3aed;
|
|
100
|
+
--k-action-contrast: #ffffff;
|
|
101
|
+
--k-focus: #9364ff;
|
|
102
|
+
--k-focus-ring: var(--k-focus);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Survey light: the brand is the accent darkened (same OKLCH hue and chroma) just enough
|
|
106
|
+
to read as text at 4.5:1 on the light canvas; the action and focus keep the
|
|
107
|
+
accent. */
|
|
38
108
|
[data-theme="light"].theme-survey,
|
|
39
|
-
[data-theme="light"] .theme-survey
|
|
40
|
-
|
|
109
|
+
[data-theme="light"] .theme-survey:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
|
|
110
|
+
:where(.theme-survey) [data-theme="light"]:where(:not(.theme-console, .theme-flow, .theme-surface, .theme-station, .theme-survey :is(.theme-console, .theme-flow, .theme-surface, .theme-station) *)) {
|
|
111
|
+
--k-bg: #f5f4ef;
|
|
112
|
+
--k-text-faint: #6a707b;
|
|
113
|
+
--k-brand: #107e6d;
|
|
114
|
+
--k-action: #16806f;
|
|
115
|
+
--k-action-contrast: #ffffff;
|
|
116
|
+
--k-focus: #16806f;
|
|
117
|
+
--k-focus-ring: var(--k-focus);
|
|
41
118
|
}
|
|
42
119
|
|
|
120
|
+
/* Console light: the brand is the lime darkened (OKLCH hue within 0.1°) just enough
|
|
121
|
+
to read as text at 4.5:1 on bg, panel and raised panel, and it keeps white
|
|
122
|
+
brand-contrast text (5.1:1; dark text would be 3.7:1). The action and focus
|
|
123
|
+
keep the brighter lime with dark text. */
|
|
43
124
|
[data-theme="light"].theme-console,
|
|
44
|
-
[data-theme="light"] .theme-console
|
|
45
|
-
|
|
125
|
+
[data-theme="light"] .theme-console:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
|
|
126
|
+
:where(.theme-console) [data-theme="light"]:where(:not(.theme-survey, .theme-flow, .theme-surface, .theme-station, .theme-console :is(.theme-survey, .theme-flow, .theme-surface, .theme-station) *)) {
|
|
127
|
+
--k-brand: #577800;
|
|
46
128
|
--k-brand-contrast: #ffffff;
|
|
129
|
+
--k-action: #6c9400;
|
|
130
|
+
--k-action-contrast: #11120f;
|
|
131
|
+
--k-focus: #6c9400;
|
|
132
|
+
--k-focus-ring: var(--k-focus);
|
|
47
133
|
--k-bg: #f3f5eb;
|
|
48
134
|
--k-panel: #fbfcf7;
|
|
49
135
|
--k-panel-raised: #eef2e6;
|
|
@@ -51,19 +137,112 @@
|
|
|
51
137
|
--k-line-strong: #8fa36f;
|
|
52
138
|
--k-text: #1e2319;
|
|
53
139
|
--k-text-muted: #596250;
|
|
54
|
-
--k-text-faint: #
|
|
140
|
+
--k-text-faint: #68705f;
|
|
55
141
|
--k-positive: #2f7d32;
|
|
56
142
|
--k-caution: #8a6500;
|
|
57
143
|
--k-negative: #b93a36;
|
|
58
144
|
--k-active: #247f75;
|
|
145
|
+
--k-status-contrast: #ffffff;
|
|
146
|
+
--k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
|
|
147
|
+
--k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
|
|
148
|
+
--k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
|
|
149
|
+
--k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
|
|
59
150
|
}
|
|
60
151
|
|
|
61
152
|
[data-theme="light"].theme-flow,
|
|
62
|
-
[data-theme="light"] .theme-flow
|
|
153
|
+
[data-theme="light"] .theme-flow:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
|
|
154
|
+
:where(.theme-flow) [data-theme="light"]:where(:not(.theme-survey, .theme-console, .theme-surface, .theme-station, .theme-flow :is(.theme-survey, .theme-console, .theme-surface, .theme-station) *)) {
|
|
63
155
|
--k-brand: #1f6f88;
|
|
156
|
+
--k-action: #1f6f88;
|
|
157
|
+
--k-action-contrast: #ffffff;
|
|
158
|
+
--k-focus: #1f6f88;
|
|
159
|
+
--k-focus-ring: var(--k-focus);
|
|
64
160
|
}
|
|
65
161
|
|
|
66
162
|
[data-theme="light"].theme-surface,
|
|
67
|
-
[data-theme="light"] .theme-surface
|
|
163
|
+
[data-theme="light"] .theme-surface:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
|
|
164
|
+
:where(.theme-surface) [data-theme="light"]:where(:not(.theme-survey, .theme-console, .theme-flow, .theme-station, .theme-surface :is(.theme-survey, .theme-console, .theme-flow, .theme-station) *)) {
|
|
68
165
|
--k-brand: #0f6b52;
|
|
166
|
+
--k-action: #0f6b52;
|
|
167
|
+
--k-action-contrast: #ffffff;
|
|
168
|
+
--k-focus: #0f6b52;
|
|
169
|
+
--k-focus-ring: var(--k-focus);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
[data-theme="light"].theme-station,
|
|
173
|
+
[data-theme="light"] .theme-station:where(:not([data-theme="dark"], [data-theme="light"] [data-theme="dark"] *)),
|
|
174
|
+
:where(.theme-station) [data-theme="light"]:where(:not(.theme-survey, .theme-console, .theme-flow, .theme-surface, .theme-station :is(.theme-survey, .theme-console, .theme-flow, .theme-surface) *)) {
|
|
175
|
+
--k-brand: #7c3aed;
|
|
176
|
+
--k-action: #7c3aed;
|
|
177
|
+
--k-action-contrast: #ffffff;
|
|
178
|
+
--k-focus: #7c3aed;
|
|
179
|
+
--k-focus-ring: var(--k-focus);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
/* Dark islands (ui#80): a data-theme="dark" element below a light element
|
|
184
|
+
takes its nearest theme's dark values. tokens.css resets it to the default
|
|
185
|
+
dark contract first; each block below repeats its theme's mode-dependent
|
|
186
|
+
base values (check:contrast fails if one drifts) and, like the light
|
|
187
|
+
blocks, yields to a nearer theme class. Without a light ancestor nothing
|
|
188
|
+
needs resetting, so these blocks do not match and the element inherits. */
|
|
189
|
+
|
|
190
|
+
[data-theme="dark"]:where(.theme-survey *):where([data-theme="light"] *):where(:not(.theme-console, .theme-flow, .theme-surface, .theme-station, .theme-survey :is(.theme-console, .theme-flow, .theme-surface, .theme-station) *)) {
|
|
191
|
+
--k-bg: #06080b;
|
|
192
|
+
--k-brand: #5ce0c6;
|
|
193
|
+
--k-action: #5ce0c6;
|
|
194
|
+
--k-action-contrast: #06080b;
|
|
195
|
+
--k-focus: #5ce0c6;
|
|
196
|
+
--k-text-faint: #78889b;
|
|
197
|
+
--k-focus-ring: var(--k-focus);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
[data-theme="dark"]:where(.theme-console *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-flow, .theme-surface, .theme-station, .theme-console :is(.theme-survey, .theme-flow, .theme-surface, .theme-station) *)) {
|
|
201
|
+
--k-brand: #c9ff4a;
|
|
202
|
+
--k-brand-contrast: #11120f;
|
|
203
|
+
--k-action: #c9ff4a;
|
|
204
|
+
--k-action-contrast: #11120f;
|
|
205
|
+
--k-focus: #c9ff4a;
|
|
206
|
+
--k-bg: #11120f;
|
|
207
|
+
--k-panel: #191b16;
|
|
208
|
+
--k-panel-raised: #20231e;
|
|
209
|
+
--k-line: #3a4035;
|
|
210
|
+
--k-line-strong: #8ea36e;
|
|
211
|
+
--k-text: #f2f0e8;
|
|
212
|
+
--k-text-muted: #a6ab9c;
|
|
213
|
+
--k-text-faint: #858b7e;
|
|
214
|
+
--k-positive: #a6d37b;
|
|
215
|
+
--k-caution: #e8c15f;
|
|
216
|
+
--k-negative: #ee776f;
|
|
217
|
+
--k-active: #84d8c8;
|
|
218
|
+
--k-status-contrast: #11120f;
|
|
219
|
+
--k-focus-ring: var(--k-focus);
|
|
220
|
+
--k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
|
|
221
|
+
--k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
|
|
222
|
+
--k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
|
|
223
|
+
--k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
[data-theme="dark"]:where(.theme-flow *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-console, .theme-surface, .theme-station, .theme-flow :is(.theme-survey, .theme-console, .theme-surface, .theme-station) *)) {
|
|
227
|
+
--k-brand: #3890ae;
|
|
228
|
+
--k-action: #2f88a6;
|
|
229
|
+
--k-action-contrast: #06080b;
|
|
230
|
+
--k-focus: #2f88a6;
|
|
231
|
+
--k-focus-ring: var(--k-focus);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
[data-theme="dark"]:where(.theme-surface *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-console, .theme-flow, .theme-station, .theme-surface :is(.theme-survey, .theme-console, .theme-flow, .theme-station) *)) {
|
|
235
|
+
--k-brand: #14a37a;
|
|
236
|
+
--k-action: #14a37a;
|
|
237
|
+
--k-action-contrast: #06080b;
|
|
238
|
+
--k-focus: #14a37a;
|
|
239
|
+
--k-focus-ring: var(--k-focus);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
[data-theme="dark"]:where(.theme-station *):where([data-theme="light"] *):where(:not(.theme-survey, .theme-console, .theme-flow, .theme-surface, .theme-station :is(.theme-survey, .theme-console, .theme-flow, .theme-surface) *)) {
|
|
243
|
+
--k-brand: #966aff;
|
|
244
|
+
--k-action: #7c3aed;
|
|
245
|
+
--k-action-contrast: #ffffff;
|
|
246
|
+
--k-focus: #9364ff;
|
|
247
|
+
--k-focus-ring: var(--k-focus);
|
|
69
248
|
}
|