collabmd 0.1.31 → 0.1.33

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 (99) hide show
  1. package/dist/client/assets/{drawio-editor-B4sHHwWJ.js → drawio-editor-BMTwP5mD.js} +1 -1
  2. package/dist/client/assets/drawioEditor-D6clB5OH.js +2 -0
  3. package/dist/client/assets/{editor-session-BKHEYkbY.js → editor-session-Dzy_85Wp.js} +2 -2
  4. package/dist/client/assets/embedded-editor-base-CgEkjNYn.css +1 -0
  5. package/dist/client/assets/{excalidraw-editor-CHbeNZu6.js → excalidraw-editor-CAP9F8CB.js} +3 -3
  6. package/dist/client/assets/{excalidrawEditor-BhaTX2Ko.js → excalidrawEditor-DD4AvzYP.js} +2 -2
  7. package/dist/client/assets/exportDocument-BbkN85c4.css +1 -0
  8. package/dist/client/assets/index-BIt5EZXz.css +1 -0
  9. package/dist/client/assets/{index-kPgEYvEX.js → index-Czg_KL-i.js} +2 -2
  10. package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  11. package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  12. package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  13. package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  14. package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  15. package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  16. package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  17. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  18. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  19. package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  20. package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  21. package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  22. package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  23. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  24. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  25. package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  26. package/dist/client/assets/main-DeowJOSp.js +1368 -0
  27. package/dist/client/assets/vault-api-client-D7Ck0CvR.css +1 -0
  28. package/dist/client/assets/vault-api-client-p7h7cXKM.js +1 -0
  29. package/dist/client/drawio-editor.html +1 -1
  30. package/dist/client/excalidraw-editor.html +1 -1
  31. package/dist/client/export-document.html +2 -2
  32. package/dist/client/index.html +2 -2
  33. package/package.json +8 -5
  34. package/src/client/app/main-entry.js +2 -0
  35. package/src/client/application/app-shell/git-feature.js +28 -36
  36. package/src/client/application/app-shell/presence-feature.js +3 -1
  37. package/src/client/application/diagram-preview-hydrator.js +24 -15
  38. package/src/client/application/mermaid-preview-hydrator.js +121 -4
  39. package/src/client/application/plantuml-preview-hydrator.js +6 -16
  40. package/src/client/application/preview-renderer.js +11 -2
  41. package/src/client/bootstrap/collabmd-app-shell.js +182 -50
  42. package/src/client/domain/excalidraw-document-switch.js +3 -0
  43. package/src/client/excalidraw-editor.js +187 -84
  44. package/src/client/infrastructure/auth-client.js +4 -4
  45. package/src/client/infrastructure/backlinks-api-client.js +18 -0
  46. package/src/client/infrastructure/comment-thread-store.js +4 -0
  47. package/src/client/infrastructure/editor-session.js +6 -0
  48. package/src/client/infrastructure/editor-view-adapter.js +23 -0
  49. package/src/client/infrastructure/git-api-client.js +134 -0
  50. package/src/client/infrastructure/plantuml-api-client.js +21 -0
  51. package/src/client/infrastructure/vault-api-client.js +115 -0
  52. package/src/client/presentation/backlinks-panel.js +6 -9
  53. package/src/client/presentation/bases-preview-controller.js +1413 -34
  54. package/src/client/presentation/comments-panel.js +50 -5
  55. package/src/client/presentation/excalidraw-embed-controller.js +251 -15
  56. package/src/client/presentation/excalidraw-embed-reconciler.js +0 -1
  57. package/src/client/presentation/file-action-controller.js +97 -0
  58. package/src/client/presentation/file-explorer-controller.js +2 -0
  59. package/src/client/presentation/file-explorer-view.js +368 -0
  60. package/src/client/presentation/file-history-view-controller.js +41 -40
  61. package/src/client/presentation/git-diff-view-controller.js +70 -53
  62. package/src/client/presentation/git-panel-controller.js +12 -30
  63. package/src/client/styles/components/scrollbars.css +5 -0
  64. package/src/client/styles/export-document.css +45 -2
  65. package/src/client/styles/features/auth-gate.css +13 -24
  66. package/src/client/styles/features/comment-card.css +6 -0
  67. package/src/client/styles/features/comments-drawer.css +38 -18
  68. package/src/client/styles/features/diagram-preview.css +33 -0
  69. package/src/client/styles/features/embedded-preview.css +37 -11
  70. package/src/client/styles/features/preview-markdown.css +559 -39
  71. package/src/client/styles/foundation/themes.css +4 -0
  72. package/src/client/styles/foundation/tokens.css +4 -3
  73. package/src/client/styles/layout/sidebar.css +72 -0
  74. package/src/server/auth/auth-common.js +224 -0
  75. package/src/server/auth/auth-constants.js +30 -0
  76. package/src/server/auth/auth-strategies.js +320 -0
  77. package/src/server/auth/create-auth-service.js +24 -545
  78. package/src/server/domain/backlink-index.js +177 -5
  79. package/src/server/domain/bases/base-definition.js +382 -0
  80. package/src/server/domain/bases/base-expression-runtime.js +1196 -0
  81. package/src/server/domain/bases/base-index-snapshot-store.js +565 -0
  82. package/src/server/domain/bases/base-query-metadata.js +242 -0
  83. package/src/server/domain/bases/base-query-results.js +215 -0
  84. package/src/server/domain/bases/base-query-service.js +406 -1992
  85. package/src/server/domain/bases/base-transform.js +116 -0
  86. package/src/server/domain/collaboration/collaboration-room.js +36 -1
  87. package/src/server/domain/collaboration/room-client-state-store.js +1 -0
  88. package/src/server/domain/collaboration/room-persistence-controller.js +1 -1
  89. package/src/server/infrastructure/http/create-vault-api-query-handler.js +161 -1
  90. package/src/server/infrastructure/http/http-response.js +56 -0
  91. package/src/server/infrastructure/persistence/vault-file-store.js +110 -0
  92. package/dist/client/assets/drawioEditor-DGpo2hcx.js +0 -2
  93. package/dist/client/assets/embedded-editor-base-DgmRcsR8.css +0 -1
  94. package/dist/client/assets/exportDocument-DmV7Gngb.css +0 -1
  95. package/dist/client/assets/index-D69HOZHr.css +0 -1
  96. package/dist/client/assets/main-DSxNTtD-.js +0 -1243
  97. package/dist/client/assets/vault-api-client-BzHQseMN.js +0 -1
  98. package/dist/client/assets/vault-api-client-De7kMvrE.css +0 -1
  99. /package/dist/client/assets/{exportDocument-CtZKWE2c.js → exportDocument-Zfwq1XVx.js} +0 -0
@@ -0,0 +1,242 @@
1
+ import { isImageAttachmentFilePath } from '../../../domain/file-kind.js';
2
+ import { parseDateValue, toDisplayText } from './base-expression-runtime.js';
3
+
4
+ const FILE_PROPERTY_DEFINITIONS = [
5
+ { id: 'file.name', kind: 'file', label: 'file name', valueType: 'text' },
6
+ { id: 'file.basename', kind: 'file', label: 'file base name', valueType: 'text' },
7
+ { id: 'file.backlinks', kind: 'file', label: 'file backlinks', valueType: 'list' },
8
+ { id: 'file.ctime', kind: 'file', label: 'created time', valueType: 'date' },
9
+ { id: 'file.embeds', kind: 'file', label: 'file embeds', valueType: 'list' },
10
+ { id: 'file.ext', kind: 'file', label: 'file extension', valueType: 'text' },
11
+ { id: 'file.folder', kind: 'file', label: 'folder', valueType: 'text' },
12
+ { id: 'file.links', kind: 'file', label: 'file links', valueType: 'list' },
13
+ { id: 'file.mtime', kind: 'file', label: 'modified time', valueType: 'date' },
14
+ { id: 'file.path', kind: 'file', label: 'file path', valueType: 'text' },
15
+ { id: 'file.size', kind: 'file', label: 'file size', valueType: 'number' },
16
+ { id: 'file.tags', kind: 'file', label: 'file tags', valueType: 'list' },
17
+ ];
18
+
19
+ function simplifyType(type) {
20
+ if (!type || type === 'unknown') {
21
+ return 'unknown';
22
+ }
23
+
24
+ return type;
25
+ }
26
+
27
+ function mergeTypes(left = 'unknown', right = 'unknown') {
28
+ if (left === 'unknown') {
29
+ return right;
30
+ }
31
+ if (right === 'unknown' || left === right) {
32
+ return left;
33
+ }
34
+
35
+ const scalarTypes = new Set(['boolean', 'date', 'number', 'text', 'image', 'link']);
36
+ if (scalarTypes.has(left) && scalarTypes.has(right)) {
37
+ if ((left === 'image' && right === 'text') || (left === 'text' && right === 'image')) {
38
+ return 'image';
39
+ }
40
+ if ((left === 'link' && right === 'text') || (left === 'text' && right === 'link')) {
41
+ return 'link';
42
+ }
43
+ return 'text';
44
+ }
45
+
46
+ if (left === 'list' || right === 'list') {
47
+ return 'list';
48
+ }
49
+
50
+ return 'unknown';
51
+ }
52
+
53
+ export function inferValueType(value) {
54
+ if (value == null || value === '') {
55
+ return 'unknown';
56
+ }
57
+
58
+ if (Array.isArray(value)) {
59
+ return 'list';
60
+ }
61
+
62
+ if (value instanceof Date) {
63
+ return 'date';
64
+ }
65
+
66
+ if (value?.__baseType === 'link') {
67
+ return 'link';
68
+ }
69
+
70
+ if (value?.__baseType === 'file') {
71
+ return 'link';
72
+ }
73
+
74
+ if (typeof value === 'boolean') {
75
+ return 'boolean';
76
+ }
77
+
78
+ if (typeof value === 'number') {
79
+ return 'number';
80
+ }
81
+
82
+ if (typeof value === 'string') {
83
+ if (isImageAttachmentFilePath(value)) {
84
+ return 'image';
85
+ }
86
+
87
+ if (parseDateValue(value)) {
88
+ return 'date';
89
+ }
90
+
91
+ return 'text';
92
+ }
93
+
94
+ return 'unknown';
95
+ }
96
+
97
+ function createSortDirections(valueType = 'text') {
98
+ switch (valueType) {
99
+ case 'date':
100
+ return [
101
+ { id: 'asc', label: 'Old to new' },
102
+ { id: 'desc', label: 'New to old' },
103
+ ];
104
+ case 'number':
105
+ return [
106
+ { id: 'asc', label: '0 → 1' },
107
+ { id: 'desc', label: '1 → 0' },
108
+ ];
109
+ default:
110
+ return [
111
+ { id: 'asc', label: 'A → Z' },
112
+ { id: 'desc', label: 'Z → A' },
113
+ ];
114
+ }
115
+ }
116
+
117
+ function createFilterOperators(valueType = 'text') {
118
+ if (valueType === 'boolean') {
119
+ return ['is', 'is not', 'is empty', 'is not empty'];
120
+ }
121
+
122
+ if (valueType === 'number' || valueType === 'date') {
123
+ return ['is', 'is not', '>', '>=', '<', '<=', 'is empty', 'is not empty'];
124
+ }
125
+
126
+ if (valueType === 'list') {
127
+ return ['contains', 'does not contain', 'is empty', 'is not empty'];
128
+ }
129
+
130
+ return ['is', 'is not', 'contains', 'does not contain', 'starts with', 'ends with', 'is empty', 'is not empty'];
131
+ }
132
+
133
+ export function createPropertyCatalogSnapshot(snapshot) {
134
+ const noteProperties = new Map();
135
+
136
+ snapshot?.rowsByPath?.forEach((row) => {
137
+ Object.entries(row?.noteProperties ?? {}).forEach(([propertyName, value]) => {
138
+ const propertyId = `note.${propertyName}`;
139
+ const current = noteProperties.get(propertyId) ?? {
140
+ id: propertyId,
141
+ kind: 'note',
142
+ label: propertyName,
143
+ valueType: 'unknown',
144
+ };
145
+ current.valueType = mergeTypes(current.valueType, inferValueType(value));
146
+ noteProperties.set(propertyId, current);
147
+ });
148
+ });
149
+
150
+ return {
151
+ fileProperties: FILE_PROPERTY_DEFINITIONS.map((entry) => ({ ...entry })),
152
+ noteProperties: Array.from(noteProperties.values()).sort((left, right) => left.label.localeCompare(right.label)),
153
+ scannedAt: snapshot?.scannedAt ?? '',
154
+ };
155
+ }
156
+
157
+ export function buildAvailableProperties({
158
+ activeView,
159
+ columns = [],
160
+ definition,
161
+ formulaValueTypes = {},
162
+ propertyCatalog,
163
+ }) {
164
+ const visibleIds = new Set(
165
+ Array.isArray(activeView?.order) && activeView.order.length > 0
166
+ ? activeView.order
167
+ : columns.map((column) => column.id),
168
+ );
169
+ const baseProperties = [
170
+ ...(propertyCatalog?.fileProperties ?? []),
171
+ ...(propertyCatalog?.noteProperties ?? []),
172
+ ];
173
+
174
+ const formulaProperties = Object.values(definition?.formulas ?? {}).map((formula) => ({
175
+ id: formula.id,
176
+ kind: 'formula',
177
+ label: definition?.properties?.[formula.id]?.displayName ?? formula.name,
178
+ valueType: simplifyType(formulaValueTypes[formula.id] ?? 'unknown'),
179
+ }));
180
+
181
+ return [...baseProperties, ...formulaProperties]
182
+ .map((property) => {
183
+ const valueType = simplifyType(property.valueType);
184
+ return {
185
+ filterOperators: createFilterOperators(valueType),
186
+ groupable: true,
187
+ id: property.id,
188
+ kind: property.kind,
189
+ label: property.label,
190
+ sortable: true,
191
+ sortDirections: createSortDirections(valueType),
192
+ valueType,
193
+ visible: visibleIds.has(property.id),
194
+ };
195
+ })
196
+ .sort((left, right) => left.label.localeCompare(right.label));
197
+ }
198
+
199
+ function flattenValues(value) {
200
+ if (Array.isArray(value)) {
201
+ return value.flatMap((entry) => flattenValues(entry));
202
+ }
203
+
204
+ if (value == null || value === '') {
205
+ return [];
206
+ }
207
+
208
+ return [value];
209
+ }
210
+
211
+ export function collectDistinctPropertyValues(rows = [], propertyId = '', query = '') {
212
+ const counts = new Map();
213
+ const normalizedQuery = String(query ?? '').trim().toLowerCase();
214
+
215
+ rows.forEach((row) => {
216
+ flattenValues(row?.rawCells?.[propertyId]).forEach((value) => {
217
+ const text = toDisplayText(value);
218
+ if (!text) {
219
+ return;
220
+ }
221
+
222
+ if (normalizedQuery && !text.toLowerCase().includes(normalizedQuery)) {
223
+ return;
224
+ }
225
+
226
+ const entry = counts.get(text) ?? {
227
+ count: 0,
228
+ text,
229
+ value,
230
+ };
231
+ entry.count += 1;
232
+ counts.set(text, entry);
233
+ });
234
+ });
235
+
236
+ return Array.from(counts.values())
237
+ .sort((left, right) => (
238
+ right.count - left.count
239
+ || left.text.localeCompare(right.text)
240
+ ))
241
+ .slice(0, 100);
242
+ }
@@ -0,0 +1,215 @@
1
+ import {
2
+ compareValues,
3
+ createEvaluationRootContext,
4
+ evaluateExpression,
5
+ serializeBaseValue,
6
+ toDisplayText,
7
+ valuesEqual,
8
+ } from './base-expression-runtime.js';
9
+
10
+ function summarizeValues(values = [], summary, definition, snapshot, thisFile) {
11
+ const meaningfulValues = values.filter((value) => value != null && value !== '');
12
+ switch (summary.type) {
13
+ case 'average': {
14
+ const numbers = meaningfulValues.map(Number).filter(Number.isFinite);
15
+ return numbers.length > 0 ? numbers.reduce((sum, value) => sum + value, 0) / numbers.length : null;
16
+ }
17
+ case 'checked':
18
+ return values.filter(Boolean).length;
19
+ case 'custom': {
20
+ const context = createEvaluationRootContext({
21
+ astCache: new Map(),
22
+ currentRow: {
23
+ file: thisFile,
24
+ noteProperties: thisFile?.properties ?? {},
25
+ },
26
+ definition,
27
+ identifierResolver: (name) => (name === 'values' ? values : undefined),
28
+ snapshot,
29
+ thisFile,
30
+ });
31
+ return evaluateExpression(summary.formula, context);
32
+ }
33
+ case 'earliest':
34
+ return meaningfulValues.reduce((acc, value) => (acc == null || compareValues(value, acc) < 0 ? value : acc), null);
35
+ case 'empty':
36
+ return values.filter((value) => value == null || value === '').length;
37
+ case 'filled':
38
+ return meaningfulValues.length;
39
+ case 'latest':
40
+ return meaningfulValues.reduce((acc, value) => (acc == null || compareValues(value, acc) > 0 ? value : acc), null);
41
+ case 'max':
42
+ return meaningfulValues.reduce((acc, value) => (acc == null || compareValues(value, acc) > 0 ? value : acc), null);
43
+ case 'median': {
44
+ const numbers = meaningfulValues.map(Number).filter(Number.isFinite).sort((left, right) => left - right);
45
+ if (numbers.length === 0) return null;
46
+ const middle = Math.floor(numbers.length / 2);
47
+ return numbers.length % 2 === 0 ? (numbers[middle - 1] + numbers[middle]) / 2 : numbers[middle];
48
+ }
49
+ case 'min':
50
+ return meaningfulValues.reduce((acc, value) => (acc == null || compareValues(value, acc) < 0 ? value : acc), null);
51
+ case 'range': {
52
+ const sorted = meaningfulValues.slice().sort(compareValues);
53
+ if (sorted.length === 0) return null;
54
+ const first = sorted[0];
55
+ const last = sorted[sorted.length - 1];
56
+ if (first instanceof Date && last instanceof Date) {
57
+ return last.getTime() - first.getTime();
58
+ }
59
+ return Number(last ?? 0) - Number(first ?? 0);
60
+ }
61
+ case 'stddev': {
62
+ const numbers = meaningfulValues.map(Number).filter(Number.isFinite);
63
+ if (numbers.length === 0) return null;
64
+ const average = numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
65
+ const variance = numbers.reduce((sum, value) => sum + ((value - average) ** 2), 0) / numbers.length;
66
+ return Math.sqrt(variance);
67
+ }
68
+ case 'sum':
69
+ return meaningfulValues.map(Number).filter(Number.isFinite).reduce((sum, value) => sum + value, 0);
70
+ case 'unchecked':
71
+ return values.filter((value) => value === false || value == null).length;
72
+ case 'unique': {
73
+ const uniqueValues = [];
74
+ meaningfulValues.forEach((value) => {
75
+ if (!uniqueValues.some((entry) => valuesEqual(entry, value))) {
76
+ uniqueValues.push(value);
77
+ }
78
+ });
79
+ return uniqueValues.length;
80
+ }
81
+ default:
82
+ return null;
83
+ }
84
+ }
85
+
86
+ function createSummaryPayload(columns, rows, summaries, definition, snapshot, thisFile) {
87
+ return summaries.map((summary) => {
88
+ const column = columns.find((entry) => entry.id === summary.property);
89
+ const values = rows.map((row) => row.rawCells[summary.property]);
90
+ const value = summarizeValues(values, summary, definition, snapshot, thisFile);
91
+ return {
92
+ id: summary.id,
93
+ label: summary.label,
94
+ property: summary.property,
95
+ propertyLabel: column?.label ?? summary.property,
96
+ value: serializeBaseValue(value, {
97
+ snapshot,
98
+ sourcePath: thisFile?.path ?? '',
99
+ }),
100
+ };
101
+ });
102
+ }
103
+
104
+ function createCsv(rows, columns) {
105
+ const escapeCell = (value) => {
106
+ const text = String(value ?? '');
107
+ if (/[,"\n]/u.test(text)) {
108
+ return `"${text.replace(/"/g, '""')}"`;
109
+ }
110
+ return text;
111
+ };
112
+
113
+ const lines = [
114
+ columns.map((column) => escapeCell(column.label)).join(','),
115
+ ...rows.map((row) => columns.map((column) => escapeCell(toDisplayText(row.rawCells[column.id]))).join(',')),
116
+ ];
117
+ return `${lines.join('\n')}\n`;
118
+ }
119
+
120
+ export function rowMatchesSearch(row, columns, query) {
121
+ const normalizedQuery = String(query ?? '').trim().toLowerCase();
122
+ if (!normalizedQuery) {
123
+ return true;
124
+ }
125
+
126
+ const haystack = columns
127
+ .map((column) => toDisplayText(row.rawCells[column.id]))
128
+ .join('\n')
129
+ .toLowerCase();
130
+ return haystack.includes(normalizedQuery);
131
+ }
132
+
133
+ export function buildQueryResultPayload({
134
+ activeView,
135
+ columns,
136
+ definition,
137
+ includeCsv = false,
138
+ rows,
139
+ snapshot,
140
+ thisFile,
141
+ }) {
142
+ const groupsByKey = new Map();
143
+ if (activeView.groupBy?.property) {
144
+ rows.forEach((row) => {
145
+ const groupValue = row.rawCells[activeView.groupBy.property];
146
+ const groupKey = toDisplayText(groupValue) || 'Empty';
147
+ const entry = groupsByKey.get(groupKey) ?? {
148
+ key: groupKey,
149
+ label: groupKey,
150
+ rawValue: groupValue,
151
+ rows: [],
152
+ value: serializeBaseValue(groupValue, {
153
+ rowFilePath: row.file.path,
154
+ snapshot,
155
+ sourcePath: thisFile?.path ?? '',
156
+ }),
157
+ };
158
+ entry.rows.push(row);
159
+ groupsByKey.set(groupKey, entry);
160
+ });
161
+ } else {
162
+ groupsByKey.set('All', {
163
+ key: 'All',
164
+ label: 'All',
165
+ rows,
166
+ value: serializeBaseValue(null),
167
+ });
168
+ }
169
+
170
+ const serializedRows = rows.map((row) => ({
171
+ cells: columns.reduce((acc, column) => {
172
+ acc[column.id] = serializeBaseValue(row.rawCells[column.id], {
173
+ rowFilePath: row.file.path,
174
+ snapshot,
175
+ sourcePath: thisFile?.path ?? '',
176
+ });
177
+ return acc;
178
+ }, {}),
179
+ file: serializeBaseValue(row.file, {
180
+ rowFilePath: row.file.path,
181
+ snapshot,
182
+ sourcePath: thisFile?.path ?? '',
183
+ }),
184
+ path: row.file.path,
185
+ }));
186
+ const rowsByPath = new Map(serializedRows.map((row) => [row.path, row]));
187
+
188
+ let groups = Array.from(groupsByKey.values());
189
+ if (activeView.groupBy?.explicitDirection) {
190
+ groups = groups.sort((left, right) => {
191
+ const delta = compareValues(left.rawValue, right.rawValue);
192
+ return activeView.groupBy.direction === 'desc' ? -delta : delta;
193
+ });
194
+ }
195
+
196
+ groups = groups.map((group) => ({
197
+ key: group.key,
198
+ label: group.label,
199
+ summaries: createSummaryPayload(columns, group.rows, activeView.summaries, definition, snapshot, thisFile),
200
+ value: group.value,
201
+ rows: group.rows.map((row) => rowsByPath.get(row.file.path)),
202
+ }));
203
+
204
+ return {
205
+ ...(includeCsv ? { csv: createCsv(rows, columns) } : {}),
206
+ groups,
207
+ rows: serializedRows,
208
+ summaries: createSummaryPayload(columns, rows, activeView.summaries, definition, snapshot, thisFile),
209
+ thisFile: serializeBaseValue(thisFile, {
210
+ snapshot,
211
+ sourcePath: thisFile?.path ?? '',
212
+ }),
213
+ totalRows: rows.length,
214
+ };
215
+ }