@the-syllabus/analysis-renderers 0.2.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.
Files changed (128) hide show
  1. package/dist/cells/RelationshipCardCell.d.ts +10 -0
  2. package/dist/cells/RelationshipCardCell.d.ts.map +1 -0
  3. package/dist/cells/RelationshipCardCell.js +91 -0
  4. package/dist/cells/RelationshipCardCell.js.map +1 -0
  5. package/dist/cells/TacticCardCell.d.ts +12 -0
  6. package/dist/cells/TacticCardCell.d.ts.map +1 -0
  7. package/dist/cells/TacticCardCell.js +77 -0
  8. package/dist/cells/TacticCardCell.js.map +1 -0
  9. package/dist/cells/TemplateCardCell.d.ts +29 -0
  10. package/dist/cells/TemplateCardCell.d.ts.map +1 -0
  11. package/dist/cells/TemplateCardCell.js +202 -0
  12. package/dist/cells/TemplateCardCell.js.map +1 -0
  13. package/dist/cells/index.d.ts +15 -0
  14. package/dist/cells/index.d.ts.map +1 -0
  15. package/dist/cells/index.js +85 -0
  16. package/dist/cells/index.js.map +1 -0
  17. package/dist/components/ConditionCards.d.ts +18 -0
  18. package/dist/components/ConditionCards.d.ts.map +1 -0
  19. package/dist/components/ConditionCards.js +28 -0
  20. package/dist/components/ConditionCards.js.map +1 -0
  21. package/dist/components/EvidenceTrail.d.ts +54 -0
  22. package/dist/components/EvidenceTrail.d.ts.map +1 -0
  23. package/dist/components/EvidenceTrail.js +98 -0
  24. package/dist/components/EvidenceTrail.js.map +1 -0
  25. package/dist/dispatch/SubRendererDispatch.d.ts +39 -0
  26. package/dist/dispatch/SubRendererDispatch.d.ts.map +1 -0
  27. package/dist/dispatch/SubRendererDispatch.js +153 -0
  28. package/dist/dispatch/SubRendererDispatch.js.map +1 -0
  29. package/dist/hooks/useProseExtraction.d.ts +38 -0
  30. package/dist/hooks/useProseExtraction.d.ts.map +1 -0
  31. package/dist/hooks/useProseExtraction.js +93 -0
  32. package/dist/hooks/useProseExtraction.js.map +1 -0
  33. package/dist/index.d.ts +32 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +38 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/provenance/ProvenanceSectionIcon.d.ts +15 -0
  38. package/dist/provenance/ProvenanceSectionIcon.d.ts.map +1 -0
  39. package/dist/provenance/ProvenanceSectionIcon.js +11 -0
  40. package/dist/provenance/ProvenanceSectionIcon.js.map +1 -0
  41. package/dist/renderers/AccordionRenderer.d.ts +29 -0
  42. package/dist/renderers/AccordionRenderer.d.ts.map +1 -0
  43. package/dist/renderers/AccordionRenderer.js +315 -0
  44. package/dist/renderers/AccordionRenderer.js.map +1 -0
  45. package/dist/renderers/CardGridRenderer.d.ts +24 -0
  46. package/dist/renderers/CardGridRenderer.d.ts.map +1 -0
  47. package/dist/renderers/CardGridRenderer.js +321 -0
  48. package/dist/renderers/CardGridRenderer.js.map +1 -0
  49. package/dist/renderers/CardRenderer.d.ts +27 -0
  50. package/dist/renderers/CardRenderer.d.ts.map +1 -0
  51. package/dist/renderers/CardRenderer.js +337 -0
  52. package/dist/renderers/CardRenderer.js.map +1 -0
  53. package/dist/renderers/IdeaEvolutionRenderer.d.ts +16 -0
  54. package/dist/renderers/IdeaEvolutionRenderer.d.ts.map +1 -0
  55. package/dist/renderers/IdeaEvolutionRenderer.js +187 -0
  56. package/dist/renderers/IdeaEvolutionRenderer.js.map +1 -0
  57. package/dist/renderers/ProseRenderer.d.ts +10 -0
  58. package/dist/renderers/ProseRenderer.d.ts.map +1 -0
  59. package/dist/renderers/ProseRenderer.js +42 -0
  60. package/dist/renderers/ProseRenderer.js.map +1 -0
  61. package/dist/renderers/RawJsonRenderer.d.ts +8 -0
  62. package/dist/renderers/RawJsonRenderer.d.ts.map +1 -0
  63. package/dist/renderers/RawJsonRenderer.js +17 -0
  64. package/dist/renderers/RawJsonRenderer.js.map +1 -0
  65. package/dist/renderers/StatSummaryRenderer.d.ts +12 -0
  66. package/dist/renderers/StatSummaryRenderer.d.ts.map +1 -0
  67. package/dist/renderers/StatSummaryRenderer.js +93 -0
  68. package/dist/renderers/StatSummaryRenderer.js.map +1 -0
  69. package/dist/renderers/SynthesisRenderer.d.ts +15 -0
  70. package/dist/renderers/SynthesisRenderer.d.ts.map +1 -0
  71. package/dist/renderers/SynthesisRenderer.js +60 -0
  72. package/dist/renderers/SynthesisRenderer.js.map +1 -0
  73. package/dist/renderers/TableRenderer.d.ts +19 -0
  74. package/dist/renderers/TableRenderer.d.ts.map +1 -0
  75. package/dist/renderers/TableRenderer.js +273 -0
  76. package/dist/renderers/TableRenderer.js.map +1 -0
  77. package/dist/styles/accordion.css +376 -0
  78. package/dist/styles/index.css +5 -0
  79. package/dist/styles/renderers.css +1049 -0
  80. package/dist/sub-renderers/SubRenderers.d.ts +73 -0
  81. package/dist/sub-renderers/SubRenderers.d.ts.map +1 -0
  82. package/dist/sub-renderers/SubRenderers.js +2462 -0
  83. package/dist/sub-renderers/SubRenderers.js.map +1 -0
  84. package/dist/tokens/DesignTokenContext.d.ts +40 -0
  85. package/dist/tokens/DesignTokenContext.d.ts.map +1 -0
  86. package/dist/tokens/DesignTokenContext.js +408 -0
  87. package/dist/tokens/DesignTokenContext.js.map +1 -0
  88. package/dist/types/designTokens.d.ts +220 -0
  89. package/dist/types/designTokens.d.ts.map +1 -0
  90. package/dist/types/designTokens.js +8 -0
  91. package/dist/types/designTokens.js.map +1 -0
  92. package/dist/types/index.d.ts +32 -0
  93. package/dist/types/index.d.ts.map +1 -0
  94. package/dist/types/index.js +5 -0
  95. package/dist/types/index.js.map +1 -0
  96. package/dist/types/styles.d.ts +38 -0
  97. package/dist/types/styles.d.ts.map +1 -0
  98. package/dist/types/styles.js +14 -0
  99. package/dist/types/styles.js.map +1 -0
  100. package/dist/utils/tokenFlattener.d.ts +14 -0
  101. package/dist/utils/tokenFlattener.d.ts.map +1 -0
  102. package/dist/utils/tokenFlattener.js +56 -0
  103. package/dist/utils/tokenFlattener.js.map +1 -0
  104. package/package.json +31 -0
  105. package/src/cells/TemplateCardCell.tsx +439 -0
  106. package/src/cells/index.ts +98 -0
  107. package/src/components/ConditionCards.tsx +109 -0
  108. package/src/components/EvidenceTrail.tsx +203 -0
  109. package/src/dispatch/SubRendererDispatch.tsx +282 -0
  110. package/src/hooks/useProseExtraction.ts +125 -0
  111. package/src/index.ts +82 -0
  112. package/src/provenance/ProvenanceSectionIcon.tsx +19 -0
  113. package/src/renderers/AccordionRenderer.tsx +609 -0
  114. package/src/renderers/CardGridRenderer.tsx +608 -0
  115. package/src/renderers/CardRenderer.tsx +517 -0
  116. package/src/renderers/ProseRenderer.tsx +85 -0
  117. package/src/renderers/RawJsonRenderer.tsx +37 -0
  118. package/src/renderers/StatSummaryRenderer.tsx +182 -0
  119. package/src/renderers/TableRenderer.tsx +470 -0
  120. package/src/styles/accordion.css +376 -0
  121. package/src/styles/index.css +5 -0
  122. package/src/styles/renderers.css +1049 -0
  123. package/src/sub-renderers/SubRenderers.tsx +3487 -0
  124. package/src/tokens/DesignTokenContext.tsx +502 -0
  125. package/src/types/designTokens.ts +236 -0
  126. package/src/types/index.ts +53 -0
  127. package/src/types/styles.ts +44 -0
  128. package/src/utils/tokenFlattener.ts +64 -0
@@ -0,0 +1,2462 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * SubRenderers — Section-level rendering components for accordion sections.
4
+ *
5
+ * These are small, focused renderers that handle one section's data within
6
+ * an accordion. The AccordionRenderer dispatches to these based on
7
+ * config.section_renderers[sectionKey].renderer_type.
8
+ *
9
+ * Available sub-renderers:
10
+ * chip_grid — Array of strings/objects → weighted chip cloud
11
+ * definition_list — Array of "Term: Definition" strings or objects → glossary layout
12
+ * mini_card_list — Array of objects → hero + grid insight cards
13
+ * key_value_table — Object or [{key, value}] → styled two-column table
14
+ * prose_block — String → formatted analysis with lede, blockquotes
15
+ * stat_row — Object → monospace stat cards
16
+ * comparison_panel — Array of objects → side-by-side comparison with headers
17
+ * timeline_strip — Array of objects with stages → evolution arc with progression
18
+ * evidence_trail — Vertical chain of evidence steps with dot markers and connectors
19
+ * ordered_flow — Ordered sequence of content units with connecting line and category badges
20
+ * intensity_matrix — Dashboard rows with horizontal intensity bars for quantitative dimensions
21
+ * move_repertoire — Grouped card list with collapsible category headers and count badges
22
+ * dialectical_pair — Two-panel tension visualization for thesis/antithesis contrasts
23
+ * rich_description_list — Stacked items with colored borders for paragraph-length descriptions
24
+ * phase_timeline — Connected timeline with prominent phase nodes for temporal data
25
+ * distribution_summary — Visual bar chart with dominant highlight, counts, and optional narrative
26
+ */
27
+ import React, { useState } from 'react';
28
+ import { EvidenceTrailSubRenderer } from '../components/EvidenceTrail';
29
+ import { EnableConditionsSubRenderer, ConstrainConditionsSubRenderer } from '../components/ConditionCards';
30
+ import { getSO } from '../types/styles';
31
+ import { useDesignTokens } from '../tokens/DesignTokenContext';
32
+ // ── Registry ─────────────────────────────────────────────
33
+ const SUB_RENDERER_MAP = {
34
+ chip_grid: ChipGrid,
35
+ definition_list: DefinitionList,
36
+ mini_card_list: MiniCardList,
37
+ key_value_table: KeyValueTable,
38
+ prose_block: ProseBlock,
39
+ stat_row: StatRow,
40
+ comparison_panel: ComparisonPanel,
41
+ timeline_strip: TimelineStrip,
42
+ evidence_trail: EvidenceTrailSubRenderer,
43
+ enabling_conditions: EnableConditionsSubRenderer,
44
+ constraining_conditions: ConstrainConditionsSubRenderer,
45
+ ordered_flow: OrderedFlow,
46
+ intensity_matrix: IntensityMatrix,
47
+ move_repertoire: MoveRepertoire,
48
+ grouped_card_list: MoveRepertoire, // alias for generic usage
49
+ dialectical_pair: DialecticalPair,
50
+ rich_description_list: RichDescriptionList,
51
+ phase_timeline: PhaseTimeline,
52
+ distribution_summary: DistributionSummary,
53
+ };
54
+ export function resolveSubRenderer(rendererType) {
55
+ return SUB_RENDERER_MAP[rendererType] || null;
56
+ }
57
+ /**
58
+ * Auto-detect the best sub-renderer for a data shape.
59
+ * Used by AccordionRenderer when no section_renderer is explicitly configured.
60
+ *
61
+ * Decision tree:
62
+ * string → prose_block
63
+ * string[] → chip_grid
64
+ * object[] with nested array fields → timeline_strip (concept evolution, stage progressions)
65
+ * object[] with title+description → mini_card_list
66
+ * object[] short items (≤3 fields, all short) → chip_grid
67
+ * flat object (no nested arrays) → key_value_table
68
+ * object with only numeric values → stat_row
69
+ */
70
+ export function autoDetectSubRenderer(data) {
71
+ if (data === null || data === undefined)
72
+ return null;
73
+ if (typeof data === 'string')
74
+ return 'prose_block';
75
+ if (Array.isArray(data) && data.length > 0) {
76
+ if (data.every(d => typeof d === 'string')) {
77
+ // Check if strings look like "Term: Definition" → use definition_list
78
+ const strs = data;
79
+ const defCount = strs.filter(s => {
80
+ const colonIdx = s.indexOf(':');
81
+ // Term before colon (1-60 chars), definition after colon (10+ chars)
82
+ return colonIdx > 1 && colonIdx < 60 && s.length > colonIdx + 10;
83
+ }).length;
84
+ if (defCount >= strs.length * 0.5)
85
+ return 'definition_list';
86
+ return 'chip_grid';
87
+ }
88
+ const firstObj = data.find(d => typeof d === 'object' && d !== null);
89
+ if (firstObj) {
90
+ const entries = Object.entries(firstObj);
91
+ const hasArrayField = entries.some(([, v]) => Array.isArray(v) && v.length > 0);
92
+ const fieldCount = entries.length;
93
+ const shortStringCount = entries.filter(([, v]) => typeof v === 'string' && v.length < 60).length;
94
+ const longStringCount = entries.filter(([, v]) => typeof v === 'string' && v.length >= 60).length;
95
+ if (hasArrayField)
96
+ return 'timeline_strip';
97
+ if (fieldCount <= 3 && shortStringCount >= fieldCount - 1 && longStringCount === 0)
98
+ return 'chip_grid';
99
+ if (longStringCount > 0)
100
+ return 'mini_card_list';
101
+ return 'mini_card_list';
102
+ }
103
+ }
104
+ if (typeof data === 'object' && !Array.isArray(data)) {
105
+ const obj = data;
106
+ const entries = Object.entries(obj).filter(([, v]) => v !== null && v !== undefined);
107
+ if (entries.length > 0 && entries.every(([, v]) => typeof v === 'number'))
108
+ return 'stat_row';
109
+ const allScalar = entries.every(([, v]) => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean');
110
+ if (allScalar)
111
+ return 'key_value_table';
112
+ }
113
+ return null;
114
+ }
115
+ // ── Helpers ──────────────────────────────────────────────
116
+ function getField(obj, field) {
117
+ if (!field)
118
+ return '';
119
+ const val = obj[field];
120
+ if (val === null || val === undefined)
121
+ return '';
122
+ return String(val);
123
+ }
124
+ function getNumField(obj, field) {
125
+ if (!field)
126
+ return null;
127
+ const val = obj[field];
128
+ if (typeof val === 'number')
129
+ return val;
130
+ if (typeof val === 'string') {
131
+ const n = parseFloat(val);
132
+ return isNaN(n) ? null : n;
133
+ }
134
+ return null;
135
+ }
136
+ const TITLE_HINTS = ['name', 'title', 'label', 'term', 'concept', 'framework_name', 'heading'];
137
+ const SUBTITLE_HINTS = ['type', 'category', 'kind', 'centrality', 'status', 'role', 'level'];
138
+ const DESC_HINTS = ['description', 'summary', 'definition', 'explanation', 'text', 'content', 'methodological_signature'];
139
+ const KEY_HINTS = ['key', 'term', 'concept', 'name', 'label'];
140
+ const VALUE_HINTS = ['value', 'definition', 'meaning', 'description', 'explanation'];
141
+ function autoDetectFields(sample) {
142
+ const result = {};
143
+ const entries = Object.entries(sample);
144
+ const shortStrings = [];
145
+ const longStrings = [];
146
+ const numericFields = [];
147
+ for (const [k, v] of entries) {
148
+ if (typeof v === 'string') {
149
+ if (v.length > 80)
150
+ longStrings.push(k);
151
+ else
152
+ shortStrings.push(k);
153
+ }
154
+ else if (typeof v === 'number') {
155
+ numericFields.push(k);
156
+ }
157
+ }
158
+ result.title = shortStrings.find(k => TITLE_HINTS.includes(k)) || shortStrings[0];
159
+ result.label = result.title;
160
+ const remaining = shortStrings.filter(k => k !== result.title);
161
+ result.subtitle = remaining.find(k => SUBTITLE_HINTS.includes(k)) || remaining[0];
162
+ result.description = longStrings.find(k => DESC_HINTS.includes(k)) || longStrings[0];
163
+ result.badge = numericFields[0];
164
+ result.count = numericFields[0];
165
+ result.key = shortStrings.find(k => KEY_HINTS.includes(k)) || shortStrings[0];
166
+ const valCandidates = [...longStrings, ...shortStrings.filter(k => k !== result.key)];
167
+ result.value = valCandidates.find(k => VALUE_HINTS.includes(k)) || valCandidates[0];
168
+ return result;
169
+ }
170
+ function resolveField(config, configKey, auto, autoKey) {
171
+ return config[configKey] || auto[autoKey];
172
+ }
173
+ // ── Color Utilities ──────────────────────────────────────
174
+ function parseAccentHSL(hex) {
175
+ if (!hex || !hex.startsWith('#') || hex.length < 7)
176
+ return { h: 220, s: 55, l: 45 };
177
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
178
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
179
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
180
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
181
+ let h = 0, s = 0;
182
+ const l = (max + min) / 2;
183
+ if (max !== min) {
184
+ const d = max - min;
185
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
186
+ if (max === r)
187
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
188
+ else if (max === g)
189
+ h = ((b - r) / d + 2) / 6;
190
+ else
191
+ h = ((r - g) / d + 4) / 6;
192
+ }
193
+ return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
194
+ }
195
+ /** Render inline markdown: **bold** → accent-underlined bold, *italic* → em */
196
+ function renderInlineMarkdown(text, accentColor) {
197
+ const parts = [];
198
+ let lastIndex = 0;
199
+ const regex = /(\*\*(.+?)\*\*|\*(.+?)\*)/g;
200
+ let match;
201
+ while ((match = regex.exec(text)) !== null) {
202
+ if (match.index > lastIndex) {
203
+ parts.push(text.slice(lastIndex, match.index));
204
+ }
205
+ if (match[2]) {
206
+ parts.push(_jsx("strong", { style: {
207
+ fontWeight: 'var(--weight-semibold, 600)',
208
+ textDecoration: 'underline',
209
+ textDecorationColor: accentColor,
210
+ textUnderlineOffset: '3px',
211
+ textDecorationThickness: '2px',
212
+ }, children: match[2] }, match.index));
213
+ }
214
+ else if (match[3]) {
215
+ parts.push(_jsx("em", { children: match[3] }, match.index));
216
+ }
217
+ lastIndex = match.index + match[0].length;
218
+ }
219
+ if (lastIndex < text.length) {
220
+ parts.push(text.slice(lastIndex));
221
+ }
222
+ return parts.length > 0 ? parts : [text];
223
+ }
224
+ // ── ChipGrid ─────────────────────────────────────────────
225
+ function ChipGrid({ data, config }) {
226
+ const [expandedIdx, setExpandedIdx] = React.useState(null);
227
+ const { getChipWeight, tokens } = useDesignTokens();
228
+ const so = getSO(config);
229
+ if (!data || !Array.isArray(data))
230
+ return null;
231
+ const firstObj = data.find(d => typeof d === 'object' && d !== null);
232
+ const auto = firstObj ? autoDetectFields(firstObj) : {};
233
+ const labelField = resolveField(config, 'label_field', auto, 'label');
234
+ const countField = resolveField(config, 'count_field', auto, 'count');
235
+ const subtitleField = resolveField(config, 'subtitle_field', auto, 'subtitle');
236
+ const descField = resolveField(config, 'description_field', auto, 'description');
237
+ const hasDetails = firstObj && ((descField && getField(firstObj, descField).length > 0) ||
238
+ Object.values(firstObj).some(v => Array.isArray(v)));
239
+ // Collect numeric values for size variation
240
+ const numericValues = [];
241
+ if (countField) {
242
+ data.forEach(item => {
243
+ if (typeof item === 'object' && item !== null) {
244
+ const n = getNumField(item, countField);
245
+ if (n !== null)
246
+ numericValues.push(n);
247
+ }
248
+ });
249
+ }
250
+ const hasNumeric = numericValues.length > 0;
251
+ const minVal = hasNumeric ? Math.min(...numericValues) : 0;
252
+ const maxVal = hasNumeric ? Math.max(...numericValues) : 1;
253
+ const valRange = maxVal - minVal || 1;
254
+ // Build chip items with weight for sorting
255
+ const chipItems = data.map((item, i) => {
256
+ const label = typeof item === 'string'
257
+ ? item
258
+ : typeof item === 'object' && item !== null
259
+ ? getField(item, labelField)
260
+ || String(Object.values(item).find(v => typeof v === 'string' && v.length < 80) || `Item ${i + 1}`)
261
+ : String(item);
262
+ const subtitle = typeof item === 'object' && item !== null && subtitleField
263
+ ? getField(item, subtitleField)
264
+ : '';
265
+ const count = typeof item === 'object' && item !== null && countField
266
+ ? getNumField(item, countField)
267
+ : null;
268
+ const weight = hasNumeric && count !== null
269
+ ? (count - minVal) / valRange
270
+ : 0.5;
271
+ return { item, label, subtitle, count, weight, originalIndex: i };
272
+ });
273
+ // Sort by weight descending for weighted cloud layout
274
+ const sortedChips = [...chipItems].sort((a, b) => b.weight - a.weight);
275
+ return (_jsxs("div", { children: [_jsx("div", { style: {
276
+ display: 'flex', gap: 'var(--space-sm, 0.5rem)', flexWrap: 'wrap',
277
+ padding: 'var(--space-md, 0.75rem)',
278
+ backgroundColor: 'var(--color-surface-alt, #f8f9fa)',
279
+ borderRadius: 'var(--radius-lg, 12px)',
280
+ border: '1px solid var(--color-border-light, #eef0f2)',
281
+ ...so?.items_container,
282
+ }, children: sortedChips.map(({ item, label, subtitle, count, weight, originalIndex }) => {
283
+ const chipColors = getChipWeight(weight);
284
+ const colors = { ...chipColors, headerBg: tokens.components.chip_header_bg, headerText: tokens.components.chip_header_text };
285
+ const isExpanded = expandedIdx === originalIndex;
286
+ const isClickable = hasDetails && typeof item === 'object' && item !== null;
287
+ // Size variation: large chips (weight > 0.7) get bigger padding/font
288
+ const sizeClass = weight > 0.7 ? 'large' : weight > 0.3 ? 'medium' : 'small';
289
+ const padH = sizeClass === 'large' ? '18px' : sizeClass === 'medium' ? '14px' : '10px';
290
+ const padV = sizeClass === 'large' ? '8px' : sizeClass === 'medium' ? '6px' : '5px';
291
+ const fontSize = sizeClass === 'large'
292
+ ? 'var(--type-body, 0.9375rem)'
293
+ : sizeClass === 'medium'
294
+ ? 'var(--type-caption, 0.8125rem)'
295
+ : 'var(--type-label, 0.6875rem)';
296
+ return (_jsxs("span", { onClick: isClickable ? () => setExpandedIdx(isExpanded ? null : originalIndex) : undefined, title: isClickable ? 'Click to expand details' : undefined, style: {
297
+ display: 'inline-flex', alignItems: 'center',
298
+ gap: 'var(--space-xs, 0.25rem)',
299
+ padding: `${padV} ${padH}`,
300
+ borderRadius: 'var(--radius-pill, 9999px)',
301
+ fontSize,
302
+ fontWeight: 'var(--weight-semibold, 600)',
303
+ backgroundColor: isExpanded ? colors.headerBg : colors.bg,
304
+ color: isExpanded ? colors.headerText : colors.text,
305
+ border: `1.5px solid ${isExpanded ? colors.headerBg : colors.border}`,
306
+ boxShadow: isExpanded
307
+ ? 'var(--shadow-md, 0 4px 6px rgba(0,0,0,0.05))'
308
+ : 'var(--shadow-xs, 0 1px 2px rgba(0,0,0,0.04))',
309
+ cursor: isClickable ? 'pointer' : 'default',
310
+ transition: `all var(--duration-fast, 150ms) var(--ease-out, ease)`,
311
+ ...so?.chip,
312
+ }, children: [_jsx("span", { style: so?.chip_label, children: label }), subtitle && (_jsx("span", { style: {
313
+ fontSize: 'var(--type-label, 0.6875rem)',
314
+ fontWeight: 'var(--weight-medium, 500)',
315
+ padding: '1px 7px', borderRadius: 'var(--radius-pill, 9999px)',
316
+ backgroundColor: isExpanded ? 'rgba(255,255,255,0.25)' : colors.headerBg,
317
+ color: colors.headerText,
318
+ letterSpacing: '0.02em',
319
+ ...so?.badge,
320
+ }, children: subtitle })), count !== null && (_jsx("span", { style: {
321
+ fontSize: 'var(--type-label, 0.6875rem)',
322
+ fontWeight: 'var(--weight-bold, 700)',
323
+ backgroundColor: isExpanded ? 'rgba(255,255,255,0.25)' : colors.headerBg,
324
+ color: colors.headerText,
325
+ borderRadius: 'var(--radius-pill, 9999px)',
326
+ padding: '1px 6px',
327
+ ...so?.badge,
328
+ }, children: count }))] }, originalIndex));
329
+ }) }), expandedIdx !== null && typeof data[expandedIdx] === 'object' && data[expandedIdx] !== null && (() => {
330
+ const obj = data[expandedIdx];
331
+ const label = getField(obj, labelField);
332
+ const subtitle = getField(obj, subtitleField);
333
+ const desc = descField ? getField(obj, descField) : '';
334
+ const expandChipColors = getChipWeight(0.6);
335
+ const colors = { ...expandChipColors, headerBg: tokens.components.chip_header_bg, headerText: tokens.components.chip_header_text };
336
+ const skipKeys = new Set([labelField, subtitleField, descField].filter(Boolean));
337
+ const remaining = Object.entries(obj).filter(([k, v]) => !skipKeys.has(k) && v !== null && v !== undefined && v !== '');
338
+ const arrayFields = remaining.filter(([, v]) => Array.isArray(v));
339
+ const scalarFields = remaining.filter(([, v]) => !Array.isArray(v) && typeof v === 'string' && v.length > 40);
340
+ const shortFields = remaining.filter(([, v]) => !Array.isArray(v) && (typeof v !== 'string' || v.length <= 40));
341
+ return (_jsxs("div", { style: {
342
+ margin: 'var(--space-sm, 0.5rem) 0',
343
+ borderRadius: 'var(--radius-md, 8px)',
344
+ overflow: 'hidden',
345
+ border: `1.5px solid ${colors.border}`,
346
+ boxShadow: 'var(--shadow-md, 0 4px 6px rgba(0,0,0,0.05))',
347
+ ...so?.chip_expanded,
348
+ }, children: [_jsxs("div", { style: {
349
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
350
+ backgroundColor: colors.headerBg,
351
+ display: 'flex', alignItems: 'center', gap: 'var(--space-sm, 0.5rem)',
352
+ }, children: [_jsx("strong", { style: {
353
+ fontSize: 'var(--type-subheading, 1.125rem)',
354
+ color: colors.headerText,
355
+ }, children: label }), subtitle && (_jsx("span", { style: {
356
+ fontSize: 'var(--type-label, 0.6875rem)',
357
+ fontWeight: 'var(--weight-medium, 500)',
358
+ padding: '2px 8px', borderRadius: 'var(--radius-pill, 9999px)',
359
+ backgroundColor: 'rgba(255,255,255,0.2)',
360
+ color: colors.headerText,
361
+ letterSpacing: '0.02em',
362
+ }, children: subtitle })), _jsx("span", { onClick: () => setExpandedIdx(null), style: {
363
+ marginLeft: 'auto', cursor: 'pointer',
364
+ fontSize: 'var(--type-label, 0.6875rem)',
365
+ color: 'rgba(255,255,255,0.7)',
366
+ padding: '2px 8px', borderRadius: 'var(--radius-sm, 4px)',
367
+ transition: `opacity var(--duration-fast, 150ms)`,
368
+ }, children: "close" })] }), _jsxs("div", { style: {
369
+ padding: 'var(--space-md, 1rem)',
370
+ backgroundColor: colors.bg,
371
+ }, children: [desc && (_jsx("p", { style: {
372
+ margin: '0 0 var(--space-sm, 0.5rem) 0',
373
+ fontSize: 'var(--type-body, 0.9375rem)',
374
+ color: 'var(--color-text, #1a1d23)',
375
+ lineHeight: 'var(--leading-relaxed, 1.65)',
376
+ }, children: desc })), shortFields.length > 0 && (_jsx("div", { style: {
377
+ display: 'flex', gap: 'var(--space-md, 0.75rem)', flexWrap: 'wrap',
378
+ marginBottom: arrayFields.length > 0 || scalarFields.length > 0 ? 'var(--space-sm, 0.5rem)' : 0,
379
+ }, children: shortFields.map(([k, v]) => (_jsxs("span", { style: { fontSize: 'var(--type-caption, 0.8125rem)', color: 'var(--color-text-muted, #6b7280)' }, children: [_jsxs("span", { className: "gen-inline-label", children: [k.replace(/_/g, ' '), ":"] }), ' ', String(v)] }, k))) })), scalarFields.map(([k, v]) => (_jsxs("div", { style: { marginBottom: 'var(--space-xs, 0.375rem)' }, children: [_jsx("span", { className: "gen-inline-label", style: { display: 'block', marginBottom: 'var(--space-2xs, 0.125rem)' }, children: k.replace(/_/g, ' ') }), _jsx("span", { style: {
380
+ fontSize: 'var(--type-caption, 0.8125rem)',
381
+ color: 'var(--color-text-muted, #6b7280)',
382
+ lineHeight: 'var(--leading-normal, 1.5)',
383
+ }, children: String(v) })] }, k))), arrayFields.map(([k, v]) => (_jsxs("div", { style: { marginTop: 'var(--space-xs, 0.375rem)' }, children: [_jsxs("span", { className: "gen-inline-label", style: { marginRight: 'var(--space-xs, 0.375rem)' }, children: [k.replace(/_/g, ' '), ":"] }), _jsx("span", { style: { display: 'inline-flex', gap: 'var(--space-xs, 0.25rem)', flexWrap: 'wrap' }, children: v.map((chip, ci) => (_jsx("span", { className: "gen-keyword-tag", children: String(chip) }, ci))) })] }, k)))] })] }));
384
+ })()] }));
385
+ }
386
+ // ── DefinitionList → Glossary/Vocabulary ─────────────────
387
+ /**
388
+ * Purpose-built renderer for glossary/vocabulary data.
389
+ * Handles:
390
+ * - Array of "Term: Definition" strings (splits on first colon)
391
+ * - Array of objects with term/definition fields
392
+ * Renders as a visually rich definition list with:
393
+ * - Prominent term styling with accent color
394
+ * - Clear definition text
395
+ * - Alternating subtle backgrounds
396
+ * - Compact, scannable layout
397
+ */
398
+ function DefinitionList({ data, config }) {
399
+ const [expandedIdx, setExpandedIdx] = React.useState(null);
400
+ const { tokens } = useDesignTokens();
401
+ const so = getSO(config);
402
+ // Capture mode support (threaded from AccordionRenderer)
403
+ const captureMode = config._captureMode;
404
+ const onCapture = config._onCapture;
405
+ const captureJobId = config._captureJobId;
406
+ const captureViewKey = config._captureViewKey;
407
+ const captureSourceType = config._captureSourceType;
408
+ const captureEntityId = config._captureEntityId;
409
+ const parentSectionKey = config._parentSectionKey;
410
+ const parentSectionTitle = config._parentSectionTitle;
411
+ if (!data || !Array.isArray(data) || data.length === 0)
412
+ return null;
413
+ // Parse items into {term, definition} pairs
414
+ const termField = config.term_field;
415
+ const defField = config.definition_field;
416
+ const items = data.map((item, i) => {
417
+ if (typeof item === 'string') {
418
+ // Split "Term: Definition" on first colon
419
+ const colonIdx = item.indexOf(':');
420
+ if (colonIdx > 0 && colonIdx < 80) {
421
+ return {
422
+ term: item.slice(0, colonIdx).trim(),
423
+ definition: item.slice(colonIdx + 1).trim(),
424
+ };
425
+ }
426
+ return { term: `Entry ${i + 1}`, definition: item };
427
+ }
428
+ if (typeof item === 'object' && item !== null) {
429
+ const obj = item;
430
+ const t = getField(obj, termField) || getField(obj, 'term') || getField(obj, 'name') || getField(obj, 'concept') || getField(obj, 'label') || '';
431
+ const d = getField(obj, defField) || getField(obj, 'definition') || getField(obj, 'description') || getField(obj, 'meaning') || '';
432
+ return { term: t || `Entry ${i + 1}`, definition: d || JSON.stringify(obj) };
433
+ }
434
+ return { term: `Entry ${i + 1}`, definition: String(item) };
435
+ });
436
+ // Color rotation for visual variety using series palette
437
+ const palette = tokens.primitives.series_palette;
438
+ const termColor = (idx) => {
439
+ const color = palette[idx % palette.length];
440
+ return {
441
+ termBg: color,
442
+ termText: 'var(--dt-text-inverse)',
443
+ dotColor: color,
444
+ hoverBg: tokens.surfaces.surface_alt,
445
+ };
446
+ };
447
+ return (_jsx("div", { style: {
448
+ display: 'flex',
449
+ flexDirection: 'column',
450
+ gap: '2px',
451
+ borderRadius: 'var(--radius-lg, 12px)',
452
+ overflow: 'hidden',
453
+ border: '1px solid var(--color-border-light, #eef0f2)',
454
+ boxShadow: 'var(--shadow-xs, 0 1px 2px rgba(0,0,0,0.04))',
455
+ ...so?.items_container,
456
+ }, children: items.map((item, i) => {
457
+ const colors = termColor(i);
458
+ const isExpanded = expandedIdx === i;
459
+ const isLong = item.definition.length > 180;
460
+ const displayDef = !isLong || isExpanded
461
+ ? item.definition
462
+ : item.definition.slice(0, 180) + '...';
463
+ return (_jsxs("div", { onClick: isLong ? () => setExpandedIdx(isExpanded ? null : i) : undefined, style: {
464
+ display: 'grid',
465
+ gridTemplateColumns: 'auto 1fr',
466
+ gap: 0,
467
+ backgroundColor: i % 2 === 0
468
+ ? 'var(--color-surface, #ffffff)'
469
+ : 'var(--color-surface-alt, #f8f9fa)',
470
+ cursor: isLong ? 'pointer' : 'default',
471
+ transition: 'background-color 150ms ease',
472
+ }, children: [_jsxs("div", { style: {
473
+ padding: '10px 14px',
474
+ display: 'flex',
475
+ alignItems: 'flex-start',
476
+ gap: '8px',
477
+ minWidth: '200px',
478
+ maxWidth: '280px',
479
+ borderRight: `3px solid ${colors.dotColor}`,
480
+ backgroundColor: i % 2 === 0 ? tokens.surfaces.surface_alt : tokens.surfaces.surface_inset,
481
+ }, children: [_jsx("span", { style: {
482
+ display: 'inline-block',
483
+ width: '7px',
484
+ height: '7px',
485
+ borderRadius: '50%',
486
+ backgroundColor: colors.dotColor,
487
+ marginTop: '6px',
488
+ flexShrink: 0,
489
+ } }), _jsx("span", { style: {
490
+ fontSize: 'var(--type-caption, 0.8125rem)',
491
+ fontWeight: 'var(--weight-bold, 700)',
492
+ color: tokens.surfaces.text_default,
493
+ lineHeight: '1.3',
494
+ letterSpacing: '0.01em',
495
+ ...so?.stat_label,
496
+ }, children: item.term })] }), _jsxs("div", { style: {
497
+ padding: '10px 16px',
498
+ fontSize: 'var(--type-body, 0.9375rem)',
499
+ fontWeight: 'var(--weight-normal, 400)',
500
+ color: 'var(--color-text, #1a1d23)',
501
+ lineHeight: 'var(--leading-relaxed, 1.6)',
502
+ display: 'flex',
503
+ alignItems: 'flex-start',
504
+ gap: '8px',
505
+ }, children: [_jsxs("div", { style: { flex: 1 }, children: [renderInlineMarkdown(displayDef, colors.dotColor), isLong && (_jsx("span", { className: "gen-show-more-link", style: { marginLeft: '6px' }, children: isExpanded ? 'show less' : 'show more' }))] }), captureMode && onCapture && (_jsx("button", { title: "Capture this item", onClick: e => {
506
+ e.stopPropagation();
507
+ onCapture({
508
+ source_view_key: captureViewKey || '',
509
+ source_section_key: parentSectionKey,
510
+ source_item_index: i,
511
+ source_renderer_type: 'definition_list',
512
+ content_type: 'item',
513
+ selected_text: `${item.term}: ${item.definition}`.slice(0, 500),
514
+ structured_data: data[i],
515
+ context_title: parentSectionKey
516
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > ${item.term}`
517
+ : `${captureViewKey || 'Analysis'} > ${item.term}`,
518
+ source_type: (captureSourceType || 'analysis'),
519
+ entity_id: captureEntityId || captureJobId || '',
520
+ depth_level: parentSectionKey ? 'L2_element' : 'L1_section',
521
+ parent_context: parentSectionKey ? {
522
+ section_key: parentSectionKey,
523
+ section_title: parentSectionTitle || '',
524
+ } : undefined,
525
+ });
526
+ }, style: {
527
+ flexShrink: 0,
528
+ background: 'none',
529
+ border: '1px solid var(--color-border, #ccc)',
530
+ borderRadius: '4px',
531
+ color: 'var(--dt-text-faint, #94a3b8)',
532
+ cursor: 'pointer',
533
+ padding: '2px 6px',
534
+ fontSize: '0.7rem',
535
+ lineHeight: 1,
536
+ marginTop: '2px',
537
+ }, children: "\uD83D\uDCCC" }))] })] }, i));
538
+ }) }));
539
+ }
540
+ // ── MiniCardList → Insight Cards ─────────────────────────
541
+ function MiniCardList({ data, config }) {
542
+ const [expandedCards, setExpandedCards] = React.useState(new Set());
543
+ const [hoveredIdx, setHoveredIdx] = React.useState(null);
544
+ const { tokens } = useDesignTokens();
545
+ // Capture mode support (threaded from AccordionRenderer)
546
+ const captureMode = config._captureMode;
547
+ const onCapture = config._onCapture;
548
+ const captureJobId = config._captureJobId;
549
+ const captureViewKey = config._captureViewKey;
550
+ const captureSourceType = config._captureSourceType;
551
+ const captureEntityId = config._captureEntityId;
552
+ const parentSectionKey = config._parentSectionKey;
553
+ const parentSectionTitle = config._parentSectionTitle;
554
+ if (!data || !Array.isArray(data))
555
+ return null;
556
+ const so = getSO(config);
557
+ const firstObj = data.find(d => typeof d === 'object' && d !== null);
558
+ const auto = firstObj ? autoDetectFields(firstObj) : {};
559
+ const titleField = resolveField(config, 'title_field', auto, 'title');
560
+ const subtitleField = resolveField(config, 'subtitle_field', auto, 'subtitle');
561
+ const badgeField = resolveField(config, 'badge_field', auto, 'badge');
562
+ const descriptionField = resolveField(config, 'description_field', auto, 'description');
563
+ // Hero card is opt-in: set hero:true in config to enable the hero pattern
564
+ const useHero = config.hero === true;
565
+ // Determine hero card: highest significance/importance/priority, or first
566
+ const SIGNIFICANCE_KEYS = ['significance', 'importance', 'priority', 'weight', 'relevance'];
567
+ let heroIdx = useHero ? 0 : -1;
568
+ if (useHero && data.length > 1 && firstObj) {
569
+ const sigField = Object.keys(firstObj).find(k => SIGNIFICANCE_KEYS.includes(k));
570
+ if (sigField) {
571
+ let maxVal = -Infinity;
572
+ data.forEach((item, i) => {
573
+ if (typeof item === 'object' && item !== null) {
574
+ const val = getNumField(item, sigField);
575
+ if (val !== null && val > maxVal) {
576
+ maxVal = val;
577
+ heroIdx = i;
578
+ }
579
+ }
580
+ });
581
+ }
582
+ }
583
+ const toggleExpand = (idx) => {
584
+ setExpandedCards(prev => {
585
+ const next = new Set(prev);
586
+ if (next.has(idx))
587
+ next.delete(idx);
588
+ else
589
+ next.add(idx);
590
+ return next;
591
+ });
592
+ };
593
+ const DESC_TRUNCATE_LEN = Infinity; // Show full text — no truncation
594
+ const renderCard = (item, idx, isHero) => {
595
+ if (typeof item !== 'object' || item === null)
596
+ return null;
597
+ const obj = item;
598
+ const title = getField(obj, titleField);
599
+ const subtitle = getField(obj, subtitleField);
600
+ const badge = getField(obj, badgeField);
601
+ const description = getField(obj, descriptionField);
602
+ const shownFields = new Set([titleField, subtitleField, badgeField, descriptionField]);
603
+ const remaining = Object.entries(obj).filter(([k, v]) => !shownFields.has(k) && v !== null && v !== undefined && v !== '');
604
+ const chipFields = remaining.filter(([, v]) => Array.isArray(v) && v.every(x => typeof x === 'string'));
605
+ const scalarFields = remaining.filter(([, v]) => !Array.isArray(v) || !v.every(x => typeof x === 'string'));
606
+ const seriesColor = tokens.primitives.series_palette[idx % tokens.primitives.series_palette.length];
607
+ const colors = {
608
+ headerBg: seriesColor,
609
+ headerText: tokens.surfaces.text_on_accent,
610
+ accent: seriesColor,
611
+ lightBg: tokens.surfaces.surface_alt,
612
+ darkText: tokens.surfaces.text_default,
613
+ border: tokens.surfaces.border_default,
614
+ };
615
+ const isHovered = hoveredIdx === idx;
616
+ const isContentExpanded = expandedCards.has(idx);
617
+ const needsTruncation = !isHero && description.length > DESC_TRUNCATE_LEN;
618
+ return (_jsxs("div", { onMouseEnter: () => setHoveredIdx(idx), onMouseLeave: () => setHoveredIdx(null), style: {
619
+ borderRadius: 'var(--radius-md, 8px)',
620
+ overflow: 'hidden',
621
+ borderLeft: `4px solid ${colors.accent}`,
622
+ border: `1px solid var(--color-border, #e2e5e9)`,
623
+ borderLeftWidth: '4px',
624
+ borderLeftColor: colors.accent,
625
+ boxShadow: isHovered
626
+ ? 'var(--shadow-md, 0 4px 6px rgba(0,0,0,0.05))'
627
+ : 'var(--shadow-sm, 0 1px 3px rgba(0,0,0,0.06))',
628
+ transform: isHovered ? 'translateY(-2px)' : 'none',
629
+ transition: `box-shadow var(--duration-fast, 150ms) var(--ease-out, ease), transform var(--duration-fast, 150ms) var(--ease-out, ease)`,
630
+ backgroundColor: 'var(--color-surface, #ffffff)',
631
+ ...(isHero ? so?.hero_card : {}),
632
+ ...so?.card,
633
+ }, children: [_jsxs("div", { style: {
634
+ padding: isHero
635
+ ? 'var(--space-md, 1rem) var(--space-lg, 1.5rem)'
636
+ : 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
637
+ display: 'flex', alignItems: 'center', gap: 'var(--space-sm, 0.5rem)', flexWrap: 'wrap',
638
+ ...so?.card_header,
639
+ }, children: [title && (_jsx("strong", { style: {
640
+ fontSize: isHero
641
+ ? 'var(--type-heading, 1.375rem)'
642
+ : 'var(--type-body, 0.9375rem)',
643
+ fontWeight: 'var(--weight-semibold, 600)',
644
+ color: 'var(--color-text, #1a1d23)',
645
+ lineHeight: 'var(--leading-tight, 1.2)',
646
+ }, children: title })), subtitle && (_jsx("span", { style: {
647
+ fontSize: 'var(--type-label, 0.6875rem)',
648
+ fontWeight: 'var(--weight-medium, 500)',
649
+ color: colors.headerBg,
650
+ padding: '2px 10px',
651
+ borderRadius: 'var(--radius-pill, 9999px)',
652
+ backgroundColor: colors.lightBg,
653
+ letterSpacing: '0.02em',
654
+ ...so?.badge,
655
+ }, children: subtitle })), badge && (_jsx("span", { style: {
656
+ fontSize: 'var(--type-label, 0.6875rem)',
657
+ fontWeight: 'var(--weight-bold, 700)',
658
+ padding: '2px 8px',
659
+ borderRadius: 'var(--radius-pill, 9999px)',
660
+ backgroundColor: colors.lightBg,
661
+ color: colors.darkText,
662
+ marginLeft: 'auto',
663
+ ...so?.badge,
664
+ }, children: badge })), captureMode && onCapture && (_jsx("button", { title: "Capture this card", onClick: e => {
665
+ e.stopPropagation();
666
+ onCapture({
667
+ source_view_key: captureViewKey || '',
668
+ source_item_index: idx,
669
+ source_renderer_type: 'mini_card_list',
670
+ content_type: 'card',
671
+ selected_text: `${title}: ${description}`.slice(0, 500),
672
+ structured_data: obj,
673
+ context_title: parentSectionKey
674
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > ${title || `Card ${idx + 1}`}`
675
+ : `${captureViewKey || 'Analysis'} > ${title || `Card ${idx + 1}`}`,
676
+ source_type: (captureSourceType || 'analysis'),
677
+ entity_id: captureEntityId || captureJobId || '',
678
+ depth_level: parentSectionKey ? 'L2_element' : 'L1_section',
679
+ parent_context: parentSectionKey ? {
680
+ section_key: parentSectionKey,
681
+ section_title: parentSectionTitle || '',
682
+ } : undefined,
683
+ });
684
+ }, style: {
685
+ background: 'none',
686
+ border: '1px solid var(--color-border, #ccc)',
687
+ borderRadius: '4px',
688
+ color: 'var(--dt-text-faint, #94a3b8)',
689
+ cursor: 'pointer',
690
+ padding: '2px 6px',
691
+ fontSize: '0.7rem',
692
+ lineHeight: 1,
693
+ marginLeft: badge ? '0' : 'auto',
694
+ }, children: "\uD83D\uDCCC" }))] }), description && (_jsxs("div", { style: {
695
+ padding: isHero
696
+ ? '0 var(--space-lg, 1.5rem) var(--space-md, 1rem)'
697
+ : '0 var(--space-md, 1rem) var(--space-sm, 0.5rem)',
698
+ ...so?.card_body,
699
+ }, children: [_jsx("p", { style: {
700
+ fontSize: isHero
701
+ ? 'var(--type-body, 0.9375rem)'
702
+ : 'var(--type-caption, 0.8125rem)',
703
+ color: 'var(--color-text, #1a1d23)',
704
+ lineHeight: 'var(--leading-relaxed, 1.65)',
705
+ margin: 0,
706
+ ...so?.prose,
707
+ }, children: needsTruncation && !isContentExpanded
708
+ ? description.slice(0, DESC_TRUNCATE_LEN) + '...'
709
+ : description }), needsTruncation && (_jsx("button", { className: "gen-show-more-link", onClick: (e) => { e.stopPropagation(); toggleExpand(idx); }, style: {
710
+ marginTop: 'var(--space-xs, 0.25rem)',
711
+ }, children: isContentExpanded ? 'show less' : 'show more' }))] })), scalarFields.length > 0 && (_jsx("div", { style: {
712
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
713
+ backgroundColor: 'var(--color-surface-alt, #f8f9fa)',
714
+ borderTop: '1px solid var(--color-border-light, #eef0f2)',
715
+ }, children: scalarFields.map(([key, value]) => (_jsxs("div", { style: { marginBottom: 'var(--space-2xs, 0.25rem)' }, children: [_jsxs("span", { className: "gen-inline-label", children: [key.replace(/_/g, ' '), ":"] }), _jsx("span", { style: {
716
+ marginLeft: 'var(--space-xs, 0.375rem)',
717
+ fontSize: 'var(--type-caption, 0.8125rem)',
718
+ color: 'var(--color-text-muted, #6b7280)',
719
+ }, children: typeof value === 'object' ? JSON.stringify(value) : String(value) })] }, key))) })), chipFields.length > 0 && (_jsx("div", { style: {
720
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
721
+ backgroundColor: colors.lightBg,
722
+ borderTop: '1px solid var(--color-border-light, #eef0f2)',
723
+ }, children: chipFields.map(([key, value]) => (_jsxs("div", { style: { marginBottom: 'var(--space-xs, 0.375rem)' }, children: [_jsx("span", { className: "gen-inline-label", style: {
724
+ display: 'block',
725
+ marginBottom: 'var(--space-2xs, 0.25rem)',
726
+ }, children: key.replace(/_/g, ' ') }), _jsx("div", { style: { display: 'flex', gap: 'var(--space-xs, 0.25rem)', flexWrap: 'wrap' }, children: value.map((v, vi) => (_jsx("span", { className: "gen-keyword-tag", children: String(v) }, vi))) })] }, key))) }))] }, idx));
727
+ };
728
+ // Single card → render as hero only if hero mode enabled
729
+ if (data.length === 1) {
730
+ return (_jsx("div", { style: { ...so?.items_container }, children: renderCard(data[0], 0, useHero) }));
731
+ }
732
+ // Default: uniform grid layout
733
+ if (!useHero) {
734
+ return (_jsx("div", { style: {
735
+ display: 'grid',
736
+ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
737
+ gap: 'var(--space-md, 1rem)',
738
+ ...so?.items_container,
739
+ }, children: data.map((item, i) => renderCard(item, i, false)) }));
740
+ }
741
+ // Multi-card layout: hero on top, rest in 2-column grid
742
+ return (_jsxs("div", { style: {
743
+ display: 'flex', flexDirection: 'column',
744
+ gap: 'var(--space-md, 1rem)',
745
+ ...so?.items_container,
746
+ }, children: [renderCard(data[heroIdx], heroIdx, true), data.length > 1 && (_jsx("div", { style: {
747
+ display: 'grid',
748
+ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
749
+ gap: 'var(--space-md, 1rem)',
750
+ }, children: data.map((item, i) => {
751
+ if (i === heroIdx)
752
+ return null;
753
+ return renderCard(item, i, false);
754
+ }) }))] }));
755
+ }
756
+ // ── KeyValueTable ────────────────────────────────────────
757
+ function KeyValueTable({ data, config }) {
758
+ const { tokens } = useDesignTokens();
759
+ const so = getSO(config);
760
+ const firstObj = Array.isArray(data)
761
+ ? data.find(d => typeof d === 'object' && d !== null)
762
+ : undefined;
763
+ const auto = firstObj ? autoDetectFields(firstObj) : {};
764
+ const keyField = resolveField(config, 'key_field', auto, 'key');
765
+ const valueField = resolveField(config, 'value_field', auto, 'value');
766
+ let rows = [];
767
+ if (Array.isArray(data)) {
768
+ rows = data.map(item => {
769
+ if (typeof item !== 'object' || item === null)
770
+ return { key: '', value: String(item) };
771
+ const obj = item;
772
+ return {
773
+ key: getField(obj, keyField) || Object.keys(obj)[0] || '',
774
+ value: getField(obj, valueField) || String(Object.values(obj).find(v => typeof v === 'string' && v.length > 20) ?? Object.values(obj)[1] ?? ''),
775
+ };
776
+ });
777
+ }
778
+ else if (typeof data === 'object' && data !== null) {
779
+ rows = Object.entries(data).map(([k, v]) => ({
780
+ key: k,
781
+ value: typeof v === 'object' ? JSON.stringify(v) : String(v ?? ''),
782
+ }));
783
+ }
784
+ if (rows.length === 0)
785
+ return null;
786
+ const isNumeric = (val) => /^[\d,.]+%?$/.test(val.trim());
787
+ return (_jsx("div", { style: {
788
+ borderRadius: 'var(--radius-md, 8px)',
789
+ overflow: 'hidden',
790
+ border: '1px solid var(--color-border, #e2e5e9)',
791
+ boxShadow: 'var(--shadow-xs, 0 1px 2px rgba(0,0,0,0.04))',
792
+ ...so?.card,
793
+ }, children: _jsx("table", { style: { width: '100%', borderCollapse: 'collapse' }, children: _jsx("tbody", { children: rows.map((row, i) => (_jsxs("tr", { style: {
794
+ backgroundColor: i % 2 === 0
795
+ ? 'var(--color-surface, #ffffff)'
796
+ : 'var(--color-surface-alt, #f8f9fa)',
797
+ }, children: [_jsx("td", { style: {
798
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
799
+ fontSize: 'var(--type-label, 0.6875rem)',
800
+ fontWeight: 'var(--weight-medium, 500)',
801
+ color: tokens.surfaces.text_default,
802
+ width: '30%', verticalAlign: 'top',
803
+ textTransform: 'capitalize',
804
+ letterSpacing: '0.02em',
805
+ borderRight: `2px solid ${tokens.surfaces.border_accent}`,
806
+ backgroundColor: i % 2 === 0
807
+ ? tokens.surfaces.surface_alt
808
+ : tokens.surfaces.surface_inset,
809
+ ...so?.stat_label,
810
+ }, children: row.key.replace(/_/g, ' ') }), _jsx("td", { style: {
811
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
812
+ fontSize: 'var(--type-body, 0.9375rem)',
813
+ fontWeight: isNumeric(row.value)
814
+ ? 'var(--weight-semibold, 600)'
815
+ : 'var(--weight-normal, 400)',
816
+ fontFamily: isNumeric(row.value) ? 'var(--font-mono, monospace)' : 'inherit',
817
+ color: 'var(--color-text, #1a1d23)',
818
+ lineHeight: 'var(--leading-normal, 1.5)',
819
+ ...(isNumeric(row.value) ? so?.stat_number : {}),
820
+ }, children: row.value })] }, i))) }) }) }));
821
+ }
822
+ function parseProseContent(text) {
823
+ const lines = text.split('\n');
824
+ const segments = [];
825
+ let currentParagraph = [];
826
+ let currentBlockquote = [];
827
+ function flushParagraph() {
828
+ if (currentParagraph.length > 0) {
829
+ const joined = currentParagraph.join(' ').trim();
830
+ if (joined)
831
+ segments.push({ type: 'paragraph', content: joined });
832
+ currentParagraph = [];
833
+ }
834
+ }
835
+ function flushBlockquote() {
836
+ if (currentBlockquote.length > 0) {
837
+ const joined = currentBlockquote.join(' ').trim();
838
+ if (joined)
839
+ segments.push({ type: 'blockquote', content: joined });
840
+ currentBlockquote = [];
841
+ }
842
+ }
843
+ for (const line of lines) {
844
+ const trimmed = line.trim();
845
+ if (/^(---+|\*\*\*+)$/.test(trimmed)) {
846
+ flushParagraph();
847
+ flushBlockquote();
848
+ segments.push({ type: 'hr', content: '' });
849
+ continue;
850
+ }
851
+ const headingMatch = trimmed.match(/^(#{1,3})\s+(.+)/);
852
+ if (headingMatch) {
853
+ flushParagraph();
854
+ flushBlockquote();
855
+ segments.push({ type: 'heading', content: headingMatch[2], level: headingMatch[1].length });
856
+ continue;
857
+ }
858
+ if (trimmed.startsWith('>')) {
859
+ flushParagraph();
860
+ currentBlockquote.push(trimmed.replace(/^>\s*/, ''));
861
+ continue;
862
+ }
863
+ if (!trimmed) {
864
+ flushBlockquote();
865
+ flushParagraph();
866
+ continue;
867
+ }
868
+ flushBlockquote();
869
+ currentParagraph.push(trimmed);
870
+ }
871
+ flushParagraph();
872
+ flushBlockquote();
873
+ return segments;
874
+ }
875
+ function ProseBlock({ data, config }) {
876
+ const { tokens } = useDesignTokens();
877
+ const so = getSO(config);
878
+ if (!data)
879
+ return null;
880
+ const text = typeof data === 'string'
881
+ ? data
882
+ : typeof data === 'object' && data !== null
883
+ ? Object.values(data).filter(v => typeof v === 'string').join('\n\n')
884
+ : String(data);
885
+ if (!text.trim())
886
+ return null;
887
+ const accentHex = so?.accent_color || tokens.components.page_accent;
888
+ const segments = parseProseContent(text);
889
+ let paragraphIndex = 0;
890
+ return (_jsx("div", { style: {
891
+ fontSize: 'var(--type-body, 0.9375rem)',
892
+ lineHeight: 'var(--leading-relaxed, 1.65)',
893
+ color: 'var(--color-text, #1a1d23)',
894
+ ...so?.prose,
895
+ }, children: segments.map((segment, i) => {
896
+ if (segment.type === 'hr') {
897
+ return (_jsx("hr", { style: {
898
+ border: 'none',
899
+ height: '1px',
900
+ backgroundColor: 'var(--color-border, #e2e5e9)',
901
+ margin: 'var(--space-lg, 1.5rem) var(--space-xl, 2rem)',
902
+ } }, i));
903
+ }
904
+ if (segment.type === 'heading') {
905
+ const headingSizes = {
906
+ 1: 'var(--type-heading, 1.375rem)',
907
+ 2: 'var(--type-subheading, 1.125rem)',
908
+ 3: 'var(--type-body, 0.9375rem)',
909
+ };
910
+ return (_jsx("p", { style: {
911
+ fontSize: headingSizes[segment.level || 3],
912
+ fontWeight: 'var(--weight-bold, 700)',
913
+ color: tokens.surfaces.text_default,
914
+ marginTop: 'var(--space-lg, 1.5rem)',
915
+ marginBottom: 'var(--space-sm, 0.5rem)',
916
+ lineHeight: 'var(--leading-tight, 1.2)',
917
+ }, children: segment.content }, i));
918
+ }
919
+ if (segment.type === 'blockquote') {
920
+ return (_jsx("blockquote", { style: {
921
+ margin: 'var(--space-md, 1rem) 0',
922
+ padding: 'var(--space-md, 1rem) var(--space-lg, 1.5rem)',
923
+ borderLeft: `4px solid ${accentHex}`,
924
+ backgroundColor: tokens.components.prose_blockquote_bg,
925
+ borderRadius: '0 var(--radius-md, 8px) var(--radius-md, 8px) 0',
926
+ fontStyle: 'italic',
927
+ color: 'var(--color-text-muted, #6b7280)',
928
+ lineHeight: 'var(--leading-loose, 1.8)',
929
+ ...so?.prose_quote,
930
+ }, children: renderInlineMarkdown(segment.content, accentHex) }, i));
931
+ }
932
+ // Paragraph: first paragraph is lede
933
+ const isLede = paragraphIndex === 0;
934
+ paragraphIndex++;
935
+ if (isLede) {
936
+ return (_jsx("p", { style: {
937
+ fontSize: 'var(--type-subheading, 1.125rem)',
938
+ fontWeight: 'var(--weight-medium, 500)',
939
+ lineHeight: 'var(--leading-snug, 1.35)',
940
+ color: 'var(--color-text, #1a1d23)',
941
+ marginTop: 0,
942
+ marginBottom: 'var(--space-md, 1rem)',
943
+ ...so?.prose_lede,
944
+ }, children: renderInlineMarkdown(segment.content, accentHex) }, i));
945
+ }
946
+ return (_jsx("p", { style: {
947
+ fontSize: 'var(--type-body, 0.9375rem)',
948
+ fontWeight: 'var(--weight-normal, 400)',
949
+ lineHeight: 'var(--leading-relaxed, 1.65)',
950
+ color: 'var(--color-text, #1a1d23)',
951
+ marginTop: 0,
952
+ marginBottom: 'var(--space-md, 1rem)',
953
+ ...so?.prose_body,
954
+ }, children: renderInlineMarkdown(segment.content, accentHex) }, i));
955
+ }) }));
956
+ }
957
+ // ── StatRow ──────────────────────────────────────────────
958
+ function StatRow({ data, config }) {
959
+ const { tokens } = useDesignTokens();
960
+ if (!data || typeof data !== 'object')
961
+ return null;
962
+ const so = getSO(config);
963
+ const obj = data;
964
+ const entries = Object.entries(obj).filter(([, v]) => v !== null && v !== undefined);
965
+ if (entries.length === 0)
966
+ return null;
967
+ return (_jsx("div", { style: {
968
+ display: 'grid',
969
+ gridTemplateColumns: `repeat(${Math.min(entries.length, 4)}, 1fr)`,
970
+ gap: 'var(--space-md, 0.75rem)',
971
+ ...so?.items_container,
972
+ }, children: entries.map(([key, value]) => (_jsxs("div", { style: {
973
+ padding: 'var(--space-md, 1rem)',
974
+ borderRadius: 'var(--radius-md, 8px)',
975
+ backgroundColor: 'var(--color-surface, #ffffff)',
976
+ border: '1px solid var(--color-border-light, #eef0f2)',
977
+ boxShadow: 'var(--shadow-xs, 0 1px 2px rgba(0,0,0,0.04))',
978
+ textAlign: 'center',
979
+ ...so?.card,
980
+ }, children: [_jsx("div", { style: {
981
+ fontSize: 'var(--type-number, 1.25rem)',
982
+ fontFamily: 'var(--font-mono, monospace)',
983
+ fontWeight: 'var(--weight-bold, 700)',
984
+ color: tokens.components.stat_number_color,
985
+ lineHeight: 'var(--leading-tight, 1.2)',
986
+ ...so?.stat_number,
987
+ }, children: typeof value === 'number' ? value.toLocaleString() : String(value) }), _jsx("div", { className: "gen-inline-label", style: {
988
+ marginTop: 'var(--space-xs, 0.25rem)',
989
+ ...so?.stat_label,
990
+ }, children: key.replace(/_/g, ' ') })] }, key))) }));
991
+ }
992
+ // ── ComparisonPanel ──────────────────────────────────────
993
+ function ComparisonPanel({ data, config }) {
994
+ const { tokens } = useDesignTokens();
995
+ if (!data || !Array.isArray(data))
996
+ return null;
997
+ const so = getSO(config);
998
+ // Capture mode support (threaded from AccordionRenderer)
999
+ const captureMode = config._captureMode;
1000
+ const onCapture = config._onCapture;
1001
+ const captureJobId = config._captureJobId;
1002
+ const captureViewKey = config._captureViewKey;
1003
+ const captureSourceType = config._captureSourceType;
1004
+ const captureEntityId = config._captureEntityId;
1005
+ const parentSectionKey = config._parentSectionKey;
1006
+ const parentSectionTitle = config._parentSectionTitle;
1007
+ const firstObj = data.find(d => typeof d === 'object' && d !== null);
1008
+ const longStrings = firstObj
1009
+ ? Object.entries(firstObj).filter(([, v]) => typeof v === 'string' && v.length > 20).map(([k]) => k)
1010
+ : [];
1011
+ const leftField = config.left_field || longStrings[0];
1012
+ const rightField = config.right_field || longStrings[1];
1013
+ const leftLabel = leftField ? leftField.replace(/_/g, ' ') : 'Left';
1014
+ const rightLabel = rightField ? rightField.replace(/_/g, ' ') : 'Right';
1015
+ return (_jsxs("div", { style: {
1016
+ display: 'flex', flexDirection: 'column',
1017
+ gap: 'var(--space-md, 0.75rem)',
1018
+ ...so?.items_container,
1019
+ }, children: [_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1px' }, children: [_jsx("div", { style: {
1020
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
1021
+ backgroundColor: tokens.primitives.series_palette[0],
1022
+ color: tokens.surfaces.text_on_accent,
1023
+ fontSize: 'var(--type-caption, 0.8125rem)',
1024
+ fontWeight: 'var(--weight-semibold, 600)',
1025
+ textTransform: 'uppercase',
1026
+ letterSpacing: '0.06em',
1027
+ borderRadius: 'var(--radius-md, 8px) 0 0 0',
1028
+ ...so?.card_header,
1029
+ }, children: leftLabel }), _jsx("div", { style: {
1030
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
1031
+ backgroundColor: tokens.primitives.series_palette[1],
1032
+ color: 'var(--dt-text-inverse)',
1033
+ fontSize: 'var(--type-caption, 0.8125rem)',
1034
+ fontWeight: 'var(--weight-semibold, 600)',
1035
+ textTransform: 'uppercase',
1036
+ letterSpacing: '0.06em',
1037
+ borderRadius: '0 var(--radius-md, 8px) 0 0',
1038
+ ...so?.card_header,
1039
+ }, children: rightLabel })] }), data.map((item, i) => {
1040
+ if (typeof item !== 'object' || item === null)
1041
+ return null;
1042
+ const obj = item;
1043
+ const left = getField(obj, leftField);
1044
+ const right = getField(obj, rightField);
1045
+ const otherFields = Object.entries(obj).filter(([k, v]) => k !== leftField && k !== rightField && v !== null && v !== undefined && v !== '');
1046
+ // Build a label for this comparison row
1047
+ const rowLabel = otherFields.length > 0
1048
+ ? otherFields.map(([k, v]) => `${k.replace(/_/g, ' ')}: ${String(v)}`).join(', ')
1049
+ : `Row ${i + 1}`;
1050
+ return (_jsxs("div", { style: {
1051
+ borderRadius: 'var(--radius-md, 8px)',
1052
+ border: '1px solid var(--color-border, #e2e5e9)',
1053
+ overflow: 'hidden',
1054
+ boxShadow: 'var(--shadow-xs, 0 1px 2px rgba(0,0,0,0.04))',
1055
+ }, children: [(otherFields.length > 0 || (captureMode && onCapture)) && (_jsxs("div", { style: {
1056
+ padding: 'var(--space-xs, 0.375rem) var(--space-md, 0.75rem)',
1057
+ backgroundColor: 'var(--color-surface-alt, #f8f9fa)',
1058
+ fontSize: 'var(--type-label, 0.6875rem)',
1059
+ color: 'var(--color-text-muted, #6b7280)',
1060
+ borderBottom: '1px solid var(--color-border-light, #eef0f2)',
1061
+ display: 'flex', gap: 'var(--space-sm, 0.5rem)', flexWrap: 'wrap',
1062
+ alignItems: 'center',
1063
+ }, children: [otherFields.map(([k, v]) => (_jsxs("span", { children: [_jsx("strong", { style: { textTransform: 'capitalize' }, children: k.replace(/_/g, ' ') }), ": ", String(v)] }, k))), captureMode && onCapture && (_jsx("button", { title: "Capture this comparison", onClick: e => {
1064
+ e.stopPropagation();
1065
+ onCapture({
1066
+ source_view_key: captureViewKey || '',
1067
+ source_section_key: parentSectionKey,
1068
+ source_item_index: i,
1069
+ source_renderer_type: 'comparison_panel',
1070
+ content_type: 'item',
1071
+ selected_text: `${leftLabel}: ${left || '—'} vs ${rightLabel}: ${right || '—'}`.slice(0, 500),
1072
+ structured_data: obj,
1073
+ context_title: parentSectionKey
1074
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > ${rowLabel}`
1075
+ : `${captureViewKey || 'Analysis'} > ${rowLabel}`,
1076
+ source_type: (captureSourceType || 'analysis'),
1077
+ entity_id: captureEntityId || captureJobId || '',
1078
+ depth_level: parentSectionKey ? 'L2_element' : 'L1_section',
1079
+ parent_context: parentSectionKey ? {
1080
+ section_key: parentSectionKey,
1081
+ section_title: parentSectionTitle || '',
1082
+ } : undefined,
1083
+ });
1084
+ }, style: {
1085
+ marginLeft: 'auto',
1086
+ flexShrink: 0,
1087
+ background: 'none',
1088
+ border: '1px solid var(--color-border, #ccc)',
1089
+ borderRadius: '4px',
1090
+ color: 'var(--dt-text-faint, #94a3b8)',
1091
+ cursor: 'pointer',
1092
+ padding: '2px 6px',
1093
+ fontSize: '0.7rem',
1094
+ lineHeight: 1,
1095
+ }, children: "\uD83D\uDCCC" }))] })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr auto 1fr' }, children: [_jsx("div", { style: {
1096
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
1097
+ fontSize: 'var(--type-body, 0.9375rem)',
1098
+ color: 'var(--color-text, #1a1d23)',
1099
+ lineHeight: 'var(--leading-normal, 1.5)',
1100
+ ...so?.card_body,
1101
+ }, children: left || _jsx("span", { style: { color: 'var(--color-text-faint, #9ca3af)', fontStyle: 'italic' }, children: "\u2014" }) }), _jsx("div", { style: {
1102
+ width: '2px',
1103
+ background: `linear-gradient(to bottom, ${tokens.surfaces.border_light}, ${tokens.surfaces.border_accent}, ${tokens.surfaces.border_light})`,
1104
+ } }), _jsx("div", { style: {
1105
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
1106
+ fontSize: 'var(--type-body, 0.9375rem)',
1107
+ color: 'var(--color-text, #1a1d23)',
1108
+ lineHeight: 'var(--leading-normal, 1.5)',
1109
+ ...so?.card_body,
1110
+ }, children: right || _jsx("span", { style: { color: 'var(--color-text-faint, #9ca3af)', fontStyle: 'italic' }, children: "\u2014" }) })] })] }, i));
1111
+ })] }));
1112
+ }
1113
+ // ── TimelineStrip → Evolution Arc ────────────────────────
1114
+ function TimelineStrip({ data, config }) {
1115
+ const { tokens } = useDesignTokens();
1116
+ const so = getSO(config);
1117
+ const [expandedCard, setExpandedCard] = React.useState(null);
1118
+ if (!data || !Array.isArray(data))
1119
+ return null;
1120
+ // TimelineStrip uses HSL-derived progressive coloring for evolution arcs.
1121
+ // Derive accent HSL from the token system's accent color.
1122
+ const accent = parseAccentHSL(so?.accent_color || tokens.components.page_accent);
1123
+ const firstObj = data.find(d => typeof d === 'object' && d !== null);
1124
+ const auto = firstObj ? autoDetectFields(firstObj) : {};
1125
+ const labelField = resolveField(config, 'label_field', auto, 'label');
1126
+ const stagesField = config.stages_field
1127
+ || (firstObj ? Object.entries(firstObj).find(([, v]) => Array.isArray(v))?.[0] : undefined);
1128
+ function renderStageNode(stage, j, totalStages) {
1129
+ // Visual progression: size and saturation increase from left to right
1130
+ const progress = totalStages > 1 ? j / (totalStages - 1) : 0.5;
1131
+ const saturation = Math.max(accent.s * 0.3, 12) + progress * 30;
1132
+ const bgLightness = 96 - progress * 8;
1133
+ const textLightness = 30 - progress * 10;
1134
+ const borderSat = Math.max(accent.s * 0.4, 15) + progress * 20;
1135
+ // Size increases with progress
1136
+ const padV = `${0.4 + progress * 0.3}rem`;
1137
+ const padH = `${0.6 + progress * 0.4}rem`;
1138
+ const fontSize = progress > 0.6
1139
+ ? 'var(--type-caption, 0.8125rem)'
1140
+ : 'var(--type-label, 0.6875rem)';
1141
+ if (typeof stage === 'string') {
1142
+ return (_jsx("div", { style: {
1143
+ padding: `${padV} ${padH}`,
1144
+ borderRadius: 'var(--radius-md, 8px)',
1145
+ backgroundColor: `hsl(${accent.h}, ${saturation}%, ${bgLightness}%)`,
1146
+ border: `1.5px solid hsl(${accent.h}, ${borderSat}%, ${78 - progress * 10}%)`,
1147
+ fontSize,
1148
+ fontWeight: 'var(--weight-medium, 500)',
1149
+ lineHeight: 'var(--leading-snug, 1.35)',
1150
+ color: `hsl(${accent.h}, ${Math.min(accent.s + 10, 75)}%, ${textLightness}%)`,
1151
+ minWidth: `${130 + progress * 40}px`,
1152
+ maxWidth: '280px',
1153
+ flexShrink: 0,
1154
+ boxShadow: 'var(--shadow-xs, 0 1px 2px rgba(0,0,0,0.04))',
1155
+ ...so?.timeline_node,
1156
+ }, children: stage }));
1157
+ }
1158
+ if (typeof stage === 'object' && stage !== null) {
1159
+ const obj = stage;
1160
+ const stageAuto = autoDetectFields(obj);
1161
+ const primaryLabel = getField(obj, 'form') || getField(obj, stageAuto.title)
1162
+ || getField(obj, 'label') || getField(obj, 'name');
1163
+ const periodLabel = getField(obj, 'period') || getField(obj, 'era') || getField(obj, 'date') || getField(obj, 'year');
1164
+ const secondaryFields = Object.entries(obj).filter(([k, v]) => !['form', 'name', 'title', 'label', 'period', 'era', 'date', 'year'].includes(k)
1165
+ && typeof v === 'string' && v.length > 0);
1166
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', flexShrink: 0 }, children: [periodLabel && (_jsx("div", { style: {
1167
+ fontSize: 'var(--type-label, 0.6875rem)',
1168
+ fontFamily: 'var(--font-mono, monospace)',
1169
+ fontWeight: 'var(--weight-semibold, 600)',
1170
+ color: `hsl(${accent.h}, ${Math.min(accent.s + 10, 70)}%, 45%)`,
1171
+ letterSpacing: '0.04em',
1172
+ marginBottom: 'var(--space-xs, 0.25rem)',
1173
+ textAlign: 'center',
1174
+ }, children: periodLabel })), _jsxs("div", { style: {
1175
+ padding: `${padV} ${padH}`,
1176
+ borderRadius: 'var(--radius-md, 8px)',
1177
+ backgroundColor: `hsl(${accent.h}, ${saturation}%, ${bgLightness}%)`,
1178
+ border: `1.5px solid hsl(${accent.h}, ${borderSat}%, ${78 - progress * 10}%)`,
1179
+ fontSize,
1180
+ lineHeight: 'var(--leading-snug, 1.35)',
1181
+ color: `hsl(${accent.h}, ${Math.min(accent.s + 10, 75)}%, ${textLightness}%)`,
1182
+ minWidth: `${130 + progress * 40}px`,
1183
+ maxWidth: '280px',
1184
+ boxShadow: 'var(--shadow-xs, 0 1px 2px rgba(0,0,0,0.04))',
1185
+ ...so?.timeline_node,
1186
+ }, children: [_jsx("div", { style: {
1187
+ fontWeight: 'var(--weight-semibold, 600)',
1188
+ marginBottom: secondaryFields.length > 0 ? 'var(--space-2xs, 0.25rem)' : 0,
1189
+ }, children: primaryLabel || 'Stage ' + (j + 1) }), secondaryFields.slice(0, 2).map(([k, v]) => (_jsxs("div", { style: {
1190
+ fontSize: 'var(--type-label, 0.6875rem)',
1191
+ color: `hsl(${accent.h}, ${Math.max(accent.s * 0.5, 15)}%, ${40 - progress * 5}%)`,
1192
+ marginTop: 'var(--space-2xs, 0.15rem)',
1193
+ }, children: [_jsxs("span", { className: "gen-inline-label", children: [k.replace(/_/g, ' '), ":"] }), ' ', String(v).length > 80 ? String(v).slice(0, 77) + '...' : String(v)] }, k)))] })] }));
1194
+ }
1195
+ return (_jsx("div", { style: {
1196
+ padding: `${padV} ${padH}`,
1197
+ borderRadius: 'var(--radius-md, 8px)',
1198
+ backgroundColor: `hsl(${accent.h}, ${saturation}%, ${bgLightness}%)`,
1199
+ border: `1px solid hsl(${accent.h}, ${borderSat}%, ${80 - progress * 8}%)`,
1200
+ fontSize: 'var(--type-label, 0.6875rem)',
1201
+ color: `hsl(${accent.h}, ${Math.min(accent.s + 10, 70)}%, ${textLightness}%)`,
1202
+ flexShrink: 0,
1203
+ ...so?.timeline_node,
1204
+ }, children: String(stage) }));
1205
+ }
1206
+ /** Render the connecting arrow between stages with gradient */
1207
+ function renderConnector(j, totalStages) {
1208
+ const progress = totalStages > 1 ? j / (totalStages - 1) : 0;
1209
+ const nextProgress = totalStages > 1 ? (j + 1) / (totalStages - 1) : 1;
1210
+ const startLight = 82 - progress * 25;
1211
+ const endLight = 82 - nextProgress * 25;
1212
+ const startSat = Math.max(accent.s * 0.3, 12) + progress * 25;
1213
+ const endSat = Math.max(accent.s * 0.3, 12) + nextProgress * 25;
1214
+ return (_jsx("div", { style: {
1215
+ display: 'flex', alignItems: 'center',
1216
+ flexShrink: 0, padding: '0 2px',
1217
+ alignSelf: 'center',
1218
+ ...so?.timeline_connector,
1219
+ }, children: _jsx("div", { style: {
1220
+ width: '24px', height: '3px',
1221
+ background: `linear-gradient(to right, hsl(${accent.h}, ${startSat}%, ${startLight}%), hsl(${accent.h}, ${endSat}%, ${endLight}%))`,
1222
+ borderRadius: '2px',
1223
+ position: 'relative',
1224
+ }, children: _jsx("div", { style: {
1225
+ position: 'absolute', right: '-5px', top: '-4px',
1226
+ width: 0, height: 0,
1227
+ borderTop: '5.5px solid transparent',
1228
+ borderBottom: '5.5px solid transparent',
1229
+ borderLeft: `8px solid hsl(${accent.h}, ${endSat}%, ${endLight}%)`,
1230
+ } }) }) }));
1231
+ }
1232
+ return (_jsx("div", { style: {
1233
+ display: 'flex', flexDirection: 'column',
1234
+ gap: 'var(--space-lg, 1.25rem)',
1235
+ ...so?.items_container,
1236
+ }, children: data.map((item, i) => {
1237
+ if (typeof item !== 'object' || item === null)
1238
+ return null;
1239
+ const obj = item;
1240
+ const label = getField(obj, labelField);
1241
+ const stages = stagesField ? obj[stagesField] : null;
1242
+ const cardId = `${label || i}`;
1243
+ const isExpanded = expandedCard === cardId;
1244
+ const metaFields = Object.entries(obj).filter(([k, v]) => k !== labelField && k !== stagesField && v !== null && v !== undefined && v !== '');
1245
+ return (_jsxs("div", { style: {
1246
+ minWidth: 0, overflow: 'hidden',
1247
+ borderRadius: 'var(--radius-lg, 12px)',
1248
+ border: '1px solid var(--color-border, #e2e5e9)',
1249
+ backgroundColor: 'var(--color-surface, #ffffff)',
1250
+ boxShadow: 'var(--shadow-sm, 0 1px 3px rgba(0,0,0,0.06))',
1251
+ ...so?.card,
1252
+ }, children: [label && (_jsxs("div", { onClick: () => setExpandedCard(isExpanded ? null : cardId), style: {
1253
+ fontSize: 'var(--type-body, 0.9375rem)',
1254
+ fontWeight: 'var(--weight-semibold, 600)',
1255
+ color: 'var(--color-text, #1a1d23)',
1256
+ padding: 'var(--space-sm, 0.625rem) var(--space-md, 1rem)',
1257
+ backgroundColor: 'var(--color-surface-alt, #f8f9fa)',
1258
+ borderBottom: '1px solid var(--color-border-light, #eef0f2)',
1259
+ cursor: metaFields.length > 0 ? 'pointer' : 'default',
1260
+ display: 'flex', alignItems: 'center',
1261
+ gap: 'var(--space-sm, 0.5rem)',
1262
+ ...so?.card_header,
1263
+ }, children: [label, Array.isArray(stages) && (_jsxs("span", { style: {
1264
+ fontSize: 'var(--type-label, 0.6875rem)',
1265
+ fontWeight: 'var(--weight-medium, 500)',
1266
+ color: 'var(--color-text-faint, #9ca3af)',
1267
+ backgroundColor: tokens.surfaces.surface_inset,
1268
+ padding: '2px 8px',
1269
+ borderRadius: 'var(--radius-pill, 9999px)',
1270
+ ...so?.badge,
1271
+ }, children: [stages.length, " stages"] }))] })), Array.isArray(stages) && stages.length > 0 && (_jsx("div", { style: {
1272
+ padding: 'var(--space-md, 0.75rem)',
1273
+ overflowX: 'auto',
1274
+ }, children: _jsx("div", { style: {
1275
+ display: 'flex', alignItems: 'flex-end',
1276
+ gap: 0,
1277
+ minWidth: 'min-content',
1278
+ }, children: stages.map((stage, j) => (_jsxs(React.Fragment, { children: [renderStageNode(stage, j, stages.length), j < stages.length - 1 && renderConnector(j, stages.length)] }, j))) }) })), isExpanded && metaFields.length > 0 && (_jsx("div", { style: {
1279
+ padding: 'var(--space-sm, 0.625rem) var(--space-md, 1rem)',
1280
+ borderTop: '1px solid var(--color-border-light, #eef0f2)',
1281
+ backgroundColor: 'var(--color-surface, #ffffff)',
1282
+ }, children: metaFields.map(([k, v]) => (_jsxs("div", { style: { marginBottom: 'var(--space-2xs, 0.25rem)' }, children: [_jsxs("span", { className: "gen-inline-label", children: [k.replace(/_/g, ' '), ":"] }), _jsx("span", { style: {
1283
+ marginLeft: 'var(--space-xs, 0.375rem)',
1284
+ fontSize: 'var(--type-caption, 0.8125rem)',
1285
+ color: 'var(--color-text, #1a1d23)',
1286
+ }, children: typeof v === 'object' ? JSON.stringify(v, null, 2) : String(v) })] }, k))) })), (!stages || !Array.isArray(stages) || stages.length === 0) && metaFields.length > 0 && (_jsx("div", { style: {
1287
+ padding: 'var(--space-sm, 0.625rem) var(--space-md, 1rem)',
1288
+ fontSize: 'var(--type-caption, 0.8125rem)',
1289
+ color: 'var(--color-text-muted, #6b7280)',
1290
+ }, children: metaFields.map(([k, v]) => (_jsxs("div", { style: { marginBottom: 'var(--space-2xs, 0.25rem)' }, children: [_jsxs("strong", { style: {
1291
+ textTransform: 'capitalize',
1292
+ fontSize: 'var(--type-label, 0.6875rem)',
1293
+ color: 'var(--color-text-muted, #6b7280)',
1294
+ }, children: [k.replace(/_/g, ' '), ":"] }), ' ', typeof v === 'string' ? v : JSON.stringify(v)] }, k))) }))] }, i));
1295
+ }) }));
1296
+ }
1297
+ // ── IntensityMatrix → Dashboard-style Intensity Bars ────────
1298
+ //
1299
+ // Each row: title + category badge + horizontal bar + expandable description.
1300
+ // Bars width proportional to ordinal intensity level. Sorted by intensity (highest first).
1301
+ //
1302
+ // Config:
1303
+ // title_field — row title
1304
+ // subtitle_field — category badge
1305
+ // intensity_field — ordinal intensity value
1306
+ // intensity_scale — ordered levels, e.g. ['low', 'medium', 'high']
1307
+ // description_field — expandable text
1308
+ // sort_by_intensity — sort by intensity descending (default true)
1309
+ function IntensityMatrix({ data, config }) {
1310
+ const [expandedIdx, setExpandedIdx] = React.useState(new Set());
1311
+ const { tokens, getSemanticColor } = useDesignTokens();
1312
+ const captureMode = config._captureMode;
1313
+ const onCapture = config._onCapture;
1314
+ const captureJobId = config._captureJobId;
1315
+ const captureViewKey = config._captureViewKey;
1316
+ const captureSourceType = config._captureSourceType;
1317
+ const captureEntityId = config._captureEntityId;
1318
+ const parentSectionKey = config._parentSectionKey;
1319
+ const parentSectionTitle = config._parentSectionTitle;
1320
+ if (!data || !Array.isArray(data) || data.length === 0)
1321
+ return null;
1322
+ const titleField = config.title_field || 'name';
1323
+ const subtitleField = config.subtitle_field || undefined;
1324
+ const intensityField = config.intensity_field || 'intensity';
1325
+ const descField = config.description_field || 'description';
1326
+ const sortByIntensity = config.sort_by_intensity !== false;
1327
+ // Build intensity scale from config or infer from data
1328
+ const configScale = config.intensity_scale;
1329
+ const intensityScale = configScale
1330
+ || (() => {
1331
+ const vals = new Set();
1332
+ data.forEach(item => {
1333
+ if (typeof item === 'object' && item !== null) {
1334
+ const v = item[intensityField];
1335
+ if (typeof v === 'string')
1336
+ vals.add(v.toLowerCase());
1337
+ }
1338
+ });
1339
+ // Default ordering heuristic
1340
+ const ordered = ['rare', 'low', 'minimal', 'occasional', 'moderate', 'medium', 'frequent', 'significant', 'high', 'very_high', 'critical'];
1341
+ const found = ordered.filter(l => vals.has(l));
1342
+ return found.length > 0 ? found : Array.from(vals);
1343
+ })();
1344
+ // Map intensity value to 0–1 fraction
1345
+ const getIntensityFraction = (val) => {
1346
+ const lower = val.toLowerCase().replace(/\s+/g, '_');
1347
+ const idx = intensityScale.findIndex(s => s.toLowerCase().replace(/\s+/g, '_') === lower);
1348
+ if (idx === -1)
1349
+ return 0.5;
1350
+ if (intensityScale.length <= 1)
1351
+ return 1;
1352
+ return (idx + 1) / intensityScale.length;
1353
+ };
1354
+ // Color for intensity bar — use semantic severity or series palette
1355
+ const getBarColor = (fraction) => {
1356
+ const palette = tokens.primitives.series_palette;
1357
+ if (fraction >= 0.8) {
1358
+ const sem = getSemanticColor('severity', 'high');
1359
+ return sem?.bg || palette[0];
1360
+ }
1361
+ if (fraction >= 0.5) {
1362
+ const sem = getSemanticColor('severity', 'medium');
1363
+ return sem?.bg || palette[4];
1364
+ }
1365
+ const sem = getSemanticColor('severity', 'low');
1366
+ return sem?.bg || palette[2];
1367
+ };
1368
+ const getBarTextColor = (fraction) => {
1369
+ if (fraction >= 0.8) {
1370
+ const sem = getSemanticColor('severity', 'high');
1371
+ return sem?.text || tokens.surfaces.text_default;
1372
+ }
1373
+ if (fraction >= 0.5) {
1374
+ const sem = getSemanticColor('severity', 'medium');
1375
+ return sem?.text || tokens.surfaces.text_default;
1376
+ }
1377
+ const sem = getSemanticColor('severity', 'low');
1378
+ return sem?.text || tokens.surfaces.text_default;
1379
+ };
1380
+ // Prepare and optionally sort items
1381
+ const items = data
1382
+ .map((item, origIdx) => {
1383
+ if (typeof item !== 'object' || item === null)
1384
+ return null;
1385
+ const obj = item;
1386
+ const intensityVal = getField(obj, intensityField);
1387
+ const fraction = getIntensityFraction(intensityVal);
1388
+ return { obj, origIdx, intensityVal, fraction };
1389
+ })
1390
+ .filter(Boolean);
1391
+ if (sortByIntensity) {
1392
+ items.sort((a, b) => b.fraction - a.fraction);
1393
+ }
1394
+ const toggleExpand = (idx) => {
1395
+ setExpandedIdx(prev => {
1396
+ const next = new Set(prev);
1397
+ if (next.has(idx))
1398
+ next.delete(idx);
1399
+ else
1400
+ next.add(idx);
1401
+ return next;
1402
+ });
1403
+ };
1404
+ const DESC_LIMIT = Infinity; // Show full text — no truncation
1405
+ return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '2px' }, children: items.map((item, displayIdx) => {
1406
+ const { obj, origIdx, intensityVal, fraction } = item;
1407
+ const title = getField(obj, titleField);
1408
+ const subtitle = subtitleField ? getField(obj, subtitleField) : '';
1409
+ const desc = getField(obj, descField);
1410
+ const isExpanded = expandedIdx.has(displayIdx);
1411
+ const needsTruncation = desc.length > DESC_LIMIT;
1412
+ const barColor = getBarColor(fraction);
1413
+ const barTextColor = getBarTextColor(fraction);
1414
+ const barWidth = Math.max(fraction * 100, 8); // minimum 8% for visibility
1415
+ return (_jsxs("div", { style: {
1416
+ backgroundColor: 'var(--color-surface, #ffffff)',
1417
+ borderLeft: `3px solid ${barColor}`,
1418
+ padding: '0',
1419
+ borderRadius: '0 var(--radius-sm, 4px) var(--radius-sm, 4px) 0',
1420
+ }, children: [_jsxs("div", { style: {
1421
+ display: 'grid',
1422
+ gridTemplateColumns: '1fr auto minmax(80px, 160px)',
1423
+ gap: 'var(--space-sm, 0.5rem)',
1424
+ alignItems: 'center',
1425
+ padding: 'var(--space-xs, 0.375rem) var(--space-md, 1rem)',
1426
+ cursor: desc ? 'pointer' : 'default',
1427
+ }, onClick: () => desc && toggleExpand(displayIdx), children: [_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', gap: 'var(--space-sm, 0.5rem)', flexWrap: 'wrap', minWidth: 0 }, children: [_jsx("span", { style: {
1428
+ fontWeight: 600,
1429
+ fontSize: 'var(--type-body, 0.9375rem)',
1430
+ color: 'var(--color-text, #1a1d23)',
1431
+ lineHeight: 'var(--leading-snug, 1.4)',
1432
+ }, children: title }), subtitle && (_jsx("span", { style: {
1433
+ fontSize: 'var(--type-label, 0.6875rem)',
1434
+ fontWeight: 500,
1435
+ color: 'var(--color-text-muted, #6b7280)',
1436
+ padding: '1px 8px',
1437
+ borderRadius: 'var(--radius-pill, 9999px)',
1438
+ backgroundColor: 'var(--color-surface-alt, #f8f9fa)',
1439
+ whiteSpace: 'nowrap',
1440
+ }, children: subtitle })), captureMode && onCapture && (_jsx("button", { title: "Capture this item", onClick: e => {
1441
+ e.stopPropagation();
1442
+ onCapture({
1443
+ source_view_key: captureViewKey || '',
1444
+ source_item_index: origIdx,
1445
+ source_renderer_type: 'intensity_matrix',
1446
+ content_type: 'item',
1447
+ selected_text: `${title} [${intensityVal}]: ${desc}`.slice(0, 500),
1448
+ structured_data: obj,
1449
+ context_title: parentSectionKey
1450
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > ${title}`
1451
+ : `${captureViewKey || 'Analysis'} > ${title}`,
1452
+ source_type: (captureSourceType || 'analysis'),
1453
+ entity_id: captureEntityId || captureJobId || '',
1454
+ depth_level: parentSectionKey ? 'L2_element' : 'L1_section',
1455
+ parent_context: parentSectionKey ? {
1456
+ section_key: parentSectionKey,
1457
+ section_title: parentSectionTitle || '',
1458
+ } : undefined,
1459
+ });
1460
+ }, style: {
1461
+ background: 'none',
1462
+ border: '1px solid var(--color-border, #ccc)',
1463
+ borderRadius: '4px',
1464
+ color: 'var(--dt-text-faint, #94a3b8)',
1465
+ cursor: 'pointer',
1466
+ padding: '2px 6px',
1467
+ fontSize: '0.7rem',
1468
+ lineHeight: 1,
1469
+ }, children: "\uD83D\uDCCC" }))] }), _jsx("span", { style: {
1470
+ fontSize: 'var(--type-label, 0.6875rem)',
1471
+ fontWeight: 700,
1472
+ color: barTextColor,
1473
+ textTransform: 'uppercase',
1474
+ letterSpacing: '0.04em',
1475
+ whiteSpace: 'nowrap',
1476
+ }, children: intensityVal.replace(/_/g, ' ') }), _jsx("div", { style: {
1477
+ height: '8px',
1478
+ backgroundColor: 'var(--color-surface-alt, #f0f1f3)',
1479
+ borderRadius: '4px',
1480
+ overflow: 'hidden',
1481
+ }, children: _jsx("div", { style: {
1482
+ width: `${barWidth}%`,
1483
+ height: '100%',
1484
+ backgroundColor: barColor,
1485
+ borderRadius: '4px',
1486
+ transition: 'width 300ms ease',
1487
+ } }) })] }), desc && isExpanded && (_jsx("div", { style: {
1488
+ padding: '0 var(--space-md, 1rem) var(--space-sm, 0.5rem)',
1489
+ fontSize: 'var(--type-caption, 0.8125rem)',
1490
+ color: 'var(--color-text-muted, #6b7280)',
1491
+ lineHeight: 'var(--leading-relaxed, 1.65)',
1492
+ borderTop: '1px solid var(--color-border-light, #eef0f2)',
1493
+ marginTop: '2px',
1494
+ paddingTop: 'var(--space-xs, 0.375rem)',
1495
+ }, children: desc })), desc && !isExpanded && needsTruncation && (_jsxs("div", { style: {
1496
+ padding: '0 var(--space-md, 1rem) var(--space-xs, 0.25rem)',
1497
+ fontSize: 'var(--type-label, 0.6875rem)',
1498
+ color: 'var(--color-text-faint, #9ca3af)',
1499
+ }, children: [desc.slice(0, DESC_LIMIT), "..."] }))] }, origIdx));
1500
+ }) }));
1501
+ }
1502
+ // ── MoveRepertoire → Grouped Intellectual Gestures ──────────
1503
+ //
1504
+ // Items grouped by a category field. Each group has a colored header with
1505
+ // count badge. Items within groups are compact rows. Groups are collapsible.
1506
+ //
1507
+ // Config:
1508
+ // title_field — item title
1509
+ // group_field — field to group by
1510
+ // description_field — item description
1511
+ // badge_field — optional extra badge per item
1512
+ // collapse_groups — start collapsed (default false)
1513
+ function MoveRepertoire({ data, config }) {
1514
+ const { tokens } = useDesignTokens();
1515
+ const [collapsedGroups, setCollapsedGroups] = React.useState(new Set());
1516
+ const [expandedDescs, setExpandedDescs] = React.useState(new Set());
1517
+ const [initialized, setInitialized] = React.useState(false);
1518
+ const captureMode = config._captureMode;
1519
+ const onCapture = config._onCapture;
1520
+ const captureJobId = config._captureJobId;
1521
+ const captureViewKey = config._captureViewKey;
1522
+ const captureSourceType = config._captureSourceType;
1523
+ const captureEntityId = config._captureEntityId;
1524
+ const parentSectionKey = config._parentSectionKey;
1525
+ const parentSectionTitle = config._parentSectionTitle;
1526
+ if (!data || !Array.isArray(data) || data.length === 0)
1527
+ return null;
1528
+ const titleField = config.title_field || 'name';
1529
+ const groupField = config.group_field || 'type';
1530
+ const descField = config.description_field || 'description';
1531
+ const badgeField = config.badge_field || undefined;
1532
+ const startCollapsed = config.collapse_groups === true;
1533
+ // Group items
1534
+ const groups = new Map();
1535
+ data.forEach((item, idx) => {
1536
+ if (typeof item !== 'object' || item === null)
1537
+ return;
1538
+ const obj = item;
1539
+ const groupVal = getField(obj, groupField) || 'Other';
1540
+ if (!groups.has(groupVal))
1541
+ groups.set(groupVal, []);
1542
+ groups.get(groupVal).push({ obj, origIdx: idx });
1543
+ });
1544
+ // Initialize collapsed state on first render with data
1545
+ if (!initialized && startCollapsed && groups.size > 0) {
1546
+ setCollapsedGroups(new Set(groups.keys()));
1547
+ setInitialized(true);
1548
+ }
1549
+ const toggleGroup = (group) => {
1550
+ setCollapsedGroups(prev => {
1551
+ const next = new Set(prev);
1552
+ if (next.has(group))
1553
+ next.delete(group);
1554
+ else
1555
+ next.add(group);
1556
+ return next;
1557
+ });
1558
+ };
1559
+ const toggleDesc = (key) => {
1560
+ setExpandedDescs(prev => {
1561
+ const next = new Set(prev);
1562
+ if (next.has(key))
1563
+ next.delete(key);
1564
+ else
1565
+ next.add(key);
1566
+ return next;
1567
+ });
1568
+ };
1569
+ const palette = tokens.primitives.series_palette;
1570
+ const DESC_LIMIT = Infinity; // Show full text — no truncation
1571
+ const groupEntries = Array.from(groups.entries());
1572
+ return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--space-md, 1rem)' }, children: groupEntries.map(([groupName, items], groupIdx) => {
1573
+ const isCollapsed = collapsedGroups.has(groupName);
1574
+ const groupColor = palette[groupIdx % palette.length];
1575
+ return (_jsxs("div", { style: {
1576
+ borderRadius: 'var(--radius-md, 8px)',
1577
+ border: '1px solid var(--color-border, #e2e5e9)',
1578
+ overflow: 'hidden',
1579
+ }, children: [_jsxs("div", { onClick: () => toggleGroup(groupName), style: {
1580
+ display: 'flex',
1581
+ alignItems: 'center',
1582
+ gap: 'var(--space-sm, 0.5rem)',
1583
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
1584
+ backgroundColor: groupColor,
1585
+ color: tokens.surfaces.text_on_accent,
1586
+ cursor: 'pointer',
1587
+ userSelect: 'none',
1588
+ }, children: [_jsx("span", { style: {
1589
+ fontSize: '11px',
1590
+ transition: 'transform 150ms ease',
1591
+ transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
1592
+ display: 'inline-block',
1593
+ }, children: "\u25BE" }), _jsx("span", { style: {
1594
+ fontWeight: 600,
1595
+ fontSize: 'var(--type-body, 0.9375rem)',
1596
+ textTransform: 'capitalize',
1597
+ }, children: groupName.replace(/_/g, ' ') }), _jsx("span", { style: {
1598
+ marginLeft: 'auto',
1599
+ fontSize: 'var(--type-label, 0.6875rem)',
1600
+ fontWeight: 700,
1601
+ backgroundColor: 'rgba(255,255,255,0.25)',
1602
+ padding: '1px 8px',
1603
+ borderRadius: 'var(--radius-pill, 9999px)',
1604
+ }, children: items.length })] }), !isCollapsed && (_jsx("div", { style: { display: 'flex', flexDirection: 'column' }, children: items.map(({ obj, origIdx }, itemIdx) => {
1605
+ const title = getField(obj, titleField);
1606
+ const desc = getField(obj, descField);
1607
+ const badge = badgeField ? getField(obj, badgeField) : '';
1608
+ const descKey = `${groupName}-${itemIdx}`;
1609
+ const isDescExpanded = expandedDescs.has(descKey);
1610
+ const needsTruncation = desc.length > DESC_LIMIT;
1611
+ return (_jsxs("div", { style: {
1612
+ padding: 'var(--space-xs, 0.375rem) var(--space-md, 1rem)',
1613
+ borderBottom: itemIdx < items.length - 1 ? '1px solid var(--color-border-light, #eef0f2)' : 'none',
1614
+ backgroundColor: itemIdx % 2 === 0 ? 'var(--color-surface, #ffffff)' : 'var(--color-surface-alt, #fafbfc)',
1615
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', gap: 'var(--space-sm, 0.5rem)', flexWrap: 'wrap' }, children: [_jsx("span", { style: {
1616
+ fontWeight: 600,
1617
+ fontSize: 'var(--type-body, 0.9375rem)',
1618
+ color: 'var(--color-text, #1a1d23)',
1619
+ lineHeight: 'var(--leading-snug, 1.4)',
1620
+ }, children: title }), badge && (_jsx("span", { style: {
1621
+ fontSize: 'var(--type-label, 0.6875rem)',
1622
+ fontWeight: 500,
1623
+ color: groupColor,
1624
+ padding: '1px 8px',
1625
+ borderRadius: 'var(--radius-pill, 9999px)',
1626
+ backgroundColor: 'var(--color-surface-alt, #f8f9fa)',
1627
+ }, children: badge })), captureMode && onCapture && (_jsx("button", { title: "Capture this item", onClick: e => {
1628
+ e.stopPropagation();
1629
+ onCapture({
1630
+ source_view_key: captureViewKey || '',
1631
+ source_item_index: origIdx,
1632
+ source_renderer_type: 'move_repertoire',
1633
+ content_type: 'item',
1634
+ selected_text: `[${groupName}] ${title}: ${desc}`.slice(0, 500),
1635
+ structured_data: obj,
1636
+ context_title: parentSectionKey
1637
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > ${groupName} > ${title}`
1638
+ : `${captureViewKey || 'Analysis'} > ${groupName} > ${title}`,
1639
+ source_type: (captureSourceType || 'analysis'),
1640
+ entity_id: captureEntityId || captureJobId || '',
1641
+ depth_level: 'L2_element',
1642
+ parent_context: parentSectionKey ? {
1643
+ section_key: parentSectionKey,
1644
+ section_title: parentSectionTitle || '',
1645
+ } : undefined,
1646
+ });
1647
+ }, style: {
1648
+ background: 'none',
1649
+ border: '1px solid var(--color-border, #ccc)',
1650
+ borderRadius: '4px',
1651
+ color: 'var(--dt-text-faint, #94a3b8)',
1652
+ cursor: 'pointer',
1653
+ padding: '2px 6px',
1654
+ fontSize: '0.7rem',
1655
+ lineHeight: 1,
1656
+ marginLeft: 'auto',
1657
+ }, children: "\uD83D\uDCCC" }))] }), desc && (_jsx("div", { style: {
1658
+ marginTop: '2px',
1659
+ fontSize: 'var(--type-caption, 0.8125rem)',
1660
+ color: 'var(--color-text-muted, #6b7280)',
1661
+ lineHeight: 'var(--leading-relaxed, 1.6)',
1662
+ cursor: needsTruncation ? 'pointer' : 'default',
1663
+ }, onClick: () => needsTruncation && toggleDesc(descKey), children: needsTruncation && !isDescExpanded
1664
+ ? desc.slice(0, DESC_LIMIT) + '...'
1665
+ : desc }))] }, itemIdx));
1666
+ }) }))] }, groupName));
1667
+ }) }));
1668
+ }
1669
+ // ── DialecticalPair → Tension/Contrast Visualization ────────
1670
+ //
1671
+ // Two panels with a central tension indicator. Left = thesis/foregrounded,
1672
+ // right = antithesis/suppressed. Central node shows relationship type.
1673
+ //
1674
+ // Config:
1675
+ // left_key, right_key — field names or sub-section keys for left/right
1676
+ // left_label, right_label — panel headers
1677
+ // relationship_label — central node text (default "vs")
1678
+ // left_title_field, right_title_field — title fields within items
1679
+ // left_description_field, right_description_field — description fields
1680
+ function DialecticalPair({ data, config }) {
1681
+ const { tokens } = useDesignTokens();
1682
+ const [expandedLeft, setExpandedLeft] = React.useState(new Set());
1683
+ const [expandedRight, setExpandedRight] = React.useState(new Set());
1684
+ const captureMode = config._captureMode;
1685
+ const onCapture = config._onCapture;
1686
+ const captureJobId = config._captureJobId;
1687
+ const captureViewKey = config._captureViewKey;
1688
+ const captureSourceType = config._captureSourceType;
1689
+ const captureEntityId = config._captureEntityId;
1690
+ const parentSectionKey = config._parentSectionKey;
1691
+ const parentSectionTitle = config._parentSectionTitle;
1692
+ const leftKey = config.left_key || 'left';
1693
+ const rightKey = config.right_key || 'right';
1694
+ const leftLabel = config.left_label || leftKey.replace(/_/g, ' ');
1695
+ const rightLabel = config.right_label || rightKey.replace(/_/g, ' ');
1696
+ const relationshipLabel = config.relationship_label || 'vs';
1697
+ const leftTitleField = config.left_title_field || undefined;
1698
+ const rightTitleField = config.right_title_field || undefined;
1699
+ const leftDescField = config.left_description_field || undefined;
1700
+ const rightDescField = config.right_description_field || undefined;
1701
+ // Resolve data shape: could be {left_key: [...], right_key: [...]} or [{left_field, right_field}, ...]
1702
+ let leftItems = [];
1703
+ let rightItems = [];
1704
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
1705
+ // Object with left/right sub-arrays
1706
+ const obj = data;
1707
+ const rawLeft = obj[leftKey];
1708
+ const rawRight = obj[rightKey];
1709
+ if (Array.isArray(rawLeft))
1710
+ leftItems = rawLeft.filter(x => typeof x === 'object' && x !== null);
1711
+ if (Array.isArray(rawRight))
1712
+ rightItems = rawRight.filter(x => typeof x === 'object' && x !== null);
1713
+ }
1714
+ else if (Array.isArray(data)) {
1715
+ // Array of paired objects — split using left_key/right_key as field names
1716
+ data.forEach(item => {
1717
+ if (typeof item !== 'object' || item === null)
1718
+ return;
1719
+ const obj = item;
1720
+ // If fields exist, treat as paired data
1721
+ const leftVal = obj[leftKey];
1722
+ const rightVal = obj[rightKey];
1723
+ if (leftVal !== undefined || rightVal !== undefined) {
1724
+ leftItems.push({ text: leftVal, ...obj });
1725
+ rightItems.push({ text: rightVal, ...obj });
1726
+ }
1727
+ });
1728
+ }
1729
+ if (leftItems.length === 0 && rightItems.length === 0)
1730
+ return null;
1731
+ const palette = tokens.primitives.series_palette;
1732
+ const leftColor = palette[0];
1733
+ const rightColor = palette[1];
1734
+ const DESC_LIMIT = Infinity; // Show full text — no truncation
1735
+ const autoFindTitle = (obj, explicitField) => {
1736
+ if (explicitField && obj[explicitField])
1737
+ return String(obj[explicitField]);
1738
+ for (const k of TITLE_HINTS) {
1739
+ if (obj[k] && typeof obj[k] === 'string')
1740
+ return String(obj[k]);
1741
+ }
1742
+ // Fallback: first short string
1743
+ const firstShort = Object.entries(obj).find(([, v]) => typeof v === 'string' && v.length < 80);
1744
+ return firstShort ? String(firstShort[1]) : '';
1745
+ };
1746
+ const autoFindDesc = (obj, explicitField) => {
1747
+ if (explicitField && obj[explicitField])
1748
+ return String(obj[explicitField]);
1749
+ for (const k of DESC_HINTS) {
1750
+ if (obj[k] && typeof obj[k] === 'string')
1751
+ return String(obj[k]);
1752
+ }
1753
+ // Fallback: longest string
1754
+ let longest = '';
1755
+ Object.values(obj).forEach(v => {
1756
+ if (typeof v === 'string' && v.length > longest.length)
1757
+ longest = v;
1758
+ });
1759
+ return longest;
1760
+ };
1761
+ const toggleLeft = (idx) => {
1762
+ setExpandedLeft(prev => {
1763
+ const next = new Set(prev);
1764
+ if (next.has(idx))
1765
+ next.delete(idx);
1766
+ else
1767
+ next.add(idx);
1768
+ return next;
1769
+ });
1770
+ };
1771
+ const toggleRight = (idx) => {
1772
+ setExpandedRight(prev => {
1773
+ const next = new Set(prev);
1774
+ if (next.has(idx))
1775
+ next.delete(idx);
1776
+ else
1777
+ next.add(idx);
1778
+ return next;
1779
+ });
1780
+ };
1781
+ const renderPanel = (items, side, color, titleFieldOverride, descFieldOverride, expandedSet, toggleFn) => (_jsx("div", { style: {
1782
+ flex: 1,
1783
+ display: 'flex',
1784
+ flexDirection: 'column',
1785
+ maxHeight: '400px',
1786
+ overflowY: 'auto',
1787
+ }, children: items.map((obj, idx) => {
1788
+ const title = autoFindTitle(obj, titleFieldOverride);
1789
+ const desc = autoFindDesc(obj, descFieldOverride);
1790
+ const isExpanded = expandedSet.has(idx);
1791
+ const needsTruncation = desc.length > DESC_LIMIT;
1792
+ return (_jsxs("div", { style: {
1793
+ padding: 'var(--space-xs, 0.375rem) var(--space-sm, 0.5rem)',
1794
+ borderBottom: idx < items.length - 1 ? '1px solid var(--color-border-light, #eef0f2)' : 'none',
1795
+ cursor: needsTruncation ? 'pointer' : 'default',
1796
+ }, onClick: () => needsTruncation && toggleFn(idx), children: [_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', gap: 'var(--space-xs, 0.25rem)' }, children: [_jsx("span", { style: {
1797
+ fontWeight: 600,
1798
+ fontSize: 'var(--type-caption, 0.8125rem)',
1799
+ color: 'var(--color-text, #1a1d23)',
1800
+ lineHeight: 'var(--leading-snug, 1.4)',
1801
+ }, children: title }), captureMode && onCapture && (_jsx("button", { title: `Capture ${side} item`, onClick: e => {
1802
+ e.stopPropagation();
1803
+ onCapture({
1804
+ source_view_key: captureViewKey || '',
1805
+ source_item_index: idx,
1806
+ source_renderer_type: 'dialectical_pair',
1807
+ content_type: 'item',
1808
+ selected_text: `[${side === 'left' ? leftLabel : rightLabel}] ${title}: ${desc}`.slice(0, 500),
1809
+ structured_data: obj,
1810
+ context_title: parentSectionKey
1811
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > ${side === 'left' ? leftLabel : rightLabel} > ${title}`
1812
+ : `${captureViewKey || 'Analysis'} > ${side === 'left' ? leftLabel : rightLabel} > ${title}`,
1813
+ source_type: (captureSourceType || 'analysis'),
1814
+ entity_id: captureEntityId || captureJobId || '',
1815
+ depth_level: 'L2_element',
1816
+ parent_context: parentSectionKey ? {
1817
+ section_key: parentSectionKey,
1818
+ section_title: parentSectionTitle || '',
1819
+ } : undefined,
1820
+ });
1821
+ }, style: {
1822
+ background: 'none',
1823
+ border: '1px solid var(--color-border, #ccc)',
1824
+ borderRadius: '4px',
1825
+ color: 'var(--dt-text-faint, #94a3b8)',
1826
+ cursor: 'pointer',
1827
+ padding: '2px 6px',
1828
+ fontSize: '0.7rem',
1829
+ lineHeight: 1,
1830
+ marginLeft: 'auto',
1831
+ flexShrink: 0,
1832
+ }, children: "\uD83D\uDCCC" }))] }), desc && (_jsx("div", { style: {
1833
+ marginTop: '2px',
1834
+ fontSize: 'var(--type-label, 0.6875rem)',
1835
+ color: 'var(--color-text-muted, #6b7280)',
1836
+ lineHeight: 'var(--leading-relaxed, 1.6)',
1837
+ }, children: needsTruncation && !isExpanded
1838
+ ? desc.slice(0, DESC_LIMIT) + '...'
1839
+ : desc }))] }, idx));
1840
+ }) }));
1841
+ return (_jsxs("div", { style: {
1842
+ borderRadius: 'var(--radius-md, 8px)',
1843
+ border: '1px solid var(--color-border, #e2e5e9)',
1844
+ overflow: 'hidden',
1845
+ }, children: [_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr auto 1fr' }, children: [_jsx("div", { style: {
1846
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
1847
+ backgroundColor: leftColor,
1848
+ color: tokens.surfaces.text_on_accent,
1849
+ fontSize: 'var(--type-caption, 0.8125rem)',
1850
+ fontWeight: 600,
1851
+ textTransform: 'uppercase',
1852
+ letterSpacing: '0.06em',
1853
+ textAlign: 'center',
1854
+ }, children: leftLabel }), _jsx("div", { style: {
1855
+ display: 'flex',
1856
+ alignItems: 'center',
1857
+ justifyContent: 'center',
1858
+ padding: '0 var(--space-sm, 0.5rem)',
1859
+ backgroundColor: tokens.surfaces.surface_alt,
1860
+ position: 'relative',
1861
+ }, children: _jsx("span", { style: {
1862
+ fontSize: 'var(--type-label, 0.6875rem)',
1863
+ fontWeight: 800,
1864
+ color: tokens.surfaces.text_muted,
1865
+ textTransform: 'uppercase',
1866
+ letterSpacing: '0.1em',
1867
+ padding: '2px 10px',
1868
+ borderRadius: 'var(--radius-pill, 9999px)',
1869
+ border: `2px solid ${tokens.surfaces.border_accent}`,
1870
+ backgroundColor: tokens.surfaces.surface_default,
1871
+ whiteSpace: 'nowrap',
1872
+ }, children: relationshipLabel }) }), _jsx("div", { style: {
1873
+ padding: 'var(--space-sm, 0.5rem) var(--space-md, 1rem)',
1874
+ backgroundColor: rightColor,
1875
+ color: tokens.surfaces.text_on_accent,
1876
+ fontSize: 'var(--type-caption, 0.8125rem)',
1877
+ fontWeight: 600,
1878
+ textTransform: 'uppercase',
1879
+ letterSpacing: '0.06em',
1880
+ textAlign: 'center',
1881
+ }, children: rightLabel })] }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr auto 1fr' }, children: [renderPanel(leftItems, 'left', leftColor, leftTitleField, leftDescField, expandedLeft, toggleLeft), _jsx("div", { style: {
1882
+ width: '3px',
1883
+ background: `linear-gradient(to bottom, ${leftColor}, ${tokens.surfaces.border_accent}, ${rightColor})`,
1884
+ } }), renderPanel(rightItems, 'right', rightColor, rightTitleField, rightDescField, expandedRight, toggleRight)] })] }));
1885
+ }
1886
+ // ── OrderedFlow → Sequential Content Units ───────────────
1887
+ //
1888
+ // Generic renderer for any ordered sequence of content units:
1889
+ // chapters, argument steps, methodology phases, dialectical moves,
1890
+ // policy stages, evidence links, etc.
1891
+ //
1892
+ // Config:
1893
+ // title_field — primary label (auto-strips "Chapter N:" / "Step N:" prefixes)
1894
+ // subtitle_field — category/role badge (colored by design token semantic lookup)
1895
+ // description_field — expandable detail text
1896
+ // number_field — explicit numbering field (falls back to extracting from title or index)
1897
+ // strip_prefix — regex to strip from title (default: /^(Chapter|Step|Phase|Stage)\s+\d+[.:]\s*/i)
1898
+ function OrderedFlow({ data, config }) {
1899
+ const [expandedIdx, setExpandedIdx] = React.useState(null);
1900
+ const { tokens, getCategoryColor } = useDesignTokens();
1901
+ if (!data || !Array.isArray(data) || data.length === 0)
1902
+ return null;
1903
+ const titleField = config.title_field || 'title';
1904
+ const subtitleField = config.subtitle_field || 'type';
1905
+ const descriptionField = config.description_field || 'description';
1906
+ const numberField = config.number_field || undefined;
1907
+ const stripPrefix = config.strip_prefix;
1908
+ const prefixRegex = stripPrefix
1909
+ ? new RegExp(stripPrefix, 'i')
1910
+ : /^(Chapter|Step|Phase|Stage|Part|Section|Move)\s+\d+[.:]\s*/i;
1911
+ const lineColor = tokens.surfaces?.border_default || '#e2e5e9';
1912
+ return (_jsxs("div", { style: { position: 'relative', paddingLeft: '36px' }, children: [_jsx("div", { style: {
1913
+ position: 'absolute',
1914
+ left: '11px',
1915
+ top: '12px',
1916
+ bottom: '12px',
1917
+ width: '2px',
1918
+ backgroundColor: lineColor,
1919
+ } }), data.map((item, idx) => {
1920
+ if (typeof item !== 'object' || item === null)
1921
+ return null;
1922
+ const obj = item;
1923
+ const title = getField(obj, titleField);
1924
+ const category = getField(obj, subtitleField);
1925
+ const categoryLower = category.toLowerCase();
1926
+ const desc = getField(obj, descriptionField);
1927
+ const isExpanded = expandedIdx === idx;
1928
+ const isLast = idx === data.length - 1;
1929
+ // Resolve step number: explicit field → extract from title → index
1930
+ let stepLabel;
1931
+ if (numberField && obj[numberField] !== undefined) {
1932
+ stepLabel = String(obj[numberField]);
1933
+ }
1934
+ else {
1935
+ const numMatch = title.match(/(\d+)/);
1936
+ stepLabel = numMatch ? numMatch[1] : String(idx + 1);
1937
+ }
1938
+ // Strip common prefixes for cleaner display
1939
+ const cleanTitle = title.replace(prefixRegex, '');
1940
+ // Color from design tokens: try categorical lookup, then series palette fallback
1941
+ const catColor = getCategoryColor?.(subtitleField, categoryLower);
1942
+ const dotColor = catColor?.text
1943
+ || tokens.primitives.series_palette[idx % tokens.primitives.series_palette.length];
1944
+ const DESC_LIMIT = Infinity; // Show full text — no truncation
1945
+ const needsTruncation = desc.length > DESC_LIMIT;
1946
+ const displayDesc = isExpanded || !needsTruncation
1947
+ ? desc
1948
+ : desc.slice(0, DESC_LIMIT) + '...';
1949
+ return (_jsxs("div", { onClick: () => needsTruncation && setExpandedIdx(isExpanded ? null : idx), style: {
1950
+ position: 'relative',
1951
+ paddingBottom: isLast ? 0 : 'var(--space-xs, 0.375rem)',
1952
+ marginBottom: isLast ? 0 : 'var(--space-xs, 0.375rem)',
1953
+ cursor: needsTruncation ? 'pointer' : 'default',
1954
+ }, children: [_jsx("div", { style: {
1955
+ position: 'absolute',
1956
+ left: '-36px',
1957
+ top: '2px',
1958
+ width: '24px',
1959
+ height: '24px',
1960
+ borderRadius: '50%',
1961
+ backgroundColor: dotColor,
1962
+ color: '#fff',
1963
+ display: 'flex',
1964
+ alignItems: 'center',
1965
+ justifyContent: 'center',
1966
+ fontSize: '11px',
1967
+ fontWeight: 700,
1968
+ zIndex: 1,
1969
+ fontFamily: 'var(--font-mono, monospace)',
1970
+ }, children: stepLabel }), _jsxs("div", { style: {
1971
+ display: 'flex',
1972
+ alignItems: 'baseline',
1973
+ gap: 'var(--space-sm, 0.5rem)',
1974
+ flexWrap: 'wrap',
1975
+ lineHeight: 'var(--leading-snug, 1.4)',
1976
+ }, children: [_jsx("span", { style: {
1977
+ fontWeight: 600,
1978
+ fontSize: 'var(--type-body, 0.9375rem)',
1979
+ color: 'var(--color-text, #1a1d23)',
1980
+ }, children: cleanTitle }), category && (_jsx("span", { style: {
1981
+ fontSize: '10px',
1982
+ fontWeight: 600,
1983
+ textTransform: 'uppercase',
1984
+ letterSpacing: '0.06em',
1985
+ color: dotColor,
1986
+ opacity: 0.85,
1987
+ }, children: category }))] }), desc && (_jsx("div", { style: {
1988
+ marginTop: '2px',
1989
+ fontSize: 'var(--type-caption, 0.8125rem)',
1990
+ color: 'var(--color-text-muted, #6b7280)',
1991
+ lineHeight: 'var(--leading-relaxed, 1.6)',
1992
+ }, children: displayDesc }))] }, idx));
1993
+ })] }));
1994
+ }
1995
+ // ── RichDescriptionList → Readable Stacked Items ──────────
1996
+ //
1997
+ // Vertically-stacked list where each item gets room to breathe.
1998
+ // Handles BOTH string arrays ("Label: description") and object arrays.
1999
+ //
2000
+ // Config:
2001
+ // title_field — For object arrays: field name for label
2002
+ // description_field — For object arrays: field name for description
2003
+ // separator — For string arrays: split label from description (default ":")
2004
+ // max_visible_chars — Auto-collapse longer descriptions (default 200)
2005
+ // badge_fields — Optional extra fields to show as badges
2006
+ function RichDescriptionList({ data, config }) {
2007
+ const [expandedIdx, setExpandedIdx] = React.useState(new Set());
2008
+ const { tokens } = useDesignTokens();
2009
+ const captureMode = config._captureMode;
2010
+ const onCapture = config._onCapture;
2011
+ const captureJobId = config._captureJobId;
2012
+ const captureViewKey = config._captureViewKey;
2013
+ const captureSourceType = config._captureSourceType;
2014
+ const captureEntityId = config._captureEntityId;
2015
+ const parentSectionKey = config._parentSectionKey;
2016
+ const parentSectionTitle = config._parentSectionTitle;
2017
+ if (!data || !Array.isArray(data) || data.length === 0)
2018
+ return null;
2019
+ const titleField = config.title_field;
2020
+ const descriptionField = config.description_field;
2021
+ const separator = config.separator || ':';
2022
+ const maxChars = config.max_visible_chars || Infinity; // Show full text — no truncation
2023
+ const badgeFields = config.badge_fields || [];
2024
+ const palette = tokens.primitives.series_palette;
2025
+ // Parse items — handle string arrays and object arrays
2026
+ const items = data.map(item => {
2027
+ if (typeof item === 'string') {
2028
+ // Parse "Label: description..." format
2029
+ const sepIdx = item.indexOf(separator);
2030
+ if (sepIdx > 0 && sepIdx < 60) {
2031
+ return {
2032
+ label: item.slice(0, sepIdx).trim(),
2033
+ description: item.slice(sepIdx + separator.length).trim(),
2034
+ badges: [],
2035
+ raw: item,
2036
+ };
2037
+ }
2038
+ // No separator found — use first ~40 chars as label
2039
+ const firstSpace = item.indexOf(' ', 30);
2040
+ if (firstSpace > 0) {
2041
+ return { label: item.slice(0, firstSpace), description: item.slice(firstSpace + 1), badges: [], raw: item };
2042
+ }
2043
+ return { label: item, description: '', badges: [], raw: item };
2044
+ }
2045
+ if (typeof item === 'object' && item !== null) {
2046
+ const obj = item;
2047
+ const label = titleField ? getField(obj, titleField) : '';
2048
+ const desc = descriptionField ? getField(obj, descriptionField) : '';
2049
+ const badges = badgeFields.map(f => getField(obj, f)).filter(Boolean);
2050
+ // If no explicit fields, auto-detect
2051
+ if (!label && !desc) {
2052
+ const auto = autoDetectFields(obj);
2053
+ return {
2054
+ label: auto.title ? getField(obj, auto.title) : '',
2055
+ description: auto.description ? getField(obj, auto.description) : '',
2056
+ badges,
2057
+ raw: item,
2058
+ };
2059
+ }
2060
+ return { label, description: desc, badges, raw: item };
2061
+ }
2062
+ return { label: String(item), description: '', badges: [], raw: item };
2063
+ });
2064
+ const toggleExpand = (idx) => {
2065
+ setExpandedIdx(prev => {
2066
+ const next = new Set(prev);
2067
+ if (next.has(idx))
2068
+ next.delete(idx);
2069
+ else
2070
+ next.add(idx);
2071
+ return next;
2072
+ });
2073
+ };
2074
+ return (_jsx("div", { style: {
2075
+ display: 'flex',
2076
+ flexDirection: 'column',
2077
+ gap: 0,
2078
+ borderRadius: 'var(--radius-md, 8px)',
2079
+ border: '1px solid var(--color-border, #e2e5e9)',
2080
+ overflow: 'hidden',
2081
+ }, children: items.map((item, idx) => {
2082
+ const color = palette[idx % palette.length];
2083
+ const isExpanded = expandedIdx.has(idx);
2084
+ const needsTruncation = item.description.length > maxChars;
2085
+ const isLast = idx === items.length - 1;
2086
+ return (_jsxs("div", { style: {
2087
+ display: 'flex',
2088
+ borderBottom: isLast ? 'none' : '1px solid var(--color-border-light, #eef0f2)',
2089
+ }, children: [_jsx("div", { style: {
2090
+ width: '4px',
2091
+ backgroundColor: color,
2092
+ flexShrink: 0,
2093
+ } }), _jsxs("div", { style: {
2094
+ flex: 1,
2095
+ padding: 'var(--space-sm, 0.75rem) var(--space-md, 1rem)',
2096
+ }, children: [_jsxs("div", { style: {
2097
+ display: 'flex',
2098
+ alignItems: 'baseline',
2099
+ gap: 'var(--space-xs, 0.25rem)',
2100
+ }, children: [_jsx("span", { style: {
2101
+ color,
2102
+ fontSize: '14px',
2103
+ lineHeight: 1,
2104
+ flexShrink: 0,
2105
+ }, children: "\u25CF" }), _jsx("span", { style: {
2106
+ fontWeight: 700,
2107
+ fontSize: 'var(--type-caption, 0.8125rem)',
2108
+ textTransform: 'uppercase',
2109
+ letterSpacing: '0.04em',
2110
+ color: 'var(--color-text, #1a1d23)',
2111
+ lineHeight: 'var(--leading-snug, 1.4)',
2112
+ }, children: item.label || `Item ${idx + 1}` }), item.badges.map((badge, bi) => (_jsx("span", { style: {
2113
+ fontSize: '10px',
2114
+ fontWeight: 600,
2115
+ textTransform: 'uppercase',
2116
+ letterSpacing: '0.05em',
2117
+ padding: '1px 6px',
2118
+ borderRadius: 'var(--radius-pill, 9999px)',
2119
+ backgroundColor: `${palette[(idx + bi + 1) % palette.length]}22`,
2120
+ color: palette[(idx + bi + 1) % palette.length],
2121
+ }, children: badge }, bi))), _jsxs("span", { style: {
2122
+ marginLeft: 'auto',
2123
+ fontSize: '10px',
2124
+ fontWeight: 500,
2125
+ color: 'var(--dt-text-faint, #94a3b8)',
2126
+ flexShrink: 0,
2127
+ }, children: ["[", idx + 1, "/", items.length, "]"] }), captureMode && onCapture && (_jsx("button", { title: "Capture this item", onClick: e => {
2128
+ e.stopPropagation();
2129
+ onCapture({
2130
+ source_view_key: captureViewKey || '',
2131
+ source_item_index: idx,
2132
+ source_renderer_type: 'rich_description_list',
2133
+ content_type: 'item',
2134
+ selected_text: `${item.label}: ${item.description}`.slice(0, 500),
2135
+ structured_data: item.raw,
2136
+ context_title: parentSectionKey
2137
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > ${item.label}`
2138
+ : `${captureViewKey || 'Analysis'} > ${item.label}`,
2139
+ source_type: (captureSourceType || 'analysis'),
2140
+ entity_id: captureEntityId || captureJobId || '',
2141
+ depth_level: parentSectionKey ? 'L2_element' : 'L1_section',
2142
+ parent_context: parentSectionKey ? {
2143
+ section_key: parentSectionKey,
2144
+ section_title: parentSectionTitle || '',
2145
+ } : undefined,
2146
+ });
2147
+ }, style: {
2148
+ background: 'none',
2149
+ border: '1px solid var(--color-border, #ccc)',
2150
+ borderRadius: '4px',
2151
+ color: 'var(--dt-text-faint, #94a3b8)',
2152
+ cursor: 'pointer',
2153
+ padding: '2px 6px',
2154
+ fontSize: '0.7rem',
2155
+ lineHeight: 1,
2156
+ flexShrink: 0,
2157
+ }, children: "\uD83D\uDCCC" }))] }), item.description && (_jsxs("div", { style: {
2158
+ marginTop: 'var(--space-2xs, 0.25rem)',
2159
+ fontSize: 'var(--type-caption, 0.8125rem)',
2160
+ color: 'var(--color-text-muted, #6b7280)',
2161
+ lineHeight: 'var(--leading-relaxed, 1.6)',
2162
+ }, children: [needsTruncation && !isExpanded
2163
+ ? item.description.slice(0, maxChars) + '...'
2164
+ : item.description, needsTruncation && (_jsx("button", { onClick: (e) => { e.stopPropagation(); toggleExpand(idx); }, className: "gen-show-more-link", style: {
2165
+ background: 'none',
2166
+ border: 'none',
2167
+ color,
2168
+ cursor: 'pointer',
2169
+ fontSize: '0.75rem',
2170
+ padding: '0 4px',
2171
+ marginLeft: '4px',
2172
+ fontWeight: 600,
2173
+ }, children: isExpanded ? 'collapse ▴' : 'expand ▾' }))] }))] })] }, idx));
2174
+ }) }));
2175
+ }
2176
+ // ── PhaseTimeline → Connected Phase Nodes ──────────────────
2177
+ //
2178
+ // Horizontal connected timeline with prominent phase nodes. Designed for
2179
+ // temporal/sequential data stored as an OBJECT with a phases array and
2180
+ // optional mode badge.
2181
+ //
2182
+ // Config:
2183
+ // phases_field — key containing the phases array (default "phases")
2184
+ // label_field — field within each phase for the label (default "label")
2185
+ // description_field — field within each phase for the description (default "description")
2186
+ // mode_field — optional top-level field for a mode badge (default "mode")
2187
+ function PhaseTimeline({ data, config }) {
2188
+ const [expandedIdx, setExpandedIdx] = React.useState(new Set());
2189
+ const { tokens } = useDesignTokens();
2190
+ const captureMode = config._captureMode;
2191
+ const onCapture = config._onCapture;
2192
+ const captureJobId = config._captureJobId;
2193
+ const captureViewKey = config._captureViewKey;
2194
+ const captureSourceType = config._captureSourceType;
2195
+ const captureEntityId = config._captureEntityId;
2196
+ const parentSectionKey = config._parentSectionKey;
2197
+ const parentSectionTitle = config._parentSectionTitle;
2198
+ if (!data || typeof data !== 'object' || Array.isArray(data))
2199
+ return null;
2200
+ const obj = data;
2201
+ const phasesField = config.phases_field || 'phases';
2202
+ const labelField = config.label_field || 'label';
2203
+ const descField = config.description_field || 'description';
2204
+ const modeField = config.mode_field || 'mode';
2205
+ const rawPhases = obj[phasesField];
2206
+ if (!Array.isArray(rawPhases) || rawPhases.length === 0)
2207
+ return null;
2208
+ const mode = obj[modeField];
2209
+ const palette = tokens.primitives.series_palette;
2210
+ const DESC_LIMIT = Infinity; // Show full text — no truncation
2211
+ const phases = rawPhases
2212
+ .filter(p => typeof p === 'object' && p !== null)
2213
+ .map(p => p);
2214
+ const toggleExpand = (idx) => {
2215
+ setExpandedIdx(prev => {
2216
+ const next = new Set(prev);
2217
+ if (next.has(idx))
2218
+ next.delete(idx);
2219
+ else
2220
+ next.add(idx);
2221
+ return next;
2222
+ });
2223
+ };
2224
+ return (_jsxs("div", { children: [mode && (_jsx("div", { style: {
2225
+ marginBottom: 'var(--space-sm, 0.75rem)',
2226
+ display: 'flex',
2227
+ justifyContent: 'center',
2228
+ }, children: _jsxs("span", { style: {
2229
+ fontSize: '10px',
2230
+ fontWeight: 700,
2231
+ textTransform: 'uppercase',
2232
+ letterSpacing: '0.08em',
2233
+ padding: '3px 12px',
2234
+ borderRadius: 'var(--radius-pill, 9999px)',
2235
+ backgroundColor: `${palette[0]}18`,
2236
+ color: palette[0],
2237
+ border: `1px solid ${palette[0]}40`,
2238
+ }, children: [mode, " temporality"] }) })), _jsx("div", { style: {
2239
+ display: 'flex',
2240
+ alignItems: 'flex-start',
2241
+ gap: 0,
2242
+ overflow: 'hidden',
2243
+ }, children: phases.map((phase, idx) => {
2244
+ const label = getField(phase, labelField);
2245
+ const desc = getField(phase, descField);
2246
+ const color = palette[idx % palette.length];
2247
+ const isExpanded = expandedIdx.has(idx);
2248
+ const needsTruncation = desc.length > DESC_LIMIT;
2249
+ const isLast = idx === phases.length - 1;
2250
+ return (_jsxs(React.Fragment, { children: [_jsxs("div", { style: {
2251
+ flex: 1,
2252
+ display: 'flex',
2253
+ flexDirection: 'column',
2254
+ alignItems: 'center',
2255
+ minWidth: 0,
2256
+ }, children: [_jsx("div", { style: {
2257
+ width: '36px',
2258
+ height: '36px',
2259
+ borderRadius: '50%',
2260
+ backgroundColor: color,
2261
+ color: '#fff',
2262
+ display: 'flex',
2263
+ alignItems: 'center',
2264
+ justifyContent: 'center',
2265
+ fontSize: '14px',
2266
+ fontWeight: 700,
2267
+ flexShrink: 0,
2268
+ zIndex: 1,
2269
+ fontFamily: 'var(--font-mono, monospace)',
2270
+ boxShadow: `0 0 0 3px ${color}30`,
2271
+ }, children: idx + 1 }), _jsx("div", { style: {
2272
+ marginTop: 'var(--space-xs, 0.375rem)',
2273
+ textAlign: 'center',
2274
+ padding: 'var(--space-xs, 0.375rem) var(--space-sm, 0.5rem)',
2275
+ borderRadius: 'var(--radius-md, 8px)',
2276
+ border: `1px solid ${color}40`,
2277
+ backgroundColor: `${color}08`,
2278
+ width: '100%',
2279
+ minHeight: '44px',
2280
+ display: 'flex',
2281
+ flexDirection: 'column',
2282
+ alignItems: 'center',
2283
+ justifyContent: 'center',
2284
+ }, children: _jsx("span", { style: {
2285
+ fontWeight: 700,
2286
+ fontSize: 'var(--type-label, 0.6875rem)',
2287
+ textTransform: 'uppercase',
2288
+ letterSpacing: '0.04em',
2289
+ color: 'var(--color-text, #1a1d23)',
2290
+ lineHeight: 'var(--leading-snug, 1.3)',
2291
+ }, children: label || `Phase ${idx + 1}` }) }), desc && (_jsxs("div", { onClick: () => needsTruncation && toggleExpand(idx), style: {
2292
+ marginTop: 'var(--space-2xs, 0.25rem)',
2293
+ fontSize: 'var(--type-label, 0.6875rem)',
2294
+ color: 'var(--color-text-muted, #6b7280)',
2295
+ lineHeight: 'var(--leading-relaxed, 1.6)',
2296
+ textAlign: 'center',
2297
+ padding: '0 var(--space-2xs, 0.125rem)',
2298
+ cursor: needsTruncation ? 'pointer' : 'default',
2299
+ }, children: [needsTruncation && !isExpanded
2300
+ ? desc.slice(0, DESC_LIMIT) + '...'
2301
+ : desc, needsTruncation && (_jsx("button", { onClick: e => { e.stopPropagation(); toggleExpand(idx); }, style: {
2302
+ background: 'none',
2303
+ border: 'none',
2304
+ color,
2305
+ cursor: 'pointer',
2306
+ fontSize: '0.7rem',
2307
+ padding: '0 3px',
2308
+ marginLeft: '2px',
2309
+ fontWeight: 600,
2310
+ }, children: isExpanded ? '▴' : '▾' }))] })), captureMode && onCapture && (_jsx("button", { title: "Capture this phase", onClick: e => {
2311
+ e.stopPropagation();
2312
+ onCapture({
2313
+ source_view_key: captureViewKey || '',
2314
+ source_item_index: idx,
2315
+ source_renderer_type: 'phase_timeline',
2316
+ content_type: 'item',
2317
+ selected_text: `[Phase ${idx + 1}] ${label}: ${desc}`.slice(0, 500),
2318
+ structured_data: phase,
2319
+ context_title: parentSectionKey
2320
+ ? `${captureViewKey || 'Analysis'} > ${parentSectionTitle || ''} > Phase: ${label}`
2321
+ : `${captureViewKey || 'Analysis'} > Phase: ${label}`,
2322
+ source_type: (captureSourceType || 'analysis'),
2323
+ entity_id: captureEntityId || captureJobId || '',
2324
+ depth_level: parentSectionKey ? 'L2_element' : 'L1_section',
2325
+ parent_context: parentSectionKey ? {
2326
+ section_key: parentSectionKey,
2327
+ section_title: parentSectionTitle || '',
2328
+ } : undefined,
2329
+ });
2330
+ }, style: {
2331
+ marginTop: 'var(--space-2xs, 0.25rem)',
2332
+ background: 'none',
2333
+ border: '1px solid var(--color-border, #ccc)',
2334
+ borderRadius: '4px',
2335
+ color: 'var(--dt-text-faint, #94a3b8)',
2336
+ cursor: 'pointer',
2337
+ padding: '2px 6px',
2338
+ fontSize: '0.7rem',
2339
+ lineHeight: 1,
2340
+ }, children: "\uD83D\uDCCC" }))] }), !isLast && (_jsxs("div", { style: {
2341
+ display: 'flex',
2342
+ alignItems: 'center',
2343
+ paddingTop: '18px',
2344
+ flexShrink: 0,
2345
+ }, children: [_jsx("div", { style: {
2346
+ width: '28px',
2347
+ height: '2px',
2348
+ background: `linear-gradient(to right, ${color}, ${palette[(idx + 1) % palette.length]})`,
2349
+ } }), _jsx("div", { style: {
2350
+ width: 0,
2351
+ height: 0,
2352
+ borderTop: '4px solid transparent',
2353
+ borderBottom: '4px solid transparent',
2354
+ borderLeft: `6px solid ${palette[(idx + 1) % palette.length]}`,
2355
+ } })] }))] }, idx));
2356
+ }) })] }));
2357
+ }
2358
+ // ── Distribution Summary ────────────────────────────────
2359
+ /**
2360
+ * Visual distribution bar chart with optional dominant pattern highlight
2361
+ * and collapsible narrative. Configurable via JSON view definitions.
2362
+ *
2363
+ * Data shape (object):
2364
+ * {
2365
+ * distribution: Record<string, number> | Array<{ key: string; count: number; major_count?: number }>
2366
+ * dominant?: string
2367
+ * narrative?: string
2368
+ * }
2369
+ *
2370
+ * Field mapping config (for data that doesn't use these exact field names):
2371
+ * distribution_field?: string -- field name for distribution data (default: "distribution")
2372
+ * dominant_field?: string -- field name for dominant item (default: "dominant")
2373
+ * narrative_field?: string -- field name for narrative text (default: "narrative")
2374
+ *
2375
+ * Display config:
2376
+ * category?: string -- design token category for colors (e.g., "tactic", "relationship")
2377
+ * dominant_label?: string -- label above dominant value (default: "Dominant")
2378
+ * count_noun?: string -- noun for total count (default: "items")
2379
+ * type_noun?: string -- noun for type count (default: "types")
2380
+ * severity_value?: string -- label for severity badges (default: "major")
2381
+ *
2382
+ * Interactive mode (passed by CardGridRenderer via _ prefix):
2383
+ * _onFilterClick?: (key: string) => void -- makes bars clickable
2384
+ * _activeFilter?: string | null -- highlights active bar
2385
+ * _groups?: Group[] -- live groups (overrides distribution field)
2386
+ */
2387
+ export function DistributionSummary({ data, config }) {
2388
+ const { getCategoryColor, getLabel } = useDesignTokens();
2389
+ const [narrativeExpanded, setNarrativeExpanded] = useState(false);
2390
+ const obj = (data && typeof data === 'object' && !Array.isArray(data))
2391
+ ? data
2392
+ : null;
2393
+ if (!obj)
2394
+ return null;
2395
+ // Config
2396
+ const category = config.category;
2397
+ const dominantLabel = config.dominant_label || 'Dominant';
2398
+ const countNoun = config.count_noun || 'items';
2399
+ const typeNoun = config.type_noun || 'types';
2400
+ const severityValue = config.severity_value || 'major';
2401
+ // Field mapping (allows data to use different field names)
2402
+ const distField = config.distribution_field || 'distribution';
2403
+ const domField = config.dominant_field || 'dominant';
2404
+ const narrField = config.narrative_field || 'narrative';
2405
+ // Interactive mode (injected by CardGridRenderer)
2406
+ const onFilterClick = config._onFilterClick;
2407
+ const activeFilter = config._activeFilter;
2408
+ const liveGroups = config._groups;
2409
+ // Build entries — prefer live groups (interactive) over static distribution
2410
+ const formatName = (key) => (category ? getLabel(category, key) : null) || key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
2411
+ let entries = [];
2412
+ const fallbackColors = { bg: '#f1f5f9', text: '#64748b', border: '#e2e8f0' };
2413
+ if (liveGroups && liveGroups.length > 0) {
2414
+ entries = liveGroups.map(g => ({
2415
+ key: g.key,
2416
+ label: g.label,
2417
+ count: g.items.length,
2418
+ majorCount: g.items.filter(i => String(i.severity || '').toLowerCase() === severityValue).length,
2419
+ colors: (category ? getCategoryColor(category, g.key) : null) || g.style || fallbackColors,
2420
+ }));
2421
+ }
2422
+ else {
2423
+ const rawDist = obj[distField];
2424
+ if (rawDist && typeof rawDist === 'object' && !Array.isArray(rawDist)) {
2425
+ entries = Object.entries(rawDist)
2426
+ .map(([key, count]) => ({
2427
+ key, count: Number(count) || 0, majorCount: 0,
2428
+ label: formatName(key),
2429
+ colors: (category ? getCategoryColor(category, key) : null) || fallbackColors,
2430
+ }))
2431
+ .sort((a, b) => b.count - a.count);
2432
+ }
2433
+ else if (Array.isArray(rawDist)) {
2434
+ entries = rawDist.map(item => {
2435
+ const key = String(item.key || '');
2436
+ return {
2437
+ key, count: Number(item.count || 0), majorCount: Number(item.major_count || 0),
2438
+ label: String(item.label || '') || formatName(key),
2439
+ colors: (category ? getCategoryColor(category, key) : null) || fallbackColors,
2440
+ };
2441
+ }).sort((a, b) => b.count - a.count);
2442
+ }
2443
+ }
2444
+ if (entries.length === 0)
2445
+ return null;
2446
+ const totalCount = entries.reduce((sum, e) => sum + e.count, 0);
2447
+ const maxCount = Math.max(...entries.map(e => e.count));
2448
+ const dominant = obj[domField];
2449
+ const narrative = obj[narrField];
2450
+ const isInteractive = Boolean(onFilterClick);
2451
+ return (_jsxs("div", { className: "ar-dist-summary", children: [_jsxs("div", { className: "gen-summary-header", children: [dominant && (_jsxs("div", { className: "gen-summary-stat", children: [_jsx("span", { className: "gen-stat-label", children: dominantLabel }), _jsx("span", { className: "gen-stat-value", children: formatName(dominant) })] })), _jsxs("div", { className: "gen-summary-counts", children: [_jsx("span", { className: "gen-summary-count-big", children: totalCount }), _jsxs("span", { className: "gen-summary-count-label", children: [countNoun, " across ", entries.length, " ", typeNoun] })] })] }), _jsxs("div", { className: "gen-dist-bars", children: [entries.map(entry => {
2452
+ const pct = Math.max(8, (entry.count / maxCount) * 100);
2453
+ const isActive = activeFilter === entry.key;
2454
+ const BarTag = isInteractive ? 'button' : 'div';
2455
+ return (_jsxs(BarTag, { type: isInteractive ? 'button' : undefined, className: `gen-dist-bar-row ${isActive ? 'gen-dist-bar-row--active' : ''}`, onClick: isInteractive ? () => onFilterClick(entry.key) : undefined, children: [_jsx("span", { className: "gen-dist-bar-label", children: entry.label }), _jsx("span", { className: "gen-dist-bar-track", children: _jsx("span", { className: "gen-dist-bar-fill", style: {
2456
+ width: `${pct}%`,
2457
+ backgroundColor: entry.colors.text,
2458
+ opacity: isActive ? 1 : 0.7,
2459
+ } }) }), _jsx("span", { className: "gen-dist-bar-count", style: { color: entry.colors.text }, children: entry.count }), entry.majorCount > 0 && (_jsxs("span", { className: "gen-dist-bar-severity", children: [entry.majorCount, " ", severityValue] }))] }, entry.key));
2460
+ }), isInteractive && activeFilter && (_jsx("button", { type: "button", className: "gen-dist-bar-clear", onClick: () => onFilterClick(''), children: "Clear filter" }))] }), narrative && (_jsxs(_Fragment, { children: [_jsx("div", { className: `gen-pattern-narrative-wrap ${narrativeExpanded ? 'gen-pattern-narrative-wrap--expanded' : ''}`, children: _jsx("p", { className: "gen-pattern-narrative", children: narrative }) }), _jsx("button", { type: "button", className: "gen-narrative-toggle", onClick: () => setNarrativeExpanded(!narrativeExpanded), children: narrativeExpanded ? 'Show less' : 'Read full analysis' })] }))] }));
2461
+ }
2462
+ //# sourceMappingURL=SubRenderers.js.map