@kontourai/survey 0.4.8 → 0.4.9
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:
|
|
@@ -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 {
|
package/package.json
CHANGED