@mindexec/cli 0.2.101 → 0.2.103

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 (25) hide show
  1. package/package.json +2 -2
  2. package/remote-hub.js +51 -2
  3. package/scripts/remote-hub-smoke.mjs +8 -0
  4. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +446 -19
  5. package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js +64 -3
  6. package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +10 -7
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +3 -2
  8. package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +1 -0
  9. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +3 -2
  10. package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +9 -2
  11. package/wwwroot/_framework/MindExecution.Core.fc9cjbjplq.dll +0 -0
  12. package/wwwroot/_framework/{MindExecution.Kernel.7k773jan4j.dll → MindExecution.Kernel.pbzp3jfync.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Admin.nr5ioavpvn.dll → MindExecution.Plugins.Admin.gxzwlji1cf.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Business.qq63kom73x.dll → MindExecution.Plugins.Business.vtaey8c59y.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Concept.w4znwcrl6x.dll → MindExecution.Plugins.Concept.ksc3wkuptm.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.Directory.u6p6pxpwxp.dll → MindExecution.Plugins.Directory.zc8ffaoknd.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.0w4vr40lnz.dll → MindExecution.Plugins.PlanMaster.flzcb87jvl.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.sdl5n9uv68.dll → MindExecution.Plugins.YouTube.q4tvods1po.dll} +0 -0
  19. package/wwwroot/_framework/{MindExecution.Shared.6us6j8zcea.dll → MindExecution.Shared.jv6r2n1bvg.dll} +0 -0
  20. package/wwwroot/_framework/{MindExecution.Web.xhlvm1d8ex.dll → MindExecution.Web.3b8az6ed0q.dll} +0 -0
  21. package/wwwroot/_framework/blazor.boot.json +21 -21
  22. package/wwwroot/index.html +3 -3
  23. package/wwwroot/service-worker-assets.js +30 -30
  24. package/wwwroot/service-worker.js +1 -1
  25. package/wwwroot/_framework/MindExecution.Core.cdplo34sb2.dll +0 -0
@@ -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) {
@@ -13278,7 +13637,10 @@
13278
13637
  frameUrl: source,
13279
13638
  receivedAt: String(readRemoteFleetObjectField(frame, 'receivedAt', 'ReceivedAt', '') || '').trim(),
13280
13639
  capturedAt: String(readRemoteFleetObjectField(frame, 'capturedAt', 'CapturedAt', '') || '').trim(),
13281
- streamId: String(readRemoteFleetObjectField(frame, 'streamId', 'StreamId', '') || '').trim()
13640
+ streamId: String(readRemoteFleetObjectField(frame, 'streamId', 'StreamId', '') || '').trim(),
13641
+ contentHash: String(readRemoteFleetObjectField(frame, 'contentHash', 'ContentHash', '') || '').trim(),
13642
+ captureMs: Number(readRemoteFleetObjectField(frame, 'captureMs', 'CaptureMs', 0)) || 0,
13643
+ sameContentStreak: Number(readRemoteFleetObjectField(frame, 'sameContentStreak', 'SameContentStreak', 0)) || 0
13282
13644
  };
13283
13645
  }
13284
13646
 
@@ -13551,7 +13913,10 @@
13551
13913
  frameUrl: String(frame?.frameUrl || '').trim(),
13552
13914
  receivedAt: String(frame?.receivedAt || '').trim(),
13553
13915
  capturedAt: String(frame?.capturedAt || '').trim(),
13554
- streamId: String(frame?.streamId || '').trim()
13916
+ streamId: String(frame?.streamId || '').trim(),
13917
+ contentHash: String(frame?.contentHash || '').trim(),
13918
+ captureMs: Number(frame?.captureMs || 0) || 0,
13919
+ sameContentStreak: Number(frame?.sameContentStreak || 0) || 0
13555
13920
  };
13556
13921
  }
13557
13922
 
@@ -13643,6 +14008,9 @@
13643
14008
  preview.dataset.remoteFleetFrameSeq = String(nextSeq || 0);
13644
14009
  preview.dataset.remoteFleetFrameUrl = frame.frameUrl;
13645
14010
  preview.dataset.remoteFleetFrameAt = frame.receivedAt || frame.capturedAt || '';
14011
+ preview.dataset.remoteFleetFrameHash = frame.contentHash || '';
14012
+ preview.dataset.remoteFleetFrameCaptureMs = String(Number(frame.captureMs || 0) || 0);
14013
+ preview.dataset.remoteFleetFrameSameContentStreak = String(Number(frame.sameContentStreak || 0) || 0);
13646
14014
 
13647
14015
  const placeholder = preview.querySelector('[data-remote-fleet-screen-placeholder="true"]');
13648
14016
  if (placeholder) {
@@ -13679,6 +14047,9 @@
13679
14047
  image.dataset.remoteFleetFrameKind = frame.kind;
13680
14048
  image.dataset.remoteFleetFrameSeq = String(nextSeq || 0);
13681
14049
  image.dataset.remoteFleetFrameUrl = frame.frameUrl;
14050
+ image.dataset.remoteFleetFrameHash = frame.contentHash || '';
14051
+ image.dataset.remoteFleetFrameCaptureMs = String(Number(frame.captureMs || 0) || 0);
14052
+ image.dataset.remoteFleetFrameSameContentStreak = String(Number(frame.sameContentStreak || 0) || 0);
13682
14053
  image.loading = frame.kind === 'thumbnail' ? 'lazy' : 'eager';
13683
14054
  image.style.display = 'block';
13684
14055
  if (image.src !== frame.frameUrl) {
@@ -13879,6 +14250,36 @@
13879
14250
  }
13880
14251
 
13881
14252
  function applyRemoteFleetFramePatchToPreview(preview, frame) {
14253
+ if (String(frame?.kind || '').toLowerCase() === 'live') {
14254
+ if (!preview || !isRemoteFleetFrameNewerForPreview(preview, frame, false)) {
14255
+ return false;
14256
+ }
14257
+
14258
+ const image = ensureRemoteFleetFrameImage(preview, frame);
14259
+ if (!image) {
14260
+ return false;
14261
+ }
14262
+
14263
+ preview._remoteFleetPendingFrame = null;
14264
+ delete preview.dataset.remoteFleetPendingFrameKind;
14265
+ delete preview.dataset.remoteFleetPendingFrameSeq;
14266
+ delete preview.dataset.remoteFleetPendingFrameUrl;
14267
+
14268
+ const committed = commitRemoteFleetFrameToPreview(preview, image, cloneRemoteFleetFramePatch(frame));
14269
+ if (committed) {
14270
+ window.RuntimeTrace?.emit?.('remote.frame.uiPatched', {
14271
+ count: 1,
14272
+ kind: frame.kind,
14273
+ seq: frame.frameSeq,
14274
+ surface: 'img-direct-live',
14275
+ contentHash: frame.contentHash || '',
14276
+ sameContentStreak: Number(frame.sameContentStreak || 0) || 0,
14277
+ captureMs: Number(frame.captureMs || 0) || 0
14278
+ });
14279
+ }
14280
+ return committed;
14281
+ }
14282
+
13882
14283
  return queueRemoteFleetFramePaint(preview, frame);
13883
14284
  }
13884
14285
 
@@ -19154,7 +19555,8 @@
19154
19555
  const hasContent = !!(`${content}`.trim());
19155
19556
  const defaultPrompts = ['Note', 'Pasted Text', '에이전트 메모'];
19156
19557
  const shouldShowPrompt = !!prompt && !defaultPrompts.includes(prompt);
19157
- const isTextualCardNode = ['note', 'code', 'text', 'markdown'].includes(contentTypeLower);
19558
+ const isCsvTableNode = contentTypeLower === CSV_TABLE_CONTENT_TYPE;
19559
+ const isTextualCardNode = ['note', 'code', 'text', 'markdown', CSV_TABLE_CONTENT_TYPE].includes(contentTypeLower);
19158
19560
  const copyablePromptText = getCopyablePromptText(nodeModel, prompt);
19159
19561
  const shouldShowPromptCopyButton = contentTypeLower === 'note' && shouldShowPrompt && !!copyablePromptText;
19160
19562
  ensurePromptCopyDelegation();
@@ -19167,7 +19569,7 @@
19167
19569
  // [Fix] Add 'map-node-note' class for Note nodes so interactions module recognizes them
19168
19570
  const isNoteNode = contentTypeLower === 'note';
19169
19571
  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' : ''}`;
19572
+ 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
19573
  container.dataset.nodeId = nodeModel.id;
19172
19574
  container.style.cssText = `
19173
19575
  width: ${width}px;
@@ -19175,7 +19577,7 @@
19175
19577
  background: ${isMediaNode ? 'transparent' : 'white'};
19176
19578
  ${isMediaNode ? 'border: 0; outline: 0;' : ''}
19177
19579
  border-radius: 0px;
19178
- padding: ${isMediaNode ? '0' : (isCodeClassNode ? '12px 8px 12px 14px' : (isTextualCardNode ? '12px 8px 12px 14px' : '12px 4px 12px 12px'))};
19580
+ padding: ${isMediaNode ? '0' : (isCsvTableNode ? '10px' : (isCodeClassNode ? '12px 8px 12px 14px' : (isTextualCardNode ? '12px 8px 12px 14px' : '12px 4px 12px 12px')))};
19179
19581
  box-sizing: border-box;
19180
19582
  overflow: hidden;
19181
19583
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
@@ -19285,7 +19687,23 @@
19285
19687
  // Variable isNoteNode declared above
19286
19688
  const isMarkdownType = ['text', 'markdown'].includes(contentTypeLower); // Removed 'note'
19287
19689
 
19288
- if (isCodeNode) {
19690
+ if (isCsvTableNode) {
19691
+ responseDiv.className = 'node-response csv-table-scroll';
19692
+ responseDiv.style.cssText = `
19693
+ width: 100%;
19694
+ height: 100%;
19695
+ flex: 1 1 auto;
19696
+ min-height: 0;
19697
+ overflow: auto;
19698
+ pointer-events: auto;
19699
+ box-sizing: border-box;
19700
+ user-select: text;
19701
+ -webkit-user-select: text;
19702
+ `;
19703
+ responseDiv.dataset.src = content;
19704
+ responseDiv.dataset.nodeId = nodeModel.id;
19705
+ renderCsvTableContent(responseDiv, nodeModel, content);
19706
+ } else if (isCodeNode) {
19289
19707
  responseDiv.className = 'node-response code-body';
19290
19708
  // ▼▼▼ [FIX] 슬림 스크롤바 인라인 스타일 추가 ▼▼▼
19291
19709
  responseDiv.style.cssText = `
@@ -19559,6 +19977,7 @@
19559
19977
  || contentTypeLower === 'markdown'
19560
19978
  || contentTypeLower === 'note'
19561
19979
  || contentTypeLower === 'memo'
19980
+ || contentTypeLower === CSV_TABLE_CONTENT_TYPE
19562
19981
  || contentTypeLower === 'templatelauncher'
19563
19982
  || contentTypeLower === 'image'
19564
19983
  || contentTypeLower === 'video'
@@ -19572,7 +19991,7 @@
19572
19991
  // text/markdown type has no Blazor template, so create dynamically.
19573
19992
  // [FIX] 'note' type also falls back to dynamic creation if not found (or if we force dynamic for consistent MD)
19574
19993
  // But usually 'note' comes from Razor. If missing, we create it.
19575
- const dynamicTypes = ['text', 'markdown', 'code', 'note', 'memo', 'templatelauncher', 'image', 'video', 'embed'];
19994
+ const dynamicTypes = ['text', 'markdown', 'code', 'note', 'memo', CSV_TABLE_CONTENT_TYPE, 'templatelauncher', 'image', 'video', 'embed'];
19576
19995
 
19577
19996
  if (remoteFleetMonitor || dynamicTypes.includes(contentTypeLower)) {
19578
19997
  log(`[MindMapCss3DManager] Creating dynamic DOM element for ${visualContentTypeLower || contentTypeLower} node ${nodeModel.id}`);
@@ -19724,7 +20143,7 @@
19724
20143
  && !isRemoteFleetMonitorNode(nodeModel);
19725
20144
  // Enable pointer events for scrollable content so user can scroll/select text
19726
20145
  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"]'
20146
+ '.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
20147
  );
19729
20148
  scrollables.forEach(el => {
19730
20149
  el.style.pointerEvents = 'auto';
@@ -19732,7 +20151,7 @@
19732
20151
  el.style.userSelect = 'none';
19733
20152
  el.style.webkitUserSelect = 'none';
19734
20153
  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')) {
20154
+ } 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
20155
  el.style.userSelect = 'none';
19737
20156
  el.style.webkitUserSelect = 'none';
19738
20157
  el.style.cursor = 'pointer';
@@ -20197,8 +20616,16 @@
20197
20616
  if (responseEl) {
20198
20617
  responseEl.style.display = (isLoading && !hasContent) ? 'none' : '';
20199
20618
  const isCodeNode = contentTypeLower === 'code';
20200
-
20201
- if (isCodeNode) {
20619
+ const isCsvTableNode = contentTypeLower === CSV_TABLE_CONTENT_TYPE;
20620
+
20621
+ if (isCsvTableNode) {
20622
+ if (!responseEl.classList.contains('csv-table-scroll')) {
20623
+ responseEl.className = 'node-response csv-table-scroll';
20624
+ }
20625
+ responseEl.dataset.src = content || '';
20626
+ responseEl.dataset.nodeId = nodeModel.id || nodeModel.Id || '';
20627
+ renderCsvTableContent(responseEl, nodeModel, content);
20628
+ } else if (isCodeNode) {
20202
20629
  renderCodeBodyContent(responseEl, content);
20203
20630
  } else {
20204
20631
  // Unified Markdown Rendering with Caching