@growth-labs/cms 0.5.29 → 0.5.31

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.
Files changed (151) hide show
  1. package/dist/engine/foundry-dispatch.d.ts +7 -2
  2. package/dist/engine/foundry-dispatch.d.ts.map +1 -1
  3. package/dist/engine/foundry-dispatch.js +24 -6
  4. package/dist/engine/foundry-dispatch.js.map +1 -1
  5. package/dist/engine/fronts-publish-intent.d.ts +174 -0
  6. package/dist/engine/fronts-publish-intent.d.ts.map +1 -0
  7. package/dist/engine/fronts-publish-intent.js +352 -0
  8. package/dist/engine/fronts-publish-intent.js.map +1 -0
  9. package/dist/engine/fronts-publish.d.ts +80 -0
  10. package/dist/engine/fronts-publish.d.ts.map +1 -0
  11. package/dist/engine/fronts-publish.js +286 -0
  12. package/dist/engine/fronts-publish.js.map +1 -0
  13. package/dist/engine/index.d.ts +7 -0
  14. package/dist/engine/index.d.ts.map +1 -1
  15. package/dist/engine/index.js +10 -0
  16. package/dist/engine/index.js.map +1 -1
  17. package/dist/engine/publication-continuity.d.ts +77 -0
  18. package/dist/engine/publication-continuity.d.ts.map +1 -0
  19. package/dist/engine/publication-continuity.js +94 -0
  20. package/dist/engine/publication-continuity.js.map +1 -0
  21. package/dist/engine/publication-receipt/canonical-json.d.ts +34 -0
  22. package/dist/engine/publication-receipt/canonical-json.d.ts.map +1 -0
  23. package/dist/engine/publication-receipt/canonical-json.js +100 -0
  24. package/dist/engine/publication-receipt/canonical-json.js.map +1 -0
  25. package/dist/engine/publication-receipt/receipt.d.ts +316 -0
  26. package/dist/engine/publication-receipt/receipt.d.ts.map +1 -0
  27. package/dist/engine/publication-receipt/receipt.js +131 -0
  28. package/dist/engine/publication-receipt/receipt.js.map +1 -0
  29. package/dist/engine/publication-receipt/signature.d.ts +36 -0
  30. package/dist/engine/publication-receipt/signature.d.ts.map +1 -0
  31. package/dist/engine/publication-receipt/signature.js +79 -0
  32. package/dist/engine/publication-receipt/signature.js.map +1 -0
  33. package/dist/engine/publication.d.ts.map +1 -1
  34. package/dist/engine/publication.js +2 -0
  35. package/dist/engine/publication.js.map +1 -1
  36. package/dist/engine/publisher.d.ts.map +1 -1
  37. package/dist/engine/publisher.js +23 -1
  38. package/dist/engine/publisher.js.map +1 -1
  39. package/dist/engine/source-sections.d.ts +20 -0
  40. package/dist/engine/source-sections.d.ts.map +1 -0
  41. package/dist/engine/source-sections.js +130 -0
  42. package/dist/engine/source-sections.js.map +1 -0
  43. package/dist/engine/validator/checks/source-section-diagnostics.d.ts +7 -0
  44. package/dist/engine/validator/checks/source-section-diagnostics.d.ts.map +1 -0
  45. package/dist/engine/validator/checks/source-section-diagnostics.js +120 -0
  46. package/dist/engine/validator/checks/source-section-diagnostics.js.map +1 -0
  47. package/dist/engine/validator/index.d.ts +10 -0
  48. package/dist/engine/validator/index.d.ts.map +1 -1
  49. package/dist/engine/validator/index.js +4 -0
  50. package/dist/engine/validator/index.js.map +1 -1
  51. package/dist/engine/validator/types.d.ts +1 -1
  52. package/dist/engine/validator/types.d.ts.map +1 -1
  53. package/dist/engine/validator/types.js +10 -0
  54. package/dist/engine/validator/types.js.map +1 -1
  55. package/dist/integration/index.d.ts.map +1 -1
  56. package/dist/integration/index.js +10 -0
  57. package/dist/integration/index.js.map +1 -1
  58. package/dist/providers/types.d.ts +1 -0
  59. package/dist/providers/types.d.ts.map +1 -1
  60. package/dist/routes/content.d.ts.map +1 -1
  61. package/dist/routes/content.js +22 -2
  62. package/dist/routes/content.js.map +1 -1
  63. package/dist/routes/fronts-publish.d.ts +10 -0
  64. package/dist/routes/fronts-publish.d.ts.map +1 -0
  65. package/dist/routes/fronts-publish.js +250 -0
  66. package/dist/routes/fronts-publish.js.map +1 -0
  67. package/dist/routes/index.d.ts +3 -0
  68. package/dist/routes/index.d.ts.map +1 -1
  69. package/dist/routes/index.js +4 -0
  70. package/dist/routes/index.js.map +1 -1
  71. package/dist/routes/surveys.d.ts.map +1 -1
  72. package/dist/routes/surveys.js +13 -1
  73. package/dist/routes/surveys.js.map +1 -1
  74. package/dist/schema/insights-ingest.d.ts +28 -28
  75. package/dist/schema/layout.d.ts +2 -2
  76. package/dist/schema/migrations.d.ts.map +1 -1
  77. package/dist/schema/migrations.js +36 -0
  78. package/dist/schema/migrations.js.map +1 -1
  79. package/dist/schema/portable-text.d.ts +2 -2
  80. package/dist/schema/tables.d.ts +1 -1
  81. package/dist/schema/tables.d.ts.map +1 -1
  82. package/dist/schema/tables.js +2 -0
  83. package/dist/schema/tables.js.map +1 -1
  84. package/dist/schema/types.d.ts +59 -0
  85. package/dist/schema/types.d.ts.map +1 -1
  86. package/dist/schema/types.js.map +1 -1
  87. package/dist/surveys/access.d.ts +2 -0
  88. package/dist/surveys/access.d.ts.map +1 -1
  89. package/dist/surveys/access.js +7 -0
  90. package/dist/surveys/access.js.map +1 -1
  91. package/dist/surveys/errors.d.ts +8 -0
  92. package/dist/surveys/errors.d.ts.map +1 -1
  93. package/dist/surveys/errors.js +12 -0
  94. package/dist/surveys/errors.js.map +1 -1
  95. package/dist/surveys/schema.d.ts +129 -54
  96. package/dist/surveys/schema.d.ts.map +1 -1
  97. package/dist/surveys/schema.js +45 -0
  98. package/dist/surveys/schema.js.map +1 -1
  99. package/dist/ui/api/fronts/publish-callback.d.ts +3 -0
  100. package/dist/ui/api/fronts/publish-callback.d.ts.map +1 -0
  101. package/dist/ui/api/fronts/publish-callback.js +8 -0
  102. package/dist/ui/api/fronts/publish-callback.js.map +1 -0
  103. package/dist/ui/api/fronts/schedule.d.ts +3 -0
  104. package/dist/ui/api/fronts/schedule.d.ts.map +1 -0
  105. package/dist/ui/api/fronts/schedule.js +8 -0
  106. package/dist/ui/api/fronts/schedule.js.map +1 -0
  107. package/dist/ui/editor/ContentForm.d.ts.map +1 -1
  108. package/dist/ui/editor/ContentForm.js +52 -1
  109. package/dist/ui/editor/ContentForm.js.map +1 -1
  110. package/dist/ui/editor/body-warnings.d.ts +27 -0
  111. package/dist/ui/editor/body-warnings.d.ts.map +1 -0
  112. package/dist/ui/editor/body-warnings.js +58 -0
  113. package/dist/ui/editor/body-warnings.js.map +1 -0
  114. package/dist/ui/screens/SurveyResponsesScreen.d.ts +7 -1
  115. package/dist/ui/screens/SurveyResponsesScreen.d.ts.map +1 -1
  116. package/dist/ui/screens/SurveyResponsesScreen.js +141 -32
  117. package/dist/ui/screens/SurveyResponsesScreen.js.map +1 -1
  118. package/migrations/0026_fronts_publish.sql +31 -0
  119. package/migrations/0027_fronts_publish_intent.sql +44 -0
  120. package/package.json +1 -1
  121. package/src/engine/foundry-dispatch.ts +22 -6
  122. package/src/engine/fronts-publish-intent.ts +676 -0
  123. package/src/engine/fronts-publish.ts +417 -0
  124. package/src/engine/index.ts +60 -0
  125. package/src/engine/publication-continuity.ts +140 -0
  126. package/src/engine/publication-receipt/canonical-json.ts +123 -0
  127. package/src/engine/publication-receipt/receipt.ts +160 -0
  128. package/src/engine/publication-receipt/signature.ts +112 -0
  129. package/src/engine/publication.ts +2 -0
  130. package/src/engine/publisher.ts +23 -1
  131. package/src/engine/source-sections.ts +153 -0
  132. package/src/engine/validator/checks/source-section-diagnostics.ts +187 -0
  133. package/src/engine/validator/index.ts +12 -0
  134. package/src/engine/validator/types.ts +19 -0
  135. package/src/integration/index.ts +10 -0
  136. package/src/providers/types.ts +1 -1
  137. package/src/routes/content.ts +23 -2
  138. package/src/routes/fronts-publish.ts +292 -0
  139. package/src/routes/index.ts +8 -0
  140. package/src/routes/surveys.ts +12 -1
  141. package/src/schema/migrations.ts +36 -0
  142. package/src/schema/tables.ts +2 -0
  143. package/src/schema/types.ts +61 -0
  144. package/src/surveys/access.ts +9 -0
  145. package/src/surveys/errors.ts +12 -0
  146. package/src/surveys/schema.ts +47 -0
  147. package/src/ui/api/fronts/publish-callback.ts +18 -0
  148. package/src/ui/api/fronts/schedule.ts +18 -0
  149. package/src/ui/editor/ContentForm.tsx +93 -0
  150. package/src/ui/editor/body-warnings.ts +78 -0
  151. package/src/ui/screens/SurveyResponsesScreen.tsx +369 -121
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useState } from 'react';
2
+ import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { canViewSurveyVerbatims } from '../../surveys/access.js';
3
4
  import { isSurveyErrorCode, SURVEY_ERROR_CODE_HEADER, SURVEY_ERROR_DEFINITIONS, } from '../../surveys/errors.js';
4
- import { SurveyResultsSchema } from '../../surveys/schema.js';
5
+ import { SurveyResultsSchema, } from '../../surveys/schema.js';
6
+ import { useWorkspace } from '../workspace-context.js';
5
7
  export function parseSurveyResultsPayload(value) {
6
8
  const parsed = SurveyResultsSchema.safeParse(value);
7
9
  return parsed.success ? parsed.data : null;
@@ -17,20 +19,26 @@ export async function classifySurveyResultsResponse(response) {
17
19
  }
18
20
  if (response.status === 404 && code === 'survey_not_found')
19
21
  return { kind: 'not_found' };
20
- if (response.status === 502 && code === 'survey_provider_malformed') {
22
+ if (response.status === 502 && code === 'survey_provider_malformed')
21
23
  return { kind: 'malformed' };
24
+ if (response.status === 502 && code === 'survey_invalid_response')
25
+ return { kind: 'malformed' };
26
+ if (response.status === 502 && code === 'survey_upstream_auth_failed') {
27
+ return { kind: 'auth_failed' };
22
28
  }
23
29
  if (response.status === 503 && code === 'survey_provider_unavailable') {
24
30
  return { kind: 'unavailable' };
25
31
  }
26
32
  return { kind: 'retryable' };
27
33
  }
34
+ const PAGE_SIZE = 100;
28
35
  export function SurveyResponsesScreen({ surveyId }) {
29
36
  const [state, setState] = useState({ kind: 'loading' });
30
- const load = useCallback(async () => {
37
+ const [offset, setOffset] = useState(0);
38
+ const load = useCallback(async (nextOffset) => {
31
39
  setState({ kind: 'loading' });
32
40
  try {
33
- const response = await fetch(`/admin/api/surveys/${encodeURIComponent(surveyId)}`);
41
+ const response = await fetch(`/admin/api/surveys/${encodeURIComponent(surveyId)}?offset=${nextOffset}`);
34
42
  setState(await classifySurveyResultsResponse(response));
35
43
  }
36
44
  catch {
@@ -38,33 +46,120 @@ export function SurveyResponsesScreen({ surveyId }) {
38
46
  }
39
47
  }, [surveyId]);
40
48
  useEffect(() => {
41
- void load();
42
- }, [load]);
43
- return _jsx(SurveyResponsesBody, { surveyId: surveyId, state: state, onRetry: load });
49
+ void load(offset);
50
+ }, [load, offset]);
51
+ const { state: workspace } = useWorkspace();
52
+ return (_jsx(SurveyResponsesBody, { surveyId: surveyId, state: state, offset: offset, role: workspace.user?.role ?? null, onRetry: () => void load(offset), onOffsetChange: setOffset }));
44
53
  }
45
- export function SurveyResponsesBody({ surveyId, state, onRetry, }) {
54
+ export function SurveyResponsesBody({ surveyId, state, offset = 0, role = null, onRetry, onOffsetChange, }) {
55
+ const canSeeVerbatims = canViewSurveyVerbatims(role);
46
56
  if (state.kind === 'loading')
47
57
  return _jsx(StatusPage, { title: "Loading survey responses\u2026" });
48
58
  if (state.kind === 'forbidden')
49
59
  return _jsx(StatusPage, { title: "You do not have permission to view survey responses." });
50
60
  if (state.kind === 'unavailable')
51
61
  return _jsx(StatusPage, { title: "Survey reporting is not configured for this site." });
62
+ if (state.kind === 'auth_failed')
63
+ return (_jsx(StatusPage, { title: "Survey reporting rejected this site\u2019s credentials.", detail: "The signing secret shared with the reporting upstream is missing or does not match.", retry: onRetry }));
52
64
  if (state.kind === 'malformed')
53
65
  return _jsx(StatusPage, { title: "Masthead received invalid survey data.", retry: onRetry });
54
66
  if (state.kind === 'not_found')
55
67
  return _jsx(StatusPage, { title: "The configured survey could not be found." });
56
68
  if (state.kind === 'retryable')
57
- return _jsx(StatusPage, { title: "Survey responses could not be loaded.", retry: onRetry });
69
+ return (_jsx(StatusPage, { title: "Survey responses could not be loaded.", detail: "The request did not reach the reporting upstream, or it failed before replying.", retry: onRetry }));
58
70
  const { data } = state;
59
- const freeTextByQuestion = new Map();
71
+ const shown = data.responses.length;
72
+ const total = data.survey.responseCount;
73
+ const rangeStart = total === 0 ? 0 : offset + 1;
74
+ const rangeEnd = offset + shown;
75
+ const hasMore = rangeEnd < total;
76
+ return (_jsxs("div", { className: "page page-wide fade-in", style: { maxWidth: 1340 }, children: [_jsxs("div", { className: "page-head", children: [_jsxs("div", { children: [_jsx("h1", { className: "page-title", children: data.survey.title }), _jsxs("div", { className: "page-sub", children: [total === 0
77
+ ? 'No responses yet'
78
+ : `Showing ${rangeStart}–${rangeEnd} of ${total} ${total === 1 ? 'response' : 'responses'}`, ' ', "\u00B7 ", data.survey.status] })] }), _jsx("a", { className: "btn", href: `/admin/api/surveys/${encodeURIComponent(surveyId)}/responses.csv`, children: "Export CSV" })] }), total === 0 ? (_jsxs("div", { className: "card", role: "status", children: [_jsx("h2", { className: "card-title", children: "No responses yet" }), _jsx("p", { className: "page-sub", children: "Aggregates and answers will appear after submission." })] })) : (_jsxs(_Fragment, { children: [_jsx(HeadlineScale, { questions: data.questions }), canSeeVerbatims ? (_jsx(VerbatimFeed, { data: data })) : (_jsxs("div", { className: "card", role: "status", style: { marginTop: 24 }, children: [_jsx("h2", { className: "card-title", children: "Individual answers are restricted" }), _jsx("p", { className: "page-sub", children: "Aggregates below are available to your role. Verbatim answers and respondent attribution require owner or senior editor." })] })), _jsx(Aggregates, { data: data }), (offset > 0 || hasMore) && onOffsetChange ? (_jsxs("div", { className: "page-head", style: { marginTop: 24 }, children: [_jsx("button", { className: "btn", type: "button", disabled: offset === 0, onClick: () => onOffsetChange(Math.max(offset - PAGE_SIZE, 0)), children: "Previous" }), _jsx("button", { className: "btn", type: "button", disabled: !hasMore, onClick: () => onOffsetChange(offset + PAGE_SIZE), children: "Next" })] })) : null] }))] }));
79
+ }
80
+ // The first scale question is the survey's headline number. Leading with the
81
+ // distribution keeps one harsh verbatim from setting the tone of a readout.
82
+ function HeadlineScale({ questions }) {
83
+ const scale = questions.find((question) => question.kind === 'scale');
84
+ if (!scale || scale.kind !== 'scale' || scale.answerCount === 0)
85
+ return null;
86
+ const midpoint = (scale.options.min + scale.options.max) / 2;
87
+ const favourable = Object.entries(scale.distribution)
88
+ .filter(([value]) => Number(value) > midpoint)
89
+ .reduce((sum, [, count]) => sum + count, 0);
90
+ const share = Math.round((favourable / scale.answerCount) * 100);
91
+ return (_jsxs("section", { className: "card", "aria-labelledby": "survey-headline", style: { marginTop: 24 }, children: [_jsx("div", { className: "kicker", children: scale.prompt }), _jsxs("h2", { className: "card-title", id: "survey-headline", children: [share, "% rate ", midpoint.toFixed(1), " or better \u00B7 average", ' ', scale.average === null ? '—' : scale.average.toFixed(2)] }), _jsx(CountTable, { values: scale.distribution })] }));
92
+ }
93
+ function VerbatimFeed({ data }) {
94
+ const textQuestionIds = new Set(data.questions.filter((question) => question.kind === 'text').map((question) => question.id));
95
+ const promptById = new Map(data.questions.map((question) => [question.id, question.prompt]));
96
+ const freeTextByResponse = new Map();
60
97
  for (const entry of data.freeText) {
61
- const entries = freeTextByQuestion.get(entry.questionId) ?? [];
62
- entries.push(entry);
63
- freeTextByQuestion.set(entry.questionId, entries);
98
+ const entries = freeTextByResponse.get(entry.responseId) ?? [];
99
+ entries.push(entry.value);
100
+ freeTextByResponse.set(entry.responseId, entries);
64
101
  }
65
- return (_jsxs("div", { className: "page page-wide fade-in", style: { maxWidth: 1340 }, children: [_jsxs("div", { className: "page-head", children: [_jsxs("div", { children: [_jsx("h1", { className: "page-title", children: data.survey.title }), _jsxs("div", { className: "page-sub", children: [data.survey.responseCount, " ", data.survey.responseCount === 1 ? 'response' : 'responses', ' ', "\u00B7 ", data.survey.status] })] }), _jsx("a", { className: "btn", href: `/admin/api/surveys/${encodeURIComponent(surveyId)}/responses.csv`, children: "Export CSV" })] }), data.survey.responseCount === 0 ? (_jsxs("div", { className: "card", role: "status", children: [_jsx("h2", { className: "card-title", children: "No responses yet" }), _jsx("p", { className: "page-sub", children: "Aggregates and free-text answers will appear after submission." })] })) : (_jsxs("section", { className: "card", "aria-labelledby": "survey-response-list", children: [_jsx("h2", { className: "card-title", id: "survey-response-list", children: "Recent responses" }), _jsx("div", { className: "masthead-rule" }), _jsxs("table", { className: "tbl", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Submitted" }), _jsx("th", { children: "Channel" }), _jsx("th", { children: "Audience" }), _jsx("th", { children: "Attribution" })] }) }), _jsx("tbody", { children: data.responses.map((response) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx("time", { dateTime: response.submittedAt, children: formatTime(response.submittedAt) }) }), _jsx("td", { children: response.channel }), _jsx("td", { children: segmentLabel(response.respondentSegment) }), _jsx("td", { children: response.anonymous ? 'Anonymous' : 'Known respondent' })] }, response.id))) })] })] })), _jsxs("section", { "aria-labelledby": "survey-aggregates", style: { marginTop: 24 }, children: [_jsx("h2", { className: "page-title", id: "survey-aggregates", style: { fontSize: 22 }, children: "Question aggregates" }), _jsx("div", { className: "grid-split-wide", style: { alignItems: 'start', marginTop: 12 }, children: data.questions.map((question) => (_jsxs("article", { className: "card", children: [_jsxs("div", { className: "kicker", children: ["Q", question.position + 1, " \u00B7 ", questionAudienceLabel(question.audience)] }), _jsx("h3", { className: "card-title", children: question.prompt }), question.counts && _jsx(CountTable, { values: question.counts }), question.distribution && (_jsxs(_Fragment, { children: [_jsxs("p", { className: "page-sub", children: ["Average: ", question.average === null ? '—' : question.average.toFixed(2)] }), _jsx(CountTable, { values: question.distribution })] })), question.kind === 'text' && (_jsxs("p", { className: "page-sub", children: [question.responseCount ?? 0, " free-text responses"] }))] }, question.id))) })] }), _jsxs("section", { "aria-labelledby": "survey-free-text", style: { marginTop: 24 }, children: [_jsx("h2", { className: "page-title", id: "survey-free-text", style: { fontSize: 22 }, children: "Free-text responses" }), data.questions
66
- .filter((question) => question.kind === 'text')
67
- .map((question) => (_jsxs("div", { className: "card", style: { marginTop: 12 }, children: [_jsx("h3", { className: "card-title", children: question.prompt }), (freeTextByQuestion.get(question.id) ?? []).length === 0 ? (_jsx("p", { className: "page-sub", children: "No free-text responses." })) : (_jsx("ul", { children: (freeTextByQuestion.get(question.id) ?? []).map((entry) => (_jsx("li", { children: entry.value }, `${entry.responseId}:${entry.submittedAt}`))) }))] }, question.id)))] })] }));
102
+ return (_jsxs("section", { "aria-labelledby": "survey-verbatims", style: { marginTop: 24 }, children: [_jsx("h2", { className: "page-title", id: "survey-verbatims", style: { fontSize: 22 }, children: "Responses" }), _jsx("div", { style: { marginTop: 12 }, children: data.responses.map((response) => {
103
+ const written = freeTextByResponse.get(response.id) ?? [];
104
+ const choices = (response.answers ?? []).filter((answer) => !textQuestionIds.has(answer.questionId));
105
+ return (_jsxs("article", { className: "card", style: { marginTop: 12 }, children: [_jsxs("div", { className: "kicker", children: [_jsx("time", { dateTime: response.submittedAt, children: formatTime(response.submittedAt) }), " \u00B7", ' ', segmentLabel(response.respondentSegment), " \u00B7 ", respondentLabel(response)] }), written.length === 0 ? (_jsx("p", { className: "page-sub", children: "No written answer." })) : (written.map((value, index) => (
106
+ // biome-ignore lint/suspicious/noArrayIndexKey: values are free text with no stable id
107
+ _jsx("blockquote", { children: value }, `${response.id}:${index}`)))), _jsx("div", { className: "chip-row", children: choices.map((answer) => (_jsx("span", { className: "chip", title: promptById.get(answer.questionId) ?? answer.questionId, children: formatAnswerValue(answer.value) }, `${response.id}:${answer.questionId}`))) })] }, response.id));
108
+ }) })] }));
109
+ }
110
+ function Aggregates({ data }) {
111
+ // Flat counts cannot express the finding that matters most — how one cohort
112
+ // differs from another — so every aggregate can be split by another question.
113
+ const splitCandidates = data.questions.filter((question) => question.kind === 'single_choice' || question.kind === 'scale');
114
+ const [splitBy, setSplitBy] = useState('');
115
+ const splitQuestion = splitCandidates.find((question) => question.id === splitBy);
116
+ const answersByResponse = useMemo(() => {
117
+ const map = new Map();
118
+ for (const response of data.responses) {
119
+ map.set(response.id, new Map((response.answers ?? []).map((answer) => [answer.questionId, answer.value])));
120
+ }
121
+ return map;
122
+ }, [data.responses]);
123
+ const cohorts = useMemo(() => {
124
+ if (!splitQuestion)
125
+ return null;
126
+ const buckets = new Map();
127
+ for (const response of data.responses) {
128
+ const value = answersByResponse.get(response.id)?.get(splitQuestion.id);
129
+ if (value === undefined)
130
+ continue;
131
+ const key = formatAnswerValue(value);
132
+ const ids = buckets.get(key) ?? [];
133
+ ids.push(response.id);
134
+ buckets.set(key, ids);
135
+ }
136
+ return buckets;
137
+ }, [answersByResponse, data.responses, splitQuestion]);
138
+ return (_jsxs("section", { "aria-labelledby": "survey-aggregates", style: { marginTop: 24 }, children: [_jsxs("div", { className: "page-head", children: [_jsx("h2", { className: "page-title", id: "survey-aggregates", style: { fontSize: 22 }, children: "Question aggregates" }), _jsxs("label", { children: [_jsx("span", { className: "page-sub", children: "Split by " }), _jsxs("select", { value: splitBy, onChange: (event) => setSplitBy(event.target.value), children: [_jsx("option", { value: "", children: "No split" }), splitCandidates.map((question) => (_jsx("option", { value: question.id, children: question.prompt }, question.id)))] })] })] }), _jsx("div", { className: "grid-split-wide", style: { alignItems: 'start', marginTop: 12 }, children: data.questions.map((question) => (_jsxs("article", { className: "card", children: [_jsxs("div", { className: "kicker", children: ["Q", question.position + 1, " \u00B7 ", questionAudienceLabel(question.audience)] }), _jsx("h3", { className: "card-title", children: question.prompt }), question.counts && _jsx(CountTable, { values: question.counts }), question.distribution && (_jsxs(_Fragment, { children: [_jsxs("p", { className: "page-sub", children: ["Average: ", question.average === null ? '—' : question.average.toFixed(2)] }), _jsx(CountTable, { values: question.distribution })] })), question.kind === 'text' && (_jsxs("p", { className: "page-sub", children: [question.responseCount ?? 0, " written answers"] })), cohorts && splitQuestion && splitQuestion.id !== question.id ? (_jsx(CrossTab, { question: question, cohorts: cohorts, answersByResponse: answersByResponse })) : null] }, question.id))) })] }));
139
+ }
140
+ function CrossTab({ question, cohorts, answersByResponse, }) {
141
+ const rows = [...cohorts.entries()].map(([cohort, responseIds]) => {
142
+ const values = responseIds
143
+ .map((id) => answersByResponse.get(id)?.get(question.id))
144
+ .filter((value) => value !== undefined);
145
+ if (question.kind === 'scale') {
146
+ const numbers = values.filter((value) => typeof value === 'number');
147
+ const average = numbers.length
148
+ ? numbers.reduce((sum, value) => sum + value, 0) / numbers.length
149
+ : null;
150
+ return { cohort, n: values.length, summary: average === null ? '—' : average.toFixed(2) };
151
+ }
152
+ const counts = new Map();
153
+ for (const value of values.flatMap((value) => (Array.isArray(value) ? value : [value]))) {
154
+ const key = formatAnswerValue(value);
155
+ counts.set(key, (counts.get(key) ?? 0) + 1);
156
+ }
157
+ const top = [...counts.entries()].sort((left, right) => right[1] - left[1])[0];
158
+ return { cohort, n: values.length, summary: top ? `${top[0]} (${top[1]})` : '—' };
159
+ });
160
+ if (rows.length === 0)
161
+ return null;
162
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "masthead-rule" }), _jsxs("table", { className: "tbl", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Cohort" }), _jsx("th", { children: "n" }), _jsx("th", { children: question.kind === 'scale' ? 'Average' : 'Most common' })] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { children: [_jsx("td", { children: row.cohort }), _jsx("td", { children: row.n }), _jsx("td", { children: row.summary })] }, row.cohort))) })] })] }));
68
163
  }
69
164
  async function readSurveyErrorCode(response) {
70
165
  const headerCode = response.headers.get(SURVEY_ERROR_CODE_HEADER);
@@ -83,28 +178,42 @@ async function readSurveyErrorCode(response) {
83
178
  function isPlainRecord(value) {
84
179
  return typeof value === 'object' && value !== null && !Array.isArray(value);
85
180
  }
181
+ function respondentLabel(response) {
182
+ if (response.respondent?.label) {
183
+ return response.respondent.detail
184
+ ? `${response.respondent.label} · ${response.respondent.detail}`
185
+ : response.respondent.label;
186
+ }
187
+ return response.anonymous ? 'Anonymous' : 'Known respondent';
188
+ }
189
+ function formatAnswerValue(value) {
190
+ if (Array.isArray(value))
191
+ return value.join(', ');
192
+ return String(value);
193
+ }
86
194
  function StatusPage({ title, detail, retry, }) {
87
- return (_jsxs("div", { className: "page", role: "status", children: [_jsx("h1", { className: "page-title", children: "Survey responses" }), _jsx("p", { className: "page-sub", children: title }), detail && _jsx("p", { className: "page-sub", children: detail }), retry && (_jsx("button", { type: "button", className: "btn", onClick: retry, children: "Retry" }))] }));
195
+ return (_jsxs("div", { className: "page", role: "status", children: [_jsx("h1", { className: "page-title", children: "Survey responses" }), _jsx("p", { className: "page-sub", children: title }), detail ? _jsx("p", { className: "page-sub", children: detail }) : null, retry ? (_jsx("button", { className: "btn", onClick: retry, type: "button", children: "Retry" })) : null] }));
88
196
  }
89
197
  function CountTable({ values }) {
90
- return (_jsx("table", { className: "tbl", children: _jsx("tbody", { children: Object.entries(values).map(([label, count]) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: label }), _jsx("td", { className: "tnum", children: count })] }, label))) }) }));
198
+ const total = Object.values(values).reduce((sum, count) => sum + count, 0);
199
+ return (_jsx("dl", { className: "count-list", children: Object.entries(values).map(([label, count]) => (_jsxs("div", { children: [_jsx("dt", { children: label }), _jsxs("dd", { children: [count, total > 0 ? ` · ${Math.round((count / total) * 100)}%` : ''] })] }, label))) }));
91
200
  }
92
- function formatTime(value) {
93
- const date = new Date(value);
94
- return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
95
- }
96
- function segmentLabel(value) {
97
- if (value === 'member')
201
+ function segmentLabel(segment) {
202
+ if (segment === 'member')
98
203
  return 'Member';
99
- if (value === 'non_member')
204
+ if (segment === 'non_member')
100
205
  return 'Non-member';
101
206
  return 'Unknown';
102
207
  }
103
- function questionAudienceLabel(value) {
104
- if (value === 'member')
105
- return 'Members only';
106
- if (value === 'non_member')
107
- return 'Non-members only';
108
- return 'All respondents';
208
+ function questionAudienceLabel(audience) {
209
+ if (audience === 'member')
210
+ return 'Members';
211
+ if (audience === 'non_member')
212
+ return 'Non-members';
213
+ return 'Everyone';
214
+ }
215
+ function formatTime(value) {
216
+ const date = new Date(value);
217
+ return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
109
218
  }
110
219
  //# sourceMappingURL=SurveyResponsesScreen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SurveyResponsesScreen.js","sourceRoot":"","sources":["../../../src/ui/screens/SurveyResponsesScreen.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SurveyResponsesScreen.js","sourceRoot":"","sources":["../../../src/ui/screens/SurveyResponsesScreen.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ -- 0026_fronts_publish: additive, flag-gated Fronts publish path (foundryd).
2
+ --
3
+ -- Adds the item-level lifecycle marker driven by foundryd's outbound
4
+ -- publish-callbacks, and a durable, event_id-keyed receiver log for those
5
+ -- callbacks (idempotency + receipt audit). The legacy D1 cron publish path
6
+ -- (content_items.status='scheduled' -> 'published') is UNTOUCHED and remains
7
+ -- the rollback lever. Only a confirmed `live` callback (with a receipt) drives
8
+ -- the item to published, through the existing content_publication_attempts 2PC
9
+ -- ledger.
10
+
11
+ ALTER TABLE content_items
12
+ ADD COLUMN fronts_publish_status TEXT
13
+ CHECK (fronts_publish_status IS NULL OR fronts_publish_status IN ('queued','publishing','live','late','failed'));
14
+
15
+ CREATE TABLE fronts_publish_events (
16
+ event_id TEXT PRIMARY KEY,
17
+ intent_id TEXT,
18
+ content_id TEXT NOT NULL,
19
+ version INTEGER,
20
+ correlation_id TEXT,
21
+ status TEXT NOT NULL CHECK (status IN ('queued','publishing','live','late','failed')),
22
+ occurred_at INTEGER,
23
+ receipt_json TEXT,
24
+ error TEXT,
25
+ published INTEGER NOT NULL DEFAULT 0,
26
+ attempt_id TEXT,
27
+ received_at INTEGER NOT NULL DEFAULT (unixepoch()),
28
+ FOREIGN KEY (content_id) REFERENCES content_items(id) ON DELETE CASCADE
29
+ );
30
+ CREATE INDEX idx_fronts_publish_events_content ON fronts_publish_events(content_id, received_at DESC);
31
+ CREATE INDEX idx_fronts_publish_events_intent ON fronts_publish_events(intent_id);
@@ -0,0 +1,44 @@
1
+ -- 0027_fronts_publish_intent: the CMS-owned canonical values a pull consumer
2
+ -- needs to build a complete Fronts publish intent (packages#500).
3
+ --
4
+ -- The 0026 schedule poll returned only presentation columns (id/type/slug/
5
+ -- title/channel/publish_at/status), so a consumer could not construct
6
+ -- `intent_id = masthead:<content_id>:<version>` or resolve publish targets and
7
+ -- media without inventing values. These three columns are the authoritative
8
+ -- CMS storage for the three values that had no source at all.
9
+ --
10
+ -- canonical_version Monotonic integer revision of the item's
11
+ -- canonical content. Starts at 1 on create and
12
+ -- is advanced by exactly one seam
13
+ -- (updateContentItem's base content_items
14
+ -- UPDATE). Deliberately NOT advanced by
15
+ -- fronts_publish_status marker writes or by
16
+ -- polling, so an intent identity is stable for
17
+ -- as long as the canonical content is.
18
+ --
19
+ -- fronts_channel_targets JSON array of publish target slugs for the
20
+ -- out-of-process publish path. NULL means "not
21
+ -- armed": hydration fails loudly rather than
22
+ -- emitting an empty target list that would read
23
+ -- as a complete intent with nothing to publish.
24
+ -- This is NOT content_items.channel, which is
25
+ -- the single editorial Fronts channel slug.
26
+ --
27
+ -- fronts_publish_trigger_token Raw CMS-owned per-item publish trigger token,
28
+ -- secret at rest exactly like
29
+ -- video_content.processing_trigger_token
30
+ -- (migration 0006). The read contract NEVER
31
+ -- returns it; it returns only
32
+ -- `sha256:<hex>` computed from it.
33
+ --
34
+ -- Additive and backwards compatible: every existing row keeps working on the
35
+ -- untouched legacy D1 cron publish path, which remains the rollback lever.
36
+
37
+ ALTER TABLE content_items
38
+ ADD COLUMN canonical_version INTEGER NOT NULL DEFAULT 1;
39
+
40
+ ALTER TABLE content_items
41
+ ADD COLUMN fronts_channel_targets TEXT;
42
+
43
+ ALTER TABLE content_items
44
+ ADD COLUMN fronts_publish_trigger_token TEXT;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-labs/cms",
3
- "version": "0.5.29",
3
+ "version": "0.5.31",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "growth-labs-cms-migrations": "./dist/cli/migrations.js"
@@ -400,8 +400,13 @@ export async function applyFoundryCallback(db: D1Database, input: CallbackInput)
400
400
 
401
401
  /**
402
402
  * Verify a HMAC-SHA256 signature over `rawBody` using `secret`.
403
- * The `signature` is expected as a lowercase hex string.
404
- * Uses Web Crypto `crypto.subtle` and a constant-time comparison.
403
+ *
404
+ * The `signature` is a hex string, with or without a `sha256=` scheme prefix.
405
+ * Foundry (and the GitHub-webhook convention it mirrors) sends
406
+ * `X-Foundry-Signature: sha256=<hex>`; the prefix MUST be stripped before the
407
+ * hex parse or every prefixed signature fails to decode and the callback 401s.
408
+ * Comparison is case-insensitive on the hex and constant-time on the bytes.
409
+ * Uses Web Crypto `crypto.subtle`.
405
410
  */
406
411
  export async function verifyHmac(
407
412
  secret: string,
@@ -410,13 +415,24 @@ export async function verifyHmac(
410
415
  ): Promise<boolean> {
411
416
  if (!signature) return false
412
417
 
418
+ // Strip the optional `sha256=` scheme prefix (case-insensitive) and any
419
+ // surrounding whitespace before decoding the hex digest.
420
+ let hex = signature.trim()
421
+ if (/^sha256=/i.test(hex)) hex = hex.slice('sha256='.length).trim()
422
+ if (!hex) return false
423
+ // Reject non-hex up front: `Number.parseInt('1z', 16)` returns 1 (parses the
424
+ // leading nibble and stops), so malformed hex would otherwise decode to a
425
+ // truncated-but-valid byte string. The constant-time compare still guards
426
+ // forgery, but a strict charset check keeps the decode honest.
427
+ if (!/^[0-9a-f]+$/i.test(hex)) return false
428
+
413
429
  let sigBytes: Uint8Array
414
430
  try {
415
431
  // Parse the hex signature
416
- if (signature.length % 2 !== 0) return false
417
- sigBytes = new Uint8Array(signature.length / 2)
418
- for (let i = 0; i < signature.length; i += 2) {
419
- const byte = Number.parseInt(signature.slice(i, i + 2), 16)
432
+ if (hex.length % 2 !== 0) return false
433
+ sigBytes = new Uint8Array(hex.length / 2)
434
+ for (let i = 0; i < hex.length; i += 2) {
435
+ const byte = Number.parseInt(hex.slice(i, i + 2), 16)
420
436
  if (Number.isNaN(byte)) return false
421
437
  sigBytes[i / 2] = byte
422
438
  }