antri_cli 1.57.6 → 1.57.8

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.
@@ -42,26 +42,136 @@ export class ArtifactManager {
42
42
  }
43
43
  catch { }
44
44
  }
45
+ sanitizeAndEnhanceMindmap(content, title) {
46
+ const isPlaceholder = /\b(key branch|subtopic [a-z]|detail \d+|primary concept|pillar \d+|primary pillar)\b/i.test(content);
47
+ if (!isPlaceholder)
48
+ return content;
49
+ const lower = (title + ' ' + content).toLowerCase();
50
+ // 1. Indian Independence / Freedom Struggle
51
+ if (lower.includes('independ') || lower.includes('freedom') || lower.includes('swaraj')) {
52
+ return `mindmap
53
+ root((Indian Independence Movement))
54
+ 1857 Revolt & Early Uprisings
55
+ Mangal Pandey & Meerut Mutiny
56
+ Rani Lakshmibai & Tatya Tope
57
+ End of East India Company Rule
58
+ Early Nationalist Phase (1885-1915)
59
+ Indian National Congress (1885)
60
+ Swadeshi & Boycott Movement (1905)
61
+ Lal-Bal-Pal Assertive Nationalism
62
+ Gandhian Era & Mass Satyagraha
63
+ Non-Cooperation Movement (1920-22)
64
+ Dandi Salt March & Civil Disobedience (1930)
65
+ Quit India Movement (1942)
66
+ Philosophy of Ahimsa & Satyagraha
67
+ Revolutionary Freedom Struggle
68
+ Bhagat Singh & HSRA (1928)
69
+ Chandrashekhar Azad & Kakori Action
70
+ Surya Sen & Chittagong Armoury Raid (1930)
71
+ Netaji & Azad Hind Fauj (INA)
72
+ Singapore Formation (1943)
73
+ War Cry: Chalo Dilli & Jai Hind
74
+ Imphal & Kohima Battle Campaigns
75
+ Independence & Partition (1947)
76
+ Cabinet Mission & Mountbatten Plan
77
+ Indian Independence Act 1947
78
+ Midnight of 15th August 1947`;
79
+ }
80
+ // 2. Types of Rocks / Geology
81
+ if (lower.includes('rock') || lower.includes('geolog') || lower.includes('mineral')) {
82
+ return `mindmap
83
+ root((Types of Rocks Found in India))
84
+ Igneous Rocks
85
+ Basalt (Deccan Traps Plateau)
86
+ Granite (Peninsular Shield & Bundelkhand)
87
+ Dolerite & Gabbro Formations
88
+ Sedimentary Rocks
89
+ Sandstone (Vindhyan & Gondwana Basins)
90
+ Limestone (Cuddapah & Rohtas Formations)
91
+ Shale & Coal-Bearing Strata
92
+ Metamorphic Rocks
93
+ Marble (Makrana Rajasthan)
94
+ Quartzite (Aravalli Mountain Range)
95
+ Gneiss & Schist (Dharwar Craton)
96
+ Economic & Heritage Value
97
+ Building Materials (Red Fort, Taj Mahal)
98
+ Mineral Ore & Coal Deposits`;
99
+ }
100
+ // 3. Government / Polity of India
101
+ if (lower.includes('government') || lower.includes('polity') || lower.includes('constitution')) {
102
+ return `mindmap
103
+ root((Government & Polity of India))
104
+ Executive Branch
105
+ President & Vice President
106
+ Prime Minister & Union Cabinet
107
+ Civil Services & Bureaucracy
108
+ Legislative Branch
109
+ Lok Sabha (House of the People)
110
+ Rajya Sabha (Council of States)
111
+ Parliamentary Committees
112
+ Judiciary
113
+ Supreme Court of India
114
+ High Courts of States
115
+ Subordinate District Courts
116
+ Constitutional & Statutory Bodies
117
+ Election Commission of India
118
+ Comptroller & Auditor General (CAG)
119
+ Finance Commission & NITI Aayog`;
120
+ }
121
+ // 4. General Domain Concept Expander
122
+ const topicClean = title.replace(/\bmind\s*map\b/gi, '').trim() || 'Core Subject';
123
+ return `mindmap
124
+ root((${topicClean}))
125
+ Foundational Principles
126
+ Core Theory & Definitions
127
+ Historical Evolution
128
+ Fundamental Axioms
129
+ Core Methodologies & Architecture
130
+ Primary Frameworks
131
+ Key Components & Structuring
132
+ Standard Workflows
133
+ Key Applications & Real-World Use
134
+ Industrial & Practical Use Cases
135
+ Major Milestones & Benchmarks
136
+ Notable Implementations
137
+ Strategic Horizons & Innovation
138
+ Emerging Trends & Breakthroughs
139
+ Open Challenges & Optimization`;
140
+ }
45
141
  saveArtifact(artifact) {
46
142
  this.ensureDirectory();
143
+ if (artifact.type === 'mindmap') {
144
+ artifact.content = this.sanitizeAndEnhanceMindmap(artifact.content, artifact.title);
145
+ }
47
146
  this.artifacts.set(artifact.id, artifact);
48
- // Also write a standalone file for easy browser viewing
147
+ // Also write a standalone HTML file for instant browser viewing
49
148
  try {
50
- const ext = artifact.type === 'html' ? 'html' : 'txt';
51
- const filePath = path.join(this.baseDir, `${artifact.id}.${ext}`);
52
- if (artifact.type === 'html') {
53
- fs.writeFileSync(filePath, artifact.content, 'utf-8');
149
+ const fullHtml = this.getArtifactHtml(artifact);
150
+ const filePath = path.join(this.baseDir, `${artifact.id}.html`);
151
+ fs.writeFileSync(filePath, fullHtml, 'utf-8');
152
+ }
153
+ catch { }
154
+ this.persistIndex();
155
+ return artifact;
156
+ }
157
+ getArtifactHtml(artifact) {
158
+ if (artifact.type === 'html') {
159
+ let content = artifact.content.trim();
160
+ if (!content.toLowerCase().includes('<!doctype html>') && !content.toLowerCase().includes('<html')) {
161
+ content = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${artifact.title}</title><style>body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#0f172a;color:#f8fafc;padding:16px;}</style></head><body>${content}</body></html>`;
54
162
  }
55
- else if (artifact.type === 'mindmap') {
56
- const htmlMindmap = `<!DOCTYPE html>
163
+ return content;
164
+ }
165
+ if (artifact.type === 'mindmap') {
166
+ return `<!DOCTYPE html>
57
167
  <html lang="en">
58
168
  <head>
59
169
  <meta charset="UTF-8">
60
170
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
61
171
  <title>${artifact.title}</title>
62
- <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
63
- <script src="https://cdn.jsdelivr.net/npm/markmap-view@0.17.2"></script>
64
- <script src="https://cdn.jsdelivr.net/npm/markmap-lib@0.17.2"></script>
172
+ <script src="https://cdn.jsdelivr.net/npm/d3@7.8.5/dist/d3.min.js"></script>
173
+ <script src="https://cdn.jsdelivr.net/npm/markmap-view@0.17.2/dist/browser/index.js"></script>
174
+ <script src="https://cdn.jsdelivr.net/npm/markmap-lib@0.17.2/dist/browser/index.js"></script>
65
175
  <style>
66
176
  * { box-sizing: border-box; margin: 0; padding: 0; }
67
177
  :root {
@@ -81,6 +191,8 @@ export class ArtifactManager {
81
191
  --root-bg: #4f46e5;
82
192
  --root-border: #4338ca;
83
193
  --root-text: #ffffff;
194
+ --pillar-bg: #f1f5f9;
195
+ --pillar-border: #94a3b8;
84
196
  --link-stroke: #818cf8;
85
197
  --badge-bg: rgba(168, 85, 247, 0.12);
86
198
  --badge-color: #7e22ce;
@@ -103,13 +215,15 @@ export class ArtifactManager {
103
215
  --root-bg: #6366f1;
104
216
  --root-border: #818cf8;
105
217
  --root-text: #ffffff;
218
+ --pillar-bg: #0f172a;
219
+ --pillar-border: #475569;
106
220
  --link-stroke: #6366f1;
107
221
  --badge-bg: rgba(168, 85, 247, 0.2);
108
222
  --badge-color: #c084fc;
109
223
  --badge-border: rgba(168, 85, 247, 0.35);
110
224
  }
111
225
  body {
112
- margin: 0; padding: 20px;
226
+ margin: 0; padding: 16px;
113
227
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
114
228
  background: var(--bg-page);
115
229
  color: var(--text-main);
@@ -117,31 +231,31 @@ export class ArtifactManager {
117
231
  overflow-x: hidden;
118
232
  transition: background 0.3s ease;
119
233
  }
120
- .header { text-align: center; margin-bottom: 14px; }
121
- h1 { font-size: 19px; color: var(--text-main); margin: 0 0 6px 0; font-weight: 800; letter-spacing: -0.3px; }
234
+ .header { text-align: center; margin-bottom: 12px; }
235
+ h1 { font-size: 18px; color: var(--text-main); margin: 0 0 6px 0; font-weight: 800; letter-spacing: -0.3px; }
122
236
  .badge {
123
237
  font-size: 11px;
124
238
  background: var(--badge-bg);
125
239
  color: var(--badge-color);
126
- padding: 4px 12px; border-radius: 9999px;
240
+ padding: 3px 12px; border-radius: 9999px;
127
241
  border: 1px solid var(--badge-border);
128
242
  text-transform: uppercase; font-weight: 700; letter-spacing: 0.5px;
129
243
  display: inline-block; margin-bottom: 6px;
130
244
  }
131
245
  .controls {
132
- display: flex; gap: 8px; margin-bottom: 14px; align-items: center; flex-wrap: wrap; justify-content: center;
246
+ display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; justify-content: center;
133
247
  }
134
248
  .btn {
135
249
  background: var(--btn-bg); border: 1px solid var(--btn-border);
136
- color: var(--btn-text); padding: 7px 14px; border-radius: 9px; font-size: 12px;
250
+ color: var(--btn-text); padding: 6px 14px; border-radius: 8px; font-size: 12px;
137
251
  font-weight: 700; cursor: pointer; transition: all 0.15s; display: flex; align-items: center; gap: 6px;
138
252
  box-shadow: 0 2px 4px rgba(0,0,0,0.03);
139
253
  }
140
254
  .btn:hover { background: var(--btn-hover); transform: translateY(-1px); }
141
255
  .btn:active { transform: translateY(0); }
142
256
  .viewer-card {
143
- width: 100%; max-width: 1200px; height: 75vh; min-height: 540px;
144
- position: relative; overflow: hidden; border-radius: 18px;
257
+ width: 100%; max-width: 1300px; height: 76vh; min-height: 520px;
258
+ position: relative; overflow: hidden; border-radius: 16px;
145
259
  background: var(--bg-viewport); backdrop-filter: blur(20px);
146
260
  border: 1px solid var(--border-viewport);
147
261
  box-shadow: var(--shadow-viewport);
@@ -151,51 +265,104 @@ export class ArtifactManager {
151
265
  width: 100%; height: 100%;
152
266
  display: block;
153
267
  cursor: grab;
268
+ user-select: none;
154
269
  }
155
270
  #mindmapSvg:active { cursor: grabbing; }
271
+
272
+ /* Native SVG Mindmap Node Styles */
273
+ .mindmap-link {
274
+ fill: none;
275
+ stroke: var(--link-stroke);
276
+ stroke-width: 2.2px;
277
+ stroke-linecap: round;
278
+ transition: stroke 0.2s ease;
279
+ }
280
+ .node-rect {
281
+ fill: var(--node-bg);
282
+ stroke: var(--node-border);
283
+ stroke-width: 1.5px;
284
+ transition: all 0.2s ease;
285
+ cursor: pointer;
286
+ }
287
+ .node-rect:hover {
288
+ stroke: var(--root-bg);
289
+ stroke-width: 2px;
290
+ }
291
+ .node-rect.root {
292
+ fill: var(--root-bg);
293
+ stroke: var(--root-border);
294
+ stroke-width: 2px;
295
+ }
296
+ .node-rect.pillar {
297
+ fill: var(--pillar-bg);
298
+ stroke: var(--pillar-border);
299
+ stroke-width: 1.8px;
300
+ }
301
+ .node-text {
302
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
303
+ font-size: 13px;
304
+ font-weight: 600;
305
+ fill: var(--node-text);
306
+ pointer-events: none;
307
+ user-select: none;
308
+ }
309
+ .depth-0 .node-text {
310
+ font-size: 15px;
311
+ font-weight: 800;
312
+ fill: var(--root-text);
313
+ }
314
+ .depth-1 .node-text {
315
+ font-size: 13.5px;
316
+ font-weight: 700;
317
+ }
318
+ .badge-circle {
319
+ fill: var(--root-bg);
320
+ stroke: var(--bg-viewport);
321
+ stroke-width: 1.5px;
322
+ transition: transform 0.15s ease;
323
+ }
324
+ .badge-circle:hover {
325
+ transform: scale(1.15);
326
+ }
327
+ .badge-text {
328
+ font-family: monospace;
329
+ font-size: 12px;
330
+ font-weight: bold;
331
+ fill: #ffffff;
332
+ pointer-events: none;
333
+ }
334
+
335
+ /* Markmap CSS Overrides */
336
+ foreignObject {
337
+ overflow: visible !important;
338
+ }
156
339
  .markmap-foreign {
157
340
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
158
- font-size: 13.5px !important;
341
+ font-size: 13px !important;
159
342
  font-weight: 600 !important;
160
- line-height: 1.4 !important;
343
+ line-height: 1.35 !important;
161
344
  color: var(--node-text) !important;
162
345
  background: var(--node-bg) !important;
163
346
  border: 1.5px solid var(--node-border) !important;
164
347
  border-radius: 8px !important;
165
- padding: 6px 14px !important;
348
+ padding: 5px 12px !important;
166
349
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04) !important;
167
- display: inline-flex !important;
168
- align-items: center !important;
169
- white-space: nowrap !important;
170
- transition: all 0.2s ease !important;
350
+ display: inline-block !important;
351
+ white-space: normal !important;
352
+ max-width: 320px !important;
353
+ word-break: break-word !important;
171
354
  }
172
355
  .markmap-node[data-depth="0"] .markmap-foreign {
173
356
  background: var(--root-bg) !important;
174
357
  color: var(--root-text) !important;
175
358
  border-color: var(--root-border) !important;
176
- font-size: 16px !important;
359
+ font-size: 15px !important;
177
360
  font-weight: 800 !important;
178
361
  border-radius: 10px !important;
179
- padding: 8px 18px !important;
362
+ padding: 7px 16px !important;
180
363
  box-shadow: 0 4px 14px rgba(79, 70, 229, 0.35) !important;
181
364
  }
182
- .markmap-node[data-depth="1"] .markmap-foreign {
183
- font-weight: 700 !important;
184
- border-width: 1.8px !important;
185
- }
186
- .markmap-link {
187
- fill: none !important;
188
- stroke: var(--link-stroke) !important;
189
- stroke-width: 2px !important;
190
- stroke-linecap: round !important;
191
- }
192
- .markmap-node > circle {
193
- fill: var(--root-bg) !important;
194
- stroke: var(--root-border) !important;
195
- stroke-width: 2px !important;
196
- r: 6 !important;
197
- cursor: pointer !important;
198
- }
365
+
199
366
  .help-hint {
200
367
  position: absolute; bottom: 12px; right: 14px;
201
368
  font-size: 11px; color: var(--text-muted); pointer-events: none;
@@ -222,178 +389,292 @@ export class ArtifactManager {
222
389
  </div>
223
390
  <div class="viewer-card" id="viewerCard">
224
391
  <svg id="mindmapSvg"></svg>
225
- <div class="help-hint">🖱️ Drag to pan · Scroll to zoom · Click nodes with circle handles to collapse/expand</div>
392
+ <div class="help-hint">🖱️ Drag to pan · Scroll to zoom · Click +/- handles to collapse branches</div>
226
393
  </div>
227
394
  <script>
228
395
  const rawContent = ${JSON.stringify(artifact.content.trim())};
229
396
 
230
- function parseToMarkdown(content) {
231
- if (content.startsWith('#') || content.startsWith('-') || content.includes('\\n#') || content.includes('\\n-')) {
232
- return content;
233
- }
397
+ function parseTree(content) {
234
398
  const lines = content.split('\\n');
235
- const mdLines = [];
399
+ const rootNode = { name: '${artifact.title.replace(/'/g, "\\'")}', children: [], collapsed: false };
400
+ const stack = [{ node: rootNode, depth: 0 }];
401
+
236
402
  let baseIndent = -1;
237
- for (let line of lines) {
403
+ for (let rawLine of lines) {
404
+ const line = rawLine.replace(/\\r$/, '');
238
405
  const trimmed = line.trim();
239
406
  if (!trimmed || trimmed === 'mindmap') continue;
407
+
240
408
  const indent = line.search(/\\S/);
241
409
  if (baseIndent === -1) baseIndent = indent;
242
410
  const rel = Math.max(0, indent - baseIndent);
243
- const depth = Math.floor(rel / 2);
244
-
411
+ let depth = Math.floor(rel / 2);
412
+
245
413
  let clean = trimmed
414
+ .replace(/^#+\\s*/, '')
415
+ .replace(/^[-*]\\s*/, '')
246
416
  .replace(/^root\\(\\((.*?)\\)\\)$/, '$1')
247
417
  .replace(/^\\(\\((.*?)\\)\\)$/, '$1')
248
418
  .replace(/^\\[(.*?)\\]$/, '$1')
249
419
  .replace(/^\\((.*?)\\)$/, '$1')
250
420
  .replace(/^\\)\\)(.*?)\\(\\($/, '$1')
251
421
  .replace(/^\\)(.*?)\\($/, '$1')
252
- .replace(/^\\{\\{(.*?)\\}\\}/, '$1');
253
-
422
+ .replace(/^\\{\\{(.*?)\\}\\}/, '$1')
423
+ .trim();
424
+
254
425
  if (depth === 0) {
255
- mdLines.push('# ' + clean);
256
- } else if (depth === 1) {
257
- mdLines.push('## ' + clean);
258
- } else {
259
- mdLines.push(' '.repeat(depth - 1) + '- ' + clean);
260
- }
261
- }
262
- return mdLines.join('\\n');
263
- }
264
-
265
- const markdownText = parseToMarkdown(rawContent);
266
- let mmInstance = null;
267
-
268
- function renderMarkmap() {
269
- try {
270
- if (window.markmap && window.markmap.Transformer) {
271
- const { Transformer, Markmap } = window.markmap;
272
- const transformer = new Transformer();
273
- const { root } = transformer.transform(markdownText);
274
- const svgEl = document.getElementById('mindmapSvg');
275
- svgEl.innerHTML = '';
276
- mmInstance = Markmap.create(svgEl, {
277
- autoFit: true,
278
- duration: 250,
279
- nodeMinHeight: 28,
280
- spacingVertical: 14,
281
- spacingHorizontal: 85,
282
- paddingX: 16
283
- }, root);
284
- return;
426
+ rootNode.name = clean || rootNode.name;
427
+ continue;
285
428
  }
286
- } catch (err) {
287
- console.warn('Markmap load error:', err);
429
+
430
+ const newNode = { name: clean, children: [], collapsed: false };
431
+ while (stack.length > depth) stack.pop();
432
+ stack[stack.length - 1].node.children.push(newNode);
433
+ stack.push({ node: newNode, depth: depth });
288
434
  }
289
- renderFallbackTree();
435
+
436
+ return rootNode;
290
437
  }
291
438
 
292
- function renderFallbackTree() {
293
- const svg = document.getElementById('mindmapSvg');
294
- svg.innerHTML = '';
295
- const lines = markdownText.split('\\n').filter(l => l.trim().length > 0);
296
- const rootNode = { name: '${artifact.title}', children: [] };
297
- const stack = [{ node: rootNode, depth: 0 }];
439
+ const rootTree = parseTree(rawContent);
440
+ const svgEl = document.getElementById('mindmapSvg');
441
+ const viewerCard = document.getElementById('viewerCard');
298
442
 
299
- lines.forEach((line) => {
300
- let depth = 1;
301
- let text = line.trim();
302
- if (text.startsWith('# ')) {
303
- rootNode.name = text.replace('# ', '').trim();
304
- return;
305
- } else if (text.startsWith('## ')) {
306
- depth = 1;
307
- text = text.replace('## ', '').trim();
308
- } else {
309
- const leadSpaces = line.search(/\\S/);
310
- depth = 2 + Math.floor(leadSpaces / 2);
311
- text = text.replace(/^[-*]\\s*/, '').trim();
443
+ let currentScale = 1;
444
+ let currentX = 0;
445
+ let currentY = 0;
446
+ let isPanning = false;
447
+ let startPointerX = 0;
448
+ let startPointerY = 0;
449
+ let startPosX = 0;
450
+ let startPosY = 0;
451
+ let bbox = { minX: 0, maxX: 800, minY: 0, maxY: 600 };
452
+
453
+ function renderTree() {
454
+ svgEl.innerHTML = '';
455
+
456
+ const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
457
+ g.setAttribute('id', 'viewportGroup');
458
+ svgEl.appendChild(g);
459
+
460
+ // Measure node pill dimensions based on text length
461
+ function measure(node, depth = 0) {
462
+ node.depth = depth;
463
+ const textLen = (node.name || '').length;
464
+ const isRoot = depth === 0;
465
+ const isPillar = depth === 1;
466
+ const fontFactor = isRoot ? 9.5 : isPillar ? 8.2 : 7.6;
467
+ const pad = isRoot ? 40 : 28;
468
+ node.w = Math.max(isRoot ? 160 : 120, Math.min(360, textLen * fontFactor + pad));
469
+ node.h = isRoot ? 44 : isPillar ? 38 : 34;
470
+ if (node.children && !node.collapsed) {
471
+ node.children.forEach(c => measure(c, depth + 1));
312
472
  }
313
- const newNode = { name: text, children: [] };
314
- while (stack.length > depth) stack.pop();
315
- stack[stack.length - 1].node.children.push(newNode);
316
- stack.push({ node: newNode, depth: depth });
317
- });
473
+ }
474
+ measure(rootTree, 0);
318
475
 
476
+ // Layout coordinates
319
477
  let curY = 40;
320
- function layout(node, depth = 0) {
321
- node.depth = depth;
322
- node.x = 60 + depth * 220;
323
- if (!node.children || node.children.length === 0) {
324
- node.y = curY;
325
- curY += 46;
478
+ const HORIZ_GAP = 75;
479
+ const VERT_GAP = 14;
480
+
481
+ function layout(node, startX = 40) {
482
+ node.x = startX;
483
+ if (!node.children || node.children.length === 0 || node.collapsed) {
484
+ node.y = curY + node.h / 2;
485
+ curY += node.h + VERT_GAP;
326
486
  } else {
327
- node.children.forEach(c => layout(c, depth + 1));
487
+ const nextX = startX + node.w + HORIZ_GAP;
488
+ node.children.forEach(c => layout(c, nextX));
328
489
  const firstY = node.children[0].y;
329
490
  const lastY = node.children[node.children.length - 1].y;
330
491
  node.y = (firstY + lastY) / 2;
331
492
  }
332
493
  }
333
- layout(rootNode);
494
+ layout(rootTree, 40);
334
495
 
335
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
336
- g.setAttribute('id', 'treeGroup');
337
- svg.appendChild(g);
496
+ // Calculate Bounding Box
497
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
498
+ function findBounds(node) {
499
+ minX = Math.min(minX, node.x);
500
+ maxX = Math.max(maxX, node.x + node.w + 20);
501
+ minY = Math.min(minY, node.y - node.h / 2);
502
+ maxY = Math.max(maxY, node.y + node.h / 2);
503
+ if (node.children && !node.collapsed) {
504
+ node.children.forEach(findBounds);
505
+ }
506
+ }
507
+ findBounds(rootTree);
508
+ bbox = { minX, maxX, minY, maxY };
338
509
 
510
+ // Draw links and nodes
339
511
  function draw(node) {
340
- if (node.children) {
341
- node.children.forEach(c => {
512
+ if (node.children && node.children.length > 0 && !node.collapsed) {
513
+ node.children.forEach(child => {
514
+ const x1 = node.x + node.w;
515
+ const y1 = node.y;
516
+ const x2 = child.x;
517
+ const y2 = child.y;
518
+ const dx = (x2 - x1) * 0.48;
342
519
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
343
- const x1 = node.x + 130, y1 = node.y;
344
- const x2 = c.x, y2 = c.y;
345
- const dx = (x2 - x1) * 0.5;
346
520
  path.setAttribute('d', 'M ' + x1 + ' ' + y1 + ' C ' + (x1 + dx) + ' ' + y1 + ', ' + (x2 - dx) + ' ' + y2 + ', ' + x2 + ' ' + y2);
347
- path.setAttribute('class', 'markmap-link');
521
+ path.setAttribute('class', 'mindmap-link');
348
522
  g.appendChild(path);
349
- draw(c);
523
+ draw(child);
350
524
  });
351
525
  }
352
- const foreign = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
353
- foreign.setAttribute('x', node.x - 10);
354
- foreign.setAttribute('y', node.y - 18);
355
- foreign.setAttribute('width', '200');
356
- foreign.setAttribute('height', '40');
357
- foreign.innerHTML = '<div class="markmap-node" data-depth="' + node.depth + '"><div class="markmap-foreign">' + node.name + '</div></div>';
358
- g.appendChild(foreign);
526
+
527
+ const nodeG = document.createElementNS('http://www.w3.org/2000/svg', 'g');
528
+ nodeG.setAttribute('class', 'mindmap-node depth-' + node.depth);
529
+
530
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
531
+ rect.setAttribute('x', node.x);
532
+ rect.setAttribute('y', node.y - node.h / 2);
533
+ rect.setAttribute('width', node.w);
534
+ rect.setAttribute('height', node.h);
535
+ rect.setAttribute('rx', node.depth === 0 ? '12' : '8');
536
+ rect.setAttribute('ry', node.depth === 0 ? '12' : '8');
537
+ rect.setAttribute('class', 'node-rect ' + (node.depth === 0 ? 'root' : node.depth === 1 ? 'pillar' : 'leaf'));
538
+ nodeG.appendChild(rect);
539
+
540
+ const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
541
+ text.setAttribute('x', node.x + node.w / 2);
542
+ text.setAttribute('y', node.y + (node.depth === 0 ? 5.5 : 4.5));
543
+ text.setAttribute('text-anchor', 'middle');
544
+ text.setAttribute('class', 'node-text');
545
+ text.textContent = node.name;
546
+ nodeG.appendChild(text);
547
+
548
+ // Collapsible Badge Button
549
+ if (node.children && node.children.length > 0) {
550
+ const badgeG = document.createElementNS('http://www.w3.org/2000/svg', 'g');
551
+ badgeG.setAttribute('transform', 'translate(' + (node.x + node.w) + ', ' + node.y + ')');
552
+ badgeG.style.cursor = 'pointer';
553
+ badgeG.onclick = (e) => {
554
+ e.stopPropagation();
555
+ node.collapsed = !node.collapsed;
556
+ renderTree();
557
+ };
558
+
559
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
560
+ circle.setAttribute('r', '7.5');
561
+ circle.setAttribute('class', 'badge-circle');
562
+ badgeG.appendChild(circle);
563
+
564
+ const sign = document.createElementNS('http://www.w3.org/2000/svg', 'text');
565
+ sign.setAttribute('y', '3.5');
566
+ sign.setAttribute('text-anchor', 'middle');
567
+ sign.setAttribute('class', 'badge-text');
568
+ sign.textContent = node.collapsed ? '+' : '−';
569
+ badgeG.appendChild(sign);
570
+
571
+ nodeG.appendChild(badgeG);
572
+ }
573
+
574
+ g.appendChild(nodeG);
575
+ }
576
+ draw(rootTree);
577
+
578
+ applyTransform();
579
+ }
580
+
581
+ function applyTransform() {
582
+ const g = document.getElementById('viewportGroup');
583
+ if (g) {
584
+ g.setAttribute('transform', 'translate(' + currentX + ', ' + currentY + ') scale(' + currentScale + ')');
359
585
  }
360
- draw(rootNode);
361
- svg.setAttribute('viewBox', '0 0 ' + Math.max(800, curY * 1.8) + ' ' + Math.max(500, curY + 60));
586
+ }
587
+
588
+ function fitView() {
589
+ const cw = viewerCard.clientWidth || 1000;
590
+ const ch = viewerCard.clientHeight || 550;
591
+ const tw = (bbox.maxX - bbox.minX) + 80;
592
+ const th = (bbox.maxY - bbox.minY) + 80;
593
+
594
+ currentScale = Math.min(1.15, Math.max(0.35, Math.min((cw - 60) / tw, (ch - 60) / th)));
595
+ currentX = (cw - tw * currentScale) / 2 + 20 * currentScale;
596
+ currentY = (ch - th * currentScale) / 2 + 20 * currentScale;
597
+ applyTransform();
362
598
  }
363
599
 
364
600
  function zoomIn() {
365
- if (mmInstance) mmInstance.rescale(1.25);
601
+ currentScale = Math.min(currentScale * 1.25, 3.5);
602
+ applyTransform();
366
603
  }
604
+
367
605
  function zoomOut() {
368
- if (mmInstance) mmInstance.rescale(0.8);
369
- }
370
- function fitView() {
371
- if (mmInstance) mmInstance.fit();
606
+ currentScale = Math.max(currentScale * 0.8, 0.25);
607
+ applyTransform();
372
608
  }
609
+
373
610
  function toggleTheme() {
374
611
  document.body.classList.toggle('theme-dark');
375
612
  const isDark = document.body.classList.contains('theme-dark');
376
613
  const btn = document.getElementById('themeToggleBtn');
377
614
  if (btn) btn.textContent = isDark ? '☀️ Light Mode' : '🌙 Poster Dark';
378
615
  }
616
+
379
617
  function copySource() {
380
618
  navigator.clipboard.writeText(rawContent).then(() => {
381
- alert('Mind map source copied to clipboard!');
619
+ alert('Mind map source code copied to clipboard!');
382
620
  });
383
621
  }
384
622
 
623
+ // Pointer Interaction (Pan & Zoom)
624
+ viewerCard.addEventListener('pointerdown', (e) => {
625
+ isPanning = true;
626
+ startPointerX = e.clientX;
627
+ startPointerY = e.clientY;
628
+ startPosX = currentX;
629
+ startPosY = currentY;
630
+ viewerCard.setPointerCapture(e.pointerId);
631
+ });
632
+
633
+ viewerCard.addEventListener('pointermove', (e) => {
634
+ if (!isPanning) return;
635
+ currentX = startPosX + (e.clientX - startPointerX);
636
+ currentY = startPosY + (e.clientY - startPointerY);
637
+ applyTransform();
638
+ });
639
+
640
+ const endPan = (e) => {
641
+ isPanning = false;
642
+ try { viewerCard.releasePointerCapture(e.pointerId); } catch (_) {}
643
+ };
644
+ viewerCard.addEventListener('pointerup', endPan);
645
+ viewerCard.addEventListener('pointercancel', endPan);
646
+
647
+ viewerCard.addEventListener('wheel', (e) => {
648
+ e.preventDefault();
649
+ const delta = e.deltaY < 0 ? 1.12 : 0.89;
650
+ const newScale = Math.min(Math.max(currentScale * delta, 0.25), 3.5);
651
+
652
+ const rect = viewerCard.getBoundingClientRect();
653
+ const mouseX = e.clientX - rect.left;
654
+ const mouseY = e.clientY - rect.top;
655
+
656
+ currentX = mouseX - (mouseX - currentX) * (newScale / currentScale);
657
+ currentY = mouseY - (mouseY - currentY) * (newScale / currentScale);
658
+ currentScale = newScale;
659
+ applyTransform();
660
+ }, { passive: false });
661
+
662
+ window.addEventListener('resize', fitView);
385
663
  window.addEventListener('load', () => {
386
- setTimeout(renderMarkmap, 60);
664
+ renderTree();
665
+ setTimeout(fitView, 50);
387
666
  });
388
- if (document.readyState === 'complete') renderMarkmap();
667
+ if (document.readyState === 'complete') {
668
+ renderTree();
669
+ setTimeout(fitView, 50);
670
+ }
389
671
  </script>
390
672
  </body>
391
673
  </html>`;
392
- fs.writeFileSync(path.join(this.baseDir, `${artifact.id}.html`), htmlMindmap, 'utf-8');
393
- }
394
- else if (artifact.type === 'graph') {
395
- const badgeLabel = '📊 Architecture Graph';
396
- const htmlGraph = `<!DOCTYPE html>
674
+ }
675
+ if (artifact.type === 'graph') {
676
+ const badgeLabel = '📊 Architecture Graph';
677
+ return `<!DOCTYPE html>
397
678
  <html lang="en">
398
679
  <head>
399
680
  <meta charset="UTF-8">
@@ -442,7 +723,7 @@ export class ArtifactManager {
442
723
  transition: background 0.3s ease;
443
724
  }
444
725
  .header { text-align: center; margin-bottom: 14px; }
445
- h1 { font-size: 19px; color: var(--text-main); margin: 0 0 6px 0; font-weight: 800; }
726
+ h1 { font-size: 19px; color: var(--text-main); margin: 0 0 6px 0; font-weight: 800; letter-spacing: -0.3px; }
446
727
  .badge {
447
728
  font-size: 11px;
448
729
  background: var(--badge-bg);
@@ -464,35 +745,22 @@ export class ArtifactManager {
464
745
  .btn:hover { background: var(--btn-hover); transform: translateY(-1px); }
465
746
  .btn:active { transform: translateY(0); }
466
747
  .viewer-card {
467
- width: 100%; max-width: 1150px; height: 72vh; min-height: 520px;
748
+ width: 100%; max-width: 1300px; height: 76vh; min-height: 520px;
468
749
  position: relative; overflow: hidden; border-radius: 18px;
469
750
  background: var(--bg-viewport); backdrop-filter: blur(20px);
470
751
  border: 1px solid var(--border-viewport);
471
752
  box-shadow: var(--shadow-viewport);
472
- cursor: grab; user-select: none; touch-action: none;
753
+ display: flex; align-items: center; justify-content: center;
754
+ touch-action: none; cursor: grab;
473
755
  }
474
- .viewer-card.grabbing { cursor: grabbing; }
475
- .canvas-wrap {
756
+ .viewer-card:active { cursor: grabbing; }
757
+ .diagram-container {
476
758
  width: 100%; height: 100%;
477
759
  display: flex; align-items: center; justify-content: center;
478
760
  transform-origin: center center;
479
- will-change: transform;
480
- }
481
- .canvas-wrap.animating {
482
- transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
483
- }
484
- .mermaid { width: 100%; text-align: center; }
485
- .mermaid svg { max-width: 100%; height: auto; }
486
- .help-hint {
487
- position: absolute; bottom: 12px; right: 14px;
488
- font-size: 11px; color: var(--text-muted); pointer-events: none;
489
- background: rgba(255, 255, 255, 0.75); padding: 3px 8px; border-radius: 6px;
490
- border: 1px solid rgba(0,0,0,0.05);
491
- }
492
- body.theme-dark .help-hint {
493
- background: rgba(18, 24, 38, 0.7);
494
- border-color: rgba(255, 255, 255, 0.08);
761
+ transition: transform 0.05s ease-out;
495
762
  }
763
+ .mermaid { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
496
764
  </style>
497
765
  </head>
498
766
  <body>
@@ -503,133 +771,64 @@ export class ArtifactManager {
503
771
  <div class="controls">
504
772
  <button class="btn" onclick="zoomIn()">🔍 Zoom +</button>
505
773
  <button class="btn" onclick="zoomOut()">🔍 Zoom -</button>
506
- <button class="btn" onclick="resetZoom()">⛶ Reset</button>
774
+ <button class="btn" onclick="resetZoom()">⛶ Fit / Reset</button>
507
775
  <button class="btn" onclick="toggleTheme()" id="themeToggleBtn">🌙 Poster Dark</button>
508
776
  </div>
509
777
  <div class="viewer-card" id="viewerCard">
510
- <div class="canvas-wrap" id="canvasWrap">
511
- <pre class="mermaid" id="mermaidDiagram">
512
- ${artifact.content.trim()}
513
- </pre>
778
+ <div class="diagram-container" id="diagramContainer">
779
+ <pre class="mermaid">${artifact.content.replace(/<antri_artifact[\s\S]*?>|<\/antri_artifact>/gi, '').trim()}</pre>
514
780
  </div>
515
- <div class="help-hint">🖱️ Drag to pan · Scroll to zoom</div>
516
781
  </div>
517
782
  <script>
518
783
  let scale = 1.0;
519
784
  let posX = 0;
520
785
  let posY = 0;
521
786
  let isDragging = false;
522
- let startPointerX = 0;
523
- let startPointerY = 0;
524
- let startPosX = 0;
525
- let startPosY = 0;
526
-
527
- const activeTouches = new Map();
528
- let initialPinchDist = 0;
529
- let initialPinchScale = 1.0;
530
-
787
+ let startX = 0;
788
+ let startY = 0;
789
+ const container = document.getElementById('diagramContainer');
531
790
  const viewerCard = document.getElementById('viewerCard');
532
- const canvasWrap = document.getElementById('canvasWrap');
533
791
 
534
792
  function updateTransform(animate = false) {
535
793
  if (animate) {
536
- canvasWrap.classList.add('animating');
537
- setTimeout(() => canvasWrap.classList.remove('animating'), 320);
794
+ container.style.transition = 'transform 0.25s cubic-bezier(0.2, 0, 0, 1)';
538
795
  } else {
539
- canvasWrap.classList.remove('animating');
796
+ container.style.transition = 'none';
540
797
  }
541
- canvasWrap.style.transform = 'translate3d(' + posX + 'px, ' + posY + 'px, 0) scale(' + scale + ')';
798
+ container.style.transform = 'translate(' + posX + 'px, ' + posY + 'px) scale(' + scale + ')';
542
799
  }
543
800
 
544
801
  viewerCard.addEventListener('pointerdown', (e) => {
545
- activeTouches.set(e.pointerId, { x: e.clientX, y: e.clientY });
546
- try { viewerCard.setPointerCapture(e.pointerId); } catch {}
547
-
548
- if (activeTouches.size === 1) {
549
- isDragging = true;
550
- viewerCard.classList.add('grabbing');
551
- startPointerX = e.clientX;
552
- startPointerY = e.clientY;
553
- startPosX = posX;
554
- startPosY = posY;
555
- } else if (activeTouches.size === 2) {
556
- isDragging = false;
557
- viewerCard.classList.remove('grabbing');
558
- const pts = Array.from(activeTouches.values());
559
- initialPinchDist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
560
- initialPinchScale = scale;
561
- }
802
+ isDragging = true;
803
+ startX = e.clientX - posX;
804
+ startY = e.clientY - posY;
805
+ viewerCard.setPointerCapture(e.pointerId);
562
806
  });
563
807
 
564
808
  viewerCard.addEventListener('pointermove', (e) => {
565
- if (!activeTouches.has(e.pointerId)) return;
566
- activeTouches.set(e.pointerId, { x: e.clientX, y: e.clientY });
567
-
568
- if (activeTouches.size === 1 && isDragging) {
569
- const dx = e.clientX - startPointerX;
570
- const dy = e.clientY - startPointerY;
571
- posX = startPosX + dx;
572
- posY = startPosY + dy;
573
- updateTransform(false);
574
- } else if (activeTouches.size === 2) {
575
- const pts = Array.from(activeTouches.values());
576
- const currentDist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
577
- if (initialPinchDist > 0) {
578
- scale = Math.min(Math.max(initialPinchScale * (currentDist / initialPinchDist), 0.2), 4.0);
579
- updateTransform(false);
580
- }
581
- }
809
+ if (!isDragging) return;
810
+ posX = e.clientX - startX;
811
+ posY = e.clientY - startY;
812
+ updateTransform(false);
582
813
  });
583
814
 
584
- function endPointer(e) {
585
- activeTouches.delete(e.pointerId);
586
- try { viewerCard.releasePointerCapture(e.pointerId); } catch {}
587
- if (activeTouches.size === 0) {
588
- isDragging = false;
589
- viewerCard.classList.remove('grabbing');
590
- } else if (activeTouches.size === 1) {
591
- isDragging = true;
592
- viewerCard.classList.add('grabbing');
593
- const remaining = Array.from(activeTouches.values())[0];
594
- startPointerX = remaining.x;
595
- startPointerY = remaining.y;
596
- startPosX = posX;
597
- startPosY = posY;
598
- }
599
- }
600
-
601
- viewerCard.addEventListener('pointerup', endPointer);
602
- viewerCard.addEventListener('pointercancel', endPointer);
815
+ const endDrag = (e) => {
816
+ isDragging = false;
817
+ try { viewerCard.releasePointerCapture(e.pointerId); } catch (_) {}
818
+ };
819
+ viewerCard.addEventListener('pointerup', endDrag);
820
+ viewerCard.addEventListener('pointercancel', endDrag);
603
821
 
604
822
  viewerCard.addEventListener('wheel', (e) => {
605
823
  e.preventDefault();
606
- const delta = e.deltaY < 0 ? 1.15 : 0.87;
607
- const newScale = Math.min(Math.max(scale * delta, 0.2), 4.0);
608
-
609
- const rect = viewerCard.getBoundingClientRect();
610
- const mouseX = e.clientX - rect.left - rect.width / 2;
611
- const mouseY = e.clientY - rect.top - rect.height / 2;
612
-
613
- posX = mouseX - (mouseX - posX) * (newScale / scale);
614
- posY = mouseY - (mouseY - posY) * (newScale / scale);
615
- scale = newScale;
824
+ const delta = e.deltaY < 0 ? 1.12 : 0.89;
825
+ scale = Math.min(Math.max(scale * delta, 0.25), 4.0);
616
826
  updateTransform(false);
617
827
  }, { passive: false });
618
828
 
619
- function zoomIn() {
620
- scale = Math.min(scale * 1.25, 4.0);
621
- updateTransform(true);
622
- }
623
- function zoomOut() {
624
- scale = Math.max(scale * 0.8, 0.2);
625
- updateTransform(true);
626
- }
627
- function resetZoom() {
628
- scale = 1.0;
629
- posX = 0;
630
- posY = 0;
631
- updateTransform(true);
632
- }
829
+ function zoomIn() { scale = Math.min(scale * 1.25, 4.0); updateTransform(true); }
830
+ function zoomOut() { scale = Math.max(scale * 0.8, 0.25); updateTransform(true); }
831
+ function resetZoom() { scale = 1.0; posX = 0; posY = 0; updateTransform(true); }
633
832
  function toggleTheme() {
634
833
  document.body.classList.toggle('theme-dark');
635
834
  const isDark = document.body.classList.contains('theme-dark');
@@ -637,32 +836,12 @@ ${artifact.content.trim()}
637
836
  if (btn) btn.textContent = isDark ? '☀️ Light Mode' : '🌙 Poster Dark';
638
837
  }
639
838
 
640
- mermaid.initialize({
641
- startOnLoad: true,
642
- theme: 'base',
643
- securityLevel: 'loose',
644
- themeVariables: {
645
- darkMode: false,
646
- background: '#ffffff',
647
- primaryColor: '#6366f1',
648
- primaryTextColor: '#0f172a',
649
- primaryBorderColor: '#4f46e5',
650
- lineColor: '#6366f1',
651
- secondaryColor: '#0ea5e9',
652
- tertiaryColor: '#10b981',
653
- fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
654
- fontSize: '14px'
655
- }
656
- });
839
+ mermaid.initialize({ startOnLoad: true, theme: 'base', securityLevel: 'loose' });
657
840
  </script>
658
841
  </body>
659
842
  </html>`;
660
- fs.writeFileSync(path.join(this.baseDir, `${artifact.id}.html`), htmlGraph, 'utf-8');
661
- }
662
843
  }
663
- catch { }
664
- this.persistIndex();
665
- return artifact;
844
+ return artifact.content;
666
845
  }
667
846
  getArtifact(id) {
668
847
  return this.artifacts.get(id) || null;