@kontourai/survey 0.4.6 → 0.4.7

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.
@@ -0,0 +1 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700;9..144,900&family=Hanken+Grotesk:wght@400;500;600;700;800&family=IBM+Plex+Mono:wght@400;500;600&display=swap");
@@ -0,0 +1,2 @@
1
+ @import "./tokens.css";
2
+ @import "./themes.css";
@@ -0,0 +1,67 @@
1
+ .theme-survey {
2
+ --k-bg: #06080b;
3
+ --k-brand: #5ce0c6;
4
+ --k-text-faint: #6f8095;
5
+ }
6
+
7
+ .theme-console {
8
+ --k-font-ui: "Aptos Narrow", "DIN Condensed", "IBM Plex Sans Condensed", "Gill Sans", sans-serif;
9
+ --k-font-display: "Aptos Narrow", "DIN Condensed", "IBM Plex Sans Condensed", "Gill Sans", sans-serif;
10
+ --k-radius-sm: 0;
11
+ --k-radius-md: 0;
12
+ --k-brand: #c9ff4a;
13
+ --k-brand-contrast: #11120f;
14
+ --k-bg: #11120f;
15
+ --k-panel: #191b16;
16
+ --k-panel-raised: #20231e;
17
+ --k-line: #3a4035;
18
+ --k-line-strong: #8ea36e;
19
+ --k-text: #f2f0e8;
20
+ --k-text-muted: #a6ab9c;
21
+ --k-text-faint: #747a6c;
22
+ --k-positive: #a6d37b;
23
+ --k-caution: #e8c15f;
24
+ --k-negative: #ee776f;
25
+ --k-active: #84d8c8;
26
+ }
27
+
28
+ .theme-flow {
29
+ --k-brand: #2f88a6;
30
+ }
31
+
32
+ .theme-surface {
33
+ --k-brand: #14a37a;
34
+ }
35
+
36
+ [data-theme="light"].theme-survey,
37
+ [data-theme="light"] .theme-survey {
38
+ --k-brand: #16806f;
39
+ }
40
+
41
+ [data-theme="light"].theme-console,
42
+ [data-theme="light"] .theme-console {
43
+ --k-brand: #6c9400;
44
+ --k-brand-contrast: #ffffff;
45
+ --k-bg: #f3f5eb;
46
+ --k-panel: #fbfcf7;
47
+ --k-panel-raised: #eef2e6;
48
+ --k-line: #ccd5bf;
49
+ --k-line-strong: #8fa36f;
50
+ --k-text: #1e2319;
51
+ --k-text-muted: #596250;
52
+ --k-text-faint: #77816d;
53
+ --k-positive: #2f7d32;
54
+ --k-caution: #8a6500;
55
+ --k-negative: #b93a36;
56
+ --k-active: #247f75;
57
+ }
58
+
59
+ [data-theme="light"].theme-flow,
60
+ [data-theme="light"] .theme-flow {
61
+ --k-brand: #1f6f88;
62
+ }
63
+
64
+ [data-theme="light"].theme-surface,
65
+ [data-theme="light"] .theme-surface {
66
+ --k-brand: #0f6b52;
67
+ }
@@ -0,0 +1,79 @@
1
+ :root {
2
+ color-scheme: dark;
3
+
4
+ /* structure */
5
+ --k-bg: #0a0e13;
6
+ --k-panel: #111824;
7
+ --k-panel-raised: #16202d;
8
+ --k-line: rgba(150, 180, 210, 0.12);
9
+ --k-line-strong: rgba(150, 180, 210, 0.22);
10
+ --k-shadow: 0 26px 60px -42px rgba(0, 0, 0, 0.95);
11
+
12
+ /* text */
13
+ --k-text: #eef3f8;
14
+ --k-text-muted: #aebccb;
15
+ --k-text-faint: #72869b;
16
+
17
+ /* brand */
18
+ --k-brand: #5ce0c6;
19
+ --k-brand-contrast: #06080b;
20
+
21
+ /* semantic status scale */
22
+ --k-positive: #34d399;
23
+ --k-caution: #f3b14b;
24
+ --k-negative: #ff6f6f;
25
+ --k-neutral: #6f8095;
26
+ --k-active: #7aa2ff;
27
+ --k-positive-soft: color-mix(in oklab, var(--k-positive) 14%, transparent);
28
+ --k-caution-soft: color-mix(in oklab, var(--k-caution) 14%, transparent);
29
+ --k-negative-soft: color-mix(in oklab, var(--k-negative) 14%, transparent);
30
+ --k-active-soft: color-mix(in oklab, var(--k-active) 14%, transparent);
31
+
32
+ /* spacing */
33
+ --k-space-1: 4px;
34
+ --k-space-2: 8px;
35
+ --k-space-3: 12px;
36
+ --k-space-4: 16px;
37
+ --k-space-5: 24px;
38
+ --k-space-6: 32px;
39
+
40
+ /* radius */
41
+ --k-radius-sm: 9px;
42
+ --k-radius-md: 14px;
43
+
44
+ /* type */
45
+ --k-text-xs: 11px;
46
+ --k-text-sm: 12.5px;
47
+ --k-text-md: 14px;
48
+ --k-text-lg: 18px;
49
+ --k-text-xl: 22px;
50
+ --k-text-2xl: clamp(26px, 3.4vw, 38px);
51
+
52
+ /* fonts */
53
+ --k-font-display: "Fraunces", Georgia, "Times New Roman", serif;
54
+ --k-font-ui: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
55
+ --k-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
56
+
57
+ /* motion */
58
+ --k-ease: cubic-bezier(0.22, 1, 0.36, 1);
59
+ --k-dur: 0.16s;
60
+ }
61
+
62
+ [data-theme="light"] {
63
+ color-scheme: light;
64
+ --k-bg: #f5f4ef;
65
+ --k-panel: #ffffff;
66
+ --k-panel-raised: #fbfaf7;
67
+ --k-line: rgba(36, 40, 46, 0.12);
68
+ --k-line-strong: rgba(36, 40, 46, 0.20);
69
+ --k-shadow: 0 18px 50px -30px rgba(0, 0, 0, 0.25);
70
+ --k-text: #202124;
71
+ --k-text-muted: #5b626b;
72
+ --k-text-faint: #707782;
73
+ --k-brand-contrast: #ffffff;
74
+ --k-positive: #168257;
75
+ --k-caution: #8a5a00;
76
+ --k-negative: #c83b3b;
77
+ --k-neutral: #5f6975;
78
+ --k-active: #3f6fd6;
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/survey",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Producer-side source, extraction, candidate, and review contracts for projecting verified claims into Surface.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -18,6 +18,12 @@
18
18
  },
19
19
  "exports": {
20
20
  ".": "./dist/src/index.js",
21
+ "./review-workbench": {
22
+ "types": "./dist/src/review-workbench/review-workbench.d.ts",
23
+ "default": "./dist/src/review-workbench/review-workbench.js"
24
+ },
25
+ "./review-workbench.css": "./dist/src/review-workbench/review-workbench.css",
26
+ "./review-workbench/standalone.css": "./dist/src/review-workbench/review-workbench.standalone.css",
21
27
  "./fixtures/public-field-review": "./dist/fixtures/public-field-review.js",
22
28
  "./fixtures/corrected-document-candidates": "./dist/fixtures/corrected-document-candidates.js",
23
29
  "./fixtures/public-directory-review-resource": "./dist/fixtures/public-directory-review-resource.js",
@@ -30,11 +36,15 @@
30
36
  "README.md"
31
37
  ],
32
38
  "scripts": {
33
- "build": "rm -rf dist && tsc",
39
+ "build": "rm -rf dist && tsc && node scripts/copy-review-workbench-package-assets.cjs",
40
+ "prepare": "npm run build",
34
41
  "typecheck": "tsc --noEmit",
35
42
  "test": "npm run build && node --test dist/tests/*.test.js",
36
- "verify": "node scripts/check-content-boundary.cjs && npm run typecheck && npm test && npm run check:review-workbench",
43
+ "test:browser": "playwright test",
44
+ "verify": "node scripts/check-content-boundary.cjs && npm run typecheck && npm test && npm run check:review-workbench-assets && npm run check:review-workbench && npm run test:browser",
37
45
  "check:content-boundary": "node scripts/check-content-boundary.cjs",
46
+ "sync:review-workbench-assets": "node scripts/sync-review-workbench-assets.cjs",
47
+ "check:review-workbench-assets": "node scripts/sync-review-workbench-assets.cjs --check",
38
48
  "check:review-workbench": "npm run check:review-workbench:static",
39
49
  "check:review-workbench:static": "npm run build && node scripts/check-review-workbench.cjs",
40
50
  "setup:repo-hooks": "node scripts/setup-repo-hooks.cjs",
@@ -44,6 +54,8 @@
44
54
  "@kontourai/surface": "^0.5.1"
45
55
  },
46
56
  "devDependencies": {
57
+ "@kontourai/console-kit": "^0.1.0",
58
+ "@playwright/test": "^1.60.0",
47
59
  "@types/node": "^25.6.0",
48
60
  "typescript": "^5.8.0"
49
61
  },
@@ -1,122 +0,0 @@
1
- import { publicDirectoryReviewItemFixture, reviewWorkbenchQueueFixtures } from "./review-workbench-data.js";
2
- export const workbenchDecisionDefinitions = {
3
- "accept-proposed": {
4
- label: "Accept proposed",
5
- effect: "Proposed value becomes the verified review outcome.",
6
- candidateRole: "proposed",
7
- status: "verified",
8
- },
9
- "keep-current": {
10
- label: "Keep current",
11
- effect: "Current value remains the verified review outcome.",
12
- candidateRole: "current",
13
- status: "verified",
14
- },
15
- "reject-proposed": {
16
- label: "Reject proposed",
17
- effect: "Proposed value is rejected and the current value remains unmodified.",
18
- candidateRole: "proposed",
19
- status: "rejected",
20
- },
21
- };
22
- export function initialReviewWorkbenchState(item = publicDirectoryReviewItemFixture) {
23
- return {
24
- item,
25
- note: "",
26
- decision: undefined,
27
- reviewedAt: "2026-06-04T00:00:00.000Z",
28
- actorId: "review-workbench-operator",
29
- };
30
- }
31
- export function initialReviewQueueSessionState(items = reviewWorkbenchQueueFixtures) {
32
- return {
33
- items,
34
- activeItemName: items[0]?.metadata.name ?? "",
35
- notesByItemName: {},
36
- decisionsByItemName: {},
37
- reviewedAt: "2026-06-04T00:00:00.000Z",
38
- actorId: "review-workbench-operator",
39
- };
40
- }
41
- export function currentReviewWorkbenchState(session) {
42
- const item = currentReviewItem(session);
43
- return {
44
- item,
45
- note: session.notesByItemName[item.metadata.name] ?? "",
46
- decision: session.decisionsByItemName[item.metadata.name],
47
- reviewedAt: session.reviewedAt,
48
- actorId: session.actorId,
49
- };
50
- }
51
- export function currentReviewItem(session) {
52
- const item = session.items.find((entry) => entry.metadata.name === session.activeItemName) ?? session.items[0];
53
- if (!item) {
54
- throw new Error("Review queue session has no ReviewItems.");
55
- }
56
- return item;
57
- }
58
- export function deriveQueueRowStatus(item, session) {
59
- const decision = session.decisionsByItemName[item.metadata.name];
60
- if (decision === "reject-proposed") {
61
- return "rejected";
62
- }
63
- if (decision === "accept-proposed" || decision === "keep-current" || item.spec.candidateSetStatus === "resolved") {
64
- return "resolved";
65
- }
66
- if (item.spec.candidateSetStatus === "escalated") {
67
- return "escalated";
68
- }
69
- if (item.metadata.name === session.activeItemName) {
70
- return "in-review";
71
- }
72
- return "pending";
73
- }
74
- export function nextUnresolvedItemName(session) {
75
- const activeIndex = session.items.findIndex((item) => item.metadata.name === session.activeItemName);
76
- const orderedItems = [
77
- ...session.items.slice(Math.max(activeIndex, 0) + 1),
78
- ...session.items.slice(0, Math.max(activeIndex, 0) + 1),
79
- ];
80
- return orderedItems.find((item) => deriveQueueRowStatus(item, session) === "pending")?.metadata.name;
81
- }
82
- export function reviewSessionSummary(session) {
83
- return session.items.reduce((summary, item) => {
84
- const decision = session.decisionsByItemName[item.metadata.name];
85
- if (decision === "accept-proposed") {
86
- return { ...summary, accepted: summary.accepted + 1 };
87
- }
88
- if (decision === "keep-current") {
89
- return { ...summary, keptCurrent: summary.keptCurrent + 1 };
90
- }
91
- if (decision === "reject-proposed") {
92
- return { ...summary, rejected: summary.rejected + 1 };
93
- }
94
- if (item.spec.candidateSetStatus === "escalated") {
95
- return { ...summary, escalated: summary.escalated + 1 };
96
- }
97
- if (item.spec.candidateSetStatus === "resolved") {
98
- return { ...summary, accepted: summary.accepted + 1 };
99
- }
100
- return { ...summary, unresolved: summary.unresolved + 1 };
101
- }, {
102
- accepted: 0,
103
- keptCurrent: 0,
104
- rejected: 0,
105
- escalated: 0,
106
- unresolved: 0,
107
- });
108
- }
109
- export function candidateForDecision(item, decision) {
110
- const definition = workbenchDecisionDefinitions[decision];
111
- const candidate = item.spec.candidates.find((entry) => entry.role === definition.candidateRole);
112
- if (!candidate) {
113
- throw new Error(`ReviewItem ${item.metadata.name} has no ${definition.candidateRole} candidate.`);
114
- }
115
- return candidate;
116
- }
117
- export function selectedCandidateRole(state) {
118
- if (!state.decision) {
119
- return undefined;
120
- }
121
- return workbenchDecisionDefinitions[state.decision].candidateRole;
122
- }