@jungjaehoon/mama-os 0.62.0 → 0.63.0

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.
@@ -427,7 +427,7 @@
427
427
 
428
428
  <div class="tab-content" id="tab-memory">
429
429
  <div class="flex-1 flex flex-col min-h-0">
430
- <!-- Compact Filter Bar (full width, top) -->
430
+ <!-- Period, kinds and search: every control is a server read of the saved timeline -->
431
431
  <div
432
432
  class="memory-controls flex items-center gap-2 px-3 py-2 border-b border-gray-200 bg-white"
433
433
  >
@@ -436,7 +436,7 @@
436
436
  id="checkpoint-sidebar-toggle"
437
437
  class="w-7 h-7 bg-white border border-gray-200 rounded flex items-center justify-center text-gray-500 hover:bg-gray-100 hover:text-gray-700"
438
438
  onclick="toggleCheckpointSidebar()"
439
- title="Toggle Decision List"
439
+ title="Show or hide the saved list"
440
440
  aria-controls="checkpoint-sidebar"
441
441
  aria-expanded="true"
442
442
  >
@@ -456,129 +456,29 @@
456
456
  <line x1="9" y1="21" x2="9" y2="9" />
457
457
  </svg>
458
458
  </button>
459
- <!-- Stats (compact inline) -->
460
- <span
461
- id="memory-stats-total"
462
- style="
463
- font-family: Inter, 'Noto Sans KR', sans-serif;
464
- font-size: 13px;
465
- font-weight: 600;
466
- color: #1a1a1a;
467
- "
468
- >-</span
469
- >
470
- <span style="font-size: 11px; color: #9e9891">decisions</span>
471
- <span style="color: #ede9e1">|</span>
472
- <span id="memory-stats-week" style="font-size: 11px; color: #6b6560"
473
- >- this week</span
474
- >
475
- <!-- Spacer -->
476
- <div style="flex: 1"></div>
477
- <!-- Topic filter -->
478
- <input
479
- list="topic-filter-list"
480
- class="px-2 py-1 bg-white border border-gray-200 rounded text-xs focus:outline-none focus:ring-1 focus:ring-yellow-400"
481
- id="topic-filter"
482
- placeholder="Filter topics..."
483
- oninput="filterByTopic(this.value)"
484
- />
485
- <datalist id="topic-filter-list"></datalist>
486
- <select
487
- class="px-2 py-1 bg-white border border-gray-200 rounded text-xs focus:outline-none focus:ring-1 focus:ring-yellow-400"
488
- id="outcome-filter"
489
- onchange="filterByOutcome(this.value)"
490
- style="display: none"
491
- >
492
- <option value="">All Outcomes</option>
493
- <option value="success">Success</option>
494
- <option value="failed">Failed</option>
495
- <option value="partial">Partial</option>
496
- <option value="pending">Pending</option>
497
- </select>
498
- <!-- Search -->
459
+ <div class="memory-periods" role="group" aria-label="Period">
460
+ <button type="button" class="memory-chip" data-memory-period="today">
461
+ Today
462
+ </button>
463
+ <button type="button" class="memory-chip" data-memory-period="7d">7 days</button>
464
+ <button type="button" class="memory-chip" data-memory-period="30d">
465
+ 30 days
466
+ </button>
467
+ <input type="date" id="memory-day" class="memory-day" aria-label="One day" />
468
+ </div>
469
+ <div
470
+ id="memory-group-chips"
471
+ class="memory-groups"
472
+ role="group"
473
+ aria-label="Kinds"
474
+ ></div>
499
475
  <input
500
- type="text"
476
+ type="search"
477
+ id="memory-timeline-search"
501
478
  class="px-2 py-1 bg-white border border-gray-200 rounded text-xs placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-yellow-400"
502
- id="search-input"
503
- placeholder="Search content..."
504
- onkeyup="handleGraphSearch(event)"
479
+ placeholder="Search saved text..."
480
+ aria-label="Search saved text"
505
481
  />
506
- <button
507
- onclick="clearFilters()"
508
- class="px-2 py-1 text-[10px] text-gray-400 hover:text-gray-600"
509
- >
510
- Clear
511
- </button>
512
- <!-- Export (collapsed) -->
513
- <details style="position: relative">
514
- <summary
515
- style="
516
- font-size: 10px;
517
- color: #9e9891;
518
- cursor: pointer;
519
- padding: 4px 8px;
520
- border: 1px solid #ede9e1;
521
- border-radius: 4px;
522
- "
523
- >
524
- Export
525
- </summary>
526
- <div
527
- style="
528
- position: absolute;
529
- right: 0;
530
- top: 100%;
531
- background: #fff;
532
- border: 1px solid #ede9e1;
533
- border-radius: 4px;
534
- padding: 4px;
535
- z-index: 10;
536
- display: flex;
537
- gap: 4px;
538
- margin-top: 2px;
539
- "
540
- >
541
- <button
542
- onclick="exportDecisions('json')"
543
- style="
544
- font-size: 10px;
545
- padding: 2px 8px;
546
- background: #f5f2ed;
547
- border: none;
548
- border-radius: 2px;
549
- cursor: pointer;
550
- "
551
- >
552
- JSON
553
- </button>
554
- <button
555
- onclick="exportDecisions('markdown')"
556
- style="
557
- font-size: 10px;
558
- padding: 2px 8px;
559
- background: #f5f2ed;
560
- border: none;
561
- border-radius: 2px;
562
- cursor: pointer;
563
- "
564
- >
565
- MD
566
- </button>
567
- <button
568
- onclick="exportDecisions('csv')"
569
- style="
570
- font-size: 10px;
571
- padding: 2px 8px;
572
- background: #f5f2ed;
573
- border: none;
574
- border-radius: 2px;
575
- cursor: pointer;
576
- "
577
- >
578
- CSV
579
- </button>
580
- </div>
581
- </details>
582
482
  </div>
583
483
 
584
484
  <!-- Main content area: Sidebar + Graph -->
@@ -586,25 +486,15 @@
586
486
  <!-- Decision List Sidebar -->
587
487
  <aside
588
488
  id="checkpoint-sidebar"
589
- class="checkpoint-sidebar w-64 border-r border-gray-200 bg-white flex flex-col min-h-0 overflow-hidden"
590
- style="min-width: 180px; max-width: 400px"
489
+ class="checkpoint-sidebar w-96 border-r border-gray-200 bg-white flex flex-col min-h-0 overflow-hidden"
490
+ style="min-width: 240px; max-width: 640px"
591
491
  >
592
- <div class="p-2 border-b border-gray-200 flex items-center justify-between">
593
- <span
594
- style="
595
- font-family: Inter, 'Noto Sans KR', sans-serif;
596
- font-size: 12px;
597
- font-weight: 600;
598
- color: #1a1a1a;
599
- "
600
- >Decisions</span
601
- >
602
- <span id="decision-list-count" style="font-size: 10px; color: #9e9891">0</span>
492
+ <div class="mt-head">
493
+ <span id="memory-timeline-range" class="mt-range">Saved</span>
494
+ <span id="memory-timeline-count" class="mt-total"></span>
603
495
  </div>
604
- <div class="flex-1 overflow-y-auto" id="decision-list" style="padding: 4px">
605
- <div style="padding: 16px; text-align: center; color: #9e9891; font-size: 11px">
606
- Loading decisions...
607
- </div>
496
+ <div class="flex-1 overflow-y-auto" id="memory-timeline">
497
+ <div class="mt-empty">Reading what was saved...</div>
608
498
  </div>
609
499
  </aside>
610
500
 
@@ -629,18 +519,25 @@
629
519
  style="background: #fafafa"
630
520
  ></div>
631
521
 
522
+ <div class="absolute inset-0 flex items-center justify-center" id="graph-empty">
523
+ <span class="graph-empty-text"
524
+ >Pick a saved record on the left to see what it rests on and links to.</span
525
+ >
526
+ </div>
527
+
632
528
  <div
633
529
  class="absolute inset-0 flex flex-col items-center justify-center bg-white/95"
634
530
  id="graph-loading"
531
+ style="display: none"
635
532
  >
636
533
  <div
637
534
  class="w-6 h-6 border-2 border-gray-400 border-t-transparent rounded-full animate-spin"
638
535
  ></div>
639
- <span class="mt-2 text-xs text-gray-500">Loading graph...</span>
536
+ <span class="mt-2 text-xs text-gray-500">Reading links...</span>
640
537
  </div>
641
538
 
642
539
  <div
643
- class="legend-panel absolute bottom-16 right-4 w-64 bg-white/95 backdrop-blur-sm border border-gray-200 rounded-lg shadow-lg p-3 z-10"
540
+ class="legend-panel absolute bottom-16 right-4 w-80 bg-white/95 backdrop-blur-sm border border-gray-200 rounded-lg shadow-lg p-3 z-10"
644
541
  id="legend-panel"
645
542
  >
646
543
  <button
@@ -662,9 +559,7 @@
662
559
  <line x1="6" y1="6" x2="18" y2="18" />
663
560
  </svg>
664
561
  </button>
665
- <h4
666
- class="text-xs font-semibold text-gray-700 text-gray-700 flex items-center gap-1.5 mb-3"
667
- >
562
+ <h4 class="text-sm font-semibold text-gray-700 flex items-center gap-1.5 mb-3">
668
563
  <svg
669
564
  xmlns="http://www.w3.org/2000/svg"
670
565
  width="14"
@@ -680,11 +575,11 @@
680
575
  <line x1="12" y1="16" x2="12" y2="12" />
681
576
  <line x1="12" y1="8" x2="12.01" y2="8" />
682
577
  </svg>
683
- Legend
578
+ What the dots and lines mean
684
579
  </h4>
685
580
  <div
686
581
  id="graph-legend-content"
687
- class="legend-content space-y-3 max-h-64 overflow-y-auto"
582
+ class="legend-content space-y-3 max-h-80 overflow-y-auto"
688
583
  ></div>
689
584
  </div>
690
585
  <button
@@ -725,7 +620,7 @@
725
620
  id="detail-modal-header"
726
621
  >
727
622
  <h3 class="text-sm font-semibold text-gray-900 truncate flex-1" id="detail-topic">
728
- Decision Detail
623
+ Record
729
624
  </h3>
730
625
  <button
731
626
  class="p-1 rounded hover:bg-gray-200 transition-colors text-gray-500 hover:text-gray-700 ml-2"
@@ -747,9 +642,12 @@
747
642
  </svg>
748
643
  </button>
749
644
  </div>
750
- <div class="flex-1 p-3 overflow-y-auto space-y-3 text-xs">
645
+ <div class="flex-1 p-3 overflow-y-auto space-y-3 text-sm">
646
+ <div class="flex gap-4 text-xs text-gray-500">
647
+ <span id="detail-kind"></span>
648
+ <span>Saved <span id="detail-created"></span></span>
649
+ </div>
751
650
  <div>
752
- <div class="font-semibold text-gray-500 uppercase tracking-wide mb-1">Decision</div>
753
651
  <div
754
652
  class="text-gray-900 leading-relaxed markdown-content"
755
653
  id="detail-decision"
@@ -757,55 +655,16 @@
757
655
  </div>
758
656
  <div>
759
657
  <div
760
- class="font-semibold text-gray-500 uppercase tracking-wide mb-1 cursor-pointer hover:text-gray-700"
658
+ class="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-1 cursor-pointer hover:text-gray-700"
761
659
  onclick="toggleReasoning()"
762
660
  >
763
- <span id="reasoning-arrow">▶</span> Reasoning
661
+ <span id="reasoning-arrow">▶</span> Reasoning and evidence
764
662
  </div>
765
663
  <div
766
664
  class="text-gray-900 leading-relaxed markdown-content hidden"
767
665
  id="detail-reasoning"
768
666
  ></div>
769
667
  </div>
770
- <div class="flex items-center gap-3">
771
- <div class="flex items-center gap-2">
772
- <span class="font-semibold text-gray-500 uppercase">Outcome:</span>
773
- <select
774
- class="px-1.5 py-0.5 bg-white border border-gray-200 rounded text-xs focus:outline-none focus:ring-1 focus:ring-yellow-400"
775
- id="detail-outcome-select"
776
- >
777
- <option value="PENDING">PENDING</option>
778
- <option value="SUCCESS">SUCCESS</option>
779
- <option value="FAILED">FAILED</option>
780
- <option value="PARTIAL">PARTIAL</option>
781
- </select>
782
- <button
783
- class="px-1.5 py-0.5 bg-gray-100 hover:bg-gray-200 rounded transition-colors"
784
- onclick="saveOutcome()"
785
- >
786
- Save
787
- </button>
788
- <span id="outcome-status"></span>
789
- </div>
790
- </div>
791
- <div class="flex gap-4">
792
- <div>
793
- <span class="font-semibold text-gray-500 uppercase">Confidence:</span>
794
- <span class="text-gray-900" id="detail-confidence"></span>
795
- </div>
796
- <div>
797
- <span class="font-semibold text-gray-500 uppercase">Created:</span>
798
- <span class="text-gray-900" id="detail-created"></span>
799
- </div>
800
- </div>
801
- <div>
802
- <div class="font-semibold text-gray-500 uppercase tracking-wide mb-1">
803
- Similar Decisions
804
- </div>
805
- <div class="text-gray-900" id="detail-similar">
806
- <span class="text-gray-400">Searching...</span>
807
- </div>
808
- </div>
809
668
  </div>
810
669
  <div
811
670
  id="decision-resize-handle"
@@ -1202,6 +1061,7 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1202
1061
 
1203
1062
  <script type="module">
1204
1063
  import { GraphModule } from '/viewer/js/modules/graph.js';
1064
+ import { MemoryTimelineModule } from '/viewer/js/modules/memory-timeline.js';
1205
1065
  import { MemoryModule } from '/viewer/js/modules/memory.js';
1206
1066
  import { WikiModule } from '/viewer/js/modules/wiki.js';
1207
1067
  import { renderRuntimeView, renderConnectorsView } from '/viewer/js/modules/system.js';
@@ -1209,6 +1069,16 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1209
1069
 
1210
1070
  const memory = new MemoryModule();
1211
1071
  const graph = new GraphModule();
1072
+ const timeline = new MemoryTimelineModule((id) => {
1073
+ graph.showRecord(id).catch((error) => {
1074
+ console.error('[MAMA] Failed to draw the record:', error);
1075
+ const empty = document.getElementById('graph-empty');
1076
+ if (empty) {
1077
+ empty.style.display = '';
1078
+ empty.textContent = `Could not read this record's links: ${error.message}`;
1079
+ }
1080
+ });
1081
+ });
1212
1082
  const wiki = new WikiModule();
1213
1083
 
1214
1084
  // Exposed for inline handlers and debugging.
@@ -1368,7 +1238,6 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1368
1238
  route: DEFAULT_ROUTE,
1369
1239
  routeKey: null,
1370
1240
  theme: 'light',
1371
- graphLoaded: false,
1372
1241
  };
1373
1242
 
1374
1243
  async function applyRoute(route) {
@@ -1400,22 +1269,7 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1400
1269
 
1401
1270
  if (route.view === 'memory') {
1402
1271
  updateCheckpointSidebarForScreenSize();
1403
- updateMemoryStats();
1404
- if (!STATE.graphLoaded) {
1405
- try {
1406
- const data = await graph.fetchData();
1407
- graph.init(data);
1408
- STATE.graphLoaded = true;
1409
- const loadingEl = document.getElementById('graph-loading');
1410
- if (loadingEl) loadingEl.style.display = 'none';
1411
- } catch (error) {
1412
- console.error('[MAMA] Failed to load graph:', error);
1413
- const loadingEl = document.getElementById('graph-loading');
1414
- if (loadingEl) {
1415
- loadingEl.innerHTML = '<span class="text-red-500">Failed to load graph data</span>';
1416
- }
1417
- }
1418
- }
1272
+ await timeline.init();
1419
1273
  return;
1420
1274
  }
1421
1275
 
@@ -1668,9 +1522,9 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1668
1522
  const isExpanding = sidebar.classList.contains('collapsed');
1669
1523
  if (isExpanding) {
1670
1524
  sidebar.classList.remove('collapsed');
1671
- sidebar.style.width = '224px'; // w-56 = 14rem = 224px
1525
+ sidebar.style.width = '';
1672
1526
  if (toggleBtn) {
1673
- toggleBtn.title = 'Hide Checkpoints';
1527
+ toggleBtn.title = 'Hide the saved list';
1674
1528
  toggleBtn.setAttribute('aria-expanded', 'true');
1675
1529
  }
1676
1530
  if (handle) handle.style.display = '';
@@ -1678,7 +1532,7 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1678
1532
  sidebar.classList.add('collapsed');
1679
1533
  sidebar.style.width = '0';
1680
1534
  if (toggleBtn) {
1681
- toggleBtn.title = 'Show Checkpoints';
1535
+ toggleBtn.title = 'Show the saved list';
1682
1536
  toggleBtn.setAttribute('aria-expanded', 'false');
1683
1537
  }
1684
1538
  if (handle) handle.style.display = 'none';
@@ -1696,17 +1550,18 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1696
1550
  // Mobile: stack layout with resizable height
1697
1551
  sidebar.classList.remove('collapsed');
1698
1552
  sidebar.style.width = '100%';
1699
- sidebar.style.height = '30vh';
1553
+ sidebar.style.height = '55vh';
1700
1554
  if (handle) {
1701
1555
  handle.style.display = '';
1702
1556
  handle.classList.add('sidebar-resize-handle--row');
1703
1557
  }
1704
1558
  if (toggleBtn) toggleBtn.setAttribute('aria-expanded', 'true');
1705
1559
  } else {
1706
- // Desktop: expand if was collapsed due to mobile
1560
+ // Desktop: the stacked layout's full width and any collapse give way to the class width.
1561
+ if (sidebar.style.width === '100%') sidebar.style.width = '';
1707
1562
  if (sidebar.classList.contains('collapsed')) {
1708
1563
  sidebar.classList.remove('collapsed');
1709
- sidebar.style.width = '224px';
1564
+ sidebar.style.width = '';
1710
1565
  }
1711
1566
  sidebar.style.height = '';
1712
1567
  if (handle) {
@@ -1846,79 +1701,6 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1846
1701
  .catch(() => (result.innerHTML = '<span class="text-red-500">Connection failed</span>'));
1847
1702
  }
1848
1703
 
1849
- function filterByTopic(topic) {
1850
- graph.filterByTopic && graph.filterByTopic(topic);
1851
- }
1852
-
1853
- function filterByOutcome(outcome) {
1854
- graph.filterByOutcome && graph.filterByOutcome(outcome);
1855
- }
1856
-
1857
- // Clear is scoped to the Graph module instance that owns the filters. Each
1858
- // control is looked up defensively: the bar is markup that can be edited,
1859
- // and one missing input must not take the whole Clear action down.
1860
- function clearFilters() {
1861
- const topicFilter = document.getElementById('topic-filter');
1862
- const outcomeFilter = document.getElementById('outcome-filter');
1863
- const searchInput = document.getElementById('search-input');
1864
- if (topicFilter) topicFilter.value = '';
1865
- if (outcomeFilter) outcomeFilter.value = '';
1866
- if (searchInput) searchInput.value = '';
1867
- graph?.clearFilters?.();
1868
- }
1869
-
1870
- async function exportDecisions(format) {
1871
- try {
1872
- const response = await fetch(`/api/memory/export?format=${format}`);
1873
- if (!response.ok) throw new Error('Export failed');
1874
-
1875
- const blob = await response.blob();
1876
- const filename =
1877
- response.headers.get('Content-Disposition')?.match(/filename="(.+)"/)?.[1] ||
1878
- `mama-export.${format}`;
1879
-
1880
- const url = URL.createObjectURL(blob);
1881
- const a = document.createElement('a');
1882
- a.href = url;
1883
- a.download = filename;
1884
- document.body.appendChild(a);
1885
- a.click();
1886
- document.body.removeChild(a);
1887
- URL.revokeObjectURL(url);
1888
- } catch (error) {
1889
- console.error('[Export] Error:', error);
1890
- alert('Export failed: ' + error.message);
1891
- }
1892
- }
1893
-
1894
- async function updateMemoryStats() {
1895
- try {
1896
- const data = await API.get('/api/dashboard/status');
1897
- const memory = data.memory || {};
1898
- const totalEl = document.getElementById('memory-stats-total');
1899
- const weekEl = document.getElementById('memory-stats-week');
1900
- if (totalEl) totalEl.textContent = memory.total || 0;
1901
- if (weekEl) weekEl.textContent = `${memory.thisWeek || 0} this week`;
1902
- } catch (error) {
1903
- console.error('[MemoryStats] Error:', error);
1904
- }
1905
- }
1906
-
1907
- function handleGraphSearch(event) {
1908
- if (event.key === 'Enter') {
1909
- graph.search && graph.search();
1910
- }
1911
- }
1912
-
1913
- function saveOutcome() {
1914
- const select = document.getElementById('detail-outcome-select');
1915
- const outcome = select.value;
1916
- const nodeId = graph.currentNodeId;
1917
- if (nodeId && outcome) {
1918
- graph.updateOutcome && graph.updateOutcome(nodeId, outcome);
1919
- }
1920
- }
1921
-
1922
1704
  // Expose to window for onclick handlers
1923
1705
  window.switchTab = switchTab;
1924
1706
  window.toggleTheme = toggleTheme;
@@ -1931,12 +1713,6 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
1931
1713
  window.hideSaveDecisionForm = hideSaveDecisionForm;
1932
1714
  window.submitSaveDecision = submitSaveDecision;
1933
1715
  window.testConnection = testConnection;
1934
- window.filterByOutcome = filterByOutcome;
1935
- window.clearFilters = clearFilters;
1936
- window.exportDecisions = exportDecisions;
1937
- window.filterByTopic = filterByTopic;
1938
- window.handleGraphSearch = handleGraphSearch;
1939
- window.saveOutcome = saveOutcome;
1940
1716
 
1941
1717
  document.addEventListener('DOMContentLoaded', () => {
1942
1718
  const userLang = navigator.language || navigator.userLanguage;