@kontourai/survey 0.4.7 → 0.4.8

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.
@@ -15,7 +15,24 @@ export function buildSurfaceProjectionPreview(item, decision) {
15
15
  };
16
16
  }
17
17
  export function formatValue(value) {
18
- return typeof value === "string" ? value : JSON.stringify(value);
18
+ if (typeof value === "string") {
19
+ return value;
20
+ }
21
+ if (value === undefined) {
22
+ return "";
23
+ }
24
+ if (value === null) {
25
+ return "null";
26
+ }
27
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
28
+ return String(value);
29
+ }
30
+ try {
31
+ return JSON.stringify(value, null, 2);
32
+ }
33
+ catch {
34
+ return String(value);
35
+ }
19
36
  }
20
37
  function selectedPreviewCandidate(item, decision) {
21
38
  if (!decision?.spec.candidateId) {
@@ -1,5 +1,5 @@
1
- import { type ReviewQueueSessionState, type ReviewWorkbenchState } from "./review-queue-session.js";
2
- import { type ReviewDecision, type ReviewSession, type ReviewSessionEvent } from "../review-resource.js";
1
+ import { type ReviewQueueSessionState, type ReviewWorkbenchDecision, type ReviewWorkbenchState } from "./review-queue-session.js";
2
+ import { type ReviewCandidate, type ReviewDecision, type ReviewSession, type ReviewSessionEvent } from "../review-resource.js";
3
3
  export { buildReviewSessionEvents, buildReviewSessionEvent, buildReviewSessionResource, candidateForDecision, currentReviewItem, currentReviewWorkbenchState, defaultReviewSessionName, deriveQueueRowStatus, initialReviewQueueSessionState, initialReviewWorkbenchState, nextUnresolvedItemName, replayReviewSessionEvents, reviewSessionSummary, reviewWorkbenchSessionStorageKey, selectedCandidateRole, workbenchDecisionDefinitions, type ReviewQueueRowStatus, type ReviewQueueSessionState, type ReviewSessionSummary, type ReviewWorkbenchDecision, type ReviewWorkbenchState, } from "./review-queue-session.js";
4
4
  export { buildSurfaceProjectionPreview, type PreviewAuthorityTrace, type PreviewCandidateHistory, type PreviewClaim, type PreviewIntegrityPosture, type PreviewReviewEvent, type PreviewSourceAuthority, type PreviewSourceEvidence, type SurfaceProjectionPreview, } from "./review-surface-preview.js";
5
5
  export declare function buildReviewDecision(state: ReviewWorkbenchState): ReviewDecision | undefined;
@@ -7,23 +7,60 @@ export interface ReviewWorkbenchSessionExport {
7
7
  readonly session: ReviewSession;
8
8
  readonly events: readonly ReviewSessionEvent[];
9
9
  readonly decisions: readonly ReviewDecision[];
10
+ readonly results: readonly ReviewWorkbenchResult[];
10
11
  }
11
12
  export interface ReviewSessionEventStore {
12
13
  load(session: ReviewQueueSessionState): readonly ReviewSessionEvent[] | undefined;
13
14
  save(session: ReviewQueueSessionState, events: readonly ReviewSessionEvent[]): void;
14
15
  }
16
+ export type ReviewSessionPersistenceStatus = "idle" | "saving" | "saved" | "error";
17
+ export interface ReviewSessionPersistenceState {
18
+ readonly status: ReviewSessionPersistenceStatus;
19
+ readonly events: readonly ReviewSessionEvent[];
20
+ readonly error?: unknown;
21
+ }
22
+ export interface ReviewSessionPersistenceRequest {
23
+ readonly session: ReviewQueueSessionState;
24
+ readonly events: readonly ReviewSessionEvent[];
25
+ readonly expectedEventCount: number;
26
+ }
27
+ export interface ReviewSessionPersistenceResult {
28
+ readonly eventCount?: number;
29
+ }
30
+ export interface PersistentReviewSessionEventStoreOptions {
31
+ readonly initialEvents?: readonly ReviewSessionEvent[];
32
+ readonly persist: (request: ReviewSessionPersistenceRequest) => Promise<ReviewSessionPersistenceResult | void>;
33
+ readonly onStatusChange?: (state: ReviewSessionPersistenceState) => void;
34
+ }
15
35
  export interface MountReviewWorkbenchOptions {
16
36
  readonly eventStore?: ReviewSessionEventStore;
17
37
  }
18
38
  export interface BrowserReviewWorkbenchConfig {
19
39
  readonly startState?: ReviewQueueSessionState | ReviewWorkbenchState;
20
40
  }
41
+ export interface ReviewWorkbenchResult {
42
+ readonly reviewItemName: string;
43
+ readonly decision: ReviewWorkbenchDecision;
44
+ readonly selectedCandidate: ReviewCandidate;
45
+ readonly selectedCandidateId: string;
46
+ readonly selectedCandidateRole?: ReviewCandidate["role"];
47
+ readonly selectedValue: unknown;
48
+ readonly selectedDisplayValue: string;
49
+ readonly unselectedCandidates: readonly ReviewCandidate[];
50
+ readonly reviewDecision: ReviewDecision;
51
+ readonly status: ReviewDecision["spec"]["status"];
52
+ readonly rationale?: string;
53
+ }
21
54
  export declare function buildReviewDecisionsFromSession(session: ReviewQueueSessionState): ReviewDecision[];
55
+ export declare function buildReviewWorkbenchResultsFromSession(session: ReviewQueueSessionState): ReviewWorkbenchResult[];
22
56
  export declare function buildReviewWorkbenchSessionExport(session: ReviewQueueSessionState, events?: readonly ReviewSessionEvent[]): ReviewWorkbenchSessionExport;
23
57
  export declare function createInMemoryReviewSessionEventStore(initialEvents?: readonly ReviewSessionEvent[]): ReviewSessionEventStore & {
24
58
  events(): readonly ReviewSessionEvent[];
25
59
  };
26
60
  export declare function createLocalStorageReviewSessionEventStore(storage: Pick<Storage, "getItem" | "setItem">, keyPrefix?: string): ReviewSessionEventStore;
61
+ export declare function createPersistentReviewSessionEventStore(options: PersistentReviewSessionEventStoreOptions): ReviewSessionEventStore & {
62
+ events(): readonly ReviewSessionEvent[];
63
+ };
27
64
  export declare function renderReviewWorkbenchHtml(state: ReviewWorkbenchState | ReviewQueueSessionState, events?: readonly ReviewSessionEvent[]): string;
28
65
  export declare function mountReviewWorkbench(root: HTMLElement, startState?: ReviewQueueSessionState | ReviewWorkbenchState, options?: MountReviewWorkbenchOptions): void;
29
66
  export declare function browserReviewWorkbenchStartState(): ReviewQueueSessionState | ReviewWorkbenchState | undefined;
@@ -51,11 +51,42 @@ export function buildReviewDecisionsFromSession(session) {
51
51
  return reviewDecision ? [reviewDecision] : [];
52
52
  });
53
53
  }
54
+ export function buildReviewWorkbenchResultsFromSession(session) {
55
+ return session.items.flatMap((item) => {
56
+ const decision = session.decisionsByItemName[item.metadata.name];
57
+ if (!decision) {
58
+ return [];
59
+ }
60
+ const state = currentReviewWorkbenchState({
61
+ ...session,
62
+ activeItemName: item.metadata.name,
63
+ });
64
+ const reviewDecision = buildReviewDecision(state);
65
+ if (!reviewDecision) {
66
+ return [];
67
+ }
68
+ const selectedCandidate = candidateForDecision(item, decision);
69
+ return [{
70
+ reviewItemName: item.metadata.name,
71
+ decision,
72
+ selectedCandidate,
73
+ selectedCandidateId: selectedCandidate.id,
74
+ selectedCandidateRole: selectedCandidate.role,
75
+ selectedValue: selectedCandidate.value,
76
+ selectedDisplayValue: formatValue(selectedCandidate.value),
77
+ unselectedCandidates: item.spec.candidates.filter((candidate) => candidate.id !== selectedCandidate.id),
78
+ reviewDecision,
79
+ status: reviewDecision.spec.status,
80
+ rationale: reviewDecision.spec.rationale,
81
+ }];
82
+ });
83
+ }
54
84
  export function buildReviewWorkbenchSessionExport(session, events = buildReviewSessionEvents(session)) {
55
85
  return {
56
86
  session: buildReviewSessionResource(session, events),
57
87
  events,
58
88
  decisions: buildReviewDecisionsFromSession(session),
89
+ results: buildReviewWorkbenchResultsFromSession(session),
59
90
  };
60
91
  }
61
92
  export function createInMemoryReviewSessionEventStore(initialEvents = []) {
@@ -88,6 +119,45 @@ export function createLocalStorageReviewSessionEventStore(storage, keyPrefix = r
88
119
  },
89
120
  };
90
121
  }
122
+ export function createPersistentReviewSessionEventStore(options) {
123
+ let savedEvents = [...(options.initialEvents ?? [])];
124
+ let lastPersistedSerialized = JSON.stringify(savedEvents);
125
+ let lastPersistedEventCount = savedEvents.length;
126
+ let pendingSave = Promise.resolve();
127
+ const emit = (state) => {
128
+ options.onStatusChange?.(state);
129
+ };
130
+ return {
131
+ events: () => [...savedEvents],
132
+ load: () => savedEvents.length > 0 ? [...savedEvents] : undefined,
133
+ save: (session, events) => {
134
+ const normalizedEvents = [...events];
135
+ const serialized = JSON.stringify(normalizedEvents);
136
+ if (serialized === lastPersistedSerialized) {
137
+ return;
138
+ }
139
+ pendingSave = pendingSave
140
+ .then(async () => {
141
+ if (serialized === lastPersistedSerialized) {
142
+ return;
143
+ }
144
+ emit({ status: "saving", events: normalizedEvents });
145
+ const result = await options.persist({
146
+ session,
147
+ events: normalizedEvents,
148
+ expectedEventCount: lastPersistedEventCount,
149
+ });
150
+ savedEvents = normalizedEvents;
151
+ lastPersistedSerialized = serialized;
152
+ lastPersistedEventCount = result?.eventCount ?? normalizedEvents.length;
153
+ emit({ status: "saved", events: normalizedEvents });
154
+ })
155
+ .catch((error) => {
156
+ emit({ status: "error", events: normalizedEvents, error });
157
+ });
158
+ },
159
+ };
160
+ }
91
161
  export function renderReviewWorkbenchHtml(state, events) {
92
162
  if ("items" in state) {
93
163
  return renderReviewQueueSessionHtml(state, events);
@@ -673,7 +743,7 @@ function refreshSessionAudit(root, session, sessionExport, renderCurrentState) {
673
743
  }
674
744
  function producerFeedbackTags(item) {
675
745
  const tags = item.spec.producerPolicy?.feedbackTags;
676
- return Array.isArray(tags) ? tags.map(String) : [];
746
+ return Array.isArray(tags) ? tags.map(formatValue) : [];
677
747
  }
678
748
  function renderCandidateCard(candidate, state) {
679
749
  const selectedRole = selectedCandidateRole(state);
@@ -734,10 +804,11 @@ function formatConfidence(value) {
734
804
  return `${Math.round(value * 100)}% (${value.toFixed(2)})`;
735
805
  }
736
806
  function titleCase(value) {
737
- return value.slice(0, 1).toUpperCase() + value.slice(1);
807
+ const displayValue = formatValue(value);
808
+ return displayValue.slice(0, 1).toUpperCase() + displayValue.slice(1);
738
809
  }
739
810
  function escapeHtml(value) {
740
- return value
811
+ return formatValue(value)
741
812
  .replaceAll("&", "&amp;")
742
813
  .replaceAll("<", "&lt;")
743
814
  .replaceAll(">", "&gt;")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontourai/survey",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
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",