@elytracms/studio 0.0.45 → 0.0.46

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 (58) hide show
  1. package/convex/cms.ts +1 -0
  2. package/convex/schema.ts +3 -0
  3. package/dist/components/ai/ai-chat-panel-lazy.d.ts +1 -0
  4. package/dist/components/ai/ai-chat-panel-lazy.js +21 -0
  5. package/dist/components/ai/ai-chat-panel-lazy.js.map +1 -0
  6. package/dist/components/ai/ai-chat-panel.d.ts +10 -0
  7. package/dist/components/ai/ai-chat-panel.js +202 -0
  8. package/dist/components/ai/ai-chat-panel.js.map +1 -0
  9. package/dist/components/ai/ai-cms-panel.d.ts +9 -0
  10. package/dist/components/ai/ai-cms-panel.js +24 -0
  11. package/dist/components/ai/ai-cms-panel.js.map +1 -0
  12. package/dist/components/ai/ai-panel-toggle.d.ts +8 -0
  13. package/dist/components/ai/ai-panel-toggle.js +27 -0
  14. package/dist/components/ai/ai-panel-toggle.js.map +1 -0
  15. package/dist/components/ai/chat-ui.d.ts +36 -0
  16. package/dist/components/ai/chat-ui.js +68 -0
  17. package/dist/components/ai/chat-ui.js.map +1 -0
  18. package/dist/components/ai/proposal-card.d.ts +15 -0
  19. package/dist/components/ai/proposal-card.js +42 -0
  20. package/dist/components/ai/proposal-card.js.map +1 -0
  21. package/dist/components/app-views/default-modules.js +2 -1
  22. package/dist/components/app-views/default-modules.js.map +1 -1
  23. package/dist/components/cms/document-diff-view.d.ts +41 -0
  24. package/dist/components/cms/document-diff-view.js +299 -0
  25. package/dist/components/cms/document-diff-view.js.map +1 -0
  26. package/dist/components/cms/version-compare.d.ts +7 -13
  27. package/dist/components/cms/version-compare.js +24 -365
  28. package/dist/components/cms/version-compare.js.map +1 -1
  29. package/dist/components/cms/versions-pane.js +1 -1
  30. package/dist/components/cms/versions-pane.js.map +1 -1
  31. package/dist/components/studio-shell.js +2 -1
  32. package/dist/components/studio-shell.js.map +1 -1
  33. package/dist/lib/ai/chat-state.d.ts +73 -0
  34. package/dist/lib/ai/chat-state.js +101 -0
  35. package/dist/lib/ai/chat-state.js.map +1 -0
  36. package/dist/lib/ai/create-flow.d.ts +116 -0
  37. package/dist/lib/ai/create-flow.js +194 -0
  38. package/dist/lib/ai/create-flow.js.map +1 -0
  39. package/dist/lib/ai/index.d.ts +4 -0
  40. package/dist/lib/ai/index.js +4 -0
  41. package/dist/lib/ai/index.js.map +1 -1
  42. package/dist/lib/ai/route-provider.d.ts +50 -0
  43. package/dist/lib/ai/route-provider.js +60 -0
  44. package/dist/lib/ai/route-provider.js.map +1 -0
  45. package/dist/lib/ai/route.d.ts +12 -0
  46. package/dist/lib/ai/route.js +42 -3
  47. package/dist/lib/ai/route.js.map +1 -1
  48. package/dist/lib/ai/tools.d.ts +65 -0
  49. package/dist/lib/ai/tools.js +63 -0
  50. package/dist/lib/ai/tools.js.map +1 -0
  51. package/dist/lib/ai-panel.d.ts +20 -0
  52. package/dist/lib/ai-panel.js +48 -0
  53. package/dist/lib/ai-panel.js.map +1 -0
  54. package/dist/lib/i18n/messages.en.js +34 -0
  55. package/dist/lib/i18n/messages.en.js.map +1 -1
  56. package/dist/lib/persistence/convex-adapter.js +2 -0
  57. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  58. package/package.json +2 -2
@@ -0,0 +1,299 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useMemo, useState } from 'react';
3
+ import { Image as ImageIcon, RotateCcw } from 'lucide-react';
4
+ import { assetSubfieldsOf, isAssetNode, readAssetId, readDocumentRef } from '@elytracms/core/cms-core';
5
+ import { Badge, Button, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
6
+ import { translate } from '@elytracms/studio/lib/i18n';
7
+ import { localeChipLabel } from '@elytracms/studio/lib/locale';
8
+ import { cn } from '@elytracms/studio/lib/utils';
9
+ import { readDiffViewMode, storeDiffViewMode } from '@elytracms/studio/lib/diff-view-preference';
10
+ import { useCollectionView } from '../collection-view/collection-view-context';
11
+ import { fieldLabel } from './cms-labels';
12
+ import { toRefArray } from './cell';
13
+ import { DIFF_TRUNCATE_THRESHOLD, mergeWordDiff, richTextToText, truncateText, truncateTokens, wordDiff, } from './version-compare-text';
14
+ export function DocumentDiffView({ diff, collection, columnHeaders, restore, summaryHint, }) {
15
+ const source = useCollectionView();
16
+ // EC-317 — the unified/side-by-side toggle is a transient, personal-local view
17
+ // preference: read once at mount, written back on every change.
18
+ const [mode, setModeState] = useState(() => readDiffViewMode());
19
+ const setMode = useCallback((next) => {
20
+ setModeState(next);
21
+ storeDiffViewMode(next);
22
+ }, []);
23
+ // EC-317 — long-value truncation state: an `expandAll` flag plus per-row overrides.
24
+ const [expandAll, setExpandAll] = useState(false);
25
+ const [expandedRows, setExpandedRows] = useState(() => new Set());
26
+ const toggleExpand = useCallback((id) => {
27
+ setExpandedRows((prev) => {
28
+ const next = new Set(prev);
29
+ if (next.has(id))
30
+ next.delete(id);
31
+ else
32
+ next.add(id);
33
+ return next;
34
+ });
35
+ }, []);
36
+ const isExpanded = useCallback((id) => expandAll || expandedRows.has(id), [expandAll, expandedRows]);
37
+ const toggleExpandAll = useCallback(() => {
38
+ setExpandAll((prev) => {
39
+ const next = !prev;
40
+ if (!next)
41
+ setExpandedRows(new Set());
42
+ return next;
43
+ });
44
+ }, []);
45
+ const diffDisplay = useMemo(() => ({ mode, expandAll, isExpanded, toggleExpand }), [mode, expandAll, isExpanded, toggleExpand]);
46
+ const fieldOrder = useMemo(() => new Map(collection.fields.map((f, i) => [f.name, i])), [collection]);
47
+ const fieldByName = useMemo(() => new Map(collection.fields.map((f) => [f.name, f])), [collection]);
48
+ const localeRank = useMemo(() => new Map(source.localeConfig.locales.map((l, i) => [l, i])), [source.localeConfig]);
49
+ const groups = useMemo(() => groupByField(diff.changes, fieldOrder, localeRank), [diff.changes, fieldOrder, localeRank]);
50
+ const groupRows = useMemo(() => {
51
+ let rowIndex = 0;
52
+ return groups.map((group) => {
53
+ const zebraStart = rowIndex;
54
+ rowIndex += group.entries.length;
55
+ return { group, field: fieldByName.get(group.field), zebraStart };
56
+ });
57
+ }, [groups, fieldByName]);
58
+ const canRestore = restore?.canRestore ?? false;
59
+ const baseVersion = restore?.baseVersion ?? 0;
60
+ const busy = restore?.busy ?? false;
61
+ const restoreFn = useCallback((scope) => restore?.onRestore(scope), [restore]);
62
+ return (_jsxs(_Fragment, { children: [_jsxs(Row, { gap: "2", align: "center", justify: "between", wrap: true, className: "px-0.5", children: [_jsx(DiffViewToggle, { mode: mode, onChange: setMode }), _jsx(Button, { variant: "ghost", size: "sm", onClick: toggleExpandAll, children: translate(expandAll ? 'cms.field.collapseAll' : 'cms.field.expandAll') })] }), _jsxs(Text, { size: "xs", tone: "muted", className: "px-0.5", children: [translate(diff.changes.length === 1 ? 'cms.version.changeSummary.one' : 'cms.version.changeSummary.other', {
63
+ count: diff.changes.length,
64
+ unchanged: diff.unchangedCount,
65
+ }), summaryHint] }), _jsx(DiffDisplayContext.Provider, { value: diffDisplay, children: _jsxs("div", { className: "pb-1", children: [_jsxs("div", { className: cn(DIFF_ROW_GRID, 'px-2 pb-1.5'), children: [_jsx(Text, { size: "xs", tone: "subtle", className: "uppercase tracking-wide", children: translate('cms.version.fieldColumnHeader') }), mode === 'unified' ? (_jsx(Text, { size: "xs", tone: "subtle", className: "uppercase tracking-wide", children: columnHeaders.unified })) : (_jsx(TwoCol, { before: _jsx(Text, { size: "xs", tone: "subtle", className: "uppercase tracking-wide", children: columnHeaders.before }), after: _jsx(Text, { size: "xs", tone: "subtle", className: "uppercase tracking-wide", children: columnHeaders.after }) }))] }), groupRows.map(({ group, field, zebraStart }) => (_jsx(FieldGroup, { group: group, field: field, zebraStart: zebraStart, baseVersion: baseVersion, canRestore: canRestore, busy: busy, onRestore: restoreFn }, group.field)))] }) })] }));
66
+ }
67
+ const DiffDisplayContext = createContext(null);
68
+ function useDiffDisplay() {
69
+ const ctx = useContext(DiffDisplayContext);
70
+ if (!ctx)
71
+ throw new Error('useDiffDisplay must be rendered under the DocumentDiffView diff area');
72
+ return ctx;
73
+ }
74
+ /** Shared grid template so the descriptor lane + header captions line up. */
75
+ const DIFF_ROW_GRID = 'grid grid-cols-[7.5rem_1fr_1.75rem] items-start gap-x-4';
76
+ /** A before | after grid with a clear central divider. */
77
+ function TwoCol({ before, after, className }) {
78
+ return (_jsxs("div", { className: cn('relative grid grid-cols-2 gap-x-8', className), children: [_jsx("div", { className: "pointer-events-none absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border", "aria-hidden": true }), _jsx("div", { className: "min-w-0 pr-1", children: before }), _jsx("div", { className: "min-w-0 pl-1", children: after })] }));
79
+ }
80
+ /** Mode-aware value display (EC-317). */
81
+ function DiffColumns({ before, after, kind }) {
82
+ const { mode } = useDiffDisplay();
83
+ if (mode === 'unified') {
84
+ return (_jsxs(Stack, { gap: "1", children: [kind !== 'added' ? _jsx(UnifiedLine, { tone: "removed", children: before }) : null, kind !== 'removed' ? _jsx(UnifiedLine, { tone: "added", children: after }) : null] }));
85
+ }
86
+ return _jsx(TwoCol, { before: before, after: after });
87
+ }
88
+ function UnifiedLine({ tone, children }) {
89
+ return (_jsxs(Row, { gap: "2", align: "start", className: cn('rounded-sm px-1.5 py-1', tone === 'removed' ? 'bg-destructive/10' : 'bg-success/10'), children: [_jsx(Text, { size: "xs", mono: true, "aria-hidden": true, className: cn('mt-0.5 shrink-0 select-none', tone === 'removed' ? 'text-destructive' : 'text-success'), children: tone === 'removed' ? '−' : '+' }), _jsx("div", { className: "min-w-0 flex-1", children: children })] }));
90
+ }
91
+ /** Wraps truncatable content with its per-row "Show more"/"Show less" control. */
92
+ function ExpandToggle({ id, isLong, expanded, children, }) {
93
+ const { expandAll, toggleExpand } = useDiffDisplay();
94
+ return (_jsxs("div", { children: [children, isLong && !expandAll ? (_jsx(Button, { variant: "link", size: "sm", className: "h-auto p-0 text-xs", onClick: () => toggleExpand(id), children: expanded ? translate('cms.version.showLess') : translate('cms.version.showMore') })) : null] }));
95
+ }
96
+ /** The unified/side-by-side segmented toggle (EC-317). */
97
+ function DiffViewToggle({ mode, onChange }) {
98
+ return (_jsxs(Row, { gap: "px", className: "rounded-md bg-muted/50 p-0.5", role: "group", "aria-label": translate('cms.version.viewModeAriaLabel'), "data-testid": "diff-view-toggle", children: [_jsx(DiffViewOption, { active: mode === 'sideBySide', label: translate('cms.version.viewSideBySide'), onClick: () => onChange('sideBySide') }), _jsx(DiffViewOption, { active: mode === 'unified', label: translate('cms.version.viewUnified'), onClick: () => onChange('unified') })] }));
99
+ }
100
+ function DiffViewOption({ active, label, onClick }) {
101
+ return (_jsx("button", { type: "button", "aria-pressed": active, "data-testid": `diff-view-option-${active ? 'active' : 'inactive'}`, onClick: onClick, className: cn('rounded-[5px] px-2.5 py-1 text-xs font-medium transition-colors', active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'), children: label }));
102
+ }
103
+ /** One field's diff rows (EC-317). */
104
+ function FieldGroup({ group, field, zebraStart, baseVersion, canRestore, busy, onRestore, }) {
105
+ const label = field ? fieldLabel(field) : group.label;
106
+ return (_jsx("div", { className: "border-t border-border/30", children: group.entries.map((entry, i) => {
107
+ const rowId = `${group.field}:${entry.locale ?? '_'}`;
108
+ const fieldRestore = canRestore && !entry.blocks ? (_jsx(RestoreIcon, { busy: busy, title: entry.locale
109
+ ? translate('cms.version.restoreFieldLocale', {
110
+ field: label,
111
+ locale: localeChipLabel(entry.locale),
112
+ version: baseVersion,
113
+ })
114
+ : translate('cms.version.restoreField', { field: label, version: baseVersion }), onClick: () => onRestore(fieldScope(entry)) })) : null;
115
+ return (_jsx(FieldRow, { zebra: (zebraStart + i) % 2 === 1, descriptor: _jsx(FieldDescriptor, { label: label, locale: entry.locale, kind: entry.kind }), restore: fieldRestore, children: _jsx(EntryBody, { change: entry, rowId: rowId, baseVersion: baseVersion, canRestore: canRestore, busy: busy, onRestore: onRestore }) }, entry.locale ?? '__base__'));
116
+ }) }));
117
+ }
118
+ /** The descriptor lane + value area, one grid row (EC-317). */
119
+ function FieldRow({ descriptor, restore, zebra, children, }) {
120
+ return (_jsxs("div", { className: cn(DIFF_ROW_GRID, 'rounded-sm px-2 py-2.5', zebra && 'bg-muted/40'), children: [_jsx("div", { className: "min-w-0", children: descriptor }), _jsx("div", { className: "min-w-0", children: children }), _jsx("div", { className: "flex justify-end", children: restore })] }));
121
+ }
122
+ /** The descriptor lane's content (EC-317). */
123
+ function FieldDescriptor({ label, locale, kind }) {
124
+ return (_jsxs(Stack, { gap: "1", children: [_jsx(Text, { size: "sm", weight: "medium", className: "min-w-0 truncate", children: label }), _jsxs(Row, { gap: "1", wrap: true, children: [locale ? (_jsx(Badge, { variant: "outline", className: "px-1 py-0 text-[0.6rem] uppercase tracking-wide text-muted-foreground", children: localeChipLabel(locale) })) : null, _jsx(KindTag, { kind: kind })] })] }));
125
+ }
126
+ function EntryBody({ change, rowId, baseVersion, canRestore, busy, onRestore, }) {
127
+ const localeScope = change.locale !== undefined ? { locale: change.locale } : {};
128
+ if (change.blocks) {
129
+ return (_jsx(Stack, { gap: "2", className: "mt-1", children: change.blocks.map((block) => (_jsx(BlockSection, { block: block, rowId: `${rowId}|block:${block.blockId}`, baseVersion: baseVersion, canRestore: canRestore, busy: busy, onRestore: () => onRestore({ kind: 'block', field: change.field, blockId: block.blockId, ...localeScope }) }, block.blockId))) }));
130
+ }
131
+ if (change.type === 'richText') {
132
+ const before = richTextToText(change.before);
133
+ const after = richTextToText(change.after);
134
+ if (before === after)
135
+ return (_jsx(MutedNote, { children: before ? translate('cms.version.textUnchanged') : translate('cms.version.contentChanged') }));
136
+ return _jsx(WordDiffCols, { id: rowId, before: before, after: after });
137
+ }
138
+ if (change.kind === 'changed' && typeof change.before === 'string' && typeof change.after === 'string') {
139
+ return _jsx(WordDiffCols, { id: rowId, before: change.before, after: change.after });
140
+ }
141
+ return (_jsx(DiffColumns, { kind: change.kind, before: _jsx(SideValue, { value: change.before, other: change.after, typeHint: change.type, id: `${rowId}|before` }), after: _jsx(SideValue, { value: change.after, other: change.before, typeHint: change.type, id: `${rowId}|after` }) }));
142
+ }
143
+ function BlockSection({ block, rowId, baseVersion, canRestore, busy, onRestore, }) {
144
+ const name = block.componentId.split('.').pop() ?? block.componentId;
145
+ return (_jsxs("div", { className: "overflow-hidden rounded-md border border-border/60", children: [_jsxs(Row, { gap: "2", align: "center", className: "border-b border-border/50 bg-muted/30 px-2 py-1.5", children: [_jsx(Text, { size: "xs", weight: "semibold", className: "min-w-0 truncate", children: name }), _jsx(Code, { tone: "muted", children: block.componentId }), _jsx(KindTag, { kind: block.kind }), _jsx(Spacer, {}), canRestore ? (_jsx(RestoreIcon, { busy: busy, title: translate('cms.version.restoreBlock', { version: baseVersion }), onClick: onRestore })) : null] }), _jsxs("div", { className: "px-2 py-2", children: [block.props && block.props.length > 0 ? (_jsx(Stack, { gap: "3", className: "border-l-2 border-border/50 pl-3", children: block.props.map((prop) => (_jsx(PropRow, { prop: prop, rowId: `${rowId}|prop:${prop.name}` }, prop.name))) })) : null, block.slotsChanged ? _jsx(MutedNote, { children: translate('cms.version.nestedContentChanged') }) : null, block.kind !== 'changed' && !block.props?.length ? (_jsx(MutedNote, { children: block.kind === 'added' ? translate('cms.version.blockAdded') : translate('cms.version.blockRemoved') })) : null] })] }));
146
+ }
147
+ function PropRow({ prop, rowId }) {
148
+ const useWordDiff = prop.kind === 'changed' && typeof prop.before === 'string' && typeof prop.after === 'string';
149
+ return (_jsxs("div", { children: [_jsx(Text, { size: "xs", tone: "subtle", className: "mb-0.5 block uppercase tracking-wide", children: prettyName(prop.name) }), useWordDiff ? (_jsx(WordDiffCols, { id: rowId, before: String(prop.before), after: String(prop.after) })) : (_jsx(DiffColumns, { kind: prop.kind, before: _jsx(SideValue, { value: prop.before, other: prop.after, id: `${rowId}|before` }), after: _jsx(SideValue, { value: prop.after, other: prop.before, id: `${rowId}|after` }) }))] }));
150
+ }
151
+ /** One side of a non-text change — the value, or an "empty" placeholder. */
152
+ function SideValue({ value, other, typeHint, id, }) {
153
+ if (value === undefined || value === null || value === '') {
154
+ return (_jsx(Text, { size: "sm", tone: "subtle", className: "italic", children: translate('cms.version.emptyValue') }));
155
+ }
156
+ return _jsx(ValueView, { value: value, other: other, typeHint: typeHint, id: id });
157
+ }
158
+ /** Read-only value display, dispatching on field type / self-describing value shape. */
159
+ function ValueView({ value, other, typeHint, id, }) {
160
+ const { isExpanded } = useDiffDisplay();
161
+ if (typeHint === 'asset' || isAssetNode(value))
162
+ return _jsx(AssetValue, { value: value });
163
+ if (typeHint === 'relation' || readDocumentRef(value))
164
+ return _jsx(RelationValue, { value: value, other: other });
165
+ if (typeHint === 'richText')
166
+ return (_jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words", children: richTextToText(value) || translate('cms.version.richText') }));
167
+ if (typeHint === 'boolean' || typeof value === 'boolean')
168
+ return (_jsx(Text, { size: "sm", mono: true, children: value === true ? translate('common.yes') : translate('common.no') }));
169
+ if (typeHint === 'date')
170
+ return (_jsx(Text, { size: "sm", mono: true, children: String(value).slice(0, 10) }));
171
+ if (typeof value === 'object')
172
+ return (_jsx(Text, { size: "sm", tone: "muted", children: Array.isArray(value)
173
+ ? translate(value.length === 1 ? 'cms.version.itemCount.one' : 'cms.version.itemCount.other', {
174
+ count: value.length,
175
+ })
176
+ : translate('cms.version.objectValue') }));
177
+ const expanded = isExpanded(id);
178
+ const { shown, isLong } = truncateText(String(value), expanded);
179
+ return (_jsx(ExpandToggle, { id: id, isLong: isLong, expanded: expanded, children: _jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words", children: shown }) }));
180
+ }
181
+ function RelationValue({ value, other }) {
182
+ const source = useCollectionView();
183
+ const refs = toRefArray(value);
184
+ if (refs.length === 0)
185
+ return (_jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words", children: String(value) }));
186
+ const otherRefs = other !== undefined ? toRefArray(other) : [];
187
+ const label = refs
188
+ .map((ref) => {
189
+ const title = source.titleOf(ref);
190
+ const collision = otherRefs.some((o) => o.id !== ref.id && source.titleOf(o) === title);
191
+ return collision ? `${title} · ${ref.id}` : title;
192
+ })
193
+ .join(', ');
194
+ return _jsx(Text, { size: "sm", children: label });
195
+ }
196
+ /** Asset-record-owned keys — resolved off the asset RECORD at render, not authored. */
197
+ const ASSET_RECORD_KEYS = new Set([
198
+ 'url', 'alt', 'width', 'height', 'focalPoint', 'contentType', 'mimeType',
199
+ 'byteSize', 'storageKey', 'title', 'filename', 'meta', 'createdAt', 'updatedAt',
200
+ ]);
201
+ function authoredAssetSubfields(value) {
202
+ return Object.entries(assetSubfieldsOf(value)).filter(([key]) => !ASSET_RECORD_KEYS.has(key));
203
+ }
204
+ function AssetValue({ value }) {
205
+ const items = (Array.isArray(value) ? value : [value])
206
+ .map((entry) => ({ id: readAssetId(entry), sub: authoredAssetSubfields(entry) }))
207
+ .filter((a) => a.id !== undefined);
208
+ if (items.length === 0)
209
+ return (_jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words", children: String(value) }));
210
+ return (_jsx("div", { className: "flex flex-wrap gap-2", children: items.map(({ id, sub }, i) => (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(AssetThumb, { id: id }), sub.length > 0 ? (_jsx(Stack, { gap: "1", className: "pl-0.5", children: sub.map(([key, val]) => (_jsxs(Text, { size: "xs", tone: "muted", className: "break-words", children: [_jsxs("span", { className: "uppercase tracking-wide text-muted-foreground/70", children: [prettyName(key), ":"] }), ' ', typeof val === 'object' && val !== null
211
+ ? translate('cms.version.objectValue')
212
+ : String(val)] }, key))) })) : null] }, `${id}-${i}`))) }));
213
+ }
214
+ /** An asset as a thumbnail (image with a resolvable URL) or a labelled badge. */
215
+ function AssetThumb({ id }) {
216
+ const source = useCollectionView();
217
+ const assetCollectionId = source.assetCollectionId ?? 'asset';
218
+ const record = source.resolveAsset?.(id);
219
+ const label = record?.title ?? record?.filename ?? source.titleOf({ collection: assetCollectionId, id });
220
+ const isImage = record?.contentType?.startsWith('image/') ?? false;
221
+ const url = record?.url;
222
+ if (isImage && url && url.startsWith('http')) {
223
+ return (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-2", children: [_jsx("img", { src: url, alt: record?.alt ?? label, loading: "lazy", className: "size-12 shrink-0 rounded border border-border bg-muted object-cover" }), _jsx(Text, { size: "xs", tone: "muted", className: "min-w-0 truncate", children: label })] }));
224
+ }
225
+ return (_jsxs(Badge, { variant: "outline", children: [_jsx(ImageIcon, { className: "size-3" }), " ", label] }));
226
+ }
227
+ // --- Prose word diff --------------------------------------------------------------
228
+ function WordDiffCols({ id, before, after }) {
229
+ const { mode, isExpanded } = useDiffDisplay();
230
+ const expanded = isExpanded(id);
231
+ const wd = useMemo(() => wordDiff(before, after), [before, after]);
232
+ const merged = useMemo(() => mergeWordDiff(wd.before, wd.after), [wd]);
233
+ if (mode === 'unified') {
234
+ const { shown, isLong } = truncateTokens(merged, expanded, DIFF_TRUNCATE_THRESHOLD);
235
+ return (_jsx(ExpandToggle, { id: id, isLong: isLong, expanded: expanded, children: _jsx(UnifiedWordTokens, { tokens: shown }) }));
236
+ }
237
+ const beforeSlice = truncateTokens(wd.before, expanded, DIFF_TRUNCATE_THRESHOLD);
238
+ const afterSlice = truncateTokens(wd.after, expanded, DIFF_TRUNCATE_THRESHOLD);
239
+ const isLong = beforeSlice.isLong || afterSlice.isLong;
240
+ return (_jsx(ExpandToggle, { id: id, isLong: isLong, expanded: expanded, children: _jsx(TwoCol, { before: _jsx(WordTokens, { tokens: beforeSlice.shown, side: "before" }), after: _jsx(WordTokens, { tokens: afterSlice.shown, side: "after" }) }) }));
241
+ }
242
+ function WordTokens({ tokens, side }) {
243
+ return (_jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words leading-relaxed", children: tokens.map((token, i) => token.changed ? (_jsx("mark", { className: cn('rounded-sm px-0.5', side === 'before'
244
+ ? 'bg-destructive/15 text-destructive line-through decoration-destructive/40'
245
+ : 'bg-success/20 text-foreground'), children: token.text }, i)) : (_jsx("span", { className: "text-muted-foreground", children: token.text }, i))) }));
246
+ }
247
+ function UnifiedWordTokens({ tokens }) {
248
+ return (_jsx(Text, { size: "sm", className: "whitespace-pre-wrap break-words leading-relaxed", children: tokens.map((token, i) => token.kind === 'same' ? (_jsx("span", { className: "text-muted-foreground", children: token.text }, i)) : (_jsx("mark", { className: cn('rounded-sm px-0.5', token.kind === 'removed'
249
+ ? 'bg-destructive/15 text-destructive line-through decoration-destructive/40'
250
+ : 'bg-success/20 text-foreground'), children: token.text }, i))) }));
251
+ }
252
+ // --- Small shared bits ------------------------------------------------------------
253
+ function KindTag({ kind }) {
254
+ if (kind === 'changed')
255
+ return null;
256
+ return (_jsxs(Text, { size: "xs", tone: "subtle", children: ["\u00B7 ", kind === 'added' ? translate('cms.version.kindNew') : translate('cms.version.kindRemoved')] }));
257
+ }
258
+ function RestoreIcon({ busy, title, onClick }) {
259
+ return (_jsx(Button, { variant: "ghost", size: "icon-sm", disabled: busy, title: title, "aria-label": title, onClick: onClick, children: _jsx(RotateCcw, {}) }));
260
+ }
261
+ function MutedNote({ children }) {
262
+ return (_jsx(Text, { size: "sm", tone: "muted", className: "italic", children: children }));
263
+ }
264
+ function fieldScope(change) {
265
+ return { kind: 'field', field: change.field, ...(change.locale !== undefined ? { locale: change.locale } : {}) };
266
+ }
267
+ /** Group changes BY FIELD (a localized field's per-locale variants together). */
268
+ function groupByField(changes, fieldOrder, localeRank) {
269
+ const map = new Map();
270
+ for (const change of changes) {
271
+ let group = map.get(change.field);
272
+ if (!group) {
273
+ group = { field: change.field, label: change.label, type: change.type, entries: [] };
274
+ map.set(change.field, group);
275
+ }
276
+ group.entries.push(change);
277
+ }
278
+ const groups = [...map.values()];
279
+ groups.sort((a, b) => (fieldOrder.get(a.field) ?? Number.MAX_SAFE_INTEGER) - (fieldOrder.get(b.field) ?? Number.MAX_SAFE_INTEGER));
280
+ for (const group of groups) {
281
+ group.entries.sort((a, b) => localeCompare(a.locale, b.locale, localeRank));
282
+ }
283
+ return groups;
284
+ }
285
+ function localeCompare(a, b, rank) {
286
+ if (a === b)
287
+ return 0;
288
+ if (a === undefined)
289
+ return -1;
290
+ if (b === undefined)
291
+ return 1;
292
+ return (rank.get(a) ?? 99) - (rank.get(b) ?? 99);
293
+ }
294
+ /** camelCase / snake prop name → a readable label. */
295
+ function prettyName(name) {
296
+ const spaced = name.replace(/[_-]+/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2');
297
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
298
+ }
299
+ //# sourceMappingURL=document-diff-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-diff-view.js","sourceRoot":"","sources":["../../../src/components/cms/document-diff-view.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEjF,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAEtG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACpF,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAA;AAChD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAEhG,OAAO,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,cAAc,EACd,QAAQ,GACT,MAAM,wBAAwB,CAAA;AAsC/B,MAAM,UAAU,gBAAgB,CAAC,EAC/B,IAAI,EACJ,UAAU,EACV,aAAa,EACb,OAAO,EACP,WAAW,GACW;IACtB,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAElC,+EAA+E;IAC/E,gEAAgE;IAChE,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAe,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAAC,CAAA;IAC7E,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,IAAkB,EAAE,EAAE;QACjD,YAAY,CAAC,IAAI,CAAC,CAAA;QAClB,iBAAiB,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,oFAAoF;IACpF,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAsB,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,CAAA;IACtF,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,EAAU,EAAE,EAAE;QAC9C,eAAe,CAAC,CAAC,IAAI,EAAE,EAAE;YACvB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAA;YAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;;gBAC5B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACjB,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAA;IAC5G,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE;YACpB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAA;YAClB,IAAI,CAAC,IAAI;gBAAE,eAAe,CAAC,IAAI,GAAG,EAAE,CAAC,CAAA;YACrC,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,EAAE,CAAC,CAAA;IACN,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,EACrD,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,CAC5C,CAAA;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IACrG,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IACnG,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAA;IACnH,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,EACxD,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,CACvC,CAAA;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE;QAC7B,IAAI,QAAQ,GAAG,CAAC,CAAA;QAChB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,UAAU,GAAG,QAAQ,CAAA;YAC3B,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAA;YAChC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,CAAA;QACnE,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAA;IAEzB,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,KAAK,CAAA;IAC/C,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,KAAK,CAAA;IACnC,MAAM,SAAS,GAAG,WAAW,CAC3B,CAAC,KAAmB,EAAE,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAClD,CAAC,OAAO,CAAC,CACV,CAAA;IAED,OAAO,CACL,8BAEE,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,OAAO,EAAC,SAAS,EAAC,IAAI,QAAC,SAAS,EAAC,QAAQ,aACnE,KAAC,cAAc,IAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,GAAI,EACjD,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAE,eAAe,YACvD,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,qBAAqB,CAAC,GAChE,IACL,EAEN,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,QAAQ,aAC5C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,iCAAiC,EAAE;wBAC1G,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;wBAC1B,SAAS,EAAE,IAAI,CAAC,cAAc;qBAC/B,CAAC,EACD,WAAW,IACP,EAEP,KAAC,kBAAkB,CAAC,QAAQ,IAAC,KAAK,EAAE,WAAW,YAC7C,eAAK,SAAS,EAAC,MAAM,aACnB,eAAK,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,aAC9C,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,yBAAyB,YAC9D,SAAS,CAAC,+BAA+B,CAAC,GACtC,EACN,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CACpB,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,yBAAyB,YAC9D,aAAa,CAAC,OAAO,GACjB,CACR,CAAC,CAAC,CAAC,CACF,KAAC,MAAM,IACL,MAAM,EACJ,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,yBAAyB,YAC9D,aAAa,CAAC,MAAM,GAChB,EAET,KAAK,EACH,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,yBAAyB,YAC9D,aAAa,CAAC,KAAK,GACf,GAET,CACH,IACG,EACL,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAC/C,KAAC,UAAU,IAET,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,SAAS,IAPf,KAAK,CAAC,KAAK,CAQhB,CACH,CAAC,IACE,GACsB,IAC7B,CACJ,CAAA;AACH,CAAC;AAWD,MAAM,kBAAkB,GAAG,aAAa,CAA0B,IAAI,CAAC,CAAA;AAEvE,SAAS,cAAc;IACrB,MAAM,GAAG,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAA;IAC1C,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAA;IACjG,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,6EAA6E;AAC7E,MAAM,aAAa,GAAG,yDAAyD,CAAA;AAE/E,0DAA0D;AAC1D,SAAS,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAA+D;IACvG,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,mCAAmC,EAAE,SAAS,CAAC,aAChE,cAAK,SAAS,EAAC,iFAAiF,wBAAe,EAC/G,cAAK,SAAS,EAAC,cAAc,YAAE,MAAM,GAAO,EAC5C,cAAK,SAAS,EAAC,cAAc,YAAE,KAAK,GAAO,IACvC,CACP,CAAA;AACH,CAAC;AAED,yCAAyC;AACzC,SAAS,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAA6D;IACrG,MAAM,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAAA;IACjC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACX,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAC,WAAW,IAAC,IAAI,EAAC,SAAS,YAAE,MAAM,GAAe,CAAC,CAAC,CAAC,IAAI,EAC5E,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAC,WAAW,IAAC,IAAI,EAAC,OAAO,YAAE,KAAK,GAAe,CAAC,CAAC,CAAC,IAAI,IACtE,CACT,CAAA;IACH,CAAC;IACD,OAAO,KAAC,MAAM,IAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,CAAA;AACjD,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAsD;IACzF,OAAO,CACL,MAAC,GAAG,IACF,GAAG,EAAC,GAAG,EACP,KAAK,EAAC,OAAO,EACb,SAAS,EAAE,EAAE,CAAC,wBAAwB,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,eAAe,CAAC,aAEnG,KAAC,IAAI,IACH,IAAI,EAAC,IAAI,EACT,IAAI,6BAEJ,SAAS,EAAE,EAAE,CAAC,6BAA6B,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,YAErG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAC1B,EACP,cAAK,SAAS,EAAC,gBAAgB,YAAE,QAAQ,GAAO,IAC5C,CACP,CAAA;AACH,CAAC;AAED,kFAAkF;AAClF,SAAS,YAAY,CAAC,EACpB,EAAE,EACF,MAAM,EACN,QAAQ,EACR,QAAQ,GAMT;IACC,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,cAAc,EAAE,CAAA;IACpD,OAAO,CACL,0BACG,QAAQ,EACR,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CACtB,KAAC,MAAM,IAAC,OAAO,EAAC,MAAM,EAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,oBAAoB,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,YAC5F,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAC1E,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAA;AACH,CAAC;AAED,0DAA0D;AAC1D,SAAS,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAkE;IACxG,OAAO,CACL,MAAC,GAAG,IACF,GAAG,EAAC,IAAI,EACR,SAAS,EAAC,8BAA8B,EACxC,IAAI,EAAC,OAAO,gBACA,SAAS,CAAC,+BAA+B,CAAC,iBAC1C,kBAAkB,aAE9B,KAAC,cAAc,IACb,MAAM,EAAE,IAAI,KAAK,YAAY,EAC7B,KAAK,EAAE,SAAS,CAAC,4BAA4B,CAAC,EAC9C,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,GACrC,EACF,KAAC,cAAc,IACb,MAAM,EAAE,IAAI,KAAK,SAAS,EAC1B,KAAK,EAAE,SAAS,CAAC,yBAAyB,CAAC,EAC3C,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,GAClC,IACE,CACP,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAA2D;IACzG,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,kBACC,MAAM,iBACP,oBAAoB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,EACjE,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,EAAE,CACX,iEAAiE,EACjE,MAAM,CAAC,CAAC,CAAC,yCAAyC,CAAC,CAAC,CAAC,6CAA6C,CACnG,YAEA,KAAK,GACC,CACV,CAAA;AACH,CAAC;AASD,sCAAsC;AACtC,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,KAAK,EACL,UAAU,EACV,WAAW,EACX,UAAU,EACV,IAAI,EACJ,SAAS,GASV;IACC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAA;IACrD,OAAO,CACL,cAAK,SAAS,EAAC,2BAA2B,YACvC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;YAC9B,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,EAAE,CAAA;YACrD,MAAM,YAAY,GAChB,UAAU,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAC5B,KAAC,WAAW,IACV,IAAI,EAAE,IAAI,EACV,KAAK,EACH,KAAK,CAAC,MAAM;oBACV,CAAC,CAAC,SAAS,CAAC,gCAAgC,EAAE;wBAC1C,KAAK,EAAE,KAAK;wBACZ,MAAM,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC;wBACrC,OAAO,EAAE,WAAW;qBACrB,CAAC;oBACJ,CAAC,CAAC,SAAS,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAEnF,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,GAC3C,CACH,CAAC,CAAC,CAAC,IAAI,CAAA;YACV,OAAO,CACL,KAAC,QAAQ,IAEP,KAAK,EAAE,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EACjC,UAAU,EAAE,KAAC,eAAe,IAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,GAAI,EACrF,OAAO,EAAE,YAAY,YAErB,KAAC,SAAS,IACR,MAAM,EAAE,KAAK,EACb,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,SAAS,GACpB,IAZG,KAAK,CAAC,MAAM,IAAI,UAAU,CAatB,CACZ,CAAA;QACH,CAAC,CAAC,GACE,CACP,CAAA;AACH,CAAC;AAED,+DAA+D;AAC/D,SAAS,QAAQ,CAAC,EAChB,UAAU,EACV,OAAO,EACP,KAAK,EACL,QAAQ,GAMT;IACC,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,aAAa,EAAE,wBAAwB,EAAE,KAAK,IAAI,aAAa,CAAC,aACjF,cAAK,SAAS,EAAC,SAAS,YAAE,UAAU,GAAO,EAC3C,cAAK,SAAS,EAAC,SAAS,YAAE,QAAQ,GAAO,EACzC,cAAK,SAAS,EAAC,kBAAkB,YAAE,OAAO,GAAO,IAC7C,CACP,CAAA;AACH,CAAC;AAED,8CAA8C;AAC9C,SAAS,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAwD;IACpG,OAAO,CACL,MAAC,KAAK,IAAC,GAAG,EAAC,GAAG,aACZ,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,QAAQ,EAAC,SAAS,EAAC,kBAAkB,YACzD,KAAK,GACD,EACP,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,IAAI,mBACd,MAAM,CAAC,CAAC,CAAC,CACR,KAAC,KAAK,IAAC,OAAO,EAAC,SAAS,EAAC,SAAS,EAAC,uEAAuE,YACvG,eAAe,CAAC,MAAM,CAAC,GAClB,CACT,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,OAAO,IAAC,IAAI,EAAE,IAAI,GAAI,IACnB,IACA,CACT,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,EACjB,MAAM,EACN,KAAK,EACL,WAAW,EACX,UAAU,EACV,IAAI,EACJ,SAAS,GAQV;IACC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAChF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,CACL,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,MAAM,YAC5B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAC5B,KAAC,YAAY,IAEX,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,GAAG,KAAK,UAAU,KAAK,CAAC,OAAO,EAAE,EACxC,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC,IANrG,KAAK,CAAC,OAAO,CAOlB,CACH,CAAC,GACI,CACT,CAAA;IACH,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC5C,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1C,IAAI,MAAM,KAAK,KAAK;YAClB,OAAO,CACL,KAAC,SAAS,cACP,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,GAChF,CACb,CAAA;QACH,OAAO,KAAC,YAAY,IAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAI,CAAA;IAClE,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvG,OAAO,KAAC,YAAY,IAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,GAAI,CAAA;IAChF,CAAC;IACD,OAAO,CACL,KAAC,WAAW,IACV,IAAI,EAAE,MAAM,CAAC,IAAI,EACjB,MAAM,EAAE,KAAC,SAAS,IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,KAAK,SAAS,GAAI,EAC9G,KAAK,EAAE,KAAC,SAAS,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,KAAK,QAAQ,GAAI,GAC5G,CACH,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,EACpB,KAAK,EACL,KAAK,EACL,WAAW,EACX,UAAU,EACV,IAAI,EACJ,SAAS,GAQV;IACC,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,WAAW,CAAA;IACpE,OAAO,CACL,eAAK,SAAS,EAAC,oDAAoD,aACjE,MAAC,GAAG,IAAC,GAAG,EAAC,GAAG,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,mDAAmD,aACvF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,MAAM,EAAC,UAAU,EAAC,SAAS,EAAC,kBAAkB,YAC3D,IAAI,GACA,EACP,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,YAAE,KAAK,CAAC,WAAW,GAAQ,EAC7C,KAAC,OAAO,IAAC,IAAI,EAAE,KAAK,CAAC,IAAI,GAAI,EAC7B,KAAC,MAAM,KAAG,EACT,UAAU,CAAC,CAAC,CAAC,CACZ,KAAC,WAAW,IACV,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,CAAC,0BAA0B,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EACtE,OAAO,EAAE,SAAS,GAClB,CACH,CAAC,CAAC,CAAC,IAAI,IACJ,EACN,eAAK,SAAS,EAAC,WAAW,aACvB,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACvC,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,kCAAkC,YACxD,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACzB,KAAC,OAAO,IAAiB,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,SAAS,IAAI,CAAC,IAAI,EAAE,IAA1D,IAAI,CAAC,IAAI,CAAqD,CAC7E,CAAC,GACI,CACT,CAAC,CAAC,CAAC,IAAI,EACP,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAC,SAAS,cAAE,SAAS,CAAC,kCAAkC,CAAC,GAAa,CAAC,CAAC,CAAC,IAAI,EAClG,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAClD,KAAC,SAAS,cACP,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,GAC3F,CACb,CAAC,CAAC,CAAC,IAAI,IACJ,IACF,CACP,CAAA;AACH,CAAC;AAED,SAAS,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAuC;IACnE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAA;IAChH,OAAO,CACL,0BACE,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,sCAAsC,YAC3E,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GACjB,EACN,WAAW,CAAC,CAAC,CAAC,CACb,KAAC,YAAY,IAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAI,CACpF,CAAC,CAAC,CAAC,CACF,KAAC,WAAW,IACV,IAAI,EAAE,IAAI,CAAC,IAAI,EACf,MAAM,EAAE,KAAC,SAAS,IAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,GAAG,KAAK,SAAS,GAAI,EACnF,KAAK,EAAE,KAAC,SAAS,IAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,GAAG,KAAK,QAAQ,GAAI,GACjF,CACH,IACG,CACP,CAAA;AACH,CAAC;AAED,4EAA4E;AAC5E,SAAS,SAAS,CAAC,EACjB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,EAAE,GAMH;IACC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,QAAQ,YAC7C,SAAS,CAAC,wBAAwB,CAAC,GAC/B,CACR,CAAA;IACH,CAAC;IACD,OAAO,KAAC,SAAS,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAI,CAAA;AAC9E,CAAC;AAED,wFAAwF;AACxF,SAAS,SAAS,CAAC,EACjB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,EAAE,GAMH;IACC,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAA;IACvC,IAAI,QAAQ,KAAK,OAAO,IAAI,WAAW,CAAC,KAAK,CAAC;QAAE,OAAO,KAAC,UAAU,IAAC,KAAK,EAAE,KAAK,GAAI,CAAA;IACnF,IAAI,QAAQ,KAAK,UAAU,IAAI,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,GAAI,CAAA;IAC3G,IAAI,QAAQ,KAAK,UAAU;QACzB,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iCAAiC,YACxD,cAAc,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,sBAAsB,CAAC,GACtD,CACR,CAAA;IACH,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,SAAS;QACtD,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,kBACjB,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,GAC7D,CACR,CAAA;IACH,IAAI,QAAQ,KAAK,MAAM;QACrB,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,kBACjB,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GACtB,CACR,CAAA;IACH,IAAI,OAAO,KAAK,KAAK,QAAQ;QAC3B,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,YACzB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBACnB,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,6BAA6B,EAAE;oBAC1F,KAAK,EAAE,KAAK,CAAC,MAAM;iBACpB,CAAC;gBACJ,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,GACnC,CACR,CAAA;IACH,MAAM,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC,CAAA;IAC/B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAA;IAC/D,OAAO,CACL,KAAC,YAAY,IAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,YACtD,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iCAAiC,YACxD,KAAK,GACD,GACM,CAChB,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,EAAE,KAAK,EAAE,KAAK,EAAuC;IAC1E,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAC9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QACnB,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iCAAiC,YACxD,MAAM,CAAC,KAAK,CAAC,GACT,CACR,CAAA;IACH,MAAM,SAAS,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC9D,MAAM,KAAK,GAAG,IAAI;SACf,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACX,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACjC,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAA;QACvF,OAAO,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IACnD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,OAAO,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,YAAE,KAAK,GAAQ,CAAA;AACvC,CAAC;AAED,uFAAuF;AACvF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAS;IACxC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU;IACxE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW;CAChF,CAAC,CAAA;AAEF,SAAS,sBAAsB,CAAC,KAAc;IAC5C,OAAO,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;AAC/F,CAAC;AAED,SAAS,UAAU,CAAC,EAAE,KAAK,EAAsB;IAC/C,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;SACnD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,sBAAsB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;SAChF,MAAM,CAAC,CAAC,CAAC,EAAiD,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAA;IACnF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QACpB,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iCAAiC,YACxD,MAAM,CAAC,KAAK,CAAC,GACT,CACR,CAAA;IACH,OAAO,CACL,cAAK,SAAS,EAAC,sBAAsB,YAClC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAC7B,eAAwB,SAAS,EAAC,qBAAqB,aACrD,KAAC,UAAU,IAAC,EAAE,EAAE,EAAE,GAAI,EACrB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAChB,KAAC,KAAK,IAAC,GAAG,EAAC,GAAG,EAAC,SAAS,EAAC,QAAQ,YAC9B,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CACvB,MAAC,IAAI,IAAW,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,aAAa,aAC5D,gBAAM,SAAS,EAAC,kDAAkD,aAAE,UAAU,CAAC,GAAG,CAAC,SAAS,EAAC,GAAG,EAC/F,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;gCACtC,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC;gCACtC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAJN,GAAG,CAKP,CACR,CAAC,GACI,CACT,CAAC,CAAC,CAAC,IAAI,KAbA,GAAG,EAAE,IAAI,CAAC,EAAE,CAchB,CACP,CAAC,GACE,CACP,CAAA;AACH,CAAC;AAED,iFAAiF;AACjF,SAAS,UAAU,CAAC,EAAE,EAAE,EAAkB;IACxC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAA;IAClC,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,OAAO,CAAA;IAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAA;IACxC,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAA;IACxG,MAAM,OAAO,GAAG,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAA;IAClE,MAAM,GAAG,GAAG,MAAM,EAAE,GAAG,CAAA;IACvB,IAAI,OAAO,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7C,OAAO,CACL,gBAAM,SAAS,EAAC,wCAAwC,aACtD,cACE,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,KAAK,EACzB,OAAO,EAAC,MAAM,EACd,SAAS,EAAC,qEAAqE,GAC/E,EACF,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,kBAAkB,YACtD,KAAK,GACD,IACF,CACR,CAAA;IACH,CAAC;IACD,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,SAAS,aACtB,KAAC,SAAS,IAAC,SAAS,EAAC,QAAQ,GAAG,OAAE,KAAK,IACjC,CACT,CAAA;AACH,CAAC;AAED,qFAAqF;AAErF,SAAS,YAAY,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAiD;IACxF,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAA;IAC7C,MAAM,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC,CAAA;IAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;IAClE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAEtE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,uBAAuB,CAAC,CAAA;QACnF,OAAO,CACL,KAAC,YAAY,IAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,YACtD,KAAC,iBAAiB,IAAC,MAAM,EAAE,KAAK,GAAI,GACvB,CAChB,CAAA;IACH,CAAC;IAED,MAAM,WAAW,GAAG,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,uBAAuB,CAAC,CAAA;IAChF,MAAM,UAAU,GAAG,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,uBAAuB,CAAC,CAAA;IAC9E,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM,CAAA;IACtD,OAAO,CACL,KAAC,YAAY,IAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,YACtD,KAAC,MAAM,IACL,MAAM,EAAE,KAAC,UAAU,IAAC,MAAM,EAAE,WAAW,CAAC,KAAK,EAAE,IAAI,EAAC,QAAQ,GAAG,EAC/D,KAAK,EAAE,KAAC,UAAU,IAAC,MAAM,EAAE,UAAU,CAAC,KAAK,EAAE,IAAI,EAAC,OAAO,GAAG,GAC5D,GACW,CAChB,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAqD;IACrF,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iDAAiD,YACxE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CACvB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CACd,eAEE,SAAS,EAAE,EAAE,CACX,mBAAmB,EACnB,IAAI,KAAK,QAAQ;gBACf,CAAC,CAAC,2EAA2E;gBAC7E,CAAC,CAAC,+BAA+B,CACpC,YAEA,KAAK,CAAC,IAAI,IARN,CAAC,CASD,CACR,CAAC,CAAC,CAAC,CACF,eAAc,SAAS,EAAC,uBAAuB,YAC5C,KAAK,CAAC,IAAI,IADF,CAAC,CAEL,CACR,CACF,GACI,CACR,CAAA;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAE,MAAM,EAAiC;IAClE,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,SAAS,EAAC,iDAAiD,YACxE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CACvB,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CACtB,eAAc,SAAS,EAAC,uBAAuB,YAC5C,KAAK,CAAC,IAAI,IADF,CAAC,CAEL,CACR,CAAC,CAAC,CAAC,CACF,eAEE,SAAS,EAAE,EAAE,CACX,mBAAmB,EACnB,KAAK,CAAC,IAAI,KAAK,SAAS;gBACtB,CAAC,CAAC,2EAA2E;gBAC7E,CAAC,CAAC,+BAA+B,CACpC,YAEA,KAAK,CAAC,IAAI,IARN,CAAC,CASD,CACR,CACF,GACI,CACR,CAAA;AACH,CAAC;AAED,qFAAqF;AAErF,SAAS,OAAO,CAAC,EAAE,IAAI,EAAiC;IACtD,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO,CACL,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,QAAQ,wBACxB,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,IACxF,CACR,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAyD;IAClG,OAAO,CACL,KAAC,MAAM,IAAC,OAAO,EAAC,OAAO,EAAC,IAAI,EAAC,SAAS,EAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,gBAAc,KAAK,EAAE,OAAO,EAAE,OAAO,YACtG,KAAC,SAAS,KAAG,GACN,CACV,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,EAAE,QAAQ,EAA2B;IACtD,OAAO,CACL,KAAC,IAAI,IAAC,IAAI,EAAC,IAAI,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,QAAQ,YAC5C,QAAQ,GACJ,CACR,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,MAAmB;IACrC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AAClH,CAAC;AAED,iFAAiF;AACjF,SAAS,YAAY,CACnB,OAAsB,EACtB,UAA+B,EAC/B,UAA+B;IAE/B,MAAM,GAAG,GAAG,IAAI,GAAG,EAA0B,CAAA;IAC7C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACjC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;YACpF,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAC9B,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC5B,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;IAChC,MAAM,CAAC,IAAI,CACT,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAC9G,CAAA;IACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;IAC7E,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,aAAa,CAAC,CAAqB,EAAE,CAAqB,EAAE,IAAyB;IAC5F,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACrB,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC,CAAA;IAC9B,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,CAAA;IAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;AAClD,CAAC;AAED,sDAAsD;AACtD,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAA;IACjF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACzD,CAAC","sourcesContent":["import { createContext, useCallback, useContext, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport { Image as ImageIcon, RotateCcw } from 'lucide-react'\nimport type { BlockChange, ChangeKind, DocumentDiff, FieldChange, PropChange } from '@elytracms/core/content'\nimport { assetSubfieldsOf, isAssetNode, readAssetId, readDocumentRef } from '@elytracms/core/cms-core'\nimport type { CollectionDef, FieldDef, RestoreScope } from '@elytracms/core/cms-core'\nimport { Badge, Button, Code, Row, Spacer, Stack, Text } from '@elytracms/studio/ui'\nimport { translate } from '@elytracms/studio/lib/i18n'\nimport { localeChipLabel } from '@elytracms/studio/lib/locale'\nimport { cn } from '@elytracms/studio/lib/utils'\nimport { readDiffViewMode, storeDiffViewMode } from '@elytracms/studio/lib/diff-view-preference'\nimport type { DiffViewMode } from '@elytracms/studio/lib/diff-view-preference'\nimport { useCollectionView } from '../collection-view/collection-view-context'\nimport { fieldLabel } from './cms-labels'\nimport { toRefArray } from './cell'\nimport {\n DIFF_TRUNCATE_THRESHOLD,\n mergeWordDiff,\n richTextToText,\n truncateText,\n truncateTokens,\n wordDiff,\n} from './version-compare-text'\nimport type { MergedDiffToken, WordToken } from './version-compare-text'\n\n/**\n * The shared document-diff RENDERER (EC-317 change-list), extracted from\n * `VersionCompare` so a single renderer serves both the version-compare surface and\n * the EC-199 AI proposal review — never two diff renderers. It draws a `DocumentDiff`\n * ({@link diffDocuments}) as the calm change-list grouped BY FIELD: a descriptor lane,\n * before | after (or a unified lane), text as a prose diff, assets as thumbnails,\n * relations as titles, long values collapsed behind \"Show more\", zebra rows.\n *\n * It is display-only over an already-computed diff: the version chrome (version\n * pickers / steppers / restore-whole) stays in `VersionCompare`; the AI proposal card\n * feeds `diffDocuments(collection, emptyBefore, proposedAfter)` and omits `restore`.\n * The per-row restore affordance renders only when `restore.canRestore` is set, so the\n * AI review (a not-yet-saved proposal) shows a clean, restore-free diff.\n *\n * Must be rendered under a `CollectionViewProvider` (the value renderers resolve\n * relation titles / asset thumbnails through `useCollectionView`).\n */\nexport interface DocumentDiffRestore {\n baseVersion: number\n canRestore: boolean\n busy: boolean\n onRestore: (scope: RestoreScope) => void\n}\n\nexport interface DocumentDiffViewProps {\n diff: DocumentDiff\n collection: CollectionDef\n /** Pre-rendered column captions (version-specific text stays with the caller). */\n columnHeaders: { unified: string; before: string; after: string }\n /** Per-row restore controls; omitted ⇒ a restore-free read-only diff (AI review). */\n restore?: DocumentDiffRestore\n /** Extra note appended to the change-count summary (e.g. a comparing-past hint). */\n summaryHint?: ReactNode\n}\n\nexport function DocumentDiffView({\n diff,\n collection,\n columnHeaders,\n restore,\n summaryHint,\n}: DocumentDiffViewProps) {\n const source = useCollectionView()\n\n // EC-317 — the unified/side-by-side toggle is a transient, personal-local view\n // preference: read once at mount, written back on every change.\n const [mode, setModeState] = useState<DiffViewMode>(() => readDiffViewMode())\n const setMode = useCallback((next: DiffViewMode) => {\n setModeState(next)\n storeDiffViewMode(next)\n }, [])\n\n // EC-317 — long-value truncation state: an `expandAll` flag plus per-row overrides.\n const [expandAll, setExpandAll] = useState(false)\n const [expandedRows, setExpandedRows] = useState<ReadonlySet<string>>(() => new Set())\n const toggleExpand = useCallback((id: string) => {\n setExpandedRows((prev) => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }, [])\n const isExpanded = useCallback((id: string) => expandAll || expandedRows.has(id), [expandAll, expandedRows])\n const toggleExpandAll = useCallback(() => {\n setExpandAll((prev) => {\n const next = !prev\n if (!next) setExpandedRows(new Set())\n return next\n })\n }, [])\n const diffDisplay = useMemo<DiffDisplayState>(\n () => ({ mode, expandAll, isExpanded, toggleExpand }),\n [mode, expandAll, isExpanded, toggleExpand],\n )\n\n const fieldOrder = useMemo(() => new Map(collection.fields.map((f, i) => [f.name, i])), [collection])\n const fieldByName = useMemo(() => new Map(collection.fields.map((f) => [f.name, f])), [collection])\n const localeRank = useMemo(() => new Map(source.localeConfig.locales.map((l, i) => [l, i])), [source.localeConfig])\n const groups = useMemo(\n () => groupByField(diff.changes, fieldOrder, localeRank),\n [diff.changes, fieldOrder, localeRank],\n )\n const groupRows = useMemo(() => {\n let rowIndex = 0\n return groups.map((group) => {\n const zebraStart = rowIndex\n rowIndex += group.entries.length\n return { group, field: fieldByName.get(group.field), zebraStart }\n })\n }, [groups, fieldByName])\n\n const canRestore = restore?.canRestore ?? false\n const baseVersion = restore?.baseVersion ?? 0\n const busy = restore?.busy ?? false\n const restoreFn = useCallback(\n (scope: RestoreScope) => restore?.onRestore(scope),\n [restore],\n )\n\n return (\n <>\n {/* EC-317 — the diff-layout toolbar: unified/side-by-side (persisted) + expand-all. */}\n <Row gap=\"2\" align=\"center\" justify=\"between\" wrap className=\"px-0.5\">\n <DiffViewToggle mode={mode} onChange={setMode} />\n <Button variant=\"ghost\" size=\"sm\" onClick={toggleExpandAll}>\n {translate(expandAll ? 'cms.field.collapseAll' : 'cms.field.expandAll')}\n </Button>\n </Row>\n\n <Text size=\"xs\" tone=\"muted\" className=\"px-0.5\">\n {translate(diff.changes.length === 1 ? 'cms.version.changeSummary.one' : 'cms.version.changeSummary.other', {\n count: diff.changes.length,\n unchanged: diff.unchangedCount,\n })}\n {summaryHint}\n </Text>\n\n <DiffDisplayContext.Provider value={diffDisplay}>\n <div className=\"pb-1\">\n <div className={cn(DIFF_ROW_GRID, 'px-2 pb-1.5')}>\n <Text size=\"xs\" tone=\"subtle\" className=\"uppercase tracking-wide\">\n {translate('cms.version.fieldColumnHeader')}\n </Text>\n {mode === 'unified' ? (\n <Text size=\"xs\" tone=\"subtle\" className=\"uppercase tracking-wide\">\n {columnHeaders.unified}\n </Text>\n ) : (\n <TwoCol\n before={\n <Text size=\"xs\" tone=\"subtle\" className=\"uppercase tracking-wide\">\n {columnHeaders.before}\n </Text>\n }\n after={\n <Text size=\"xs\" tone=\"subtle\" className=\"uppercase tracking-wide\">\n {columnHeaders.after}\n </Text>\n }\n />\n )}\n </div>\n {groupRows.map(({ group, field, zebraStart }) => (\n <FieldGroup\n key={group.field}\n group={group}\n field={field}\n zebraStart={zebraStart}\n baseVersion={baseVersion}\n canRestore={canRestore}\n busy={busy}\n onRestore={restoreFn}\n />\n ))}\n </div>\n </DiffDisplayContext.Provider>\n </>\n )\n}\n\n// --- EC-317 diff-display context (mode + truncation) ------------------------------\n\ninterface DiffDisplayState {\n mode: DiffViewMode\n expandAll: boolean\n isExpanded: (id: string) => boolean\n toggleExpand: (id: string) => void\n}\n\nconst DiffDisplayContext = createContext<DiffDisplayState | null>(null)\n\nfunction useDiffDisplay(): DiffDisplayState {\n const ctx = useContext(DiffDisplayContext)\n if (!ctx) throw new Error('useDiffDisplay must be rendered under the DocumentDiffView diff area')\n return ctx\n}\n\n/** Shared grid template so the descriptor lane + header captions line up. */\nconst DIFF_ROW_GRID = 'grid grid-cols-[7.5rem_1fr_1.75rem] items-start gap-x-4'\n\n/** A before | after grid with a clear central divider. */\nfunction TwoCol({ before, after, className }: { before: ReactNode; after: ReactNode; className?: string }) {\n return (\n <div className={cn('relative grid grid-cols-2 gap-x-8', className)}>\n <div className=\"pointer-events-none absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border\" aria-hidden />\n <div className=\"min-w-0 pr-1\">{before}</div>\n <div className=\"min-w-0 pl-1\">{after}</div>\n </div>\n )\n}\n\n/** Mode-aware value display (EC-317). */\nfunction DiffColumns({ before, after, kind }: { before: ReactNode; after: ReactNode; kind: ChangeKind }) {\n const { mode } = useDiffDisplay()\n if (mode === 'unified') {\n return (\n <Stack gap=\"1\">\n {kind !== 'added' ? <UnifiedLine tone=\"removed\">{before}</UnifiedLine> : null}\n {kind !== 'removed' ? <UnifiedLine tone=\"added\">{after}</UnifiedLine> : null}\n </Stack>\n )\n }\n return <TwoCol before={before} after={after} />\n}\n\nfunction UnifiedLine({ tone, children }: { tone: 'removed' | 'added'; children: ReactNode }) {\n return (\n <Row\n gap=\"2\"\n align=\"start\"\n className={cn('rounded-sm px-1.5 py-1', tone === 'removed' ? 'bg-destructive/10' : 'bg-success/10')}\n >\n <Text\n size=\"xs\"\n mono\n aria-hidden\n className={cn('mt-0.5 shrink-0 select-none', tone === 'removed' ? 'text-destructive' : 'text-success')}\n >\n {tone === 'removed' ? '−' : '+'}\n </Text>\n <div className=\"min-w-0 flex-1\">{children}</div>\n </Row>\n )\n}\n\n/** Wraps truncatable content with its per-row \"Show more\"/\"Show less\" control. */\nfunction ExpandToggle({\n id,\n isLong,\n expanded,\n children,\n}: {\n id: string\n isLong: boolean\n expanded: boolean\n children: ReactNode\n}) {\n const { expandAll, toggleExpand } = useDiffDisplay()\n return (\n <div>\n {children}\n {isLong && !expandAll ? (\n <Button variant=\"link\" size=\"sm\" className=\"h-auto p-0 text-xs\" onClick={() => toggleExpand(id)}>\n {expanded ? translate('cms.version.showLess') : translate('cms.version.showMore')}\n </Button>\n ) : null}\n </div>\n )\n}\n\n/** The unified/side-by-side segmented toggle (EC-317). */\nfunction DiffViewToggle({ mode, onChange }: { mode: DiffViewMode; onChange: (mode: DiffViewMode) => void }) {\n return (\n <Row\n gap=\"px\"\n className=\"rounded-md bg-muted/50 p-0.5\"\n role=\"group\"\n aria-label={translate('cms.version.viewModeAriaLabel')}\n data-testid=\"diff-view-toggle\"\n >\n <DiffViewOption\n active={mode === 'sideBySide'}\n label={translate('cms.version.viewSideBySide')}\n onClick={() => onChange('sideBySide')}\n />\n <DiffViewOption\n active={mode === 'unified'}\n label={translate('cms.version.viewUnified')}\n onClick={() => onChange('unified')}\n />\n </Row>\n )\n}\n\nfunction DiffViewOption({ active, label, onClick }: { active: boolean; label: string; onClick: () => void }) {\n return (\n <button\n type=\"button\"\n aria-pressed={active}\n data-testid={`diff-view-option-${active ? 'active' : 'inactive'}`}\n onClick={onClick}\n className={cn(\n 'rounded-[5px] px-2.5 py-1 text-xs font-medium transition-colors',\n active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {label}\n </button>\n )\n}\n\ninterface FieldGroupData {\n field: string\n label: string\n type: FieldDef['type']\n entries: FieldChange[]\n}\n\n/** One field's diff rows (EC-317). */\nfunction FieldGroup({\n group,\n field,\n zebraStart,\n baseVersion,\n canRestore,\n busy,\n onRestore,\n}: {\n group: FieldGroupData\n field: FieldDef | undefined\n zebraStart: number\n baseVersion: number\n canRestore: boolean\n busy: boolean\n onRestore: (scope: RestoreScope) => void\n}) {\n const label = field ? fieldLabel(field) : group.label\n return (\n <div className=\"border-t border-border/30\">\n {group.entries.map((entry, i) => {\n const rowId = `${group.field}:${entry.locale ?? '_'}`\n const fieldRestore =\n canRestore && !entry.blocks ? (\n <RestoreIcon\n busy={busy}\n title={\n entry.locale\n ? translate('cms.version.restoreFieldLocale', {\n field: label,\n locale: localeChipLabel(entry.locale),\n version: baseVersion,\n })\n : translate('cms.version.restoreField', { field: label, version: baseVersion })\n }\n onClick={() => onRestore(fieldScope(entry))}\n />\n ) : null\n return (\n <FieldRow\n key={entry.locale ?? '__base__'}\n zebra={(zebraStart + i) % 2 === 1}\n descriptor={<FieldDescriptor label={label} locale={entry.locale} kind={entry.kind} />}\n restore={fieldRestore}\n >\n <EntryBody\n change={entry}\n rowId={rowId}\n baseVersion={baseVersion}\n canRestore={canRestore}\n busy={busy}\n onRestore={onRestore}\n />\n </FieldRow>\n )\n })}\n </div>\n )\n}\n\n/** The descriptor lane + value area, one grid row (EC-317). */\nfunction FieldRow({\n descriptor,\n restore,\n zebra,\n children,\n}: {\n descriptor: ReactNode\n restore: ReactNode\n zebra: boolean\n children: ReactNode\n}) {\n return (\n <div className={cn(DIFF_ROW_GRID, 'rounded-sm px-2 py-2.5', zebra && 'bg-muted/40')}>\n <div className=\"min-w-0\">{descriptor}</div>\n <div className=\"min-w-0\">{children}</div>\n <div className=\"flex justify-end\">{restore}</div>\n </div>\n )\n}\n\n/** The descriptor lane's content (EC-317). */\nfunction FieldDescriptor({ label, locale, kind }: { label: string; locale?: string; kind: ChangeKind }) {\n return (\n <Stack gap=\"1\">\n <Text size=\"sm\" weight=\"medium\" className=\"min-w-0 truncate\">\n {label}\n </Text>\n <Row gap=\"1\" wrap>\n {locale ? (\n <Badge variant=\"outline\" className=\"px-1 py-0 text-[0.6rem] uppercase tracking-wide text-muted-foreground\">\n {localeChipLabel(locale)}\n </Badge>\n ) : null}\n <KindTag kind={kind} />\n </Row>\n </Stack>\n )\n}\n\nfunction EntryBody({\n change,\n rowId,\n baseVersion,\n canRestore,\n busy,\n onRestore,\n}: {\n change: FieldChange\n rowId: string\n baseVersion: number\n canRestore: boolean\n busy: boolean\n onRestore: (scope: RestoreScope) => void\n}) {\n const localeScope = change.locale !== undefined ? { locale: change.locale } : {}\n if (change.blocks) {\n return (\n <Stack gap=\"2\" className=\"mt-1\">\n {change.blocks.map((block) => (\n <BlockSection\n key={block.blockId}\n block={block}\n rowId={`${rowId}|block:${block.blockId}`}\n baseVersion={baseVersion}\n canRestore={canRestore}\n busy={busy}\n onRestore={() => onRestore({ kind: 'block', field: change.field, blockId: block.blockId, ...localeScope })}\n />\n ))}\n </Stack>\n )\n }\n if (change.type === 'richText') {\n const before = richTextToText(change.before)\n const after = richTextToText(change.after)\n if (before === after)\n return (\n <MutedNote>\n {before ? translate('cms.version.textUnchanged') : translate('cms.version.contentChanged')}\n </MutedNote>\n )\n return <WordDiffCols id={rowId} before={before} after={after} />\n }\n if (change.kind === 'changed' && typeof change.before === 'string' && typeof change.after === 'string') {\n return <WordDiffCols id={rowId} before={change.before} after={change.after} />\n }\n return (\n <DiffColumns\n kind={change.kind}\n before={<SideValue value={change.before} other={change.after} typeHint={change.type} id={`${rowId}|before`} />}\n after={<SideValue value={change.after} other={change.before} typeHint={change.type} id={`${rowId}|after`} />}\n />\n )\n}\n\nfunction BlockSection({\n block,\n rowId,\n baseVersion,\n canRestore,\n busy,\n onRestore,\n}: {\n block: BlockChange\n rowId: string\n baseVersion: number\n canRestore: boolean\n busy: boolean\n onRestore: () => void\n}) {\n const name = block.componentId.split('.').pop() ?? block.componentId\n return (\n <div className=\"overflow-hidden rounded-md border border-border/60\">\n <Row gap=\"2\" align=\"center\" className=\"border-b border-border/50 bg-muted/30 px-2 py-1.5\">\n <Text size=\"xs\" weight=\"semibold\" className=\"min-w-0 truncate\">\n {name}\n </Text>\n <Code tone=\"muted\">{block.componentId}</Code>\n <KindTag kind={block.kind} />\n <Spacer />\n {canRestore ? (\n <RestoreIcon\n busy={busy}\n title={translate('cms.version.restoreBlock', { version: baseVersion })}\n onClick={onRestore}\n />\n ) : null}\n </Row>\n <div className=\"px-2 py-2\">\n {block.props && block.props.length > 0 ? (\n <Stack gap=\"3\" className=\"border-l-2 border-border/50 pl-3\">\n {block.props.map((prop) => (\n <PropRow key={prop.name} prop={prop} rowId={`${rowId}|prop:${prop.name}`} />\n ))}\n </Stack>\n ) : null}\n {block.slotsChanged ? <MutedNote>{translate('cms.version.nestedContentChanged')}</MutedNote> : null}\n {block.kind !== 'changed' && !block.props?.length ? (\n <MutedNote>\n {block.kind === 'added' ? translate('cms.version.blockAdded') : translate('cms.version.blockRemoved')}\n </MutedNote>\n ) : null}\n </div>\n </div>\n )\n}\n\nfunction PropRow({ prop, rowId }: { prop: PropChange; rowId: string }) {\n const useWordDiff = prop.kind === 'changed' && typeof prop.before === 'string' && typeof prop.after === 'string'\n return (\n <div>\n <Text size=\"xs\" tone=\"subtle\" className=\"mb-0.5 block uppercase tracking-wide\">\n {prettyName(prop.name)}\n </Text>\n {useWordDiff ? (\n <WordDiffCols id={rowId} before={String(prop.before)} after={String(prop.after)} />\n ) : (\n <DiffColumns\n kind={prop.kind}\n before={<SideValue value={prop.before} other={prop.after} id={`${rowId}|before`} />}\n after={<SideValue value={prop.after} other={prop.before} id={`${rowId}|after`} />}\n />\n )}\n </div>\n )\n}\n\n/** One side of a non-text change — the value, or an \"empty\" placeholder. */\nfunction SideValue({\n value,\n other,\n typeHint,\n id,\n}: {\n value: unknown\n other?: unknown\n typeHint?: FieldDef['type']\n id: string\n}) {\n if (value === undefined || value === null || value === '') {\n return (\n <Text size=\"sm\" tone=\"subtle\" className=\"italic\">\n {translate('cms.version.emptyValue')}\n </Text>\n )\n }\n return <ValueView value={value} other={other} typeHint={typeHint} id={id} />\n}\n\n/** Read-only value display, dispatching on field type / self-describing value shape. */\nfunction ValueView({\n value,\n other,\n typeHint,\n id,\n}: {\n value: unknown\n other?: unknown\n typeHint?: FieldDef['type']\n id: string\n}) {\n const { isExpanded } = useDiffDisplay()\n if (typeHint === 'asset' || isAssetNode(value)) return <AssetValue value={value} />\n if (typeHint === 'relation' || readDocumentRef(value)) return <RelationValue value={value} other={other} />\n if (typeHint === 'richText')\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {richTextToText(value) || translate('cms.version.richText')}\n </Text>\n )\n if (typeHint === 'boolean' || typeof value === 'boolean')\n return (\n <Text size=\"sm\" mono>\n {value === true ? translate('common.yes') : translate('common.no')}\n </Text>\n )\n if (typeHint === 'date')\n return (\n <Text size=\"sm\" mono>\n {String(value).slice(0, 10)}\n </Text>\n )\n if (typeof value === 'object')\n return (\n <Text size=\"sm\" tone=\"muted\">\n {Array.isArray(value)\n ? translate(value.length === 1 ? 'cms.version.itemCount.one' : 'cms.version.itemCount.other', {\n count: value.length,\n })\n : translate('cms.version.objectValue')}\n </Text>\n )\n const expanded = isExpanded(id)\n const { shown, isLong } = truncateText(String(value), expanded)\n return (\n <ExpandToggle id={id} isLong={isLong} expanded={expanded}>\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {shown}\n </Text>\n </ExpandToggle>\n )\n}\n\nfunction RelationValue({ value, other }: { value: unknown; other?: unknown }) {\n const source = useCollectionView()\n const refs = toRefArray(value)\n if (refs.length === 0)\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {String(value)}\n </Text>\n )\n const otherRefs = other !== undefined ? toRefArray(other) : []\n const label = refs\n .map((ref) => {\n const title = source.titleOf(ref)\n const collision = otherRefs.some((o) => o.id !== ref.id && source.titleOf(o) === title)\n return collision ? `${title} · ${ref.id}` : title\n })\n .join(', ')\n return <Text size=\"sm\">{label}</Text>\n}\n\n/** Asset-record-owned keys — resolved off the asset RECORD at render, not authored. */\nconst ASSET_RECORD_KEYS = new Set<string>([\n 'url', 'alt', 'width', 'height', 'focalPoint', 'contentType', 'mimeType',\n 'byteSize', 'storageKey', 'title', 'filename', 'meta', 'createdAt', 'updatedAt',\n])\n\nfunction authoredAssetSubfields(value: unknown): [string, unknown][] {\n return Object.entries(assetSubfieldsOf(value)).filter(([key]) => !ASSET_RECORD_KEYS.has(key))\n}\n\nfunction AssetValue({ value }: { value: unknown }) {\n const items = (Array.isArray(value) ? value : [value])\n .map((entry) => ({ id: readAssetId(entry), sub: authoredAssetSubfields(entry) }))\n .filter((a): a is { id: string; sub: [string, unknown][] } => a.id !== undefined)\n if (items.length === 0)\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words\">\n {String(value)}\n </Text>\n )\n return (\n <div className=\"flex flex-wrap gap-2\">\n {items.map(({ id, sub }, i) => (\n <div key={`${id}-${i}`} className=\"flex flex-col gap-1\">\n <AssetThumb id={id} />\n {sub.length > 0 ? (\n <Stack gap=\"1\" className=\"pl-0.5\">\n {sub.map(([key, val]) => (\n <Text key={key} size=\"xs\" tone=\"muted\" className=\"break-words\">\n <span className=\"uppercase tracking-wide text-muted-foreground/70\">{prettyName(key)}:</span>{' '}\n {typeof val === 'object' && val !== null\n ? translate('cms.version.objectValue')\n : String(val)}\n </Text>\n ))}\n </Stack>\n ) : null}\n </div>\n ))}\n </div>\n )\n}\n\n/** An asset as a thumbnail (image with a resolvable URL) or a labelled badge. */\nfunction AssetThumb({ id }: { id: string }) {\n const source = useCollectionView()\n const assetCollectionId = source.assetCollectionId ?? 'asset'\n const record = source.resolveAsset?.(id)\n const label = record?.title ?? record?.filename ?? source.titleOf({ collection: assetCollectionId, id })\n const isImage = record?.contentType?.startsWith('image/') ?? false\n const url = record?.url\n if (isImage && url && url.startsWith('http')) {\n return (\n <span className=\"inline-flex min-w-0 items-center gap-2\">\n <img\n src={url}\n alt={record?.alt ?? label}\n loading=\"lazy\"\n className=\"size-12 shrink-0 rounded border border-border bg-muted object-cover\"\n />\n <Text size=\"xs\" tone=\"muted\" className=\"min-w-0 truncate\">\n {label}\n </Text>\n </span>\n )\n }\n return (\n <Badge variant=\"outline\">\n <ImageIcon className=\"size-3\" /> {label}\n </Badge>\n )\n}\n\n// --- Prose word diff --------------------------------------------------------------\n\nfunction WordDiffCols({ id, before, after }: { id: string; before: string; after: string }) {\n const { mode, isExpanded } = useDiffDisplay()\n const expanded = isExpanded(id)\n const wd = useMemo(() => wordDiff(before, after), [before, after])\n const merged = useMemo(() => mergeWordDiff(wd.before, wd.after), [wd])\n\n if (mode === 'unified') {\n const { shown, isLong } = truncateTokens(merged, expanded, DIFF_TRUNCATE_THRESHOLD)\n return (\n <ExpandToggle id={id} isLong={isLong} expanded={expanded}>\n <UnifiedWordTokens tokens={shown} />\n </ExpandToggle>\n )\n }\n\n const beforeSlice = truncateTokens(wd.before, expanded, DIFF_TRUNCATE_THRESHOLD)\n const afterSlice = truncateTokens(wd.after, expanded, DIFF_TRUNCATE_THRESHOLD)\n const isLong = beforeSlice.isLong || afterSlice.isLong\n return (\n <ExpandToggle id={id} isLong={isLong} expanded={expanded}>\n <TwoCol\n before={<WordTokens tokens={beforeSlice.shown} side=\"before\" />}\n after={<WordTokens tokens={afterSlice.shown} side=\"after\" />}\n />\n </ExpandToggle>\n )\n}\n\nfunction WordTokens({ tokens, side }: { tokens: WordToken[]; side: 'before' | 'after' }) {\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words leading-relaxed\">\n {tokens.map((token, i) =>\n token.changed ? (\n <mark\n key={i}\n className={cn(\n 'rounded-sm px-0.5',\n side === 'before'\n ? 'bg-destructive/15 text-destructive line-through decoration-destructive/40'\n : 'bg-success/20 text-foreground',\n )}\n >\n {token.text}\n </mark>\n ) : (\n <span key={i} className=\"text-muted-foreground\">\n {token.text}\n </span>\n ),\n )}\n </Text>\n )\n}\n\nfunction UnifiedWordTokens({ tokens }: { tokens: MergedDiffToken[] }) {\n return (\n <Text size=\"sm\" className=\"whitespace-pre-wrap break-words leading-relaxed\">\n {tokens.map((token, i) =>\n token.kind === 'same' ? (\n <span key={i} className=\"text-muted-foreground\">\n {token.text}\n </span>\n ) : (\n <mark\n key={i}\n className={cn(\n 'rounded-sm px-0.5',\n token.kind === 'removed'\n ? 'bg-destructive/15 text-destructive line-through decoration-destructive/40'\n : 'bg-success/20 text-foreground',\n )}\n >\n {token.text}\n </mark>\n ),\n )}\n </Text>\n )\n}\n\n// --- Small shared bits ------------------------------------------------------------\n\nfunction KindTag({ kind }: { kind: FieldChange['kind'] }) {\n if (kind === 'changed') return null\n return (\n <Text size=\"xs\" tone=\"subtle\">\n · {kind === 'added' ? translate('cms.version.kindNew') : translate('cms.version.kindRemoved')}\n </Text>\n )\n}\n\nfunction RestoreIcon({ busy, title, onClick }: { busy: boolean; title: string; onClick: () => void }) {\n return (\n <Button variant=\"ghost\" size=\"icon-sm\" disabled={busy} title={title} aria-label={title} onClick={onClick}>\n <RotateCcw />\n </Button>\n )\n}\n\nfunction MutedNote({ children }: { children: ReactNode }) {\n return (\n <Text size=\"sm\" tone=\"muted\" className=\"italic\">\n {children}\n </Text>\n )\n}\n\nfunction fieldScope(change: FieldChange): RestoreScope {\n return { kind: 'field', field: change.field, ...(change.locale !== undefined ? { locale: change.locale } : {}) }\n}\n\n/** Group changes BY FIELD (a localized field's per-locale variants together). */\nfunction groupByField(\n changes: FieldChange[],\n fieldOrder: Map<string, number>,\n localeRank: Map<string, number>,\n): FieldGroupData[] {\n const map = new Map<string, FieldGroupData>()\n for (const change of changes) {\n let group = map.get(change.field)\n if (!group) {\n group = { field: change.field, label: change.label, type: change.type, entries: [] }\n map.set(change.field, group)\n }\n group.entries.push(change)\n }\n const groups = [...map.values()]\n groups.sort(\n (a, b) =>\n (fieldOrder.get(a.field) ?? Number.MAX_SAFE_INTEGER) - (fieldOrder.get(b.field) ?? Number.MAX_SAFE_INTEGER),\n )\n for (const group of groups) {\n group.entries.sort((a, b) => localeCompare(a.locale, b.locale, localeRank))\n }\n return groups\n}\n\nfunction localeCompare(a: string | undefined, b: string | undefined, rank: Map<string, number>): number {\n if (a === b) return 0\n if (a === undefined) return -1\n if (b === undefined) return 1\n return (rank.get(a) ?? 99) - (rank.get(b) ?? 99)\n}\n\n/** camelCase / snake prop name → a readable label. */\nfunction prettyName(name: string): string {\n const spaced = name.replace(/[_-]+/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n return spaced.charAt(0).toUpperCase() + spaced.slice(1)\n}\n"]}
@@ -1,16 +1,10 @@
1
1
  /**
2
- * EC-298 — the version compare view. Renders as the sticky **Compare** content tab
3
- * (the Versions sidebar pane stays on as the navigator).
4
- *
5
- * A calm change-list grouped BY FIELD (a localized field's per-locale variants sit
6
- * together): a left **descriptor lane** (EC-317 — field label + locale) identifies
7
- * each row, next to its before | after value (or one merged **unified** lane —
8
- * a persisted transient view preference, EC-317). Text reads as a prose diff
9
- * (changed words lit in place — rich text via its markdown projection); assets as
10
- * thumbnails; relations as titles. A value past a length threshold collapses
11
- * behind a per-row "Show more" (+ a global "Expand/Collapse all") — presentation
12
- * only, the semantic/identity diff underneath (EC-298) is never touched. Rows
13
- * zebra-stripe for scanability. Each field/locale (and each block, at property
14
- * level) restores back to the base version with a single ↩.
2
+ * EC-298/317 — the version compare view: the sticky **Compare** content tab. This
3
+ * shell owns the version SELECTION chrome (the two re-pickable sides, the base
4
+ * stepper, restore-whole); the change-list itself is drawn by the shared
5
+ * {@link DocumentDiffView} (EC-317 descriptor lane, unified/side-by-side toggle,
6
+ * truncation, zebra) — the same renderer the EC-199 AI proposal review reuses, so
7
+ * there is exactly one diff renderer. Each field/locale (and block) restores back to
8
+ * the base version with a single ↩ (wired through `DocumentDiffView`'s `restore`).
15
9
  */
16
10
  export declare function VersionCompare(): import("react").JSX.Element | null;