@mindexec/cli 0.2.100 → 0.2.102

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.100",
3
+ "version": "0.2.102",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -29,6 +29,9 @@
29
29
  let _cssImageDisplayPumpScheduled = false;
30
30
  const CSS_VIDEO_PROXY_MAX_DEVICE_PIXEL_RATIO = 2;
31
31
  const VIDEO_PLAYBACK_INTENT_METADATA_KEY = 'VideoPlaybackIntent';
32
+ const CSV_TABLE_CONTENT_TYPE = 'csv-table';
33
+ const CSV_TABLE_MAX_RENDER_ROWS = 500;
34
+ const CSV_TABLE_MAX_RENDER_COLUMNS = 80;
32
35
  const REMOTE_FLEET_DISPLAY_NAME = 'Multi Desktop Monitor';
33
36
  const REMOTE_FLEET_LEGACY_DISPLAY_NAME = 'Remote Fleet Monitor';
34
37
  const _cssVideoProxyStateByVideo = new WeakMap();
@@ -53,6 +56,254 @@
53
56
  }
54
57
 
55
58
  syncMindCanvasPlatformRenderClasses();
59
+
60
+ function detectCsvDelimiter(csvText) {
61
+ const text = String(csvText || '');
62
+ const candidates = [',', '\t', ';', '|'];
63
+ const scores = new Map(candidates.map(candidate => [candidate, 0]));
64
+ let inQuotes = false;
65
+ let currentLine = 0;
66
+
67
+ for (let i = 0; i < text.length && currentLine < 12; i++) {
68
+ const ch = text[i];
69
+ const next = text[i + 1];
70
+ if (ch === '"') {
71
+ if (inQuotes && next === '"') {
72
+ i++;
73
+ } else {
74
+ inQuotes = !inQuotes;
75
+ }
76
+ continue;
77
+ }
78
+ if (!inQuotes && (ch === '\n' || ch === '\r')) {
79
+ currentLine++;
80
+ if (ch === '\r' && next === '\n') i++;
81
+ continue;
82
+ }
83
+ if (!inQuotes && scores.has(ch)) {
84
+ scores.set(ch, scores.get(ch) + 1);
85
+ }
86
+ }
87
+
88
+ let best = ',';
89
+ let bestScore = -1;
90
+ scores.forEach((score, delimiter) => {
91
+ if (score > bestScore) {
92
+ best = delimiter;
93
+ bestScore = score;
94
+ }
95
+ });
96
+ return best;
97
+ }
98
+
99
+ function parseCsvRows(csvText, options = {}) {
100
+ const text = String(csvText || '');
101
+ if (!text) {
102
+ return { rows: [], delimiter: options.delimiter || ',', truncatedRows: false };
103
+ }
104
+
105
+ const delimiter = options.delimiter || detectCsvDelimiter(text);
106
+ const maxRows = Math.max(1, Number(options.maxRows || 1000));
107
+ const maxColumns = Math.max(1, Number(options.maxColumns || 200));
108
+ const rows = [];
109
+ let row = [];
110
+ let cell = '';
111
+ let inQuotes = false;
112
+ let truncatedRows = false;
113
+
114
+ const pushCell = () => {
115
+ if (row.length < maxColumns) {
116
+ row.push(cell);
117
+ }
118
+ cell = '';
119
+ };
120
+
121
+ const pushRow = () => {
122
+ pushCell();
123
+ if (row.length > 1 || String(row[0] || '').length > 0) {
124
+ if (rows.length < maxRows) {
125
+ rows.push(row);
126
+ } else {
127
+ truncatedRows = true;
128
+ }
129
+ }
130
+ row = [];
131
+ };
132
+
133
+ for (let i = 0; i < text.length; i++) {
134
+ const ch = text[i];
135
+ const next = text[i + 1];
136
+
137
+ if (ch === '"') {
138
+ if (inQuotes && next === '"') {
139
+ cell += '"';
140
+ i++;
141
+ } else {
142
+ inQuotes = !inQuotes;
143
+ }
144
+ continue;
145
+ }
146
+
147
+ if (!inQuotes && ch === delimiter) {
148
+ pushCell();
149
+ continue;
150
+ }
151
+
152
+ if (!inQuotes && (ch === '\n' || ch === '\r')) {
153
+ pushRow();
154
+ if (ch === '\r' && next === '\n') i++;
155
+ continue;
156
+ }
157
+
158
+ cell += ch;
159
+ }
160
+
161
+ if (cell.length > 0 || row.length > 0 || text.endsWith(delimiter)) {
162
+ pushRow();
163
+ }
164
+
165
+ return { rows, delimiter, truncatedRows };
166
+ }
167
+
168
+ function summarizeCsv(csvText) {
169
+ const parsed = parseCsvRows(csvText, {
170
+ maxRows: 10001,
171
+ maxColumns: CSV_TABLE_MAX_RENDER_COLUMNS
172
+ });
173
+ const columnCount = parsed.rows.reduce((max, row) => Math.max(max, row.length), 0);
174
+ return {
175
+ rowCount: Math.max(0, parsed.rows.length - 1),
176
+ columnCount,
177
+ delimiter: parsed.delimiter,
178
+ truncatedRows: parsed.truncatedRows
179
+ };
180
+ }
181
+
182
+ function compareCsvCells(a, b) {
183
+ const left = String(a ?? '').trim();
184
+ const right = String(b ?? '').trim();
185
+ const leftNumber = Number(left.replace(/,/g, ''));
186
+ const rightNumber = Number(right.replace(/,/g, ''));
187
+ if (left && right && Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
188
+ return leftNumber - rightNumber;
189
+ }
190
+ return left.localeCompare(right, undefined, {
191
+ numeric: true,
192
+ sensitivity: 'base'
193
+ });
194
+ }
195
+
196
+ function renderCsvTableContent(responseDiv, nodeModel, csvText) {
197
+ if (!(responseDiv instanceof HTMLElement)) {
198
+ return;
199
+ }
200
+
201
+ responseDiv.innerHTML = '';
202
+ const parsed = parseCsvRows(csvText, {
203
+ maxRows: CSV_TABLE_MAX_RENDER_ROWS + 1,
204
+ maxColumns: CSV_TABLE_MAX_RENDER_COLUMNS
205
+ });
206
+ const rows = parsed.rows;
207
+ const columnCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
208
+
209
+ if (columnCount === 0) {
210
+ return;
211
+ }
212
+
213
+ const headerRow = rows[0] || [];
214
+ const headers = Array.from({ length: columnCount }, (_, index) => {
215
+ const value = String(headerRow[index] ?? '').trim();
216
+ return value || `Column ${index + 1}`;
217
+ });
218
+ const dataRows = rows.slice(1).map((cells, index) => ({
219
+ cells,
220
+ originalIndex: index
221
+ }));
222
+
223
+ const table = document.createElement('table');
224
+ table.className = 'csv-table';
225
+ table.dataset.nodeId = nodeModel.id || nodeModel.Id || '';
226
+
227
+ const thead = document.createElement('thead');
228
+ const headerTr = document.createElement('tr');
229
+ const tbody = document.createElement('tbody');
230
+ let sortColumn = -1;
231
+ let sortDirection = 'asc';
232
+
233
+ const renderBody = () => {
234
+ tbody.innerHTML = '';
235
+ const nextRows = dataRows.slice();
236
+ if (sortColumn >= 0) {
237
+ nextRows.sort((a, b) => {
238
+ const result = compareCsvCells(a.cells[sortColumn], b.cells[sortColumn]);
239
+ if (result !== 0) {
240
+ return sortDirection === 'asc' ? result : -result;
241
+ }
242
+ return a.originalIndex - b.originalIndex;
243
+ });
244
+ }
245
+
246
+ nextRows.forEach(rowData => {
247
+ const tr = document.createElement('tr');
248
+ for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
249
+ const td = document.createElement('td');
250
+ td.className = 'csv-table-cell';
251
+ td.textContent = String(rowData.cells[columnIndex] ?? '');
252
+ tr.appendChild(td);
253
+ }
254
+ tbody.appendChild(tr);
255
+ });
256
+ };
257
+
258
+ const updateHeaderSortState = () => {
259
+ headerTr.querySelectorAll('.csv-table-header-button').forEach((button, index) => {
260
+ const active = index === sortColumn;
261
+ button.dataset.sort = active ? sortDirection : '';
262
+ button.setAttribute('aria-sort', active ? (sortDirection === 'asc' ? 'ascending' : 'descending') : 'none');
263
+ });
264
+ };
265
+
266
+ headers.forEach((header, index) => {
267
+ const th = document.createElement('th');
268
+ const button = document.createElement('button');
269
+ button.type = 'button';
270
+ button.className = 'csv-table-header-button';
271
+ button.textContent = header;
272
+ button.dataset.columnIndex = String(index);
273
+ button.setAttribute('aria-sort', 'none');
274
+ button.addEventListener('mousedown', event => {
275
+ event.preventDefault();
276
+ event.stopPropagation();
277
+ });
278
+ button.addEventListener('click', event => {
279
+ event.preventDefault();
280
+ event.stopPropagation();
281
+ if (sortColumn === index) {
282
+ sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
283
+ } else {
284
+ sortColumn = index;
285
+ sortDirection = 'asc';
286
+ }
287
+ updateHeaderSortState();
288
+ renderBody();
289
+ });
290
+ th.appendChild(button);
291
+ headerTr.appendChild(th);
292
+ });
293
+
294
+ thead.appendChild(headerTr);
295
+ table.appendChild(thead);
296
+ table.appendChild(tbody);
297
+ responseDiv.appendChild(table);
298
+ renderBody();
299
+ }
300
+
301
+ window.MindMapCsvTable = {
302
+ detectCsvDelimiter,
303
+ parseCsvRows,
304
+ summarizeCsv,
305
+ renderCsvTableContent
306
+ };
56
307
  // ▲▲▲ [Perf] ▲▲▲
57
308
 
58
309
  // ▼▼▼ [New] 기본 마크다운 스타일 주입 ▼▼▼
@@ -98,11 +349,118 @@
98
349
  .markdown-body a:hover { text-decoration: underline; }
99
350
  .markdown-body img { max-width: 100%; box-sizing: content-box; background-color: #fff; }
100
351
  .markdown-body table { border-spacing: 0; border-collapse: collapse; width: 100%; margin-bottom: 1em; }
101
- .markdown-body table th, .markdown-body table td { padding: 6px 13px; border: 1px solid #dfe2e5; }
102
- .markdown-body table th { font-weight: 600; background-color: #f6f8fa; }
103
- .markdown-body hr { height: 0.25em; padding: 0; margin: 24px 0; background-color: #e1e4e8; border: 0; }
104
- /* Force list markers to be visible */
105
- .markdown-body ul li { list-style-type: disc; }
352
+ .markdown-body table th, .markdown-body table td { padding: 6px 13px; border: 1px solid #dfe2e5; }
353
+ .markdown-body table th { font-weight: 600; background-color: #f6f8fa; }
354
+ .markdown-body hr { height: 0.25em; padding: 0; margin: 24px 0; background-color: #e1e4e8; border: 0; }
355
+ .map-node-csv-table {
356
+ border: 1px solid rgba(203, 213, 225, 0.92);
357
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.10);
358
+ }
359
+ .csv-table-scroll {
360
+ width: 100%;
361
+ height: 100%;
362
+ flex: 1 1 auto;
363
+ min-height: 0;
364
+ overflow: auto;
365
+ background: #ffffff;
366
+ border: 1px solid rgba(226, 232, 240, 0.96);
367
+ border-radius: 6px;
368
+ box-sizing: border-box;
369
+ scrollbar-width: thin;
370
+ scrollbar-color: rgba(100, 116, 139, 0.36) transparent;
371
+ pointer-events: auto;
372
+ padding-right: 0 !important;
373
+ user-select: text;
374
+ -webkit-user-select: text;
375
+ }
376
+ .csv-table-scroll::-webkit-scrollbar {
377
+ width: 8px;
378
+ height: 8px;
379
+ }
380
+ .csv-table-scroll::-webkit-scrollbar-thumb {
381
+ background: rgba(100, 116, 139, 0.34);
382
+ border-radius: 999px;
383
+ }
384
+ .csv-table-scroll:hover::-webkit-scrollbar-thumb {
385
+ background: rgba(71, 85, 105, 0.48);
386
+ }
387
+ .csv-table {
388
+ width: max-content;
389
+ min-width: 100%;
390
+ border-collapse: separate;
391
+ border-spacing: 0;
392
+ color: #111827;
393
+ font-size: 12px;
394
+ line-height: 1.45;
395
+ }
396
+ .csv-table th {
397
+ position: sticky;
398
+ top: 0;
399
+ z-index: 2;
400
+ padding: 0;
401
+ border-bottom: 1px solid #cbd5e1;
402
+ border-right: 1px solid #e2e8f0;
403
+ background: #f8fafc;
404
+ text-align: left;
405
+ white-space: nowrap;
406
+ }
407
+ .csv-table th:last-child,
408
+ .csv-table td:last-child {
409
+ border-right: 0;
410
+ }
411
+ .csv-table-header-button {
412
+ width: 100%;
413
+ min-width: 88px;
414
+ border: 0;
415
+ background: transparent;
416
+ color: #334155;
417
+ cursor: pointer;
418
+ display: flex;
419
+ align-items: center;
420
+ justify-content: space-between;
421
+ gap: 10px;
422
+ padding: 8px 10px;
423
+ font: inherit;
424
+ font-weight: 700;
425
+ text-align: left;
426
+ white-space: nowrap;
427
+ }
428
+ .csv-table-header-button::after {
429
+ content: "";
430
+ width: 0;
431
+ height: 0;
432
+ border-left: 4px solid transparent;
433
+ border-right: 4px solid transparent;
434
+ opacity: 0.38;
435
+ }
436
+ .csv-table-header-button[data-sort="asc"]::after {
437
+ border-bottom: 6px solid #2563eb;
438
+ opacity: 1;
439
+ }
440
+ .csv-table-header-button[data-sort="desc"]::after {
441
+ border-top: 6px solid #2563eb;
442
+ opacity: 1;
443
+ }
444
+ .csv-table-header-button:hover,
445
+ .csv-table-header-button:focus-visible {
446
+ background: #eef2ff;
447
+ outline: none;
448
+ }
449
+ .csv-table td {
450
+ max-width: 280px;
451
+ padding: 7px 10px;
452
+ border-right: 1px solid #eef2f7;
453
+ border-bottom: 1px solid #eef2f7;
454
+ overflow: hidden;
455
+ text-overflow: ellipsis;
456
+ white-space: nowrap;
457
+ background: #ffffff;
458
+ }
459
+ .csv-table tr:nth-child(even) td {
460
+ background: #fbfdff;
461
+ }
462
+ /* Force list markers to be visible */
463
+ .markdown-body ul li { list-style-type: disc; }
106
464
  .markdown-body ol li { list-style-type: decimal; }
107
465
 
108
466
  /* ▼▼▼ [NEW] 슬림 스크롤바 스타일 - 모든 텍스트/노트/코드 영역 (Razor + JS 동적노드 전체) ▼▼▼ */
@@ -3514,14 +3872,15 @@
3514
3872
  const CSS3D_WRAPPER_WEBKIT_TRANSITION = '-webkit-box-shadow .22s ease, -webkit-filter .22s ease';
3515
3873
  const TEXT_OVERLAY_SUPPORTED_TYPES = new Set(['text', 'markdown', 'note', 'code', 'memo']);
3516
3874
  const TEXT_SELECTION_OVERLAY_SUPPORTED_TYPES = new Set(['text', 'markdown']);
3517
- const TEXT_INTERACTION_SCROLLABLE_SELECTORS = '.node-response, .note-content, .markdown-body, .prose, .note-textarea, .code-body, .code-content, .pdf-content, .text-content, .file-content, .map-node-memo__body, .map-node-memo__body-view, .map-node-memo__agent-plan-body, .map-node-memo__agent-console-body, pre, code';
3518
- const TEXT_INTERACTION_CONTENT_SELECTORS = '.node-response, .code-body, .text-content, .markdown-body, .prose';
3875
+ const TEXT_INTERACTION_SCROLLABLE_SELECTORS = '.node-response, .note-content, .markdown-body, .prose, .note-textarea, .code-body, .code-content, .csv-table-scroll, .csv-table-scroll *, .pdf-content, .text-content, .file-content, .map-node-memo__body, .map-node-memo__body-view, .map-node-memo__agent-plan-body, .map-node-memo__agent-console-body, pre, code';
3876
+ const TEXT_INTERACTION_CONTENT_SELECTORS = '.node-response, .code-body, .text-content, .markdown-body, .prose, .csv-table-scroll, .csv-table-cell';
3519
3877
  const TEXT_OVERLAY_SCROLL_SYNC_SELECTORS = [
3520
3878
  '.node-response',
3521
3879
  '.note-content',
3522
3880
  '.markdown-body',
3523
3881
  '.prose',
3524
3882
  '.code-body',
3883
+ '.csv-table-scroll',
3525
3884
  '.note-textarea',
3526
3885
  '.map-node-memo__body',
3527
3886
  '.map-node-memo__body-view',
@@ -3625,7 +3984,7 @@
3625
3984
  }
3626
3985
 
3627
3986
  const contentType = String(nodeModel?.contentType ?? nodeModel?.ContentType ?? '').trim().toLowerCase();
3628
- return ['memo', 'note', 'text', 'markdown', 'code', 'embed', 'pdf', 'image', 'video'].includes(contentType);
3987
+ return ['memo', 'note', 'text', 'markdown', 'code', CSV_TABLE_CONTENT_TYPE, 'embed', 'pdf', 'image', 'video'].includes(contentType);
3629
3988
  }
3630
3989
 
3631
3990
  function isAgentStyledMemoNode(nodeModel) {
@@ -19154,7 +19513,8 @@
19154
19513
  const hasContent = !!(`${content}`.trim());
19155
19514
  const defaultPrompts = ['Note', 'Pasted Text', '에이전트 메모'];
19156
19515
  const shouldShowPrompt = !!prompt && !defaultPrompts.includes(prompt);
19157
- const isTextualCardNode = ['note', 'code', 'text', 'markdown'].includes(contentTypeLower);
19516
+ const isCsvTableNode = contentTypeLower === CSV_TABLE_CONTENT_TYPE;
19517
+ const isTextualCardNode = ['note', 'code', 'text', 'markdown', CSV_TABLE_CONTENT_TYPE].includes(contentTypeLower);
19158
19518
  const copyablePromptText = getCopyablePromptText(nodeModel, prompt);
19159
19519
  const shouldShowPromptCopyButton = contentTypeLower === 'note' && shouldShowPrompt && !!copyablePromptText;
19160
19520
  ensurePromptCopyDelegation();
@@ -19167,7 +19527,7 @@
19167
19527
  // [Fix] Add 'map-node-note' class for Note nodes so interactions module recognizes them
19168
19528
  const isNoteNode = contentTypeLower === 'note';
19169
19529
  const isCodeClassNode = contentTypeLower === 'code';
19170
- container.className = `map-node css3d-dynamic-node${isImageNode ? ' map-node-image-container' : ''}${isEmbedNode ? ' map-node-embed' : ''}${isNoteNode ? ' map-node-note' : ''}${isCodeClassNode ? ' map-node-code' : ''}${isAgentNode ? ' map-node-agent' : ''}`;
19530
+ container.className = `map-node css3d-dynamic-node${isImageNode ? ' map-node-image-container' : ''}${isEmbedNode ? ' map-node-embed' : ''}${isNoteNode ? ' map-node-note' : ''}${isCodeClassNode ? ' map-node-code' : ''}${isCsvTableNode ? ' map-node-csv-table' : ''}${isAgentNode ? ' map-node-agent' : ''}`;
19171
19531
  container.dataset.nodeId = nodeModel.id;
19172
19532
  container.style.cssText = `
19173
19533
  width: ${width}px;
@@ -19175,7 +19535,7 @@
19175
19535
  background: ${isMediaNode ? 'transparent' : 'white'};
19176
19536
  ${isMediaNode ? 'border: 0; outline: 0;' : ''}
19177
19537
  border-radius: 0px;
19178
- padding: ${isMediaNode ? '0' : (isCodeClassNode ? '12px 8px 12px 14px' : (isTextualCardNode ? '12px 8px 12px 14px' : '12px 4px 12px 12px'))};
19538
+ padding: ${isMediaNode ? '0' : (isCsvTableNode ? '10px' : (isCodeClassNode ? '12px 8px 12px 14px' : (isTextualCardNode ? '12px 8px 12px 14px' : '12px 4px 12px 12px')))};
19179
19539
  box-sizing: border-box;
19180
19540
  overflow: hidden;
19181
19541
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
@@ -19285,7 +19645,23 @@
19285
19645
  // Variable isNoteNode declared above
19286
19646
  const isMarkdownType = ['text', 'markdown'].includes(contentTypeLower); // Removed 'note'
19287
19647
 
19288
- if (isCodeNode) {
19648
+ if (isCsvTableNode) {
19649
+ responseDiv.className = 'node-response csv-table-scroll';
19650
+ responseDiv.style.cssText = `
19651
+ width: 100%;
19652
+ height: 100%;
19653
+ flex: 1 1 auto;
19654
+ min-height: 0;
19655
+ overflow: auto;
19656
+ pointer-events: auto;
19657
+ box-sizing: border-box;
19658
+ user-select: text;
19659
+ -webkit-user-select: text;
19660
+ `;
19661
+ responseDiv.dataset.src = content;
19662
+ responseDiv.dataset.nodeId = nodeModel.id;
19663
+ renderCsvTableContent(responseDiv, nodeModel, content);
19664
+ } else if (isCodeNode) {
19289
19665
  responseDiv.className = 'node-response code-body';
19290
19666
  // ▼▼▼ [FIX] 슬림 스크롤바 인라인 스타일 추가 ▼▼▼
19291
19667
  responseDiv.style.cssText = `
@@ -19559,6 +19935,7 @@
19559
19935
  || contentTypeLower === 'markdown'
19560
19936
  || contentTypeLower === 'note'
19561
19937
  || contentTypeLower === 'memo'
19938
+ || contentTypeLower === CSV_TABLE_CONTENT_TYPE
19562
19939
  || contentTypeLower === 'templatelauncher'
19563
19940
  || contentTypeLower === 'image'
19564
19941
  || contentTypeLower === 'video'
@@ -19572,7 +19949,7 @@
19572
19949
  // text/markdown type has no Blazor template, so create dynamically.
19573
19950
  // [FIX] 'note' type also falls back to dynamic creation if not found (or if we force dynamic for consistent MD)
19574
19951
  // But usually 'note' comes from Razor. If missing, we create it.
19575
- const dynamicTypes = ['text', 'markdown', 'code', 'note', 'memo', 'templatelauncher', 'image', 'video', 'embed'];
19952
+ const dynamicTypes = ['text', 'markdown', 'code', 'note', 'memo', CSV_TABLE_CONTENT_TYPE, 'templatelauncher', 'image', 'video', 'embed'];
19576
19953
 
19577
19954
  if (remoteFleetMonitor || dynamicTypes.includes(contentTypeLower)) {
19578
19955
  log(`[MindMapCss3DManager] Creating dynamic DOM element for ${visualContentTypeLower || contentTypeLower} node ${nodeModel.id}`);
@@ -19724,7 +20101,7 @@
19724
20101
  && !isRemoteFleetMonitorNode(nodeModel);
19725
20102
  // Enable pointer events for scrollable content so user can scroll/select text
19726
20103
  const scrollables = clonedElement.querySelectorAll(
19727
- '.node-response, .note-content, .markdown-body, .prose, .note-textarea, textarea, [id^="node-response-"], [id^="node-textarea-"], .code-body, .text-content, .embed-content, .embed-card, .embed-card *, .embed-action, .embed-play-button, .node-prompt-copy-button, .node-prompt-copy-button *, iframe, .map-node-memo__title, .map-node-memo__body, .map-node-memo__icon-button, .map-node-memo__icon-popover, .map-node-memo__icon-option, .map-node-memo__agent-role-select, .map-node-memo__agent-role-select-button, .map-node-memo__agent-role-menu, .map-node-memo__agent-role-option, .map-node-memo__agent-action, .map-node-memo__agent-plan-panel, .map-node-memo__agent-plan-body, .map-node-memo__agent-plan-body *, .map-node-memo__agent-console-panel, .map-node-memo__agent-console-body, .map-node-memo__agent-console-resize, .map-node-memo__agent-console-resize *, .map-node-memo__agent-result-link, .map-node-template-card, .map-node-template-card *, [data-template-card-interactive="true"]'
20104
+ '.node-response, .note-content, .markdown-body, .prose, .note-textarea, textarea, [id^="node-response-"], [id^="node-textarea-"], .code-body, .text-content, .csv-table-scroll, .csv-table-scroll *, .csv-table-header-button, .embed-content, .embed-card, .embed-card *, .embed-action, .embed-play-button, .node-prompt-copy-button, .node-prompt-copy-button *, iframe, .map-node-memo__title, .map-node-memo__body, .map-node-memo__icon-button, .map-node-memo__icon-popover, .map-node-memo__icon-option, .map-node-memo__agent-role-select, .map-node-memo__agent-role-select-button, .map-node-memo__agent-role-menu, .map-node-memo__agent-role-option, .map-node-memo__agent-action, .map-node-memo__agent-plan-panel, .map-node-memo__agent-plan-body, .map-node-memo__agent-plan-body *, .map-node-memo__agent-console-panel, .map-node-memo__agent-console-body, .map-node-memo__agent-console-resize, .map-node-memo__agent-console-resize *, .map-node-memo__agent-result-link, .map-node-template-card, .map-node-template-card *, [data-template-card-interactive="true"]'
19728
20105
  );
19729
20106
  scrollables.forEach(el => {
19730
20107
  el.style.pointerEvents = 'auto';
@@ -19732,7 +20109,7 @@
19732
20109
  el.style.userSelect = 'none';
19733
20110
  el.style.webkitUserSelect = 'none';
19734
20111
  el.style.cursor = 'ns-resize';
19735
- } else if (el.matches('.embed-action, .embed-play-button, .embed-card, .embed-card *, .node-prompt-copy-button, .node-prompt-copy-button *, iframe, .map-node-memo__icon-button, .map-node-memo__icon-popover, .map-node-memo__icon-option, .map-node-memo__agent-role-select, .map-node-memo__agent-role-select-button, .map-node-memo__agent-role-menu, .map-node-memo__agent-role-option, .map-node-memo__agent-action, .map-node-memo__agent-result-link, .template-card__option, .template-card__generate')) {
20112
+ } else if (el.matches('.csv-table-header-button, .embed-action, .embed-play-button, .embed-card, .embed-card *, .node-prompt-copy-button, .node-prompt-copy-button *, iframe, .map-node-memo__icon-button, .map-node-memo__icon-popover, .map-node-memo__icon-option, .map-node-memo__agent-role-select, .map-node-memo__agent-role-select-button, .map-node-memo__agent-role-menu, .map-node-memo__agent-role-option, .map-node-memo__agent-action, .map-node-memo__agent-result-link, .template-card__option, .template-card__generate')) {
19736
20113
  el.style.userSelect = 'none';
19737
20114
  el.style.webkitUserSelect = 'none';
19738
20115
  el.style.cursor = 'pointer';
@@ -20197,8 +20574,16 @@
20197
20574
  if (responseEl) {
20198
20575
  responseEl.style.display = (isLoading && !hasContent) ? 'none' : '';
20199
20576
  const isCodeNode = contentTypeLower === 'code';
20200
-
20201
- if (isCodeNode) {
20577
+ const isCsvTableNode = contentTypeLower === CSV_TABLE_CONTENT_TYPE;
20578
+
20579
+ if (isCsvTableNode) {
20580
+ if (!responseEl.classList.contains('csv-table-scroll')) {
20581
+ responseEl.className = 'node-response csv-table-scroll';
20582
+ }
20583
+ responseEl.dataset.src = content || '';
20584
+ responseEl.dataset.nodeId = nodeModel.id || nodeModel.Id || '';
20585
+ renderCsvTableContent(responseEl, nodeModel, content);
20586
+ } else if (isCodeNode) {
20202
20587
  renderCodeBodyContent(responseEl, content);
20203
20588
  } else {
20204
20589
  // Unified Markdown Rendering with Caching
@@ -71,7 +71,17 @@ window.MindMapDnD = (function () {
71
71
  });
72
72
  }
73
73
 
74
- // 3. Video handler
74
+ // 3. CSV table handler
75
+ if (!window.MindMapFileRegistry.some(h => h.name === 'CoreCSV')) {
76
+ window.MindMapFileRegistry.push({
77
+ name: 'CoreCSV',
78
+ priority: 6,
79
+ check: isCsvFile,
80
+ getType: () => CSV_TABLE_CONTENT_TYPE
81
+ });
82
+ }
83
+
84
+ // 4. Video handler
75
85
  if (!window.MindMapFileRegistry.some(h => h.name === 'CoreVideo')) {
76
86
  window.MindMapFileRegistry.push({
77
87
  name: 'CoreVideo',
@@ -109,7 +119,7 @@ window.MindMapDnD = (function () {
109
119
  '.cpp', '.c', '.h', '.go', '.rs', '.rb', '.php',
110
120
  '.swift', '.kt', '.json', '.xml', '.yaml', '.yml',
111
121
  '.sql', '.html', '.css', '.scss', '.less', '.sh',
112
- '.bat', '.ps1', '.csv', '.razor' // Added .razor for Blazor
122
+ '.bat', '.ps1', '.razor' // Added .razor for Blazor
113
123
  ];
114
124
  window.MindMapFileRegistry.push({
115
125
  name: 'CoreCode',
@@ -123,12 +133,21 @@ window.MindMapDnD = (function () {
123
133
  }
124
134
  // ▲▲▲ [New] ▲▲▲
125
135
 
126
- console.log('[MindMapDnD] Core file handlers (PDF, Image, Video, Text, Code) registered');
136
+ console.log('[MindMapDnD] Core file handlers (PDF, Image, Video, CSV, Text, Code) registered');
127
137
  }
128
138
  registerCoreHandlers();
129
139
 
130
140
  const eventHandlers = new WeakMap();
131
141
  const GRID_SNAP = 10;
142
+ const CSV_TABLE_CONTENT_TYPE = 'csv-table';
143
+
144
+ function isCsvFile(file) {
145
+ const name = String(file?.name || '').toLowerCase();
146
+ const mime = String(file?.type || '').toLowerCase();
147
+ return mime === 'text/csv' ||
148
+ mime === 'application/csv' ||
149
+ name.endsWith('.csv');
150
+ }
132
151
 
133
152
  function computeDroppedImageNodeSize(originalWidth, originalHeight) {
134
153
  if (window.mindMap?.computeImageNodeSize) {
@@ -148,6 +167,30 @@ window.MindMapDnD = (function () {
148
167
  return { width, height };
149
168
  }
150
169
 
170
+ async function readCsvFileText(file) {
171
+ if (file && typeof file.text === 'function') {
172
+ return await file.text();
173
+ }
174
+
175
+ return await new Promise((resolve, reject) => {
176
+ const reader = new FileReader();
177
+ reader.onload = () => resolve(String(reader.result || ''));
178
+ reader.onerror = () => reject(reader.error || new Error('CSV read failed'));
179
+ reader.readAsText(file);
180
+ });
181
+ }
182
+
183
+ function summarizeCsvText(csvText) {
184
+ if (window.MindMapCsvTable?.summarizeCsv) {
185
+ return window.MindMapCsvTable.summarizeCsv(csvText);
186
+ }
187
+
188
+ const firstLine = String(csvText || '').split(/\r\n|\n|\r/, 1)[0] || '';
189
+ const columnCount = firstLine ? Math.max(1, firstLine.split(',').length) : 0;
190
+ const rowCount = Math.max(0, String(csvText || '').split(/\r\n|\n|\r/).filter(Boolean).length - 1);
191
+ return { rowCount, columnCount, delimiter: ',' };
192
+ }
193
+
151
194
  // ▼▼▼ [Profiling] Drag statistics tracking ▼▼▼
152
195
  let _dragStats = {
153
196
  startTime: 0,
@@ -1081,6 +1124,24 @@ window.MindMapDnD = (function () {
1081
1124
  }
1082
1125
 
1083
1126
  isLoading = false;
1127
+ } else if (type === CSV_TABLE_CONTENT_TYPE) {
1128
+ console.log(`[DnD] Processing CSV ${index + 1}: ${file.name}`);
1129
+ finalResponse = await readCsvFileText(file);
1130
+ displayUrl = finalResponse;
1131
+ finalWidth = 720;
1132
+ finalHeight = 420;
1133
+ isLoading = false;
1134
+ const csvSummary = summarizeCsvText(finalResponse);
1135
+ metadata = {
1136
+ assetKind: 'csv',
1137
+ fileName: file.name || '',
1138
+ fileSize: String(file.size || 0),
1139
+ fileMime: file.type || 'text/csv',
1140
+ fileLastModified: String(file.lastModified || ''),
1141
+ CsvDelimiter: csvSummary.delimiter || ',',
1142
+ CsvRowCount: String(csvSummary.rowCount ?? 0),
1143
+ CsvColumnCount: String(csvSummary.columnCount ?? 0)
1144
+ };
1084
1145
  } else if (type === 'text') {
1085
1146
  finalResponse = '⏳ Analyzing file...';
1086
1147
  displayUrl = finalResponse;
@@ -672,7 +672,7 @@ window.MindMapInteractions = (function () {
672
672
 
673
673
  function isReadOnlyTextSelectionNode(target) {
674
674
  const contentType = getNodeContentType(target);
675
- return (contentType === 'text' || contentType === 'markdown') && !isAgentNode(target);
675
+ return (contentType === 'text' || contentType === 'markdown' || contentType === 'csv-table') && !isAgentNode(target);
676
676
  }
677
677
 
678
678
  function isTemplateCardInteractiveTarget(target) {
@@ -2855,8 +2855,8 @@ window.MindMapInteractions = (function () {
2855
2855
  }
2856
2856
  // ▲▲▲ [New] ▲▲▲
2857
2857
 
2858
- const DEFAULT_SCROLLABLE_SELECTORS = '.node-response, .note-content, .markdown-body, .prose, .note-textarea, .code-body, .code-content, .pdf-content, .text-content, .file-content, .map-node-memo__body, .map-node-memo__body-view, pre, code';
2859
- const DEFAULT_TEXT_CONTENT_SELECTORS = '.node-response, .code-body, .text-content, .markdown-body, .prose';
2858
+ const DEFAULT_SCROLLABLE_SELECTORS = '.node-response, .note-content, .markdown-body, .prose, .note-textarea, .code-body, .code-content, .csv-table-scroll, .csv-table-scroll *, .pdf-content, .text-content, .file-content, .map-node-memo__body, .map-node-memo__body-view, pre, code';
2859
+ const DEFAULT_TEXT_CONTENT_SELECTORS = '.node-response, .code-body, .text-content, .markdown-body, .prose, .csv-table-scroll, .csv-table-cell';
2860
2860
 
2861
2861
  function getScrollableSelectors() {
2862
2862
  return window.MindMapCss3DManager?.getTextInteractionScrollableSelectors?.()
@@ -3868,10 +3868,13 @@ window.MindMapInteractions = (function () {
3868
3868
  e.target.classList.contains('node-response') ||
3869
3869
  e.target.classList.contains('text-content') ||
3870
3870
  e.target.classList.contains('markdown-body') ||
3871
- e.target.closest('.code-body') ||
3872
- e.target.closest('.node-response') ||
3873
- e.target.closest('.text-content') ||
3874
- e.target.closest('.markdown-body');
3871
+ e.target.classList.contains('csv-table-scroll') ||
3872
+ e.target.classList.contains('csv-table-cell') ||
3873
+ e.target.closest('.code-body') ||
3874
+ e.target.closest('.node-response') ||
3875
+ e.target.closest('.text-content') ||
3876
+ e.target.closest('.markdown-body') ||
3877
+ e.target.closest('.csv-table-scroll');
3875
3878
 
3876
3879
  // ▼▼▼ [2-Stage Click] text-based 노드가 이미 편집 모드(CSS3D)인 경우에만 텍스트 선택 허용 ▼▼▼
3877
3880
  if (supportsTextInteraction && isClickOnContent && !isMultiModifier && isSelected) {
@@ -366,6 +366,7 @@
366
366
  contentType === 'memo' ||
367
367
  contentType === 'markdown' ||
368
368
  contentType === 'code' ||
369
+ contentType === 'csv-table' ||
369
370
  isAgentLikeLodNode(model);
370
371
  }
371
372
 
@@ -7526,7 +7527,7 @@
7526
7527
  if (!root) return '';
7527
7528
 
7528
7529
  const values = [];
7529
- const editableSelector = 'textarea, input, [contenteditable="true"], .note-content, .node-response, .markdown-body, .code-body, .text-content, .map-node-memo__body, .map-node-memo__body-view';
7530
+ const editableSelector = 'textarea, input, [contenteditable="true"], .note-content, .node-response, .markdown-body, .code-body, .text-content, .csv-table-scroll, .csv-table-cell, .map-node-memo__body, .map-node-memo__body-view';
7530
7531
  const targets = root.matches?.(editableSelector)
7531
7532
  ? [root, ...root.querySelectorAll?.(editableSelector) || []]
7532
7533
  : Array.from(root.querySelectorAll?.(editableSelector) || []);
@@ -7614,7 +7615,7 @@
7614
7615
  const root = entry?.cssObject?.element || null;
7615
7616
  if (!root || typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') return null;
7616
7617
 
7617
- const selector = 'textarea, [id^="node-response-"], [id^="node-textarea-"], .note-content, .node-response, .markdown-body, .code-body, .simple-content, .markdown-content, .map-node-memo__body, .map-node-memo__body-view';
7618
+ const selector = 'textarea, [id^="node-response-"], [id^="node-textarea-"], .note-content, .node-response, .markdown-body, .code-body, .csv-table-scroll, .simple-content, .markdown-content, .map-node-memo__body, .map-node-memo__body-view';
7618
7619
  const target = root.matches?.(selector)
7619
7620
  ? root
7620
7621
  : root.querySelector?.(selector);
@@ -45,6 +45,7 @@ function shouldIndexResponse(contentType) {
45
45
  || contentType === 'memo'
46
46
  || contentType === 'markdown'
47
47
  || contentType === 'code'
48
+ || contentType === 'csv-table'
48
49
  || contentType === 'directory';
49
50
  }
50
51
 
@@ -34,7 +34,7 @@
34
34
  gray: { border: 0xd1d5db, accent: 0x6b7280 },
35
35
  slate: { border: 0xcbd5e1, accent: 0x64748b }
36
36
  };
37
- const NODE_TEXT_SELECTION_SELECTORS = '.node-response, .code-body, .text-content, .markdown-body, textarea, .map-node-memo__title, .map-node-memo__body, .map-node-memo__body-view, .map-node-memo__body-view *';
37
+ const NODE_TEXT_SELECTION_SELECTORS = '.node-response, .code-body, .text-content, .markdown-body, .csv-table-scroll, .csv-table-cell, textarea, .map-node-memo__title, .map-node-memo__body, .map-node-memo__body-view, .map-node-memo__body-view *';
38
38
  const NODE_INTERACTIVE_CONTENT_SELECTORS = `${NODE_TEXT_SELECTION_SELECTORS}, .embed-content, .embed-card, .embed-card *, .embed-action, .embed-play-button, iframe, .map-node-memo__icon-button, .map-node-memo__icon-option, .map-node-memo__icon-popover, .map-node-memo__agent-action, .map-node-memo__agent-result-link, .map-node-memo__agent-plan-panel, .map-node-memo__agent-plan-body, .map-node-memo__agent-plan-body *, .map-node-memo__agent-console-panel, .map-node-memo__agent-console-body, .map-node-memo__agent-console-body *, .map-node-memo__agent-console-resize, .map-node-memo__agent-console-resize *`;
39
39
  const MEMO_EDIT_ONLY_SELECTORS = '.map-node-memo__title, .map-node-memo__body, .map-node-memo__icon-button, .map-node-memo__icon-option, .map-node-memo__icon-popover';
40
40
 
@@ -3984,7 +3984,7 @@
3984
3984
  async function switchToCss3D(module, nodeId) {
3985
3985
  const nodeEntry = module.nodeObjectsById.get(nodeId);
3986
3986
  // ▼▼▼ [Log] Reason for skipping switch (SwitchToCss3D) ▼▼▼
3987
- const supportedTypes = ['note', 'memo', 'code', 'text', 'markdown', 'image', 'video', 'embed'];
3987
+ const supportedTypes = ['note', 'memo', 'code', 'text', 'markdown', 'csv-table', 'image', 'video', 'embed'];
3988
3988
  if (!nodeEntry || !supportedTypes.includes(nodeEntry.model.contentType) || !nodeEntry.cssObject) {
3989
3989
  log(`[MindMapNodes] SwitchToCss3D(${nodeId}) SKIPPED (Not a valid, initialized text-based node).`);
3990
3990
  return;
@@ -4154,6 +4154,7 @@
4154
4154
  nodeEntry.model.contentType === 'memo' ||
4155
4155
  nodeEntry.model.contentType === 'markdown' ||
4156
4156
  nodeEntry.model.contentType === 'code' ||
4157
+ nodeEntry.model.contentType === 'csv-table' ||
4157
4158
  nodeEntry.model.contentType === 'image' ||
4158
4159
  nodeEntry.model.contentType === 'video' ||
4159
4160
  nodeEntry.model.contentType === 'embed';
@@ -17,6 +17,9 @@
17
17
  const ENABLE_WEBGL_GLOW = false;
18
18
  const DEFAULT_MIN_NODE_WIDTH = 200;
19
19
  const DEFAULT_MIN_NODE_HEIGHT = 100;
20
+ const CSV_TABLE_CONTENT_TYPE = 'csv-table';
21
+ const CSV_TABLE_MIN_WIDTH = 320;
22
+ const CSV_TABLE_MIN_HEIGHT = 180;
20
23
  const REMOTE_FLEET_SEMANTIC_TYPE = 'RemoteFleetMonitor';
21
24
  const REMOTE_FLEET_MONITOR_MIN_WIDTH = 520;
22
25
  const REMOTE_FLEET_MONITOR_MIN_HEIGHT = 320;
@@ -30,6 +33,10 @@
30
33
  return String(metadata?.SemanticType || metadata?.semanticType || '').trim() === REMOTE_FLEET_SEMANTIC_TYPE;
31
34
  }
32
35
 
36
+ function isCsvTableNodeModel(nodeModel) {
37
+ return String(nodeModel?.contentType ?? nodeModel?.ContentType ?? '').trim().toLowerCase() === CSV_TABLE_CONTENT_TYPE;
38
+ }
39
+
33
40
  function clampRemoteFleetResizeDimensions(
34
41
  nodeModel,
35
42
  width,
@@ -38,10 +45,10 @@
38
45
  fallbackMinHeight = DEFAULT_MIN_NODE_HEIGHT) {
39
46
  const minWidth = isRemoteFleetMonitorNodeModel(nodeModel)
40
47
  ? REMOTE_FLEET_MONITOR_MIN_WIDTH
41
- : fallbackMinWidth;
48
+ : (isCsvTableNodeModel(nodeModel) ? CSV_TABLE_MIN_WIDTH : fallbackMinWidth);
42
49
  const minHeight = isRemoteFleetMonitorNodeModel(nodeModel)
43
50
  ? REMOTE_FLEET_MONITOR_MIN_HEIGHT
44
- : fallbackMinHeight;
51
+ : (isCsvTableNodeModel(nodeModel) ? CSV_TABLE_MIN_HEIGHT : fallbackMinHeight);
45
52
 
46
53
  const numericWidth = Number(width);
47
54
  const numericHeight = Number(height);
@@ -198,7 +198,7 @@
198
198
  if (!isNativeSelectFocusedInside(state.rootElement)) {
199
199
  state.nativeSelectInteractionActive = false;
200
200
  }
201
- }, 0);
201
+ }, 500);
202
202
  }
203
203
 
204
204
  function shouldIgnoreDocumentClickForNativeSelect(state) {
@@ -246,6 +246,35 @@
246
246
  clientY >= rect.top &&
247
247
  clientY <= rect.bottom;
248
248
  }
249
+
250
+ function isSurfaceOwnedPointerEvent(state, rootElement, event) {
251
+ if (!state || !event) {
252
+ return false;
253
+ }
254
+
255
+ return isEventInsideRoot(rootElement, event) ||
256
+ isPointerWithinElementBounds(rootElement, event) ||
257
+ isEventInsideIgnoredSelector(state.options?.ignoreSelector, event);
258
+ }
259
+
260
+ function rememberSurfacePointerDown(state, event) {
261
+ if (!state || !event) {
262
+ return;
263
+ }
264
+
265
+ const currentRootElement = resolveSurfaceRootElement(state.rootElement, state.options);
266
+ const isInsideSurface = isSurfaceOwnedPointerEvent(state, currentRootElement, event);
267
+ state.lastPointerDownInsideSurface = isInsideSurface;
268
+ state.lastPointerDownAt = getInteractionTime();
269
+
270
+ if (isInsideSurface) {
271
+ state.suppressNextDocumentClick = true;
272
+ }
273
+
274
+ if (isNativeSelectEvent(event)) {
275
+ markNativeSelectInteraction(state);
276
+ }
277
+ }
249
278
 
250
279
  function getUploadDropPosition(viewport) {
251
280
  const fallbackRect = viewport.getBoundingClientRect();
@@ -468,6 +497,10 @@
468
497
  document.removeEventListener('click', state.documentClickHandler, false);
469
498
  }
470
499
 
500
+ if (state.documentPointerDownHandler) {
501
+ document.removeEventListener('pointerdown', state.documentPointerDownHandler, true);
502
+ }
503
+
471
504
  if (state.helper) {
472
505
  surfaceOutsideClickCloseStatesByHelper.delete(state.helper);
473
506
  }
@@ -494,10 +527,13 @@
494
527
  suppressNextDocumentClick: false,
495
528
  nativeSelectInteractionActive: false,
496
529
  nativeSelectInteractionAt: 0,
530
+ lastPointerDownInsideSurface: false,
531
+ lastPointerDownAt: 0,
497
532
  rootPointerDownHandler: null,
498
533
  rootClickHandler: null,
499
534
  rootChangeHandler: null,
500
535
  rootFocusOutHandler: null,
536
+ documentPointerDownHandler: null,
501
537
  documentClickHandler: null
502
538
  };
503
539
 
@@ -526,6 +562,10 @@
526
562
  scheduleNativeSelectInteractionClear(state);
527
563
  };
528
564
 
565
+ state.documentPointerDownHandler = (event) => {
566
+ rememberSurfacePointerDown(state, event);
567
+ };
568
+
529
569
  state.documentClickHandler = (event) => {
530
570
  if (typeof event.button === 'number' && event.button !== 0) {
531
571
  return;
@@ -555,6 +595,10 @@
555
595
  return;
556
596
  }
557
597
 
598
+ if (state.lastPointerDownInsideSurface === true && getInteractionTime() - state.lastPointerDownAt < 1000) {
599
+ return;
600
+ }
601
+
558
602
  if (isEventInsideRoot(currentRootElement, event)) {
559
603
  return;
560
604
  }
@@ -575,6 +619,7 @@
575
619
  state.rootElement.addEventListener('click', state.rootClickHandler, true);
576
620
  state.rootElement.addEventListener('change', state.rootChangeHandler, true);
577
621
  state.rootElement.addEventListener('focusout', state.rootFocusOutHandler, true);
622
+ document.addEventListener('pointerdown', state.documentPointerDownHandler, true);
578
623
  document.addEventListener('click', state.documentClickHandler, false);
579
624
 
580
625
  surfaceOutsideClickCloseStatesByHelper.set(helper, state);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "mainAssemblyName": "MindExecution.Web",
3
3
  "resources": {
4
- "hash": "sha256-we9qenVtePc2CABTfoN+/D96wdPuqIl4CgKuk3znAUA=",
4
+ "hash": "sha256-UGhBNyGMI2YVNBv0VAxRK/+ISHOLlYLKc6pg6eePs8k=",
5
5
  "fingerprinting": {
6
6
  "Google.Protobuf.9h59ukbel7.dll": "Google.Protobuf.dll",
7
7
  "Markdig.d1j7v41cl1.dll": "Markdig.dll",
@@ -127,12 +127,12 @@
127
127
  "MindExecution.Kernel.7k773jan4j.dll": "MindExecution.Kernel.dll",
128
128
  "MindExecution.Plugins.Admin.nr5ioavpvn.dll": "MindExecution.Plugins.Admin.dll",
129
129
  "MindExecution.Plugins.Business.qq63kom73x.dll": "MindExecution.Plugins.Business.dll",
130
- "MindExecution.Plugins.Concept.w4znwcrl6x.dll": "MindExecution.Plugins.Concept.dll",
130
+ "MindExecution.Plugins.Concept.za4g3ep0s3.dll": "MindExecution.Plugins.Concept.dll",
131
131
  "MindExecution.Plugins.Directory.u6p6pxpwxp.dll": "MindExecution.Plugins.Directory.dll",
132
- "MindExecution.Plugins.PlanMaster.0w4vr40lnz.dll": "MindExecution.Plugins.PlanMaster.dll",
133
- "MindExecution.Plugins.YouTube.sdl5n9uv68.dll": "MindExecution.Plugins.YouTube.dll",
134
- "MindExecution.Shared.fprczldv6z.dll": "MindExecution.Shared.dll",
135
- "MindExecution.Web.xhlvm1d8ex.dll": "MindExecution.Web.dll",
132
+ "MindExecution.Plugins.PlanMaster.dybjnpy716.dll": "MindExecution.Plugins.PlanMaster.dll",
133
+ "MindExecution.Plugins.YouTube.77htf13h5q.dll": "MindExecution.Plugins.YouTube.dll",
134
+ "MindExecution.Shared.d74ehlrxzm.dll": "MindExecution.Shared.dll",
135
+ "MindExecution.Web.0bxjy0fhk2.dll": "MindExecution.Web.dll",
136
136
  "dotnet.js": "dotnet.js",
137
137
  "dotnet.native.qc8g39g30v.js": "dotnet.native.js",
138
138
  "dotnet.native.boem75ye5i.wasm": "dotnet.native.wasm",
@@ -280,16 +280,16 @@
280
280
  "netstandard.yvr3prsx0x.dll": "sha256-EksNn8Luo4bOWqJ6X7dIe9qG9oOqwOVzjH2xYyMNi+E=",
281
281
  "MindExecution.Core.cdplo34sb2.dll": "sha256-sLc0yEeGYQhFPxxNKLQsHllAhwjKhNn+pfgRY0bHbpo=",
282
282
  "MindExecution.Kernel.7k773jan4j.dll": "sha256-B5H6hzB6g8sWLqXUoXifv8CbLqVt7g1lLkkinTBKF8E=",
283
- "MindExecution.Plugins.Concept.w4znwcrl6x.dll": "sha256-oNGAmbTjkFdVfZeYbc4jYWtyQa3c8Tle5LIXoFye4yo=",
284
- "MindExecution.Plugins.PlanMaster.0w4vr40lnz.dll": "sha256-gPWCEzDE7cJLOFTJOhja3JXJzkHFWd4dzfRdsdTxwps=",
285
- "MindExecution.Shared.fprczldv6z.dll": "sha256-veGmVlpQw2b1zDaUWReHRSLbiZ2IloG79uo1GLlduEo=",
286
- "MindExecution.Web.xhlvm1d8ex.dll": "sha256-azcD8wLJIxuNSplzTVvh2ndLwdcu20xz1t8Jh23Dr7Q="
283
+ "MindExecution.Plugins.Concept.za4g3ep0s3.dll": "sha256-NRvSoxbUc1XekEfarbQRsx+C7Q5zF8CSX8leSW6sUpA=",
284
+ "MindExecution.Plugins.PlanMaster.dybjnpy716.dll": "sha256-u+FNpu8goaKb3wbqL/kEQ/1iB9qwPxPBJGzSjEhxRU0=",
285
+ "MindExecution.Shared.d74ehlrxzm.dll": "sha256-ySEVzQvBsNIQdJhAk6znArycY1ZuaHW8XrzGIzNZL9U=",
286
+ "MindExecution.Web.0bxjy0fhk2.dll": "sha256-pD4Kd3YtszSDufXc+DeiOjslAUWzpeSMY0CHpwwRyKc="
287
287
  },
288
288
  "lazyAssembly": {
289
289
  "MindExecution.Plugins.Admin.nr5ioavpvn.dll": "sha256-TWIfHGuEUUD1JtyuhfH4NM5JkvqAt8KcDVrWxvmwnVc=",
290
290
  "MindExecution.Plugins.Business.qq63kom73x.dll": "sha256-4lgIcKclWQIjijrBJJFu84fgAH4MsDCN/myhG/K9OOU=",
291
291
  "MindExecution.Plugins.Directory.u6p6pxpwxp.dll": "sha256-BjJ6/XNfp5wd/1RTdh99X9RR0I1GV6t4bp5A+RlAK9s=",
292
- "MindExecution.Plugins.YouTube.sdl5n9uv68.dll": "sha256-gNZE7M6cqiF9aEJYdZtUzt5bYPkh5Be7LKuyh286u2Q="
292
+ "MindExecution.Plugins.YouTube.77htf13h5q.dll": "sha256-rYCDS5JysOa68J0RrPVkS461WQtEM50fU7lwzDwx4uY="
293
293
  }
294
294
  },
295
295
  "cacheBootResources": true,
@@ -7,8 +7,8 @@
7
7
  <title>MindExec | Run your ideas as AI task graphs</title>
8
8
  <meta name="description" content="MindExec is an AI execution canvas for solo builders, researchers, developers, and creators. Start with free browser tools, then move serious work into saved MindCanvas projects." />
9
9
  <base href="/" />
10
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260616-mdm-screen-wall-v560" />
11
- <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-mdm-screen-wall-v560" />
10
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/app.css?v=20260616-csv-table-node-v562" />
11
+ <link rel="stylesheet" href="_content/MindExecution.Shared/css/mind-map-overrides.css?v=20260616-csv-table-node-v562" />
12
12
  <!-- ?쇄뼹??Font Awesome (local) ?쇄뼹??-->
13
13
  <link rel="stylesheet" href="_content/MindExecution.Shared/lib/font-awesome/css/all.min.css" />
14
14
  <!-- ?꿎뼯??-->
@@ -579,7 +579,7 @@
579
579
  }
580
580
 
581
581
  const base = '_content/MindExecution.Shared/js/';
582
- const scriptVersion = '20260616-mdm-screen-wall-v560';
582
+ const scriptVersion = '20260616-csv-table-node-v562';
583
583
  const scriptUrl = (script) => `${base}${script}?v=${scriptVersion}`;
584
584
  console.log(`[Script Loader] Shared JS version: ${scriptVersion}`);
585
585
  const criticalScripts = [
@@ -1,5 +1,5 @@
1
1
  self.assetsManifest = {
2
- "version": "i4nijHuW",
2
+ "version": "trZeDvyF",
3
3
  "assets": [
4
4
  {
5
5
  "hash": "sha256-+CSYMcqLNTsq3VnH11jgYyOCCdxvHzL74CBmo4sCmMU=",
@@ -86,7 +86,7 @@
86
86
  "url": "_content/MindExecution.Shared/js/mind-map-core.js.backup"
87
87
  },
88
88
  {
89
- "hash": "sha256-MWVSM/VwUCyDL+ohY/7e6qDkkosGiE9E3t3DbiGJJEs=",
89
+ "hash": "sha256-AXK7W1xsaRJiZWWYSc7SrzizpChwI0pRl9jvcC3x518=",
90
90
  "url": "_content/MindExecution.Shared/js/mind-map-css3d-manager.js"
91
91
  },
92
92
  {
@@ -94,7 +94,7 @@
94
94
  "url": "_content/MindExecution.Shared/js/mind-map-dev-guards.js"
95
95
  },
96
96
  {
97
- "hash": "sha256-dJFVGib0fUeandu5Tvb2Wtpq1w7XwxD8g2Dd+loVK+Q=",
97
+ "hash": "sha256-NZcjN5Pbkxuki2/z029Yog58DHkZDsmDU5baRgMruis=",
98
98
  "url": "_content/MindExecution.Shared/js/mind-map-dnd.js"
99
99
  },
100
100
  {
@@ -106,7 +106,7 @@
106
106
  "url": "_content/MindExecution.Shared/js/mind-map-glow-shader.js"
107
107
  },
108
108
  {
109
- "hash": "sha256-QJh7a93uHYeGLXH8R87u9/oky9uJLW2p6eSMODlGjY0=",
109
+ "hash": "sha256-l1g4NKeSMjorH0cZmdjmzRef2W6vPhmZgoNeo4TWKl8=",
110
110
  "url": "_content/MindExecution.Shared/js/mind-map-interactions.js"
111
111
  },
112
112
  {
@@ -114,7 +114,7 @@
114
114
  "url": "_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js"
115
115
  },
116
116
  {
117
- "hash": "sha256-NTCDYnA/mG2tplidYmMv50OhHhdXjP985NlsjsyQ38g=",
117
+ "hash": "sha256-/I+97B4sBuC4ON9Ytp2crB+yaSXnxgr1wq35P/GZ4O0=",
118
118
  "url": "_content/MindExecution.Shared/js/mind-map-lod-renderer.js"
119
119
  },
120
120
  {
@@ -130,11 +130,11 @@
130
130
  "url": "_content/MindExecution.Shared/js/mind-map-multi-select.js"
131
131
  },
132
132
  {
133
- "hash": "sha256-xJssU1XGBgy2NYVRe5JKRRFmQNq7BufzFXhZjHDq/lU=",
133
+ "hash": "sha256-t9Clrdzh+IKymPvwLSXd4sHSiTkGKj6Xb8cbTl5Q/aA=",
134
134
  "url": "_content/MindExecution.Shared/js/mind-map-node-search-worker.js"
135
135
  },
136
136
  {
137
- "hash": "sha256-MmWAqcPEjWAGKxEpAAwKMDEaaN5eZS7jpmC7fiXircc=",
137
+ "hash": "sha256-Du/v38HmdprkhvETqiO0mx3Ez3rYmEvjQnDMIQclta0=",
138
138
  "url": "_content/MindExecution.Shared/js/mind-map-nodes.js"
139
139
  },
140
140
  {
@@ -146,7 +146,7 @@
146
146
  "url": "_content/MindExecution.Shared/js/mind-map-object-manager.js.backup"
147
147
  },
148
148
  {
149
- "hash": "sha256-qoG82XBRkzLFD9Fm/Rmr0k6P+IWL4ZxUyTY4EIfc1hM=",
149
+ "hash": "sha256-7gt5GUp6oiL+vW1pakGSnwgtljCyI3Qh96qAU0hV7Ak=",
150
150
  "url": "_content/MindExecution.Shared/js/mind-map-pipeline.js"
151
151
  },
152
152
  {
@@ -170,7 +170,7 @@
170
170
  "url": "_content/MindExecution.Shared/js/mind-map-visibility-worker.js"
171
171
  },
172
172
  {
173
- "hash": "sha256-icukeXd/DhA/7V4F4NOJP9jiQfzjjjosaH7PK2ymqAg=",
173
+ "hash": "sha256-XrXV7l2M7v2rA3bI7rk6Wwd5J8Qaqllr0UCgmhrstpo=",
174
174
  "url": "_content/MindExecution.Shared/js/mindmap-toolbar.js"
175
175
  },
176
176
  {
@@ -426,28 +426,28 @@
426
426
  "url": "_framework/MindExecution.Plugins.Business.qq63kom73x.dll"
427
427
  },
428
428
  {
429
- "hash": "sha256-oNGAmbTjkFdVfZeYbc4jYWtyQa3c8Tle5LIXoFye4yo=",
430
- "url": "_framework/MindExecution.Plugins.Concept.w4znwcrl6x.dll"
429
+ "hash": "sha256-NRvSoxbUc1XekEfarbQRsx+C7Q5zF8CSX8leSW6sUpA=",
430
+ "url": "_framework/MindExecution.Plugins.Concept.za4g3ep0s3.dll"
431
431
  },
432
432
  {
433
433
  "hash": "sha256-BjJ6/XNfp5wd/1RTdh99X9RR0I1GV6t4bp5A+RlAK9s=",
434
434
  "url": "_framework/MindExecution.Plugins.Directory.u6p6pxpwxp.dll"
435
435
  },
436
436
  {
437
- "hash": "sha256-gPWCEzDE7cJLOFTJOhja3JXJzkHFWd4dzfRdsdTxwps=",
438
- "url": "_framework/MindExecution.Plugins.PlanMaster.0w4vr40lnz.dll"
437
+ "hash": "sha256-u+FNpu8goaKb3wbqL/kEQ/1iB9qwPxPBJGzSjEhxRU0=",
438
+ "url": "_framework/MindExecution.Plugins.PlanMaster.dybjnpy716.dll"
439
439
  },
440
440
  {
441
- "hash": "sha256-gNZE7M6cqiF9aEJYdZtUzt5bYPkh5Be7LKuyh286u2Q=",
442
- "url": "_framework/MindExecution.Plugins.YouTube.sdl5n9uv68.dll"
441
+ "hash": "sha256-rYCDS5JysOa68J0RrPVkS461WQtEM50fU7lwzDwx4uY=",
442
+ "url": "_framework/MindExecution.Plugins.YouTube.77htf13h5q.dll"
443
443
  },
444
444
  {
445
- "hash": "sha256-veGmVlpQw2b1zDaUWReHRSLbiZ2IloG79uo1GLlduEo=",
446
- "url": "_framework/MindExecution.Shared.fprczldv6z.dll"
445
+ "hash": "sha256-ySEVzQvBsNIQdJhAk6znArycY1ZuaHW8XrzGIzNZL9U=",
446
+ "url": "_framework/MindExecution.Shared.d74ehlrxzm.dll"
447
447
  },
448
448
  {
449
- "hash": "sha256-azcD8wLJIxuNSplzTVvh2ndLwdcu20xz1t8Jh23Dr7Q=",
450
- "url": "_framework/MindExecution.Web.xhlvm1d8ex.dll"
449
+ "hash": "sha256-pD4Kd3YtszSDufXc+DeiOjslAUWzpeSMY0CHpwwRyKc=",
450
+ "url": "_framework/MindExecution.Web.0bxjy0fhk2.dll"
451
451
  },
452
452
  {
453
453
  "hash": "sha256-IsZJ91/OW+fHzNqIgEc7Y072ns8z9dGritiSyvR9Wgc=",
@@ -770,7 +770,7 @@
770
770
  "url": "_framework/Websocket.Client.vapounvmnl.dll"
771
771
  },
772
772
  {
773
- "hash": "sha256-vjnwzCMMAs+8it6vEUPeOD7pI/ekFwMr484KaDdpugE=",
773
+ "hash": "sha256-QgwmwowtArQ5iNVyNt2nUZTQbYBdG21lKd44yCDJOos=",
774
774
  "url": "_framework/blazor.boot.json"
775
775
  },
776
776
  {
@@ -834,7 +834,7 @@
834
834
  "url": "image-manifest.json"
835
835
  },
836
836
  {
837
- "hash": "sha256-sZOOfmQyxo9FNFTY9d3r4uYgRhh9Z2b5ve3569JDamU=",
837
+ "hash": "sha256-WIDt2/+ryWe5EOcz23Yqn18VQk2Y8TmNDtuwUyIZEgM=",
838
838
  "url": "index.html"
839
839
  },
840
840
  {
@@ -1,4 +1,4 @@
1
- /* Manifest version: i4nijHuW */
1
+ /* Manifest version: trZeDvyF */
2
2
  // Hosted deployments should prefer the network over stale offline caches.
3
3
  // This service worker immediately clears old Blazor offline caches and unregisters itself.
4
4