@kontourai/survey 0.4.8 → 0.4.10

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 CHANGED
@@ -87,6 +87,16 @@ application's `body` or `:root` styles. Hosts should mount into an element like:
87
87
  The package also exposes `@kontourai/survey/review-workbench/standalone.css` for
88
88
  the standalone demo page. Use that only when Survey owns the whole page.
89
89
 
90
+ For the full consumer path from `ReviewItem` construction through persisted
91
+ review events, exported results, and optional Surface projection, see
92
+ [`docs/consumer-integration-guide.md`](docs/consumer-integration-guide.md).
93
+ That guide also covers the server-side apply boundary: producers should derive
94
+ write results from reviewed snapshots plus persisted events, not from
95
+ browser-computed decisions or exported result payloads.
96
+ For the current decision on why Survey is not adding a generic review adapter
97
+ builder yet, see
98
+ [`docs/consumer-adapter-abstraction-assessment.md`](docs/consumer-adapter-abstraction-assessment.md).
99
+
90
100
  ## Contributor checks
91
101
 
92
102
  Install the repo-owned Git hooks once per clone:
@@ -231,6 +231,7 @@
231
231
  .survey-workbench-embed .workbench{
232
232
  position: relative;
233
233
  z-index: 1;
234
+ container-type: inline-size;
234
235
  min-height: 100vh;
235
236
  padding: 30px 26px 60px;
236
237
  }
@@ -239,7 +240,7 @@
239
240
  display: grid;
240
241
  grid-template-rows: auto minmax(0, 1fr);
241
242
  gap: 22px;
242
- max-width: 1280px;
243
+ max-width: 1360px;
243
244
  margin: 0 auto;
244
245
  }
245
246
 
@@ -359,7 +360,7 @@
359
360
 
360
361
  .survey-workbench-embed .queue-layout{
361
362
  display: grid;
362
- grid-template-columns: 300px minmax(0, 1fr);
363
+ grid-template-columns: 280px minmax(0, 1fr);
363
364
  gap: 22px;
364
365
  align-items: start;
365
366
  }
@@ -636,10 +637,11 @@
636
637
 
637
638
  .survey-workbench-embed .content-grid{
638
639
  display: grid;
639
- grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
640
+ grid-template-columns: minmax(0, 1fr);
640
641
  grid-template-areas:
641
- "focus decision"
642
- "candidates decision";
642
+ "focus"
643
+ "decision"
644
+ "candidates";
643
645
  gap: 22px;
644
646
  align-items: start;
645
647
  }
@@ -774,7 +776,7 @@
774
776
  display: grid;
775
777
  grid-template-rows: auto auto minmax(0, 1fr);
776
778
  gap: 16px;
777
- min-height: 560px;
779
+ min-height: 460px;
778
780
  padding: 20px;
779
781
  position: relative;
780
782
  overflow: hidden;
@@ -916,6 +918,7 @@
916
918
  .survey-workbench-embed .decision-column{
917
919
  grid-area: decision;
918
920
  display: grid;
921
+ grid-template-columns: minmax(0, 1fr);
919
922
  gap: 18px;
920
923
  animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
921
924
  animation-delay: 0.15s;
@@ -1068,7 +1071,7 @@
1068
1071
 
1069
1072
  .survey-workbench-embed .preview-section-grid{
1070
1073
  display: grid;
1071
- grid-template-columns: minmax(0, 1fr);
1074
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1072
1075
  gap: 10px;
1073
1076
  }
1074
1077
 
@@ -1271,6 +1274,69 @@
1271
1274
  }
1272
1275
  }
1273
1276
 
1277
+ @container (max-width: 1100px) {
1278
+ .survey-workbench-embed .topbar,
1279
+ .survey-workbench-embed .queue-layout,
1280
+ .survey-workbench-embed .focus-evidence{
1281
+ grid-template-columns: 1fr;
1282
+ }
1283
+
1284
+ .survey-workbench-embed .queue-layout{
1285
+ grid-template-areas:
1286
+ "content"
1287
+ "queue";
1288
+ }
1289
+
1290
+ .survey-workbench-embed .queue-panel{
1291
+ grid-area: queue;
1292
+ position: static;
1293
+ }
1294
+
1295
+ .survey-workbench-embed .content-grid{
1296
+ grid-area: content;
1297
+ grid-template-columns: 1fr;
1298
+ grid-template-areas:
1299
+ "focus"
1300
+ "decision"
1301
+ "candidates";
1302
+ }
1303
+
1304
+ .survey-workbench-embed .content-grid,
1305
+ .survey-workbench-embed .review-main,
1306
+ .survey-workbench-embed .candidate-grid,
1307
+ .survey-workbench-embed .decision-column{
1308
+ min-width: 0;
1309
+ }
1310
+ }
1311
+
1312
+ @container (max-width: 860px) {
1313
+ .survey-workbench-embed .candidate-grid,
1314
+ .survey-workbench-embed .focus-values,
1315
+ .survey-workbench-embed .preview-section-grid{
1316
+ grid-template-columns: 1fr;
1317
+ }
1318
+
1319
+ .survey-workbench-embed .candidate-grid::before{
1320
+ display: none;
1321
+ }
1322
+
1323
+ .survey-workbench-embed .candidate-card{
1324
+ min-height: auto;
1325
+ }
1326
+ }
1327
+
1328
+ @container (max-width: 620px) {
1329
+ .survey-workbench-embed .topbar .meta-grid{
1330
+ display: none;
1331
+ }
1332
+
1333
+ .survey-workbench-embed .decision-buttons,
1334
+ .survey-workbench-embed .summary-grid,
1335
+ .survey-workbench-embed .audit-grid{
1336
+ grid-template-columns: 1fr;
1337
+ }
1338
+ }
1339
+
1274
1340
  @media (max-width: 560px) {
1275
1341
  .survey-workbench-embed .workbench{
1276
1342
  padding: 14px 10px 36px;
@@ -9,6 +9,15 @@ export interface ReviewWorkbenchSessionExport {
9
9
  readonly decisions: readonly ReviewDecision[];
10
10
  readonly results: readonly ReviewWorkbenchResult[];
11
11
  }
12
+ export type ReviewSessionReplayIssueCode = "unknown-active-item" | "unknown-review-item" | "unknown-candidate";
13
+ export interface ReviewSessionReplayIssue {
14
+ readonly code: ReviewSessionReplayIssueCode;
15
+ readonly eventName: string;
16
+ readonly sequence: number;
17
+ readonly reviewItemName?: string;
18
+ readonly candidateId?: string;
19
+ readonly message: string;
20
+ }
12
21
  export interface ReviewSessionEventStore {
13
22
  load(session: ReviewQueueSessionState): readonly ReviewSessionEvent[] | undefined;
14
23
  save(session: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): void;
@@ -54,6 +63,9 @@ export interface ReviewWorkbenchResult {
54
63
  export declare function buildReviewDecisionsFromSession(session: ReviewQueueSessionState): ReviewDecision[];
55
64
  export declare function buildReviewWorkbenchResultsFromSession(session: ReviewQueueSessionState): ReviewWorkbenchResult[];
56
65
  export declare function buildReviewWorkbenchSessionExport(session: ReviewQueueSessionState, events?: readonly ReviewSessionEvent[]): ReviewWorkbenchSessionExport;
66
+ export declare function validateReviewSessionEventsForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewSessionReplayIssue[];
67
+ export declare function replayReviewSessionEventsForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewQueueSessionState;
68
+ export declare function buildReviewWorkbenchSessionExportForSnapshot(snapshot: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): ReviewWorkbenchSessionExport;
57
69
  export declare function createInMemoryReviewSessionEventStore(initialEvents?: readonly ReviewSessionEvent[]): ReviewSessionEventStore & {
58
70
  events(): readonly ReviewSessionEvent[];
59
71
  };
@@ -89,6 +89,60 @@ export function buildReviewWorkbenchSessionExport(session, events = buildReviewS
89
89
  results: buildReviewWorkbenchResultsFromSession(session),
90
90
  };
91
91
  }
92
+ export function validateReviewSessionEventsForSnapshot(snapshot, events) {
93
+ const itemsByName = new Map(snapshot.items.map((item) => [item.metadata.name, item]));
94
+ return events.flatMap((event) => {
95
+ const issues = [];
96
+ const activeItemName = event.spec.activeItemName;
97
+ const reviewItemName = event.spec.reviewItemName;
98
+ const itemName = reviewItemName ?? activeItemName;
99
+ const eventRef = {
100
+ eventName: event.metadata.name,
101
+ sequence: event.spec.sequence,
102
+ };
103
+ if (activeItemName && !itemsByName.has(activeItemName)) {
104
+ issues.push({
105
+ ...eventRef,
106
+ code: "unknown-active-item",
107
+ reviewItemName: activeItemName,
108
+ message: `ReviewSessionEvent ${event.metadata.name} references active item ${activeItemName}, but the supplied session snapshot does not contain that ReviewItem.`,
109
+ });
110
+ }
111
+ if (reviewItemName && !itemsByName.has(reviewItemName)) {
112
+ issues.push({
113
+ ...eventRef,
114
+ code: "unknown-review-item",
115
+ reviewItemName,
116
+ message: `ReviewSessionEvent ${event.metadata.name} references review item ${reviewItemName}, but the supplied session snapshot does not contain that ReviewItem.`,
117
+ });
118
+ }
119
+ if (event.spec.candidateId && itemName && itemsByName.has(itemName)) {
120
+ const item = itemsByName.get(itemName);
121
+ const hasCandidate = item?.spec.candidates.some((candidate) => candidate.id === event.spec.candidateId);
122
+ if (!hasCandidate) {
123
+ issues.push({
124
+ ...eventRef,
125
+ code: "unknown-candidate",
126
+ reviewItemName: itemName,
127
+ candidateId: event.spec.candidateId,
128
+ message: `ReviewSessionEvent ${event.metadata.name} references candidate ${event.spec.candidateId}, but ReviewItem ${itemName} in the supplied session snapshot does not contain that candidate.`,
129
+ });
130
+ }
131
+ }
132
+ return issues;
133
+ });
134
+ }
135
+ export function replayReviewSessionEventsForSnapshot(snapshot, events) {
136
+ const issues = validateReviewSessionEventsForSnapshot(snapshot, events);
137
+ if (issues.length > 0) {
138
+ throw new Error(`Review session events do not match the supplied session snapshot: ${issues.map((issue) => issue.message).join(" ")}`);
139
+ }
140
+ return replayReviewSessionEvents(snapshot, events);
141
+ }
142
+ export function buildReviewWorkbenchSessionExportForSnapshot(snapshot, events) {
143
+ const replayedSession = replayReviewSessionEventsForSnapshot(snapshot, events);
144
+ return buildReviewWorkbenchSessionExport(replayedSession, events);
145
+ }
92
146
  export function createInMemoryReviewSessionEventStore(initialEvents = []) {
93
147
  let savedEvents = [...initialEvents];
94
148
  return {
@@ -78,6 +78,7 @@ p {
78
78
  .workbench {
79
79
  position: relative;
80
80
  z-index: 1;
81
+ container-type: inline-size;
81
82
  min-height: 100vh;
82
83
  padding: 30px 26px 60px;
83
84
  }
@@ -86,7 +87,7 @@ p {
86
87
  display: grid;
87
88
  grid-template-rows: auto minmax(0, 1fr);
88
89
  gap: 22px;
89
- max-width: 1280px;
90
+ max-width: 1360px;
90
91
  margin: 0 auto;
91
92
  }
92
93
 
@@ -206,7 +207,7 @@ h1 {
206
207
 
207
208
  .queue-layout {
208
209
  display: grid;
209
- grid-template-columns: 300px minmax(0, 1fr);
210
+ grid-template-columns: 280px minmax(0, 1fr);
210
211
  gap: 22px;
211
212
  align-items: start;
212
213
  }
@@ -483,10 +484,11 @@ h1 {
483
484
 
484
485
  .content-grid {
485
486
  display: grid;
486
- grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
487
+ grid-template-columns: minmax(0, 1fr);
487
488
  grid-template-areas:
488
- "focus decision"
489
- "candidates decision";
489
+ "focus"
490
+ "decision"
491
+ "candidates";
490
492
  gap: 22px;
491
493
  align-items: start;
492
494
  }
@@ -621,7 +623,7 @@ h1 {
621
623
  display: grid;
622
624
  grid-template-rows: auto auto minmax(0, 1fr);
623
625
  gap: 16px;
624
- min-height: 560px;
626
+ min-height: 460px;
625
627
  padding: 20px;
626
628
  position: relative;
627
629
  overflow: hidden;
@@ -763,6 +765,7 @@ h1 {
763
765
  .decision-column {
764
766
  grid-area: decision;
765
767
  display: grid;
768
+ grid-template-columns: minmax(0, 1fr);
766
769
  gap: 18px;
767
770
  animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
768
771
  animation-delay: 0.15s;
@@ -915,7 +918,7 @@ textarea:focus {
915
918
 
916
919
  .preview-section-grid {
917
920
  display: grid;
918
- grid-template-columns: minmax(0, 1fr);
921
+ grid-template-columns: repeat(2, minmax(0, 1fr));
919
922
  gap: 10px;
920
923
  }
921
924
 
@@ -1118,6 +1121,69 @@ pre {
1118
1121
  }
1119
1122
  }
1120
1123
 
1124
+ @container (max-width: 1100px) {
1125
+ .topbar,
1126
+ .queue-layout,
1127
+ .focus-evidence {
1128
+ grid-template-columns: 1fr;
1129
+ }
1130
+
1131
+ .queue-layout {
1132
+ grid-template-areas:
1133
+ "content"
1134
+ "queue";
1135
+ }
1136
+
1137
+ .queue-panel {
1138
+ grid-area: queue;
1139
+ position: static;
1140
+ }
1141
+
1142
+ .content-grid {
1143
+ grid-area: content;
1144
+ grid-template-columns: 1fr;
1145
+ grid-template-areas:
1146
+ "focus"
1147
+ "decision"
1148
+ "candidates";
1149
+ }
1150
+
1151
+ .content-grid,
1152
+ .review-main,
1153
+ .candidate-grid,
1154
+ .decision-column {
1155
+ min-width: 0;
1156
+ }
1157
+ }
1158
+
1159
+ @container (max-width: 860px) {
1160
+ .candidate-grid,
1161
+ .focus-values,
1162
+ .preview-section-grid {
1163
+ grid-template-columns: 1fr;
1164
+ }
1165
+
1166
+ .candidate-grid::before {
1167
+ display: none;
1168
+ }
1169
+
1170
+ .candidate-card {
1171
+ min-height: auto;
1172
+ }
1173
+ }
1174
+
1175
+ @container (max-width: 620px) {
1176
+ .topbar .meta-grid {
1177
+ display: none;
1178
+ }
1179
+
1180
+ .decision-buttons,
1181
+ .summary-grid,
1182
+ .audit-grid {
1183
+ grid-template-columns: 1fr;
1184
+ }
1185
+ }
1186
+
1121
1187
  @media (max-width: 560px) {
1122
1188
  .workbench {
1123
1189
  padding: 14px 10px 36px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/survey",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
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",