@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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { isScoreBlind } from "./review-queue-session.js";
|
|
2
|
+
const CONTENT_TEXT = {
|
|
3
|
+
value: "Value found",
|
|
4
|
+
conflicting: "Conflicting values",
|
|
5
|
+
unsupported: "Not supported by verifier",
|
|
6
|
+
excluded: "Proposals excluded",
|
|
7
|
+
not_covered: "Not read",
|
|
8
|
+
};
|
|
9
|
+
const LIFECYCLE_TEXT = {
|
|
10
|
+
pending: "Needs review",
|
|
11
|
+
accepted: "Accepted",
|
|
12
|
+
rejected: "Rejected",
|
|
13
|
+
could_not_confirm: "Could not confirm",
|
|
14
|
+
superseded: "Superseded",
|
|
15
|
+
};
|
|
16
|
+
const WORKBENCH_LIFECYCLE = {
|
|
17
|
+
"accept-proposed": "accepted",
|
|
18
|
+
"select-proposed": "accepted",
|
|
19
|
+
"keep-current": "accepted",
|
|
20
|
+
"reject-proposed": "rejected",
|
|
21
|
+
"could-not-confirm": "could_not_confirm",
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The field-state panel: one row per field the extraction was asked for or
|
|
25
|
+
* proposed, with its derived content state, its lifecycle, and its signals.
|
|
26
|
+
* Fields with no review card (not read, only excluded proposals, no proposal)
|
|
27
|
+
* are listed too, so a run that lost content never reads as complete.
|
|
28
|
+
*
|
|
29
|
+
* The lifecycle of a field in the queue is read from the live session, so it
|
|
30
|
+
* follows the reviewer's decisions. Only what the session cannot know comes
|
|
31
|
+
* from the derived states: a decision carried forward from an earlier round,
|
|
32
|
+
* and a superseded one. In a score-blind session a content state derived from
|
|
33
|
+
* verifier records is shown as the state its candidates alone give.
|
|
34
|
+
*/
|
|
35
|
+
export function renderFieldStatesHtml(states, session) {
|
|
36
|
+
if (states.length === 0)
|
|
37
|
+
return "";
|
|
38
|
+
const itemsByName = new Map(session.items.map((item) => [item.metadata.name, item]));
|
|
39
|
+
const rows = states.map((state) => {
|
|
40
|
+
const item = state.reviewItemName !== undefined ? itemsByName.get(state.reviewItemName) : undefined;
|
|
41
|
+
const content = state.content === "unsupported" && isScoreBlind(session)
|
|
42
|
+
? (item && item.spec.candidates.filter((candidate) => candidate.role === "proposed").length > 1 ? "conflicting" : "value")
|
|
43
|
+
: state.content;
|
|
44
|
+
const live = item ? session.decisionsByItemName[item.metadata.name] : undefined;
|
|
45
|
+
const lifecycle = live !== undefined
|
|
46
|
+
? WORKBENCH_LIFECYCLE[live]
|
|
47
|
+
: state.lifecycle === "superseded" || state.decisionBasis === "carried-forward"
|
|
48
|
+
? state.lifecycle
|
|
49
|
+
: state.reviewItemName !== undefined ? "pending" : undefined;
|
|
50
|
+
const carried = live === undefined && state.decisionBasis === "carried-forward";
|
|
51
|
+
const signals = [
|
|
52
|
+
...(state.signals.incompleteRun ? ["The extraction stopped before reading all of the source."] : []),
|
|
53
|
+
...(state.signals.excludedProposals ? [`${state.signals.excludedProposals} proposal${state.signals.excludedProposals === 1 ? " was" : "s were"} excluded: the excerpt is not at the cited span.`] : []),
|
|
54
|
+
...(state.signals.unresolvedImport ? ["The import could not ground any value."] : []),
|
|
55
|
+
];
|
|
56
|
+
const label = `${state.slot.fieldOrBehavior}${state.slot.pathIndices?.length ? `[${state.slot.pathIndices.join("][")}]` : ""}`;
|
|
57
|
+
return `
|
|
58
|
+
<li class="field-state-row" data-testid="field-state" data-field="${escapeHtml(label)}" data-content="${content ?? "none"}" data-lifecycle="${lifecycle ?? "none"}"${carried ? ' data-basis="carried-forward"' : ""}>
|
|
59
|
+
<span class="field-state-name">${escapeHtml(label)}</span>
|
|
60
|
+
<span class="field-state-chips">
|
|
61
|
+
<span class="chip state-content ${content ?? "none"}" data-testid="field-state-content">${escapeHtml(content === undefined ? "No proposal" : CONTENT_TEXT[content])}</span>
|
|
62
|
+
${lifecycle === undefined ? "" : `<span class="chip state-lifecycle ${lifecycle}" data-testid="field-state-lifecycle">${escapeHtml(LIFECYCLE_TEXT[lifecycle])}${carried ? " · carried forward" : ""}</span>`}
|
|
63
|
+
</span>
|
|
64
|
+
${content === undefined ? `<span class="field-state-note">No value was proposed. That is not the same as the source not stating one.</span>` : ""}
|
|
65
|
+
${signals.map((signal) => `<span class="field-state-note signal" data-testid="field-state-signal">${escapeHtml(signal)}</span>`).join("")}
|
|
66
|
+
</li>`;
|
|
67
|
+
}).join("");
|
|
68
|
+
const unread = states.filter((state) => state.content === "not_covered").length;
|
|
69
|
+
return `
|
|
70
|
+
<section class="field-states" data-testid="field-states" aria-label="Field states">
|
|
71
|
+
<h2>Field states</h2>
|
|
72
|
+
${unread > 0 ? `<p class="field-states-lead" data-testid="field-states-incomplete">${unread} field${unread === 1 ? " was" : "s were"} not read: the extraction stopped short. ${unread === 1 ? "It has" : "They have"} no value to review, and that does not mean the source lacks ${unread === 1 ? "it" : "them"}.</p>` : ""}
|
|
73
|
+
<ul>${rows}</ul>
|
|
74
|
+
</section>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
function escapeHtml(value) {
|
|
78
|
+
return String(value)
|
|
79
|
+
.replaceAll("&", "&")
|
|
80
|
+
.replaceAll("<", "<")
|
|
81
|
+
.replaceAll(">", ">")
|
|
82
|
+
.replaceAll('"', """)
|
|
83
|
+
.replaceAll("'", "'");
|
|
84
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type ReviewCandidate, type ReviewItem, type ReviewSession, type ReviewSessionEvent, type ReviewSessionEventSpec } from "../../src/review-resource.js";
|
|
1
|
+
import { type ReviewCandidate, type ReviewItem, type ReviewSession, type ReviewSessionEvent, type ReviewSessionEventSpec, type ReviewSessionPresentation, type ReviewSessionSampling } from "../../src/review-resource.js";
|
|
2
|
+
export type { ReviewSessionPresentation, ReviewSessionSampling } from "../../src/review-resource.js";
|
|
2
3
|
/**
|
|
3
4
|
* The decisions a reviewer can record on a ReviewItem.
|
|
4
5
|
*
|
|
@@ -31,6 +32,10 @@ export interface ReviewWorkbenchState {
|
|
|
31
32
|
* decision and ignored for every other one.
|
|
32
33
|
*/
|
|
33
34
|
readonly selectedCandidateId?: string;
|
|
35
|
+
/** The session's presentation; recorded on the decision. See {@link ReviewSessionPresentation}. */
|
|
36
|
+
readonly presentation?: ReviewSessionPresentation;
|
|
37
|
+
/** How the session's items were chosen; recorded on the decision. */
|
|
38
|
+
readonly sampling?: ReviewSessionSampling;
|
|
34
39
|
}
|
|
35
40
|
export interface ReviewQueueSessionState {
|
|
36
41
|
readonly items: readonly ReviewItem[];
|
|
@@ -53,7 +58,47 @@ export interface ReviewQueueSessionState {
|
|
|
53
58
|
* `select-proposed` decision and never read it.
|
|
54
59
|
*/
|
|
55
60
|
readonly selectedCandidateIdsByItemName?: Readonly<Record<string, string>>;
|
|
61
|
+
/**
|
|
62
|
+
* Session-level presentation, set when the session is opened. Every
|
|
63
|
+
* decision event is stamped with it and replay refuses a mismatch; a
|
|
64
|
+
* `ReviewQueueBinding` also covers it. Without a binding, an edit to both the
|
|
65
|
+
* snapshot and every event stamp is not detectable. Absent: scores are shown
|
|
66
|
+
* and decisions record no presentation.
|
|
67
|
+
*/
|
|
68
|
+
readonly presentation?: ReviewSessionPresentation;
|
|
69
|
+
/** How the session's items were chosen. Absent: decisions record no sampling. */
|
|
70
|
+
readonly sampling?: ReviewSessionSampling;
|
|
56
71
|
}
|
|
72
|
+
/** Whether the session hides confidence and verifier results from its reviewer. */
|
|
73
|
+
export declare function isScoreBlind(session: {
|
|
74
|
+
readonly presentation?: ReviewSessionPresentation;
|
|
75
|
+
}): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Throws unless the session's presentation and sampling are well formed:
|
|
78
|
+
* `scoreBlind` a boolean, and a `random-audit` sampling with a rate in (0, 1]
|
|
79
|
+
* and a non-empty seed. Unknown keys are refused so a decision never records
|
|
80
|
+
* a condition nothing reads.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertReviewSessionConditions(session: {
|
|
83
|
+
readonly presentation?: unknown;
|
|
84
|
+
readonly sampling?: unknown;
|
|
85
|
+
}): void;
|
|
86
|
+
/**
|
|
87
|
+
* The session conditions a decision event carries, or `undefined` for a
|
|
88
|
+
* session that declares none. Stamped on every decision event so replay can
|
|
89
|
+
* refuse events recorded under other conditions than the snapshot's: sighted
|
|
90
|
+
* events replayed over a blind snapshot, or a snapshot whose presentation was
|
|
91
|
+
* changed after decisions were recorded.
|
|
92
|
+
*/
|
|
93
|
+
export declare function sessionConditionsStamp(session: {
|
|
94
|
+
readonly presentation?: ReviewSessionPresentation;
|
|
95
|
+
readonly sampling?: ReviewSessionSampling;
|
|
96
|
+
}): {
|
|
97
|
+
presentation?: ReviewSessionPresentation;
|
|
98
|
+
sampling?: ReviewSessionSampling;
|
|
99
|
+
} | undefined;
|
|
100
|
+
export declare function assertAuditRate(rate: unknown): asserts rate is number;
|
|
101
|
+
export declare function assertAuditSeed(seed: unknown): asserts seed is string;
|
|
57
102
|
export interface ReviewSessionSummary {
|
|
58
103
|
readonly accepted: number;
|
|
59
104
|
readonly keptCurrent: number;
|
|
@@ -4,6 +4,71 @@ import { checkEditedValueForItem } from "./edited-value.js";
|
|
|
4
4
|
import { assertSoleCandidateId, reviewResourceApiVersion, } from "../../src/review-resource.js";
|
|
5
5
|
export const reviewWorkbenchSessionStorageKey = "kontourai.survey.review-workbench.session-events.v1";
|
|
6
6
|
export const defaultReviewSessionName = "review-workbench-session";
|
|
7
|
+
/** Whether the session hides confidence and verifier results from its reviewer. */
|
|
8
|
+
export function isScoreBlind(session) {
|
|
9
|
+
return session.presentation?.scoreBlind === true;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Throws unless the session's presentation and sampling are well formed:
|
|
13
|
+
* `scoreBlind` a boolean, and a `random-audit` sampling with a rate in (0, 1]
|
|
14
|
+
* and a non-empty seed. Unknown keys are refused so a decision never records
|
|
15
|
+
* a condition nothing reads.
|
|
16
|
+
*/
|
|
17
|
+
export function assertReviewSessionConditions(session) {
|
|
18
|
+
const { presentation, sampling } = session;
|
|
19
|
+
if (presentation !== undefined) {
|
|
20
|
+
if (!isPlainRecord(presentation) || Object.keys(presentation).join(",") !== "scoreBlind" || typeof presentation.scoreBlind !== "boolean") {
|
|
21
|
+
throw new Error("Review session presentation must be { scoreBlind: boolean }.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (sampling !== undefined) {
|
|
25
|
+
if (!isPlainRecord(sampling))
|
|
26
|
+
throw new Error("Review session sampling must be an object.");
|
|
27
|
+
const keys = Object.keys(sampling).sort().join(",");
|
|
28
|
+
if (sampling.kind === "queue") {
|
|
29
|
+
if (keys !== "kind")
|
|
30
|
+
throw new Error("Review session sampling of kind queue takes no rate or seed.");
|
|
31
|
+
}
|
|
32
|
+
else if (sampling.kind === "random-audit") {
|
|
33
|
+
if (keys !== "kind,rate,seed")
|
|
34
|
+
throw new Error("Review session sampling of kind random-audit requires exactly rate and seed.");
|
|
35
|
+
assertAuditRate(sampling.rate);
|
|
36
|
+
assertAuditSeed(sampling.seed);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
throw new Error("Review session sampling kind must be queue or random-audit.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The session conditions a decision event carries, or `undefined` for a
|
|
45
|
+
* session that declares none. Stamped on every decision event so replay can
|
|
46
|
+
* refuse events recorded under other conditions than the snapshot's: sighted
|
|
47
|
+
* events replayed over a blind snapshot, or a snapshot whose presentation was
|
|
48
|
+
* changed after decisions were recorded.
|
|
49
|
+
*/
|
|
50
|
+
export function sessionConditionsStamp(session) {
|
|
51
|
+
if (session.presentation === undefined && session.sampling === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
return {
|
|
54
|
+
...(session.presentation !== undefined ? { presentation: { scoreBlind: session.presentation.scoreBlind } } : {}),
|
|
55
|
+
...(session.sampling !== undefined ? { sampling: { ...session.sampling } } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function assertAuditRate(rate) {
|
|
59
|
+
if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0 || rate > 1) {
|
|
60
|
+
throw new Error("A random-audit rate must be a number greater than 0 and at most 1.");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function assertAuditSeed(seed) {
|
|
64
|
+
if (typeof seed !== "string" || !AUDIT_SEED.test(seed)) {
|
|
65
|
+
throw new Error("A random-audit seed must be a stable identity: 1-256 characters from letters, digits and . _ : @ / + ~ -, starting with a letter or digit.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const AUDIT_SEED = /^[A-Za-z0-9][A-Za-z0-9._:@/+~-]{0,255}$/;
|
|
69
|
+
function isPlainRecord(value) {
|
|
70
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
71
|
+
}
|
|
7
72
|
export const workbenchDecisionDefinitions = {
|
|
8
73
|
"accept-proposed": {
|
|
9
74
|
label: "Accept proposed",
|
|
@@ -67,6 +132,8 @@ export function currentReviewWorkbenchState(session) {
|
|
|
67
132
|
selectedCandidateId: session.selectedCandidateIdsByItemName?.[item.metadata.name],
|
|
68
133
|
reviewedAt: session.reviewedAt,
|
|
69
134
|
actorId: session.actorId,
|
|
135
|
+
...(session.presentation !== undefined ? { presentation: session.presentation } : {}),
|
|
136
|
+
...(session.sampling !== undefined ? { sampling: session.sampling } : {}),
|
|
70
137
|
};
|
|
71
138
|
}
|
|
72
139
|
export function currentReviewItem(session) {
|
|
@@ -360,9 +427,13 @@ export function buildReviewSessionEvents(session, sessionName = defaultReviewSes
|
|
|
360
427
|
actor: session.actorId,
|
|
361
428
|
reviewedAt: session.reviewedAt,
|
|
362
429
|
});
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
430
|
+
const conditions = sessionConditionsStamp(session);
|
|
431
|
+
const data = {
|
|
432
|
+
...(editedValue !== undefined
|
|
433
|
+
? { workbenchDecision: decision, workbenchEditedValue: editedValue }
|
|
434
|
+
: { workbenchDecision: decision, ...(attemptEvidenceIds?.length ? { attemptEvidenceIds } : {}) }),
|
|
435
|
+
...(conditions ? { sessionConditions: conditions } : {}),
|
|
436
|
+
};
|
|
366
437
|
events.push(buildReviewSessionEvent(session, {
|
|
367
438
|
sessionName,
|
|
368
439
|
sequence: events.length + 1,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReviewSessionEvent } from "../review-resource.js";
|
|
2
2
|
import { type ReviewQueueSessionState } from "./review-queue-session.js";
|
|
3
|
-
export type ReviewSessionReplayIssueCode = "invalid-sequence" | "duplicate-sequence" | "non-contiguous-sequence" | "unknown-active-item" | "unknown-review-item" | "unknown-candidate" | "missing-review-item" | "invalid-workbench-decision" | "decision-candidate-mismatch" | "invalid-conflict-selection" | "decision-status-mismatch" | "decision-resolution-mismatch" | "missing-resolution-reason" | "edited-value-not-editable" | "edited-value-type-mismatch";
|
|
3
|
+
export type ReviewSessionReplayIssueCode = "invalid-sequence" | "duplicate-sequence" | "non-contiguous-sequence" | "unknown-active-item" | "unknown-review-item" | "unknown-candidate" | "missing-review-item" | "invalid-workbench-decision" | "decision-candidate-mismatch" | "invalid-conflict-selection" | "decision-status-mismatch" | "decision-resolution-mismatch" | "missing-resolution-reason" | "edited-value-not-editable" | "edited-value-type-mismatch" | "session-conditions-mismatch";
|
|
4
4
|
export interface ReviewSessionReplayIssue {
|
|
5
5
|
readonly code: ReviewSessionReplayIssueCode;
|
|
6
6
|
readonly eventName: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { candidateForDecision, conflictSelectionIssue, decisionSelectsNoCandidate, isClearedWorkbenchDecisionEvent, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
1
|
+
import { candidateForDecision, conflictSelectionIssue, decisionSelectsNoCandidate, isClearedWorkbenchDecisionEvent, sessionConditionsStamp, workbenchDecisionDefinitions, } from "./review-queue-session.js";
|
|
2
2
|
import { checkEditedValueForItem } from "./edited-value.js";
|
|
3
|
+
import { canonicalJson } from "./canonical.js";
|
|
3
4
|
export function validateReviewSessionEventsForSnapshot(snapshot, events) {
|
|
4
5
|
const itemsByName = new Map(snapshot.items.map((item) => [item.metadata.name, item]));
|
|
5
6
|
const sequenceIssues = validateEventSequence(events);
|
|
@@ -44,6 +45,19 @@ export function validateReviewSessionEventsForSnapshot(snapshot, events) {
|
|
|
44
45
|
}
|
|
45
46
|
else if (event.spec.eventType === "decision-changed" || event.spec.eventType === "decision-submitted") {
|
|
46
47
|
const decision = replayableWorkbenchDecision(event.spec.data?.workbenchDecision);
|
|
48
|
+
// A decision recorded under other presentation or sampling than the
|
|
49
|
+
// snapshot's must not be replayed as if made under the snapshot's.
|
|
50
|
+
const expectedConditions = sessionConditionsStamp(snapshot);
|
|
51
|
+
const carried = event.spec.data?.sessionConditions;
|
|
52
|
+
if (decision && canonicalJson(carried ?? null) !== canonicalJson(expectedConditions ?? null)) {
|
|
53
|
+
issues.push({
|
|
54
|
+
...eventRef,
|
|
55
|
+
code: "session-conditions-mismatch",
|
|
56
|
+
reviewItemName,
|
|
57
|
+
candidateId: event.spec.candidateId,
|
|
58
|
+
message: `ReviewSessionEvent ${event.metadata.name} was recorded under session conditions ${JSON.stringify(carried ?? "none")}, but the snapshot's are ${JSON.stringify(expectedConditions ?? "none")}.`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
47
61
|
if (!decision) {
|
|
48
62
|
issues.push({
|
|
49
63
|
...eventRef,
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
export declare const REVIEW_WORKBENCH_CSS: string;
|
|
2
|
+
/** Literal dark token defaults for the element's :host, from @kontourai/ui tokens.css :root. */
|
|
3
|
+
export declare const REVIEW_WORKBENCH_DARK_TOKEN_DECLARATIONS: string;
|
|
4
|
+
/** Literal light token values for the element's color-scheme="light", from @kontourai/ui tokens.css [data-theme="light"]. */
|
|
5
|
+
export declare const REVIEW_WORKBENCH_LIGHT_TOKEN_DECLARATIONS: string;
|
|
6
|
+
/** Each built-in preset's dark and light values, from @kontourai/ui themes.css. */
|
|
7
|
+
export declare const REVIEW_WORKBENCH_THEME_TOKEN_DECLARATIONS: Readonly<Record<string, {
|
|
8
|
+
readonly dark: string;
|
|
9
|
+
readonly light: string;
|
|
10
|
+
}>>;
|
|
2
11
|
export default REVIEW_WORKBENCH_CSS;
|