@kontourai/survey 0.7.2 → 0.8.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.
@@ -654,7 +654,7 @@ function renderReviewFocus(state, presentationAdapter) {
654
654
  <dl class="focus-evidence">
655
655
  ${fieldItem("Target", presentation.targetLabel)}
656
656
  ${fieldItem("Proposed source", proposed.source.sourceRef)}
657
- ${fieldItem("Proposed excerpt", proposed.locator?.excerpt ?? "none", "excerpt")}
657
+ ${fieldItemClamped("Proposed excerpt", proposed.locator?.excerpt ?? "none", "excerpt")}
658
658
  </dl>
659
659
  </section>
660
660
  `;
@@ -782,13 +782,18 @@ function renderCanonicalClaim(preview) {
782
782
  ]);
783
783
  }
784
784
  function renderReviewEvent(preview) {
785
- return renderPreviewSection("Review event", "surface-review-event", [
786
- ["Actor", preview.reviewEvent?.actor ?? "unknown"],
787
- ["Reviewed at", preview.reviewEvent?.reviewedAt ?? "not recorded"],
788
- ["Status", preview.reviewEvent?.status ?? "pending"],
789
- ["Rationale", preview.reviewEvent?.rationale ?? "No reviewer rationale provided."],
790
- ["Outcome", preview.reviewEvent?.reviewOutcomeId ?? "not provided"],
791
- ]);
785
+ return `
786
+ <section class="preview-section" data-testid="surface-review-event">
787
+ <h3>${escapeHtml("Review event")}</h3>
788
+ <dl class="field-stack compact">
789
+ ${fieldItem("Actor", preview.reviewEvent?.actor ?? "unknown")}
790
+ ${fieldItem("Reviewed at", preview.reviewEvent?.reviewedAt ?? "not recorded")}
791
+ ${fieldItem("Status", preview.reviewEvent?.status ?? "pending")}
792
+ ${fieldItem("Rationale", preview.reviewEvent?.rationale ?? "No reviewer rationale provided.", "rationale-clamp")}
793
+ ${fieldItem("Outcome", preview.reviewEvent?.reviewOutcomeId ?? "not provided")}
794
+ </dl>
795
+ </section>
796
+ `;
792
797
  }
793
798
  function renderIntegrityPosture(preview) {
794
799
  return renderPreviewSection("Integrity posture", "surface-integrity-posture", [
@@ -806,32 +811,68 @@ function renderAuthorityTrace(preview) {
806
811
  ], preview.authorityTrace.status === "empty" ? " is-neutral" : "");
807
812
  }
808
813
  function renderCandidateHistory(preview) {
809
- const rows = preview.candidateHistory.length === 0
810
- ? [["History", "No unselected candidates."]]
811
- : preview.candidateHistory.flatMap((candidate) => [
812
- ["History", candidate.historyLabel],
813
- ["Value", candidate.value],
814
- ]);
815
- const references = preview.candidateHistory.flatMap((candidate) => [
816
- ["Candidate ID", candidate.candidateId],
817
- ]);
818
- return renderPreviewSection("Unselected candidate history", "surface-candidate-history", rows, undefined, references);
814
+ if (preview.candidateHistory.length === 0) {
815
+ return renderPreviewSection("Unselected candidate history", "surface-candidate-history", [["History", "No unselected candidates."]], undefined, []);
816
+ }
817
+ const VISIBLE_COUNT = 3;
818
+ const total = preview.candidateHistory.length;
819
+ const visibleCandidates = preview.candidateHistory.slice(0, VISIBLE_COUNT);
820
+ const overflowCandidates = preview.candidateHistory.slice(VISIBLE_COUNT);
821
+ const renderHistoryRows = (candidates) => candidates.flatMap((candidate) => [
822
+ fieldItem("History", candidate.historyLabel),
823
+ fieldItem("Value", candidate.value),
824
+ ]).join("");
825
+ const referenceRows = preview.candidateHistory.map((candidate) => fieldItem("Candidate ID", candidate.candidateId)).join("");
826
+ const overflowHtml = overflowCandidates.length > 0
827
+ ? `<div class="history-overflow">${renderHistoryRows(overflowCandidates)}</div>`
828
+ : "";
829
+ const expanderHtml = overflowCandidates.length > 0
830
+ ? `<button class="history-expand-btn" type="button" data-history-expand aria-expanded="false">
831
+ <span class="expand-label">view all (${total})</span>
832
+ <span class="collapse-label">show less</span>
833
+ </button>`
834
+ : "";
835
+ return `
836
+ <section class="preview-section" data-testid="surface-candidate-history">
837
+ <h3>${escapeHtml("Unselected candidate history")}</h3>
838
+ <div class="history-expander${overflowCandidates.length > 0 ? "" : ""}" data-history-expander>
839
+ <dl class="field-stack compact">
840
+ ${renderHistoryRows(visibleCandidates)}
841
+ ${overflowHtml}
842
+ </dl>
843
+ ${expanderHtml}
844
+ </div>
845
+ ${preview.candidateHistory.length > 0 ? `<details class="reference-details">
846
+ <summary>IDs and trace links</summary>
847
+ <dl class="field-stack compact">${referenceRows}</dl>
848
+ </details>` : ""}
849
+ </section>
850
+ `;
819
851
  }
820
852
  function renderSourceEvidence(preview) {
821
- const rows = [
822
- ["Source URL", preview.sourceEvidence.sourceRef],
823
- ["Excerpt", preview.sourceEvidence.excerpt],
824
- ["Extractor", preview.sourceEvidence.extractor],
825
- ["Observed", preview.sourceEvidence.observedAt],
826
- ];
827
- const references = [
853
+ // Excerpt uses a 3-line clamp wrapper with a toggle button.
854
+ const clampedExcerptHtml = fieldItemClamped("Excerpt", preview.sourceEvidence.excerpt, "excerpt");
855
+ // Build source evidence section with clamped excerpt inline
856
+ return `
857
+ <section class="preview-section" data-testid="surface-source-evidence">
858
+ <h3>${escapeHtml("Source evidence")}</h3>
859
+ <dl class="field-stack compact">
860
+ ${fieldItem("Source URL", preview.sourceEvidence.sourceRef)}
861
+ ${clampedExcerptHtml}
862
+ ${fieldItem("Extractor", preview.sourceEvidence.extractor)}
863
+ ${fieldItem("Observed", preview.sourceEvidence.observedAt)}
864
+ ${preview.sourceEvidence.sourceAuthority ? [
865
+ fieldItem("Source authority class", preview.sourceEvidence.sourceAuthority.authorityClass),
866
+ fieldItem("Declared by", preview.sourceEvidence.sourceAuthority.declaredBy),
867
+ fieldItem("Authority scope", preview.sourceEvidence.sourceAuthority.scope),
868
+ ].join("") : ""}
869
+ </dl>
870
+ ${renderReferenceDetails([
828
871
  ["Source ID", preview.sourceEvidence.sourceId],
829
872
  ["Extraction ID", preview.sourceEvidence.extractionId],
830
- ];
831
- if (preview.sourceEvidence.sourceAuthority) {
832
- rows.push(["Source authority class", preview.sourceEvidence.sourceAuthority.authorityClass], ["Declared by", preview.sourceEvidence.sourceAuthority.declaredBy], ["Authority scope", preview.sourceEvidence.sourceAuthority.scope]);
833
- }
834
- return renderPreviewSection("Source evidence", "surface-source-evidence", rows);
873
+ ])}
874
+ </section>
875
+ `;
835
876
  }
836
877
  function renderPreviewSection(title, testId, rows, extraClass = "", references = []) {
837
878
  return `
@@ -969,6 +1010,8 @@ function renderCurrentState(root, session, controller, presentationAdapter) {
969
1010
  controller.renderCurrentState();
970
1011
  });
971
1012
  bindQueueDrawer(root);
1013
+ bindClampToggles(root);
1014
+ bindHistoryExpanders(root);
972
1015
  }
973
1016
  function queueSessionFromStartState(startState) {
974
1017
  if ("items" in startState) {
@@ -1015,6 +1058,29 @@ function bindNextUnresolved(root, goToNextUnresolved) {
1015
1058
  });
1016
1059
  });
1017
1060
  }
1061
+ function bindClampToggles(root) {
1062
+ root.querySelectorAll("[data-clamp-toggle]").forEach((button) => {
1063
+ button.addEventListener("click", () => {
1064
+ const clampContainer = button.closest("[data-clamp]");
1065
+ if (!clampContainer)
1066
+ return;
1067
+ const expanded = clampContainer.classList.toggle("is-expanded");
1068
+ button.setAttribute("aria-expanded", String(expanded));
1069
+ button.textContent = expanded ? "less" : "more";
1070
+ });
1071
+ });
1072
+ }
1073
+ function bindHistoryExpanders(root) {
1074
+ root.querySelectorAll("[data-history-expand]").forEach((button) => {
1075
+ button.addEventListener("click", () => {
1076
+ const expander = button.closest("[data-history-expander]");
1077
+ if (!expander)
1078
+ return;
1079
+ const expanded = expander.classList.toggle("is-expanded");
1080
+ button.setAttribute("aria-expanded", String(expanded));
1081
+ });
1082
+ });
1083
+ }
1018
1084
  function bindQueueDrawer(root) {
1019
1085
  const openButton = root.querySelector("[data-testid='mobile-queue-open']");
1020
1086
  const closeButton = root.querySelector("[data-testid='queue-drawer-close']");
@@ -1081,7 +1147,12 @@ function refreshDecisionOutputs(root, state, renderCurrentState, presentationAda
1081
1147
  }
1082
1148
  const wrapper = document.createElement("div");
1083
1149
  wrapper.innerHTML = renderSurfacePreview(state, presentationAdapter);
1084
- preview.replaceWith(wrapper.firstElementChild);
1150
+ const newPreview = wrapper.firstElementChild;
1151
+ preview.replaceWith(newPreview);
1152
+ if (newPreview && typeof newPreview.querySelectorAll === "function") {
1153
+ bindClampToggles(newPreview);
1154
+ bindHistoryExpanders(newPreview);
1155
+ }
1085
1156
  }
1086
1157
  function refreshSessionAudit(root, session, sessionExport, renderCurrentState) {
1087
1158
  const audit = root.querySelector("[data-testid='session-audit']");
@@ -1125,7 +1196,7 @@ function renderCandidateCard(candidate, state, presentationAdapter) {
1125
1196
  <dl class="field-stack">
1126
1197
  ${fieldItem("Source URL", presentation.sourceText)}
1127
1198
  ${fieldItem("Locator", candidate.locator?.locator ?? candidate.locator?.scheme ?? "none")}
1128
- ${fieldItem("Excerpt", candidate.locator?.excerpt ?? "none", "excerpt")}
1199
+ ${fieldItemClamped("Excerpt", candidate.locator?.excerpt ?? "none", "excerpt")}
1129
1200
  ${fieldItem("Extraction confidence", confidence === undefined ? "unknown" : formatConfidence(confidence))}
1130
1201
  ${fieldItem("Extractor", candidate.extraction.extractor ?? "unknown")}
1131
1202
  </dl>
@@ -1153,6 +1224,17 @@ function metaItem(label, value) {
1153
1224
  </div>
1154
1225
  `;
1155
1226
  }
1227
+ function fieldItemClamped(label, value, extraClass = "") {
1228
+ return `
1229
+ <div class="field ${extraClass}">
1230
+ <dt class="field-label">${escapeHtml(label)}</dt>
1231
+ <div class="excerpt-clamp" data-clamp>
1232
+ <dd class="field-value">${escapeHtml(value)}</dd>
1233
+ <button class="clamp-toggle" type="button" data-clamp-toggle aria-expanded="false">more</button>
1234
+ </div>
1235
+ </div>
1236
+ `;
1237
+ }
1156
1238
  function fieldItem(label, value, extraClass = "") {
1157
1239
  return `
1158
1240
  <div class="field ${extraClass}">