kiro-memory 1.7.1 → 1.8.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.
@@ -1083,7 +1083,7 @@ var require_react_development = __commonJS({
1083
1083
  }
1084
1084
  return dispatcher.useContext(Context);
1085
1085
  }
1086
- function useState10(initialState) {
1086
+ function useState11(initialState) {
1087
1087
  var dispatcher = resolveDispatcher();
1088
1088
  return dispatcher.useState(initialState);
1089
1089
  }
@@ -1095,7 +1095,7 @@ var require_react_development = __commonJS({
1095
1095
  var dispatcher = resolveDispatcher();
1096
1096
  return dispatcher.useRef(initialValue);
1097
1097
  }
1098
- function useEffect8(create, deps) {
1098
+ function useEffect10(create, deps) {
1099
1099
  var dispatcher = resolveDispatcher();
1100
1100
  return dispatcher.useEffect(create, deps);
1101
1101
  }
@@ -1107,11 +1107,11 @@ var require_react_development = __commonJS({
1107
1107
  var dispatcher = resolveDispatcher();
1108
1108
  return dispatcher.useLayoutEffect(create, deps);
1109
1109
  }
1110
- function useCallback5(callback, deps) {
1110
+ function useCallback6(callback, deps) {
1111
1111
  var dispatcher = resolveDispatcher();
1112
1112
  return dispatcher.useCallback(callback, deps);
1113
1113
  }
1114
- function useMemo2(create, deps) {
1114
+ function useMemo3(create, deps) {
1115
1115
  var dispatcher = resolveDispatcher();
1116
1116
  return dispatcher.useMemo(create, deps);
1117
1117
  }
@@ -1874,19 +1874,19 @@ var require_react_development = __commonJS({
1874
1874
  exports.memo = memo;
1875
1875
  exports.startTransition = startTransition;
1876
1876
  exports.unstable_act = act;
1877
- exports.useCallback = useCallback5;
1877
+ exports.useCallback = useCallback6;
1878
1878
  exports.useContext = useContext;
1879
1879
  exports.useDebugValue = useDebugValue;
1880
1880
  exports.useDeferredValue = useDeferredValue;
1881
- exports.useEffect = useEffect8;
1881
+ exports.useEffect = useEffect10;
1882
1882
  exports.useId = useId;
1883
1883
  exports.useImperativeHandle = useImperativeHandle;
1884
1884
  exports.useInsertionEffect = useInsertionEffect;
1885
1885
  exports.useLayoutEffect = useLayoutEffect;
1886
- exports.useMemo = useMemo2;
1886
+ exports.useMemo = useMemo3;
1887
1887
  exports.useReducer = useReducer;
1888
1888
  exports.useRef = useRef5;
1889
- exports.useState = useState10;
1889
+ exports.useState = useState11;
1890
1890
  exports.useSyncExternalStore = useSyncExternalStore;
1891
1891
  exports.useTransition = useTransition;
1892
1892
  exports.version = ReactVersion;
@@ -2382,9 +2382,9 @@ var require_react_dom_development = __commonJS({
2382
2382
  if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== "undefined" && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart === "function") {
2383
2383
  __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
2384
2384
  }
2385
- var React8 = require_react();
2385
+ var React9 = require_react();
2386
2386
  var Scheduler = require_scheduler();
2387
- var ReactSharedInternals = React8.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
2387
+ var ReactSharedInternals = React9.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
2388
2388
  var suppressWarning = false;
2389
2389
  function setSuppressWarning(newSuppressWarning) {
2390
2390
  {
@@ -3991,7 +3991,7 @@ var require_react_dom_development = __commonJS({
3991
3991
  {
3992
3992
  if (props.value == null) {
3993
3993
  if (typeof props.children === "object" && props.children !== null) {
3994
- React8.Children.forEach(props.children, function(child) {
3994
+ React9.Children.forEach(props.children, function(child) {
3995
3995
  if (child == null) {
3996
3996
  return;
3997
3997
  }
@@ -23581,11 +23581,11 @@ var require_client = __commonJS({
23581
23581
  });
23582
23582
 
23583
23583
  // src/ui/viewer/index.tsx
23584
- var import_react11 = __toESM(require_react(), 1);
23584
+ var import_react12 = __toESM(require_react(), 1);
23585
23585
  var import_client = __toESM(require_client(), 1);
23586
23586
 
23587
23587
  // src/ui/viewer/App.tsx
23588
- var import_react10 = __toESM(require_react(), 1);
23588
+ var import_react11 = __toESM(require_react(), 1);
23589
23589
 
23590
23590
  // src/ui/viewer/components/Header.tsx
23591
23591
  var import_react2 = __toESM(require_react(), 1);
@@ -23609,9 +23609,9 @@ function getTypeBadgeClasses(type) {
23609
23609
  };
23610
23610
  return map[type] || { bg: "bg-zinc-500/10 dark:bg-zinc-500/10", text: "text-zinc-600 dark:text-zinc-400", dot: "bg-zinc-500" };
23611
23611
  }
23612
- function timeAgo(epochSeconds) {
23613
- const now = Date.now() / 1e3;
23614
- const diff = Math.max(0, now - epochSeconds);
23612
+ function timeAgo(epoch) {
23613
+ const epochMs = epoch > 1e12 ? epoch : epoch * 1e3;
23614
+ const diff = Math.max(0, (Date.now() - epochMs) / 1e3);
23615
23615
  if (diff < 60) return "just now";
23616
23616
  if (diff < 3600) {
23617
23617
  const m = Math.floor(diff / 60);
@@ -23630,14 +23630,31 @@ function timeAgo(epochSeconds) {
23630
23630
  const w = Math.floor(diff / 604800);
23631
23631
  return `${w}w ago`;
23632
23632
  }
23633
- const date = new Date(epochSeconds * 1e3);
23633
+ const date = new Date(epochMs);
23634
23634
  return date.toLocaleDateString("en-US", { day: "numeric", month: "short" });
23635
23635
  }
23636
+ function formatTokenCount(tokens) {
23637
+ if (tokens >= 1e6) return `${(tokens / 1e6).toFixed(1)}M`;
23638
+ if (tokens >= 1e3) return `${(tokens / 1e3).toFixed(1)}k`;
23639
+ return String(tokens);
23640
+ }
23641
+ function formatDuration(minutes) {
23642
+ if (minutes < 1) return "<1m";
23643
+ if (minutes < 60) return `${Math.round(minutes)}m`;
23644
+ const h = Math.floor(minutes / 60);
23645
+ const m = Math.round(minutes % 60);
23646
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
23647
+ }
23636
23648
 
23637
23649
  // src/ui/viewer/components/SearchBar.tsx
23650
+ var SOURCE_BADGE = {
23651
+ vector: { bg: "bg-violet-500/15", text: "text-violet-400", label: "semantic" },
23652
+ keyword: { bg: "bg-amber-500/15", text: "text-amber-400", label: "keyword" },
23653
+ hybrid: { bg: "bg-cyan-500/15", text: "text-cyan-400", label: "hybrid" }
23654
+ };
23638
23655
  function SearchBar() {
23639
23656
  const [query, setQuery] = (0, import_react.useState)("");
23640
- const [results, setResults] = (0, import_react.useState)(null);
23657
+ const [results, setResults] = (0, import_react.useState)([]);
23641
23658
  const [isOpen, setIsOpen] = (0, import_react.useState)(false);
23642
23659
  const [isSearching, setIsSearching] = (0, import_react.useState)(false);
23643
23660
  const [selectedIndex, setSelectedIndex] = (0, import_react.useState)(0);
@@ -23645,14 +23662,15 @@ function SearchBar() {
23645
23662
  const debounceRef = (0, import_react.useRef)();
23646
23663
  const doSearch = (0, import_react.useCallback)(async (q) => {
23647
23664
  if (!q.trim()) {
23648
- setResults(null);
23665
+ setResults([]);
23649
23666
  return;
23650
23667
  }
23651
23668
  setIsSearching(true);
23652
23669
  try {
23653
- const res = await fetch(`/api/search?q=${encodeURIComponent(q)}&limit=8`);
23670
+ const res = await fetch(`/api/hybrid-search?q=${encodeURIComponent(q)}&limit=10`);
23654
23671
  if (res.ok) {
23655
- setResults(await res.json());
23672
+ const data = await res.json();
23673
+ setResults(data.results || []);
23656
23674
  setSelectedIndex(0);
23657
23675
  }
23658
23676
  } catch (err) {
@@ -23670,9 +23688,21 @@ function SearchBar() {
23670
23688
  const close = (0, import_react.useCallback)(() => {
23671
23689
  setIsOpen(false);
23672
23690
  setQuery("");
23673
- setResults(null);
23691
+ setResults([]);
23674
23692
  setSelectedIndex(0);
23675
23693
  }, []);
23694
+ const total = results.length;
23695
+ const openSelected = (0, import_react.useCallback)(() => {
23696
+ if (total === 0) return;
23697
+ const item = results[selectedIndex];
23698
+ if (!item) return;
23699
+ const targetId = `obs-${item.id}`;
23700
+ close();
23701
+ setTimeout(() => {
23702
+ const el = document.querySelector(`[data-id="${targetId}"]`);
23703
+ if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
23704
+ }, 100);
23705
+ }, [results, selectedIndex, total, close]);
23676
23706
  (0, import_react.useEffect)(() => {
23677
23707
  const handler = (e) => {
23678
23708
  if ((e.metaKey || e.ctrlKey) && e.key === "k") {
@@ -23681,11 +23711,23 @@ function SearchBar() {
23681
23711
  setTimeout(() => inputRef.current?.focus(), 50);
23682
23712
  }
23683
23713
  if (e.key === "Escape") close();
23714
+ if (!isOpen) return;
23715
+ if (e.key === "ArrowDown") {
23716
+ e.preventDefault();
23717
+ setSelectedIndex((prev) => Math.min(prev + 1, total - 1));
23718
+ }
23719
+ if (e.key === "ArrowUp") {
23720
+ e.preventDefault();
23721
+ setSelectedIndex((prev) => Math.max(prev - 1, 0));
23722
+ }
23723
+ if (e.key === "Enter" && total > 0) {
23724
+ e.preventDefault();
23725
+ openSelected();
23726
+ }
23684
23727
  };
23685
23728
  window.addEventListener("keydown", handler);
23686
23729
  return () => window.removeEventListener("keydown", handler);
23687
- }, [close]);
23688
- const total = results ? results.observations.length + results.summaries.length : 0;
23730
+ }, [close, isOpen, total, openSelected]);
23689
23731
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
23690
23732
  "button",
23691
23733
  {
@@ -23693,12 +23735,13 @@ function SearchBar() {
23693
23735
  setIsOpen(true);
23694
23736
  setTimeout(() => inputRef.current?.focus(), 50);
23695
23737
  },
23696
- className: "flex items-center gap-2.5 flex-1 max-w-md px-3 py-2 rounded-lg bg-surface-2 border border-border text-zinc-500 hover:text-zinc-300 hover:border-border-hover transition-all cursor-text"
23738
+ className: "flex items-center gap-2.5 flex-1 max-w-md px-3 py-2 rounded-lg bg-surface-2 border border-border text-zinc-500 hover:text-zinc-300 hover:border-border-hover transition-all cursor-text",
23739
+ "aria-label": "Search memories"
23697
23740
  },
23698
23741
  /* @__PURE__ */ import_react.default.createElement("svg", { className: "w-4 h-4 flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "11", cy: "11", r: "8" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "m21 21-4.3-4.3" })),
23699
23742
  /* @__PURE__ */ import_react.default.createElement("span", { className: "text-sm" }, "Search memories..."),
23700
23743
  /* @__PURE__ */ import_react.default.createElement("kbd", { className: "ml-auto hidden sm:inline text-[11px] text-zinc-600 bg-surface-3 px-1.5 py-0.5 rounded font-mono border border-border" }, typeof navigator !== "undefined" && navigator.platform?.includes("Mac") ? "\u2318K" : "Ctrl+K")
23701
- ), isOpen && /* @__PURE__ */ import_react.default.createElement("div", { className: "fixed inset-0 z-[999] bg-black/60 backdrop-blur-sm animate-fade-in", onClick: close }, /* @__PURE__ */ import_react.default.createElement("div", { className: "mx-auto mt-[12vh] w-full max-w-xl animate-scale-in px-4", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-surface-1 border border-border rounded-xl shadow-2xl overflow-hidden" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-3 px-4 py-3.5 border-b border-border" }, /* @__PURE__ */ import_react.default.createElement("svg", { className: "w-5 h-5 text-accent-violet flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "11", cy: "11", r: "8" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "m21 21-4.3-4.3" })), /* @__PURE__ */ import_react.default.createElement(
23744
+ ), isOpen && /* @__PURE__ */ import_react.default.createElement("div", { className: "fixed inset-0 z-[999] bg-black/60 backdrop-blur-sm animate-fade-in", onClick: close, role: "dialog", "aria-modal": "true", "aria-label": "Search" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "mx-auto mt-[12vh] w-full max-w-xl animate-scale-in px-4", onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bg-surface-1 border border-border rounded-xl shadow-2xl overflow-hidden" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-3 px-4 py-3.5 border-b border-border" }, /* @__PURE__ */ import_react.default.createElement("svg", { className: "w-5 h-5 text-accent-violet flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "11", cy: "11", r: "8" }), /* @__PURE__ */ import_react.default.createElement("path", { d: "m21 21-4.3-4.3" })), /* @__PURE__ */ import_react.default.createElement(
23702
23745
  "input",
23703
23746
  {
23704
23747
  ref: inputRef,
@@ -23707,22 +23750,73 @@ function SearchBar() {
23707
23750
  placeholder: "Search observations, summaries, concepts...",
23708
23751
  value: query,
23709
23752
  onChange: handleChange,
23753
+ role: "combobox",
23754
+ "aria-expanded": total > 0,
23755
+ "aria-controls": "search-results",
23756
+ "aria-activedescendant": total > 0 ? `search-item-${selectedIndex}` : void 0,
23710
23757
  autoFocus: true
23711
23758
  }
23712
- ), isSearching && /* @__PURE__ */ import_react.default.createElement("div", { className: "w-4 h-4 border-2 border-accent-violet/30 border-t-accent-violet rounded-full animate-spin" }), /* @__PURE__ */ import_react.default.createElement("kbd", { className: "text-[10px] text-zinc-500 bg-surface-3 px-1.5 py-0.5 rounded font-mono border border-border cursor-pointer", onClick: close }, "ESC")), results && /* @__PURE__ */ import_react.default.createElement("div", { className: "max-h-[360px] overflow-y-auto" }, total === 0 && !isSearching && query.trim() && /* @__PURE__ */ import_react.default.createElement("div", { className: "px-4 py-10 text-center" }, /* @__PURE__ */ import_react.default.createElement("p", { className: "text-sm text-zinc-500" }, 'No results for "', query, '"')), results.observations.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "px-4 py-2 text-[11px] font-semibold uppercase tracking-wider text-zinc-500" }, "Observations"), results.observations.map((obs, idx) => {
23713
- const badge = getTypeBadgeClasses(obs.type);
23714
- return /* @__PURE__ */ import_react.default.createElement("div", { key: `obs-${obs.id}`, className: `flex items-start gap-3 px-4 py-2.5 transition-colors ${idx === selectedIndex ? "bg-surface-2" : "hover:bg-surface-2/50"}` }, /* @__PURE__ */ import_react.default.createElement("div", { className: `w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${badge.dot}` }), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-sm text-zinc-200 truncate" }, obs.title), obs.text && /* @__PURE__ */ import_react.default.createElement("div", { className: "text-xs text-zinc-500 truncate mt-0.5" }, obs.text.substring(0, 100)), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2 mt-1" }, /* @__PURE__ */ import_react.default.createElement("span", { className: `text-[10px] font-medium px-1.5 py-0.5 rounded ${badge.bg} ${badge.text}` }, obs.type), /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, timeAgo(obs.created_at_epoch)))));
23715
- })), results.summaries.length > 0 && /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "px-4 py-2 text-[11px] font-semibold uppercase tracking-wider text-zinc-500 border-t border-border" }, "Summaries"), results.summaries.map((sum) => /* @__PURE__ */ import_react.default.createElement("div", { key: `sum-${sum.id}`, className: "flex items-start gap-3 px-4 py-2.5 hover:bg-surface-2/50 transition-colors" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "w-2 h-2 rounded-full mt-1.5 flex-shrink-0 bg-accent-cyan" }), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-sm text-zinc-200 truncate" }, sum.request || "Session Summary"), sum.completed && /* @__PURE__ */ import_react.default.createElement("div", { className: "text-xs text-zinc-500 truncate mt-0.5" }, sum.completed.substring(0, 100)), /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, timeAgo(sum.created_at_epoch))))))), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-4 px-4 py-2.5 border-t border-border text-[11px] text-zinc-600" }, /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "\u2191\u2193"), "navigate"), /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "\u21B5"), "open"), /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "esc"), "close"))))));
23759
+ ), isSearching && /* @__PURE__ */ import_react.default.createElement("div", { className: "w-4 h-4 border-2 border-accent-violet/30 border-t-accent-violet rounded-full animate-spin" }), /* @__PURE__ */ import_react.default.createElement("kbd", { className: "text-[10px] text-zinc-500 bg-surface-3 px-1.5 py-0.5 rounded font-mono border border-border cursor-pointer", onClick: close }, "ESC")), /* @__PURE__ */ import_react.default.createElement("div", { id: "search-results", className: "max-h-[360px] overflow-y-auto", role: "listbox" }, total === 0 && !isSearching && query.trim() && /* @__PURE__ */ import_react.default.createElement("div", { className: "px-4 py-10 text-center" }, /* @__PURE__ */ import_react.default.createElement("p", { className: "text-sm text-zinc-500" }, 'No results for "', query, '"')), results.map((item, idx) => {
23760
+ const badge = getTypeBadgeClasses(item.type);
23761
+ const srcBadge = SOURCE_BADGE[item.source] || SOURCE_BADGE.keyword;
23762
+ return /* @__PURE__ */ import_react.default.createElement(
23763
+ "div",
23764
+ {
23765
+ key: item.id,
23766
+ id: `search-item-${idx}`,
23767
+ role: "option",
23768
+ "aria-selected": idx === selectedIndex,
23769
+ className: `flex items-start gap-3 px-4 py-2.5 cursor-pointer transition-colors ${idx === selectedIndex ? "bg-surface-2" : "hover:bg-surface-2/50"}`,
23770
+ onClick: () => {
23771
+ setSelectedIndex(idx);
23772
+ openSelected();
23773
+ }
23774
+ },
23775
+ /* @__PURE__ */ import_react.default.createElement("div", { className: `w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${badge.dot}` }),
23776
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-sm text-zinc-200 truncate" }, item.title), item.content && /* @__PURE__ */ import_react.default.createElement("div", { className: "text-xs text-zinc-500 truncate mt-0.5" }, item.content.substring(0, 120)), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-2 mt-1" }, /* @__PURE__ */ import_react.default.createElement("span", { className: `text-[10px] font-medium px-1.5 py-0.5 rounded ${badge.bg} ${badge.text}` }, item.type), /* @__PURE__ */ import_react.default.createElement("span", { className: `text-[10px] font-medium px-1.5 py-0.5 rounded ${srcBadge.bg} ${srcBadge.text}` }, srcBadge.label), /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, timeAgo(item.created_at_epoch)), item.score > 0 && /* @__PURE__ */ import_react.default.createElement("span", { className: "text-[10px] text-zinc-700 font-mono" }, (item.score * 100).toFixed(0), "%")))
23777
+ );
23778
+ })), /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-center gap-4 px-4 py-2.5 border-t border-border text-[11px] text-zinc-600" }, /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "\u2191\u2193"), "navigate"), /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "\u21B5"), "open"), /* @__PURE__ */ import_react.default.createElement("span", null, /* @__PURE__ */ import_react.default.createElement("kbd", { className: "px-1 py-0.5 rounded bg-surface-3 border border-border font-mono mr-1" }, "esc"), "close"))))));
23716
23779
  }
23717
23780
 
23718
23781
  // src/ui/viewer/components/Header.tsx
23719
- function Header({ isConnected, resolvedTheme, onThemeToggle, currentView, onViewChange }) {
23720
- return /* @__PURE__ */ import_react2.default.createElement("header", { className: "flex items-center gap-4 px-6 h-14 bg-surface-1 border-b border-border z-50" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center gap-3 flex-shrink-0" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "w-8 h-8 rounded-lg bg-accent-violet flex items-center justify-center" }, /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-[18px] h-[18px] text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2a4 4 0 0 1 4 4c0 1.95-1.4 3.58-3.25 3.93a1 1 0 0 0-.75.97V13" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2a4 4 0 0 0-4 4c0 1.95 1.4 3.58 3.25 3.93a1 1 0 0 1 .75.97V13" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M9 18h6" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M10 22h4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 13v5" }))), /* @__PURE__ */ import_react2.default.createElement("div", null, /* @__PURE__ */ import_react2.default.createElement("h1", { className: "text-[15px] font-bold text-zinc-100 leading-none" }, "Kiro Memory"), /* @__PURE__ */ import_react2.default.createElement("span", { className: "text-[11px] text-zinc-500 mt-0.5 block" }, "Memory Dashboard"))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hidden md:block w-px h-6 bg-border" }), /* @__PURE__ */ import_react2.default.createElement(SearchBar, null), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-1" }), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 rounded-lg bg-surface-2 border border-border" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: `w-2 h-2 rounded-full ${isConnected ? "bg-accent-green animate-pulse-dot" : "bg-zinc-500"}` }), /* @__PURE__ */ import_react2.default.createElement("span", { className: `text-xs font-medium ${isConnected ? "text-accent-green" : "text-zinc-500"}` }, isConnected ? "Live" : "Offline")), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center rounded-lg bg-surface-2 border border-border p-0.5" }, /* @__PURE__ */ import_react2.default.createElement(
23782
+ function formatAgo(ms) {
23783
+ if (ms <= 0) return "";
23784
+ const sec = Math.floor(ms / 1e3);
23785
+ if (sec < 5) return "just now";
23786
+ if (sec < 60) return `${sec}s ago`;
23787
+ const min = Math.floor(sec / 60);
23788
+ if (min < 60) return `${min}m ago`;
23789
+ return `${Math.floor(min / 60)}h ago`;
23790
+ }
23791
+ function Header({ isConnected, lastEventTime, resolvedTheme, themePreference, onThemeChange, currentView, onViewChange, onMenuToggle }) {
23792
+ const cycleTheme = () => {
23793
+ const order = ["dark", "light", "system"];
23794
+ const current = order.indexOf(themePreference);
23795
+ onThemeChange(order[(current + 1) % order.length]);
23796
+ };
23797
+ const [now, setNow] = (0, import_react2.useState)(Date.now());
23798
+ (0, import_react2.useEffect)(() => {
23799
+ const t = setInterval(() => setNow(Date.now()), 5e3);
23800
+ return () => clearInterval(t);
23801
+ }, []);
23802
+ const agoText = lastEventTime > 0 ? formatAgo(now - lastEventTime) : "";
23803
+ const isFresh = lastEventTime > 0 && now - lastEventTime < 3e3;
23804
+ return /* @__PURE__ */ import_react2.default.createElement("header", { className: "flex items-center gap-4 px-4 md:px-6 h-14 bg-surface-1 border-b border-border z-50" }, onMenuToggle && /* @__PURE__ */ import_react2.default.createElement(
23805
+ "button",
23806
+ {
23807
+ onClick: onMenuToggle,
23808
+ className: "md:hidden w-8 h-8 rounded-lg bg-surface-2 border border-border text-zinc-400 hover:text-zinc-100 hover:bg-surface-3 transition-all flex items-center justify-center flex-shrink-0",
23809
+ "aria-label": "Apri menu laterale"
23810
+ },
23811
+ /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("line", { x1: "4", y1: "6", x2: "20", y2: "6" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "4", y1: "12", x2: "20", y2: "12" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "4", y1: "18", x2: "20", y2: "18" }))
23812
+ ), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center gap-3 flex-shrink-0" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "w-8 h-8 rounded-lg bg-accent-violet flex items-center justify-center" }, /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-[18px] h-[18px] text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2a4 4 0 0 1 4 4c0 1.95-1.4 3.58-3.25 3.93a1 1 0 0 0-.75.97V13" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2a4 4 0 0 0-4 4c0 1.95 1.4 3.58 3.25 3.93a1 1 0 0 1 .75.97V13" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M9 18h6" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M10 22h4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 13v5" }))), /* @__PURE__ */ import_react2.default.createElement("div", null, /* @__PURE__ */ import_react2.default.createElement("h1", { className: "text-[15px] font-bold text-zinc-100 leading-none" }, "Kiro Memory"), /* @__PURE__ */ import_react2.default.createElement("span", { className: "text-[11px] text-zinc-500 mt-0.5 block" }, "Memory Dashboard"))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hidden md:block w-px h-6 bg-border" }), /* @__PURE__ */ import_react2.default.createElement(SearchBar, null), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex-1" }), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hidden sm:flex items-center gap-2 px-3 py-1.5 rounded-lg bg-surface-2 border border-border" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: `w-2 h-2 rounded-full transition-all ${isFresh ? "bg-accent-green scale-125" : isConnected ? "bg-accent-green animate-pulse-dot" : "bg-zinc-500"}` }), /* @__PURE__ */ import_react2.default.createElement("span", { className: `text-xs font-medium ${isConnected ? "text-accent-green" : "text-zinc-500"}` }, isConnected ? "Live" : "Offline"), agoText && /* @__PURE__ */ import_react2.default.createElement("span", { className: "text-[10px] text-zinc-600 ml-1" }, agoText)), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hidden sm:flex items-center rounded-lg bg-surface-2 border border-border p-0.5", role: "tablist", "aria-label": "View mode" }, /* @__PURE__ */ import_react2.default.createElement(
23721
23813
  "button",
23722
23814
  {
23723
23815
  onClick: () => onViewChange("feed"),
23724
23816
  className: `flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${currentView === "feed" ? "bg-surface-3 text-zinc-100 shadow-sm" : "text-zinc-500 hover:text-zinc-300"}`,
23725
- title: "Memory Feed"
23817
+ role: "tab",
23818
+ "aria-selected": currentView === "feed",
23819
+ "aria-label": "Memory Feed"
23726
23820
  },
23727
23821
  /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "6", x2: "21", y2: "6" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "12", x2: "21", y2: "12" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "18", x2: "21", y2: "18" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "3", y1: "6", x2: "3.01", y2: "6" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "3", y1: "12", x2: "3.01", y2: "12" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "3", y1: "18", x2: "3.01", y2: "18" })),
23728
23822
  "Feed"
@@ -23731,34 +23825,44 @@ function Header({ isConnected, resolvedTheme, onThemeToggle, currentView, onView
23731
23825
  {
23732
23826
  onClick: () => onViewChange("analytics"),
23733
23827
  className: `flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${currentView === "analytics" ? "bg-surface-3 text-zinc-100 shadow-sm" : "text-zinc-500 hover:text-zinc-300"}`,
23734
- title: "Analytics Dashboard"
23828
+ role: "tab",
23829
+ "aria-selected": currentView === "analytics",
23830
+ "aria-label": "Analytics Dashboard"
23735
23831
  },
23736
23832
  /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("line", { x1: "18", y1: "20", x2: "18", y2: "10" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "12", y1: "20", x2: "12", y2: "4" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "6", y1: "20", x2: "6", y2: "14" })),
23737
23833
  "Analytics"
23834
+ ), /* @__PURE__ */ import_react2.default.createElement(
23835
+ "button",
23836
+ {
23837
+ onClick: () => onViewChange("sessions"),
23838
+ className: `flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${currentView === "sessions" ? "bg-surface-3 text-zinc-100 shadow-sm" : "text-zinc-500 hover:text-zinc-300"}`,
23839
+ role: "tab",
23840
+ "aria-selected": currentView === "sessions",
23841
+ "aria-label": "Sessions"
23842
+ },
23843
+ /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ import_react2.default.createElement("polyline", { points: "12 6 12 12 16 14" })),
23844
+ "Sessions"
23738
23845
  )), /* @__PURE__ */ import_react2.default.createElement(
23739
23846
  "button",
23740
23847
  {
23741
- onClick: onThemeToggle,
23848
+ onClick: cycleTheme,
23742
23849
  className: "w-8 h-8 rounded-lg bg-surface-2 border border-border text-zinc-400 hover:text-zinc-100 hover:bg-surface-3 hover:border-border-hover transition-all flex items-center justify-center",
23743
- title: resolvedTheme === "dark" ? "Light mode" : "Dark mode"
23850
+ title: `Theme: ${themePreference}`,
23851
+ "aria-label": `Theme: ${themePreference}. Click to change`
23744
23852
  },
23745
- resolvedTheme === "dark" ? /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "12", r: "4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" })) : /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }))
23853
+ themePreference === "system" ? /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "8", y1: "21", x2: "16", y2: "21" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "12", y1: "17", x2: "12", y2: "21" })) : resolvedTheme === "dark" ? /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "12", cy: "12", r: "4" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" })) : /* @__PURE__ */ import_react2.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }))
23746
23854
  ));
23747
23855
  }
23748
23856
 
23749
23857
  // src/ui/viewer/components/Sidebar.tsx
23750
23858
  var import_react3 = __toESM(require_react(), 1);
23751
23859
  var TYPE_CONFIG = {
23752
- "file-write": { color: "bg-accent-green", label: "File writes" },
23753
- "file-read": { color: "bg-accent-cyan", label: "File reads" },
23860
+ "file-write": { color: "bg-accent-green", label: "Changes" },
23861
+ "file-read": { color: "bg-accent-cyan", label: "Reads" },
23754
23862
  "command": { color: "bg-accent-amber", label: "Commands" },
23755
23863
  "research": { color: "bg-accent-blue", label: "Research" },
23756
23864
  "delegation": { color: "bg-accent-violet", label: "Delegations" },
23757
- "tool-use": { color: "bg-zinc-400", label: "Tool usage" },
23758
- "constraint": { color: "bg-red-500", label: "Constraints" },
23759
- "decision": { color: "bg-orange-500", label: "Decisions" },
23760
- "heuristic": { color: "bg-indigo-500", label: "Heuristics" },
23761
- "rejected": { color: "bg-slate-500", label: "Rejected" }
23865
+ "tool-use": { color: "bg-zinc-400", label: "Tools" }
23762
23866
  };
23763
23867
  var PROJECT_COLORS = [
23764
23868
  { bg: "bg-accent-violet/15", text: "text-accent-violet", ring: "ring-accent-violet/30" },
@@ -23786,6 +23890,7 @@ function Sidebar({
23786
23890
  }) {
23787
23891
  const [editingProject, setEditingProject] = (0, import_react3.useState)(null);
23788
23892
  const [editValue, setEditValue] = (0, import_react3.useState)("");
23893
+ const [renameFeedback, setRenameFeedback] = (0, import_react3.useState)(null);
23789
23894
  const editInputRef = (0, import_react3.useRef)(null);
23790
23895
  (0, import_react3.useEffect)(() => {
23791
23896
  if (editingProject && editInputRef.current) {
@@ -23800,7 +23905,13 @@ function Sidebar({
23800
23905
  };
23801
23906
  const confirmEdit = async () => {
23802
23907
  if (editingProject && editValue.trim()) {
23803
- await onRenameProject(editingProject, editValue.trim());
23908
+ try {
23909
+ await onRenameProject(editingProject, editValue.trim());
23910
+ setRenameFeedback({ project: editingProject, success: true });
23911
+ } catch {
23912
+ setRenameFeedback({ project: editingProject, success: false });
23913
+ }
23914
+ setTimeout(() => setRenameFeedback(null), 2e3);
23804
23915
  }
23805
23916
  setEditingProject(null);
23806
23917
  };
@@ -23845,9 +23956,11 @@ function Sidebar({
23845
23956
  },
23846
23957
  /* @__PURE__ */ import_react3.default.createElement("div", { className: `w-7 h-7 rounded-md flex items-center justify-center flex-shrink-0 text-[11px] font-bold ${pc.bg} ${pc.text}` }, initials),
23847
23958
  /* @__PURE__ */ import_react3.default.createElement("span", { className: "flex-1 truncate" }, getDisplayName(project)),
23959
+ renameFeedback?.project === project && /* @__PURE__ */ import_react3.default.createElement("span", { className: `text-[10px] font-medium animate-fade-in ${renameFeedback.success ? "text-accent-green" : "text-accent-rose"}` }, renameFeedback.success ? "Saved" : "Error"),
23848
23960
  /* @__PURE__ */ import_react3.default.createElement(
23849
- "span",
23961
+ "button",
23850
23962
  {
23963
+ type: "button",
23851
23964
  onClick: (e) => startEditing(project, e),
23852
23965
  className: "opacity-0 group-hover:opacity-100 text-zinc-600 hover:text-zinc-300 transition-all p-0.5",
23853
23966
  title: "Rename"
@@ -23861,11 +23974,11 @@ function Sidebar({
23861
23974
  "label",
23862
23975
  {
23863
23976
  key: type,
23864
- className: `flex items-center gap-3 px-3 py-2 rounded-lg text-sm cursor-pointer transition-all ${isActive ? "text-zinc-300 hover:text-zinc-100" : "text-zinc-600 hover:text-zinc-400"}`
23977
+ className: `flex items-center gap-3 px-3 py-2 rounded-lg text-sm cursor-pointer transition-all ${isActive ? "text-zinc-300 hover:text-zinc-100" : "text-zinc-500 hover:text-zinc-400"}`
23865
23978
  },
23866
- /* @__PURE__ */ import_react3.default.createElement("div", { className: `w-4 h-4 rounded border flex items-center justify-center flex-shrink-0 transition-all ${isActive ? "bg-accent-violet border-accent-violet" : "bg-transparent border-zinc-600"}` }, isActive && /* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-2.5 h-2.5 text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3" }, /* @__PURE__ */ import_react3.default.createElement("polyline", { points: "20 6 9 17 4 12" }))),
23867
- /* @__PURE__ */ import_react3.default.createElement("input", { type: "checkbox", checked: isActive, onChange: () => onToggleType(type), className: "sr-only" }),
23868
- /* @__PURE__ */ import_react3.default.createElement("div", { className: `w-2.5 h-2.5 rounded-full flex-shrink-0 ${config.color} ${isActive ? "opacity-100" : "opacity-30"}` }),
23979
+ /* @__PURE__ */ import_react3.default.createElement("input", { type: "checkbox", checked: isActive, onChange: () => onToggleType(type), className: "sr-only", "aria-label": `Filter ${config.label}` }),
23980
+ /* @__PURE__ */ import_react3.default.createElement("div", { className: `w-4 h-4 rounded border flex items-center justify-center flex-shrink-0 transition-all ${isActive ? "bg-accent-violet border-accent-violet" : "bg-transparent border-zinc-600"}`, "aria-hidden": "true" }, isActive && /* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-2.5 h-2.5 text-white", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3" }, /* @__PURE__ */ import_react3.default.createElement("polyline", { points: "20 6 9 17 4 12" }))),
23981
+ /* @__PURE__ */ import_react3.default.createElement("div", { className: `w-2.5 h-2.5 rounded-full flex-shrink-0 ${config.color} ${isActive ? "opacity-100" : "opacity-30"}`, "aria-hidden": "true" }),
23869
23982
  /* @__PURE__ */ import_react3.default.createElement("span", { className: "flex-1" }, config.label)
23870
23983
  );
23871
23984
  }))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mx-4 h-px bg-border" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "p-4" }, /* @__PURE__ */ import_react3.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-3 px-2" }, "Statistics"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "grid grid-cols-2 gap-2" }, [
@@ -23873,38 +23986,134 @@ function Sidebar({
23873
23986
  { label: "Summaries", value: stats.summaries, color: "text-accent-cyan" },
23874
23987
  { label: "Prompts", value: stats.prompts, color: "text-accent-amber" },
23875
23988
  { label: "Projects", value: projects.length, color: "text-accent-green" }
23876
- ].map((item) => /* @__PURE__ */ import_react3.default.createElement("div", { key: item.label, className: "rounded-lg bg-surface-2 border border-border px-3 py-3 text-center" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: `text-xl font-bold tabular-nums ${item.color}` }, item.value), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-1" }, item.label))))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mt-auto px-4 py-4" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-[10px] text-zinc-700 font-mono text-center" }, "Kiro Memory v1.5.0")));
23989
+ ].map((item) => /* @__PURE__ */ import_react3.default.createElement("div", { key: item.label, className: "rounded-lg bg-surface-2 border border-border px-3 py-3 text-center" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: `text-xl font-bold tabular-nums ${item.color}` }, item.value), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-1" }, item.label))))), (stats.tokenEconomics.discoveryTokens > 0 || stats.tokenEconomics.readTokens > 0) && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("div", { className: "mx-4 h-px bg-border" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "p-4" }, /* @__PURE__ */ import_react3.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-3 px-2" }, "Token Economics"), /* @__PURE__ */ import_react3.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-3 py-2.5" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-between mb-1.5" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-[10px] uppercase tracking-wider text-zinc-600" }, "Discovery"), /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-xs font-bold text-amber-400 tabular-nums" }, formatTokenCount(stats.tokenEconomics.discoveryTokens))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-between mb-1.5" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-[10px] uppercase tracking-wider text-zinc-600" }, "Read cost"), /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-xs font-bold text-cyan-400 tabular-nums" }, formatTokenCount(stats.tokenEconomics.readTokens))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "h-px bg-border my-1.5" }), /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-[10px] uppercase tracking-wider text-zinc-600" }, "Savings"), /* @__PURE__ */ import_react3.default.createElement("span", { className: "text-xs font-bold text-emerald-400 tabular-nums" }, formatTokenCount(stats.tokenEconomics.savings)))), stats.tokenEconomics.discoveryTokens > 0 && /* @__PURE__ */ import_react3.default.createElement("div", { className: "rounded-md overflow-hidden h-2 bg-surface-3" }, /* @__PURE__ */ import_react3.default.createElement(
23990
+ "div",
23991
+ {
23992
+ className: "h-full bg-gradient-to-r from-emerald-500 to-emerald-400 transition-all",
23993
+ style: { width: `${Math.min(100, Math.round(stats.tokenEconomics.readTokens / stats.tokenEconomics.discoveryTokens * 100))}%` }
23994
+ }
23995
+ )), stats.tokenEconomics.discoveryTokens > 0 && /* @__PURE__ */ import_react3.default.createElement("p", { className: "text-[10px] text-zinc-600 text-center" }, Math.round((1 - stats.tokenEconomics.readTokens / stats.tokenEconomics.discoveryTokens) * 100), "% token reduction")))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "mt-auto px-4 py-4 space-y-2" }, /* @__PURE__ */ import_react3.default.createElement("div", { className: "flex items-center justify-center gap-3" }, /* @__PURE__ */ import_react3.default.createElement("a", { href: "https://github.com/auriti-web-design/kiro-memory", target: "_blank", rel: "noopener noreferrer", className: "text-zinc-600 hover:text-zinc-400 transition-colors", title: "GitHub" }, /* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ import_react3.default.createElement("path", { d: "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z" }))), /* @__PURE__ */ import_react3.default.createElement("a", { href: "https://auritidesign.it/docs/kiro-memory/", target: "_blank", rel: "noopener noreferrer", className: "text-zinc-600 hover:text-zinc-400 transition-colors", title: "Documentazione" }, /* @__PURE__ */ import_react3.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react3.default.createElement("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" }), /* @__PURE__ */ import_react3.default.createElement("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" })))), /* @__PURE__ */ import_react3.default.createElement("div", { className: "text-[10px] text-zinc-700 font-mono text-center" }, "Kiro Memory v1.8.0")));
23877
23996
  }
23878
23997
 
23879
23998
  // src/ui/viewer/components/Feed.tsx
23880
23999
  var import_react4 = __toESM(require_react(), 1);
23881
24000
  var TYPE_STYLES = {
23882
- "file-write": { border: "border-l-emerald-500", bg: "bg-emerald-500/10", text: "text-emerald-400", dot: "bg-emerald-500", label: "file-write" },
23883
- "file-read": { border: "border-l-cyan-500", bg: "bg-cyan-500/10", text: "text-cyan-400", dot: "bg-cyan-500", label: "file-read" },
24001
+ "file-write": { border: "border-l-emerald-500", bg: "bg-emerald-500/10", text: "text-emerald-400", dot: "bg-emerald-500", label: "change" },
24002
+ "file-read": { border: "border-l-cyan-500", bg: "bg-cyan-500/10", text: "text-cyan-400", dot: "bg-cyan-500", label: "read" },
23884
24003
  "command": { border: "border-l-amber-500", bg: "bg-amber-500/10", text: "text-amber-400", dot: "bg-amber-500", label: "command" },
23885
24004
  "research": { border: "border-l-blue-500", bg: "bg-blue-500/10", text: "text-blue-400", dot: "bg-blue-500", label: "research" },
23886
24005
  "delegation": { border: "border-l-violet-500", bg: "bg-violet-500/10", text: "text-violet-400", dot: "bg-violet-500", label: "delegation" },
23887
- "tool-use": { border: "border-l-zinc-500", bg: "bg-zinc-500/10", text: "text-zinc-400", dot: "bg-zinc-500", label: "tool-use" },
23888
- "constraint": { border: "border-l-red-500", bg: "bg-red-500/10", text: "text-red-400", dot: "bg-red-500", label: "constraint" },
23889
- "decision": { border: "border-l-orange-500", bg: "bg-orange-500/10", text: "text-orange-400", dot: "bg-orange-500", label: "decision" },
23890
- "heuristic": { border: "border-l-indigo-500", bg: "bg-indigo-500/10", text: "text-indigo-400", dot: "bg-indigo-500", label: "heuristic" },
23891
- "rejected": { border: "border-l-slate-500", bg: "bg-slate-500/10", text: "text-slate-400", dot: "bg-slate-500", label: "rejected" }
24006
+ "tool-use": { border: "border-l-zinc-500", bg: "bg-zinc-500/10", text: "text-zinc-400", dot: "bg-zinc-500", label: "tool" }
23892
24007
  };
23893
24008
  function getTypeStyle(type) {
23894
24009
  return TYPE_STYLES[type] || TYPE_STYLES["tool-use"];
23895
24010
  }
24011
+ var CONCEPT_COLORS = {
24012
+ "testing": "bg-emerald-500/15 text-emerald-400 ring-emerald-500/25",
24013
+ "ui-component": "bg-violet-500/15 text-violet-400 ring-violet-500/25",
24014
+ "hooks": "bg-violet-500/15 text-violet-400 ring-violet-500/25",
24015
+ "database": "bg-amber-500/15 text-amber-400 ring-amber-500/25",
24016
+ "api": "bg-blue-500/15 text-blue-400 ring-blue-500/25",
24017
+ "configuration": "bg-zinc-500/15 text-zinc-400 ring-zinc-500/25",
24018
+ "styling": "bg-pink-500/15 text-pink-400 ring-pink-500/25",
24019
+ "types": "bg-cyan-500/15 text-cyan-400 ring-cyan-500/25",
24020
+ "sdk": "bg-indigo-500/15 text-indigo-400 ring-indigo-500/25",
24021
+ "build": "bg-orange-500/15 text-orange-400 ring-orange-500/25",
24022
+ "devops": "bg-rose-500/15 text-rose-400 ring-rose-500/25",
24023
+ "documentation": "bg-teal-500/15 text-teal-400 ring-teal-500/25",
24024
+ "search": "bg-sky-500/15 text-sky-400 ring-sky-500/25",
24025
+ "backend": "bg-lime-500/15 text-lime-400 ring-lime-500/25",
24026
+ "git": "bg-orange-500/15 text-orange-400 ring-orange-500/25",
24027
+ "dependencies": "bg-yellow-500/15 text-yellow-400 ring-yellow-500/25",
24028
+ "debugging": "bg-red-500/15 text-red-400 ring-red-500/25",
24029
+ "code-quality": "bg-emerald-500/15 text-emerald-400 ring-emerald-500/25",
24030
+ "networking": "bg-blue-500/15 text-blue-400 ring-blue-500/25",
24031
+ "module-system": "bg-indigo-500/15 text-indigo-400 ring-indigo-500/25",
24032
+ "tech-debt": "bg-red-500/15 text-red-400 ring-red-500/25",
24033
+ "security": "bg-red-500/15 text-red-400 ring-red-500/25",
24034
+ "performance": "bg-amber-500/15 text-amber-400 ring-amber-500/25",
24035
+ "error-handling": "bg-rose-500/15 text-rose-400 ring-rose-500/25"
24036
+ };
24037
+ function basename(filePath) {
24038
+ return filePath.split("/").pop() || filePath;
24039
+ }
24040
+ function stripProjectRoot(path) {
24041
+ return path.replace(/^\/home\/[^/]+\/[^/]+\//, "");
24042
+ }
24043
+ function generateNarrative(obs) {
24044
+ if (obs.narrative) return obs.narrative;
24045
+ const title = obs.title || "";
24046
+ switch (obs.type) {
24047
+ case "file-write": {
24048
+ if (title.startsWith("Written: ") || title.startsWith("Modified ") || title.startsWith("Created ")) {
24049
+ const path = title.replace(/^(Written|Modified|Created):?\s*/, "");
24050
+ const fileName = basename(path);
24051
+ const isEdit = obs.text ? obs.text.includes('"old_string"') : true;
24052
+ return `${isEdit ? "Modified" : "Created"} **${fileName}**`;
24053
+ }
24054
+ return title || "File changed";
24055
+ }
24056
+ case "file-read": {
24057
+ if (title.startsWith("Searched for ") || title.startsWith("Searched codebase")) return title;
24058
+ if (title.startsWith("Read: ")) return `Read **${basename(title.replace("Read: ", ""))}**`;
24059
+ return title ? `Read **${basename(title)}**` : "File read";
24060
+ }
24061
+ case "command": {
24062
+ if (title.startsWith("Executed: ")) {
24063
+ const cmd = title.replace("Executed: ", "").split("|")[0].split("2>&1")[0].split("&&")[0].trim();
24064
+ const shortCmd = cmd.length > 70 ? cmd.substring(0, 67) + "..." : cmd;
24065
+ return `Ran \`${shortCmd}\``;
24066
+ }
24067
+ return title || "Command executed";
24068
+ }
24069
+ case "research": {
24070
+ if (title.startsWith("Searched: ")) return `Web search: "${title.replace("Searched: ", "")}"`;
24071
+ if (title.startsWith("Fetched ")) return `Fetched content from ${title.replace("Fetched ", "")}`;
24072
+ return title || "Research performed";
24073
+ }
24074
+ case "delegation": {
24075
+ const shortTitle = title.length > 100 ? title.substring(0, 97) + "..." : title;
24076
+ return `Delegated: ${shortTitle || "sub-task"}`;
24077
+ }
24078
+ default:
24079
+ return title || `Tool executed (${obs.type})`;
24080
+ }
24081
+ }
24082
+ function getDetailLine(obs) {
24083
+ const parts = [];
24084
+ if (obs.files_modified) {
24085
+ const files = obs.files_modified.split(", ").map(stripProjectRoot);
24086
+ parts.push(`${files.length} file${files.length > 1 ? "s" : ""} modified`);
24087
+ }
24088
+ if (obs.files_read) {
24089
+ const files = obs.files_read.split(", ").map(stripProjectRoot);
24090
+ if (files.length > 1) parts.push(`${files.length} files read`);
24091
+ }
24092
+ return parts.length > 0 ? parts.join(" \xB7 ") : null;
24093
+ }
24094
+ function renderMarkdown(text) {
24095
+ return text.split("**").map((segment, i) => {
24096
+ if (i % 2 === 1) return /* @__PURE__ */ import_react4.default.createElement("strong", { key: i, className: "text-zinc-100 font-semibold" }, segment);
24097
+ return segment.split("`").map(
24098
+ (part, j) => j % 2 === 1 ? /* @__PURE__ */ import_react4.default.createElement("code", { key: `${i}-${j}`, className: "text-amber-400/80 bg-amber-500/10 px-1 py-0.5 rounded text-[12px]" }, part) : /* @__PURE__ */ import_react4.default.createElement("span", { key: `${i}-${j}` }, part)
24099
+ );
24100
+ });
24101
+ }
23896
24102
  function Feed({ observations, summaries, prompts, onLoadMore, isLoading, hasMore, getDisplayName }) {
23897
- const items = [...observations, ...summaries, ...prompts].sort(
23898
- (a, b) => b.created_at_epoch - a.created_at_epoch
24103
+ const items = (0, import_react4.useMemo)(
24104
+ () => [...observations, ...summaries, ...prompts].sort(
24105
+ (a, b) => b.created_at_epoch - a.created_at_epoch
24106
+ ),
24107
+ [observations, summaries, prompts]
23899
24108
  );
23900
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: "space-y-3" }, items.map((item, index) => {
24109
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "space-y-3", "aria-live": "polite", "aria-label": "Memory feed" }, items.map((item, index) => {
23901
24110
  const stagger = index < 8 ? `stagger-${index + 1}` : "";
23902
24111
  if ("type" in item && "title" in item) {
23903
- return /* @__PURE__ */ import_react4.default.createElement("div", { key: `obs-${item.id}`, className: `opacity-0 animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(ObservationCard, { obs: item, getDisplayName }));
24112
+ return /* @__PURE__ */ import_react4.default.createElement("div", { key: `obs-${item.id}`, "data-id": `obs-${item.id}`, className: `animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(ObservationCard, { obs: item, getDisplayName }));
23904
24113
  } else if ("request" in item) {
23905
- return /* @__PURE__ */ import_react4.default.createElement("div", { key: `sum-${item.id}`, className: `opacity-0 animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(SummaryCard, { summary: item, getDisplayName }));
24114
+ return /* @__PURE__ */ import_react4.default.createElement("div", { key: `sum-${item.id}`, "data-id": `sum-${item.id}`, className: `animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(SummaryCard, { summary: item, getDisplayName }));
23906
24115
  } else {
23907
- return /* @__PURE__ */ import_react4.default.createElement("div", { key: `prompt-${item.id}`, className: `opacity-0 animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(PromptCard, { prompt: item, getDisplayName }));
24116
+ return /* @__PURE__ */ import_react4.default.createElement("div", { key: `prompt-${item.id}`, "data-id": `prompt-${item.id}`, className: `animate-slide-up ${stagger}` }, /* @__PURE__ */ import_react4.default.createElement(PromptCard, { prompt: item, getDisplayName }));
23908
24117
  }
23909
24118
  }), hasMore && items.length > 0 && /* @__PURE__ */ import_react4.default.createElement("div", { className: "pt-2" }, /* @__PURE__ */ import_react4.default.createElement(
23910
24119
  "button",
@@ -23918,30 +24127,25 @@ function Feed({ observations, summaries, prompts, onLoadMore, isLoading, hasMore
23918
24127
  }
23919
24128
  function ObservationCard({ obs, getDisplayName }) {
23920
24129
  const style = getTypeStyle(obs.type);
23921
- const [expanded, setExpanded] = (0, import_react4.useState)(false);
23922
- const longContent = obs.text && obs.text.length > 300;
23923
- const displayText = longContent && !expanded ? obs.text.substring(0, 280) + "..." : obs.text;
23924
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: `bg-surface-1 border border-border rounded-lg border-l-[3px] ${style.border} shadow-card hover:shadow-card-hover hover:border-border-hover transition-all` }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md ${style.bg} ${style.text}` }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `w-1.5 h-1.5 rounded-full ${style.dot}` }), style.label), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(obs.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-xs text-zinc-600 font-mono ml-auto" }, timeAgo(obs.created_at_epoch))), /* @__PURE__ */ import_react4.default.createElement("h3", { className: "text-[15px] font-semibold text-zinc-100 leading-snug" }, obs.title)), obs.subtitle && /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-1" }, /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs italic text-zinc-500" }, obs.subtitle)), obs.text && /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-3" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "text-sm text-zinc-400 leading-relaxed whitespace-pre-wrap break-words" }, displayText), longContent && /* @__PURE__ */ import_react4.default.createElement(
23925
- "button",
23926
- {
23927
- onClick: () => setExpanded(!expanded),
23928
- className: "text-xs text-accent-violet hover:text-accent-violet/80 mt-1.5 font-medium transition-colors"
23929
- },
23930
- expanded ? "Show less" : "Show more"
23931
- )), obs.narrative && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mx-4 mb-3 p-3 rounded-md bg-surface-2 border border-border" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "block text-[10px] font-semibold uppercase tracking-wider text-blue-400 mb-1" }, "Narrative"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs text-zinc-400 leading-relaxed" }, obs.narrative)), obs.facts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mx-4 mb-3 p-3 rounded-md bg-surface-2 border border-border" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "block text-[10px] font-semibold uppercase tracking-wider text-cyan-400 mb-1" }, "Facts"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs text-zinc-400 leading-relaxed" }, obs.facts)), (obs.files_modified || obs.files_read) && /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex flex-wrap gap-2 px-4 pb-3" }, obs.files_modified && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-medium text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-md" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" }), /* @__PURE__ */ import_react4.default.createElement("path", { d: "M14 2v6h6" })), obs.files_modified.split(",").length, " modified"), obs.files_read && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-medium text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-md" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7Z" })), obs.files_read.split(",").length, " read")), obs.concepts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex flex-wrap gap-1.5 px-4 pb-4" }, obs.concepts.split(", ").map((concept, i) => /* @__PURE__ */ import_react4.default.createElement("span", { key: i, className: "text-[11px] text-zinc-500 bg-surface-3 px-2 py-0.5 rounded-md border border-border" }, concept))));
24130
+ const narrative = generateNarrative(obs);
24131
+ const detail = getDetailLine(obs);
24132
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: `bg-surface-1 border border-border rounded-lg border-l-[3px] ${style.border} shadow-card hover:shadow-card-hover hover:border-border-hover transition-all` }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 py-3.5" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md ${style.bg} ${style.text}` }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `w-1.5 h-1.5 rounded-full ${style.dot}` }), style.label), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(obs.project)), obs.is_stale === 1 && /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-md bg-amber-500/10 text-amber-400 ring-1 ring-amber-500/25", title: "File modificato dopo l'osservazione" }, "stale"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[11px] text-zinc-500 font-mono ml-auto tabular-nums" }, timeAgo(obs.created_at_epoch))), /* @__PURE__ */ import_react4.default.createElement("h4", { className: "text-sm text-zinc-200 leading-snug" }, renderMarkdown(narrative)), obs.subtitle && obs.subtitle !== obs.title && /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-[12px] text-zinc-500 mt-1 font-mono truncate" }, stripProjectRoot(obs.subtitle)), detail && /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-[11px] text-zinc-600 mt-1.5" }, detail), /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mt-2.5" }, obs.concepts && /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex flex-wrap gap-1" }, obs.concepts.split(", ").map((concept, i) => {
24133
+ const colorClass = CONCEPT_COLORS[concept.trim()] || "bg-zinc-500/15 text-zinc-400 ring-zinc-500/25";
24134
+ return /* @__PURE__ */ import_react4.default.createElement("span", { key: i, className: `text-[10px] font-medium px-1.5 py-0.5 rounded ring-1 ${colorClass}` }, concept.trim());
24135
+ })), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[10px] text-zinc-700 font-mono ml-auto tabular-nums" }, "#", obs.id))));
23932
24136
  }
23933
24137
  function SummaryCard({ summary, getDisplayName }) {
23934
24138
  const sections = [
23935
24139
  { label: "Investigated", value: summary.investigated, color: "text-blue-400" },
23936
24140
  { label: "Learned", value: summary.learned, color: "text-emerald-400" },
23937
24141
  { label: "Completed", value: summary.completed, color: "text-violet-400" },
23938
- { label: "Next steps", value: summary.next_steps, color: "text-amber-400" },
24142
+ { label: "Next Steps", value: summary.next_steps, color: "text-amber-400" },
23939
24143
  { label: "Notes", value: summary.notes, color: "text-zinc-400" }
23940
24144
  ].filter((s) => s.value);
23941
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-cyan-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-cyan-500/10 text-cyan-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" }), /* @__PURE__ */ import_react4.default.createElement("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" })), "Summary"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(summary.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-xs text-zinc-600 font-mono ml-auto" }, timeAgo(summary.created_at_epoch))), summary.request && /* @__PURE__ */ import_react4.default.createElement("h3", { className: "text-[15px] font-semibold text-zinc-100 leading-snug" }, summary.request)), /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-4 space-y-2" }, sections.map(({ label, value, color }) => /* @__PURE__ */ import_react4.default.createElement("div", { key: label, className: "p-3 rounded-md bg-surface-2 border border-border" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `block text-[10px] font-semibold uppercase tracking-wider mb-1 ${color}` }, label), /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs text-zinc-400 leading-relaxed" }, value)))));
24145
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-cyan-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-cyan-500/10 text-cyan-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" }), /* @__PURE__ */ import_react4.default.createElement("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" })), "Session Summary"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(summary.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[11px] text-zinc-500 font-mono ml-auto" }, timeAgo(summary.created_at_epoch))), summary.request && /* @__PURE__ */ import_react4.default.createElement("h3", { className: "text-[15px] font-semibold text-zinc-100 leading-snug" }, summary.request)), /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-4 space-y-2" }, sections.map(({ label, value, color }) => /* @__PURE__ */ import_react4.default.createElement("div", { key: label, className: "p-3 rounded-md bg-surface-2 border border-border" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: `block text-[10px] font-semibold uppercase tracking-wider mb-1 ${color}` }, label), /* @__PURE__ */ import_react4.default.createElement("p", { className: "text-xs text-zinc-400 leading-relaxed" }, value)))));
23942
24146
  }
23943
24147
  function PromptCard({ prompt, getDisplayName }) {
23944
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-rose-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-rose-500/10 text-rose-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })), "Prompt #", prompt.prompt_number), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(prompt.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-xs text-zinc-600 font-mono ml-auto" }, timeAgo(prompt.created_at_epoch)))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-4" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "p-3 rounded-md bg-surface-0 border border-border font-mono text-sm text-zinc-300 leading-relaxed whitespace-pre-wrap break-words" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-rose-400 select-none mr-2" }, "$"), prompt.prompt_text)));
24148
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg border-l-[3px] border-l-rose-500 shadow-card hover:shadow-card-hover hover:border-border-hover transition-all" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pt-4 pb-2" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide px-2 py-0.5 rounded-md bg-rose-500/10 text-rose-400" }, /* @__PURE__ */ import_react4.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react4.default.createElement("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })), "Prompt #", prompt.prompt_number), /* @__PURE__ */ import_react4.default.createElement("span", { className: "inline-flex items-center text-[11px] font-medium px-2 py-0.5 rounded-md bg-accent-violet/10 text-accent-violet" }, getDisplayName(prompt.project)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "text-[11px] text-zinc-500 font-mono ml-auto" }, timeAgo(prompt.created_at_epoch)))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "px-4 pb-4" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "p-3 rounded-md bg-surface-0 border border-border text-sm text-zinc-300 leading-relaxed whitespace-pre-wrap break-words" }, prompt.prompt_text)));
23945
24149
  }
23946
24150
 
23947
24151
  // src/ui/viewer/components/Analytics.tsx
@@ -23949,6 +24153,7 @@ var import_react6 = __toESM(require_react(), 1);
23949
24153
 
23950
24154
  // src/ui/viewer/hooks/useAnalytics.ts
23951
24155
  var import_react5 = __toESM(require_react(), 1);
24156
+ var POLL_INTERVAL = 3e4;
23952
24157
  function useAnalytics(project) {
23953
24158
  const [data, setData] = (0, import_react5.useState)({
23954
24159
  overview: null,
@@ -23958,7 +24163,6 @@ function useAnalytics(project) {
23958
24163
  isLoading: true
23959
24164
  });
23960
24165
  const mountedRef = (0, import_react5.useRef)(true);
23961
- const debounceRef = (0, import_react5.useRef)(null);
23962
24166
  const fetchAnalytics = (0, import_react5.useCallback)(async () => {
23963
24167
  if (!mountedRef.current) return;
23964
24168
  const params = project ? `?project=${encodeURIComponent(project)}` : "";
@@ -23982,30 +24186,18 @@ function useAnalytics(project) {
23982
24186
  }
23983
24187
  }
23984
24188
  }, [project]);
23985
- const debouncedRefresh = (0, import_react5.useCallback)(() => {
23986
- if (debounceRef.current) clearTimeout(debounceRef.current);
23987
- debounceRef.current = setTimeout(() => {
23988
- fetchAnalytics();
23989
- }, 2e3);
23990
- }, [fetchAnalytics]);
23991
24189
  (0, import_react5.useEffect)(() => {
23992
24190
  mountedRef.current = true;
23993
24191
  setData((prev) => ({ ...prev, isLoading: true }));
23994
24192
  fetchAnalytics();
23995
- const eventSource = new EventSource("/events");
23996
- const onUpdate = () => debouncedRefresh();
23997
- eventSource.addEventListener("observation-created", onUpdate);
23998
- eventSource.addEventListener("summary-created", onUpdate);
23999
- eventSource.addEventListener("session-created", onUpdate);
24193
+ const interval = setInterval(() => {
24194
+ if (mountedRef.current) fetchAnalytics();
24195
+ }, POLL_INTERVAL);
24000
24196
  return () => {
24001
24197
  mountedRef.current = false;
24002
- if (debounceRef.current) clearTimeout(debounceRef.current);
24003
- eventSource.removeEventListener("observation-created", onUpdate);
24004
- eventSource.removeEventListener("summary-created", onUpdate);
24005
- eventSource.removeEventListener("session-created", onUpdate);
24006
- eventSource.close();
24198
+ clearInterval(interval);
24007
24199
  };
24008
- }, [fetchAnalytics, debouncedRefresh]);
24200
+ }, [fetchAnalytics]);
24009
24201
  return data;
24010
24202
  }
24011
24203
 
@@ -24072,7 +24264,7 @@ function Analytics({ currentFilter, getDisplayName }) {
24072
24264
  value: sessionStats ? `${sessionStats.total > 0 ? Math.round(sessionStats.completed / sessionStats.total * 100) : 0}%` : "\u2014",
24073
24265
  color: "text-accent-green"
24074
24266
  }
24075
- )), timeline.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Activity Timeline (30 days)"), /* @__PURE__ */ import_react6.default.createElement(TimelineChart, { entries: timeline })), typeDistribution.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Observation Types"), /* @__PURE__ */ import_react6.default.createElement(TypeDistributionChart, { entries: typeDistribution })), sessionStats && sessionStats.total > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Sessions"), /* @__PURE__ */ import_react6.default.createElement(SessionStatsPanel, { stats: sessionStats })));
24267
+ )), overview.tokenEconomics && overview.tokenEconomics.discoveryTokens > 0 && /* @__PURE__ */ import_react6.default.createElement(TokenEconomicsPanel, { economics: overview.tokenEconomics }), timeline.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Activity Timeline (30 days)"), /* @__PURE__ */ import_react6.default.createElement(TimelineChart, { entries: timeline })), typeDistribution.length > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Observation Types"), /* @__PURE__ */ import_react6.default.createElement(TypeDistributionChart, { entries: typeDistribution })), sessionStats && sessionStats.total > 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Sessions"), /* @__PURE__ */ import_react6.default.createElement(SessionStatsPanel, { stats: sessionStats })));
24076
24268
  }
24077
24269
  function StatCard({ label, value, sub, color }) {
24078
24270
  return /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: `text-2xl font-bold tabular-nums ${color}` }, value.toLocaleString()), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, label), sub && /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] text-zinc-600 mt-0.5" }, sub));
@@ -24083,80 +24275,31 @@ function MiniStat({ label, value, color }) {
24083
24275
  function TimelineChart({ entries }) {
24084
24276
  const [hoveredIndex, setHoveredIndex] = (0, import_react6.useState)(null);
24085
24277
  const maxCount = Math.max(...entries.map((e) => e.count), 1);
24086
- const barWidth = Math.max(4, Math.min(16, Math.floor(600 / entries.length) - 2));
24087
24278
  const chartHeight = 120;
24088
- const chartWidth = entries.length * (barWidth + 2);
24089
- return /* @__PURE__ */ import_react6.default.createElement("div", { className: "relative overflow-x-auto" }, /* @__PURE__ */ import_react6.default.createElement(
24090
- "svg",
24091
- {
24092
- width: Math.max(chartWidth, 200),
24093
- height: chartHeight + 24,
24094
- className: "w-full",
24095
- viewBox: `0 0 ${Math.max(chartWidth, 200)} ${chartHeight + 24}`,
24096
- preserveAspectRatio: "none"
24097
- },
24098
- entries.map((entry, i) => {
24099
- const barHeight = Math.max(2, entry.count / maxCount * chartHeight);
24100
- const x = i * (barWidth + 2);
24101
- const y = chartHeight - barHeight;
24102
- const isHovered = hoveredIndex === i;
24103
- return /* @__PURE__ */ import_react6.default.createElement(
24104
- "g",
24105
- {
24106
- key: entry.day,
24107
- onMouseEnter: () => setHoveredIndex(i),
24108
- onMouseLeave: () => setHoveredIndex(null)
24109
- },
24110
- /* @__PURE__ */ import_react6.default.createElement(
24111
- "rect",
24112
- {
24113
- x,
24114
- y,
24115
- width: barWidth,
24116
- height: barHeight,
24117
- rx: 2,
24118
- className: `transition-all ${isHovered ? "fill-accent-violet" : "fill-accent-violet/60"}`
24119
- }
24120
- ),
24121
- isHovered && /* @__PURE__ */ import_react6.default.createElement("g", null, /* @__PURE__ */ import_react6.default.createElement(
24122
- "rect",
24123
- {
24124
- x: Math.max(0, x - 30),
24125
- y: Math.max(0, y - 28),
24126
- width: 70,
24127
- height: 22,
24128
- rx: 4,
24129
- className: "fill-surface-3"
24130
- }
24131
- ), /* @__PURE__ */ import_react6.default.createElement(
24132
- "text",
24133
- {
24134
- x: Math.max(35, x + barWidth / 2),
24135
- y: Math.max(15, y - 13),
24136
- textAnchor: "middle",
24137
- className: "fill-zinc-200 text-[10px] font-mono"
24138
- },
24139
- entry.count,
24140
- " \xB7 ",
24141
- entry.day.slice(5)
24142
- ))
24143
- );
24144
- }),
24145
- entries.map((entry, i) => {
24146
- if (i % Math.max(1, Math.floor(entries.length / 6)) !== 0) return null;
24147
- return /* @__PURE__ */ import_react6.default.createElement(
24148
- "text",
24279
+ const labelInterval = Math.max(1, Math.floor(entries.length / 6));
24280
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "h-5 text-center" }, hoveredIndex !== null && entries[hoveredIndex] && /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[11px] font-mono text-zinc-300 bg-surface-3 px-2.5 py-1 rounded" }, entries[hoveredIndex].day.slice(5), " \u2014 ", /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-accent-violet font-semibold" }, entries[hoveredIndex].count), " observations")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-end gap-[2px]", style: { height: chartHeight } }, entries.map((entry, i) => {
24281
+ const heightPct = Math.max(1.5, entry.count / maxCount * 100);
24282
+ const isHovered = hoveredIndex === i;
24283
+ return /* @__PURE__ */ import_react6.default.createElement(
24284
+ "div",
24285
+ {
24286
+ key: entry.day,
24287
+ className: "flex-1 min-w-0 cursor-pointer transition-all duration-150",
24288
+ style: { height: `${heightPct}%` },
24289
+ onMouseEnter: () => setHoveredIndex(i),
24290
+ onMouseLeave: () => setHoveredIndex(null)
24291
+ },
24292
+ /* @__PURE__ */ import_react6.default.createElement(
24293
+ "div",
24149
24294
  {
24150
- key: `label-${i}`,
24151
- x: i * (barWidth + 2) + barWidth / 2,
24152
- y: chartHeight + 16,
24153
- textAnchor: "middle",
24154
- className: "fill-zinc-600 text-[8px] font-mono"
24155
- },
24156
- entry.day.slice(5)
24157
- );
24158
- })
24159
- ));
24295
+ className: `w-full h-full rounded-t transition-colors ${isHovered ? "bg-accent-violet" : "bg-accent-violet/50"}`
24296
+ }
24297
+ )
24298
+ );
24299
+ })), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex" }, entries.map((entry, i) => {
24300
+ const showLabel = i % labelInterval === 0 || i === entries.length - 1;
24301
+ return /* @__PURE__ */ import_react6.default.createElement("div", { key: `label-${i}`, className: "flex-1 min-w-0 text-center" }, showLabel && /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[10px] font-mono text-zinc-600" }, entry.day.slice(5)));
24302
+ })));
24160
24303
  }
24161
24304
  function TypeDistributionChart({ entries }) {
24162
24305
  const total = entries.reduce((sum, e) => sum + e.count, 0);
@@ -24184,29 +24327,112 @@ function SessionStatsPanel({ stats }) {
24184
24327
  }
24185
24328
  )), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center justify-between mt-1" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[10px] text-zinc-600" }, stats.completed, " completed"), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[10px] text-zinc-600" }, stats.total, " total"))), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center gap-3 p-3 rounded-lg bg-surface-2 border border-border" }, /* @__PURE__ */ import_react6.default.createElement("svg", { className: "w-4 h-4 text-accent-cyan flex-shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react6.default.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ import_react6.default.createElement("polyline", { points: "12 6 12 12 16 14" })), /* @__PURE__ */ import_react6.default.createElement("div", null, /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-sm font-semibold text-zinc-200" }, formatDuration(stats.avgDurationMinutes)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-xs text-zinc-500 ml-2" }, "avg session duration"))));
24186
24329
  }
24187
- function formatDuration(minutes) {
24188
- if (minutes < 1) return "<1m";
24189
- if (minutes < 60) return `${Math.round(minutes)}m`;
24190
- const h = Math.floor(minutes / 60);
24191
- const m = Math.round(minutes % 60);
24192
- return m > 0 ? `${h}h ${m}m` : `${h}h`;
24330
+ function TokenEconomicsPanel({ economics }) {
24331
+ const { discoveryTokens, readTokens, savings, reductionPct } = economics;
24332
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "bg-surface-1 border border-border rounded-lg p-5" }, /* @__PURE__ */ import_react6.default.createElement("h3", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mb-4" }, "Token Economics"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "grid grid-cols-3 gap-3 mb-4" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-4 py-3 text-center" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-lg font-bold text-amber-400 tabular-nums" }, formatTokenCount(discoveryTokens)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-0.5" }, "Discovery"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[9px] text-zinc-700 mt-0.5" }, "tokens spent")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-4 py-3 text-center" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-lg font-bold text-cyan-400 tabular-nums" }, formatTokenCount(readTokens)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-0.5" }, "Read Cost"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[9px] text-zinc-700 mt-0.5" }, "to reuse context")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-2 border border-border px-4 py-3 text-center" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-lg font-bold text-emerald-400 tabular-nums" }, formatTokenCount(savings)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-0.5" }, "Savings"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[9px] text-zinc-700 mt-0.5" }, "tokens saved"))), /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center justify-between text-[10px] text-zinc-500" }, /* @__PURE__ */ import_react6.default.createElement("span", null, "Read cost vs Discovery cost"), /* @__PURE__ */ import_react6.default.createElement("span", { className: "font-bold text-emerald-400" }, reductionPct, "% reduction")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "h-3 bg-surface-3 rounded-full overflow-hidden flex" }, /* @__PURE__ */ import_react6.default.createElement(
24333
+ "div",
24334
+ {
24335
+ className: "h-full bg-gradient-to-r from-cyan-500 to-cyan-400 rounded-l-full transition-all",
24336
+ style: { width: `${Math.min(100, discoveryTokens > 0 ? Math.round(readTokens / discoveryTokens * 100) : 0)}%` }
24337
+ }
24338
+ ), /* @__PURE__ */ import_react6.default.createElement(
24339
+ "div",
24340
+ {
24341
+ className: "h-full bg-gradient-to-r from-emerald-500/40 to-emerald-400/40 rounded-r-full transition-all flex-1"
24342
+ }
24343
+ )), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center justify-between text-[9px] text-zinc-700" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "w-2 h-2 rounded-full bg-cyan-500 inline-block" }), "Read: ", formatTokenCount(readTokens)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "flex items-center gap-1" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "w-2 h-2 rounded-full bg-emerald-500/40 inline-block" }), "Saved: ", formatTokenCount(savings)))));
24193
24344
  }
24194
24345
 
24195
- // src/ui/viewer/hooks/useSSE.ts
24346
+ // src/ui/viewer/components/Sessions.tsx
24196
24347
  var import_react7 = __toESM(require_react(), 1);
24348
+ var STATUS_STYLES = {
24349
+ active: { dot: "bg-accent-green animate-pulse-dot", text: "text-accent-green", label: "Active" },
24350
+ completed: { dot: "bg-accent-blue", text: "text-accent-blue", label: "Completed" },
24351
+ failed: { dot: "bg-accent-rose", text: "text-accent-rose", label: "Failed" }
24352
+ };
24353
+ function Sessions({ currentFilter, getDisplayName }) {
24354
+ const [sessions, setSessions] = (0, import_react7.useState)([]);
24355
+ const [isLoading, setIsLoading] = (0, import_react7.useState)(true);
24356
+ const [expandedId, setExpandedId] = (0, import_react7.useState)(null);
24357
+ (0, import_react7.useEffect)(() => {
24358
+ setIsLoading(true);
24359
+ const params = currentFilter ? `?project=${encodeURIComponent(currentFilter)}` : "";
24360
+ fetch(`/api/sessions${params}`).then((r) => r.ok ? r.json() : []).then((data) => setSessions(Array.isArray(data) ? data : [])).catch(() => setSessions([])).finally(() => setIsLoading(false));
24361
+ }, [currentFilter]);
24362
+ if (isLoading) {
24363
+ return /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col items-center justify-center py-20" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-6 h-6 border-2 border-accent-violet/30 border-t-accent-violet rounded-full animate-spin mb-4" }), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-sm text-zinc-500" }, "Loading sessions..."));
24364
+ }
24365
+ if (sessions.length === 0) {
24366
+ return /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-16 h-16 rounded-2xl bg-surface-2 border border-border flex items-center justify-center mb-5" }, /* @__PURE__ */ import_react7.default.createElement("svg", { className: "w-7 h-7 text-zinc-600", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }, /* @__PURE__ */ import_react7.default.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ import_react7.default.createElement("polyline", { points: "12 6 12 12 16 14" }))), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-base font-semibold text-zinc-300 mb-2" }, "No sessions found"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-sm text-zinc-500 max-w-xs leading-relaxed" }, "Start a coding session to see it tracked here."));
24367
+ }
24368
+ const total = sessions.length;
24369
+ const completed = sessions.filter((s) => s.status === "completed").length;
24370
+ const active = sessions.filter((s) => s.status === "active").length;
24371
+ const avgDuration = (() => {
24372
+ const completedSessions = sessions.filter((s) => s.completed_at_epoch && s.started_at_epoch);
24373
+ if (completedSessions.length === 0) return 0;
24374
+ const totalMs = completedSessions.reduce((sum, s) => sum + ((s.completed_at_epoch || 0) - s.started_at_epoch), 0);
24375
+ return totalMs / completedSessions.length / 6e4;
24376
+ })();
24377
+ return /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "grid grid-cols-2 lg:grid-cols-4 gap-3" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-violet" }, total), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Total")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-green" }, active), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Active")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-blue" }, completed), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Completed")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-4 py-4" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-2xl font-bold tabular-nums text-accent-cyan" }, formatDuration(avgDuration)), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[11px] font-semibold uppercase tracking-wider text-zinc-500 mt-1" }, "Avg Duration"))), /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-2" }, sessions.map((session) => {
24378
+ const style = STATUS_STYLES[session.status] || STATUS_STYLES.active;
24379
+ const isExpanded = expandedId === session.id;
24380
+ const duration = session.completed_at_epoch ? (session.completed_at_epoch - session.started_at_epoch) / 6e4 : (Date.now() - session.started_at_epoch) / 6e4;
24381
+ return /* @__PURE__ */ import_react7.default.createElement(
24382
+ "div",
24383
+ {
24384
+ key: session.id,
24385
+ className: "bg-surface-1 border border-border rounded-lg overflow-hidden transition-all hover:border-border-hover"
24386
+ },
24387
+ /* @__PURE__ */ import_react7.default.createElement(
24388
+ "button",
24389
+ {
24390
+ onClick: () => setExpandedId(isExpanded ? null : session.id),
24391
+ className: "w-full flex items-center gap-3 px-4 py-3 text-left",
24392
+ "aria-expanded": isExpanded
24393
+ },
24394
+ /* @__PURE__ */ import_react7.default.createElement("div", { className: `w-2.5 h-2.5 rounded-full flex-shrink-0 ${style.dot}` }),
24395
+ /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-sm font-medium text-zinc-200 truncate" }, session.user_prompt || "Session"), /* @__PURE__ */ import_react7.default.createElement("span", { className: `text-[10px] font-medium px-1.5 py-0.5 rounded ${style.text} bg-surface-3` }, style.label)), /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex items-center gap-3 mt-1" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[11px] text-zinc-500" }, getDisplayName(session.project)), /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, timeAgo(session.started_at_epoch)), /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[10px] text-zinc-600 font-mono" }, formatDuration(duration)))),
24396
+ /* @__PURE__ */ import_react7.default.createElement(
24397
+ "svg",
24398
+ {
24399
+ className: `w-4 h-4 text-zinc-600 transition-transform ${isExpanded ? "rotate-180" : ""}`,
24400
+ viewBox: "0 0 24 24",
24401
+ fill: "none",
24402
+ stroke: "currentColor",
24403
+ strokeWidth: "2"
24404
+ },
24405
+ /* @__PURE__ */ import_react7.default.createElement("path", { d: "m6 9 6 6 6-6" })
24406
+ )
24407
+ ),
24408
+ isExpanded && /* @__PURE__ */ import_react7.default.createElement("div", { className: "px-4 pb-3 pt-0 border-t border-border space-y-2 animate-fade-in" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "grid grid-cols-2 gap-3 text-xs" }, /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Session ID"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px] truncate" }, session.content_session_id)), /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Memory ID"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px] truncate" }, session.memory_session_id || "\u2014")), /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Started"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px]" }, new Date(session.started_at_epoch).toLocaleString())), /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-zinc-600" }, "Completed"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-zinc-400 font-mono text-[11px]" }, session.completed_at_epoch ? new Date(session.completed_at_epoch).toLocaleString() : "\u2014"))), session.user_prompt && /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-[11px] text-zinc-600" }, "Prompt"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-xs text-zinc-400 mt-0.5 leading-relaxed" }, session.user_prompt)))
24409
+ );
24410
+ })));
24411
+ }
24412
+
24413
+ // src/ui/viewer/hooks/useSSE.ts
24414
+ var import_react8 = __toESM(require_react(), 1);
24415
+ var POLL_INTERVAL2 = 3e4;
24197
24416
  function useSSE() {
24198
- const [state, setState] = (0, import_react7.useState)({
24417
+ const [state, setState] = (0, import_react8.useState)({
24199
24418
  observations: [],
24200
24419
  summaries: [],
24201
24420
  prompts: [],
24202
24421
  projects: [],
24203
- isConnected: false
24422
+ isConnected: false,
24423
+ lastEventTime: 0
24204
24424
  });
24205
- const mountedRef = (0, import_react7.useRef)(true);
24206
- (0, import_react7.useEffect)(() => {
24425
+ const mountedRef = (0, import_react8.useRef)(true);
24426
+ const touchLastEvent = (0, import_react8.useCallback)(() => {
24427
+ if (mountedRef.current) {
24428
+ setState((prev) => ({ ...prev, lastEventTime: Date.now() }));
24429
+ }
24430
+ }, []);
24431
+ (0, import_react8.useEffect)(() => {
24207
24432
  mountedRef.current = true;
24208
24433
  let eventSource = null;
24209
24434
  let retryTimeout = null;
24435
+ let pollInterval = null;
24210
24436
  let retryCount = 0;
24211
24437
  const MAX_RETRY_DELAY = 3e4;
24212
24438
  const fetchObservations = async () => {
@@ -24214,7 +24440,7 @@ function useSSE() {
24214
24440
  const res = await fetch("/api/observations?limit=50");
24215
24441
  if (res.ok && mountedRef.current) {
24216
24442
  const observations = await res.json();
24217
- setState((prev) => ({ ...prev, observations }));
24443
+ setState((prev) => ({ ...prev, observations, lastEventTime: Date.now() }));
24218
24444
  }
24219
24445
  } catch (err) {
24220
24446
  console.error("Failed to fetch observations:", err);
@@ -24269,6 +24495,15 @@ function useSSE() {
24269
24495
  const onPrompt = () => {
24270
24496
  fetchPrompts();
24271
24497
  };
24498
+ const onSession = () => {
24499
+ fetchProjects();
24500
+ };
24501
+ const startPolling = () => {
24502
+ if (pollInterval) clearInterval(pollInterval);
24503
+ pollInterval = setInterval(() => {
24504
+ if (mountedRef.current) fetchAll();
24505
+ }, POLL_INTERVAL2);
24506
+ };
24272
24507
  let wasConnected = false;
24273
24508
  const connect = () => {
24274
24509
  if (!mountedRef.current) return;
@@ -24280,7 +24515,7 @@ function useSSE() {
24280
24515
  }
24281
24516
  wasConnected = true;
24282
24517
  retryCount = 0;
24283
- setState((prev) => ({ ...prev, isConnected: true }));
24518
+ setState((prev) => ({ ...prev, isConnected: true, lastEventTime: Date.now() }));
24284
24519
  };
24285
24520
  eventSource.onerror = () => {
24286
24521
  if (!mountedRef.current) return;
@@ -24289,6 +24524,7 @@ function useSSE() {
24289
24524
  eventSource.removeEventListener("observation-created", onObservation);
24290
24525
  eventSource.removeEventListener("summary-created", onSummary);
24291
24526
  eventSource.removeEventListener("prompt-created", onPrompt);
24527
+ eventSource.removeEventListener("session-created", onSession);
24292
24528
  eventSource.close();
24293
24529
  }
24294
24530
  eventSource = null;
@@ -24299,35 +24535,39 @@ function useSSE() {
24299
24535
  eventSource.addEventListener("observation-created", onObservation);
24300
24536
  eventSource.addEventListener("summary-created", onSummary);
24301
24537
  eventSource.addEventListener("prompt-created", onPrompt);
24538
+ eventSource.addEventListener("session-created", onSession);
24302
24539
  };
24303
24540
  fetchAll();
24304
24541
  connect();
24542
+ startPolling();
24305
24543
  return () => {
24306
24544
  mountedRef.current = false;
24307
24545
  if (eventSource) {
24308
24546
  eventSource.removeEventListener("observation-created", onObservation);
24309
24547
  eventSource.removeEventListener("summary-created", onSummary);
24310
24548
  eventSource.removeEventListener("prompt-created", onPrompt);
24549
+ eventSource.removeEventListener("session-created", onSession);
24311
24550
  eventSource.close();
24312
24551
  }
24313
24552
  if (retryTimeout) clearTimeout(retryTimeout);
24553
+ if (pollInterval) clearInterval(pollInterval);
24314
24554
  };
24315
24555
  }, []);
24316
24556
  return state;
24317
24557
  }
24318
24558
 
24319
24559
  // src/ui/viewer/hooks/useTheme.ts
24320
- var import_react8 = __toESM(require_react(), 1);
24560
+ var import_react9 = __toESM(require_react(), 1);
24321
24561
  function useTheme() {
24322
- const [preference, setPreference] = (0, import_react8.useState)("dark");
24323
- const [resolvedTheme, setResolvedTheme] = (0, import_react8.useState)("dark");
24324
- (0, import_react8.useEffect)(() => {
24562
+ const [preference, setPreference] = (0, import_react9.useState)("dark");
24563
+ const [resolvedTheme, setResolvedTheme] = (0, import_react9.useState)("dark");
24564
+ (0, import_react9.useEffect)(() => {
24325
24565
  const saved = localStorage.getItem("kiro-memory-theme");
24326
24566
  if (saved) {
24327
24567
  setPreference(saved);
24328
24568
  }
24329
24569
  }, []);
24330
- (0, import_react8.useEffect)(() => {
24570
+ (0, import_react9.useEffect)(() => {
24331
24571
  let resolved;
24332
24572
  if (preference === "system") {
24333
24573
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
@@ -24342,7 +24582,7 @@ function useTheme() {
24342
24582
  document.documentElement.classList.remove("dark");
24343
24583
  }
24344
24584
  }, [preference]);
24345
- (0, import_react8.useEffect)(() => {
24585
+ (0, import_react9.useEffect)(() => {
24346
24586
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
24347
24587
  const handler = (e) => {
24348
24588
  if (preference === "system") {
@@ -24366,11 +24606,11 @@ function useTheme() {
24366
24606
  }
24367
24607
 
24368
24608
  // src/ui/viewer/hooks/useProjectAliases.ts
24369
- var import_react9 = __toESM(require_react(), 1);
24609
+ var import_react10 = __toESM(require_react(), 1);
24370
24610
  function useProjectAliases() {
24371
- const [aliases, setAliases] = (0, import_react9.useState)({});
24372
- const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
24373
- (0, import_react9.useEffect)(() => {
24611
+ const [aliases, setAliases] = (0, import_react10.useState)({});
24612
+ const [isLoading, setIsLoading] = (0, import_react10.useState)(false);
24613
+ (0, import_react10.useEffect)(() => {
24374
24614
  const fetchAliases = async () => {
24375
24615
  try {
24376
24616
  const res = await fetch("/api/project-aliases");
@@ -24384,10 +24624,10 @@ function useProjectAliases() {
24384
24624
  };
24385
24625
  fetchAliases();
24386
24626
  }, []);
24387
- const getDisplayName = (0, import_react9.useCallback)((project) => {
24627
+ const getDisplayName = (0, import_react10.useCallback)((project) => {
24388
24628
  return aliases[project] || project;
24389
24629
  }, [aliases]);
24390
- const updateAlias = (0, import_react9.useCallback)(async (project, displayName) => {
24630
+ const updateAlias = (0, import_react10.useCallback)(async (project, displayName) => {
24391
24631
  setIsLoading(true);
24392
24632
  try {
24393
24633
  const res = await fetch(`/api/project-aliases/${encodeURIComponent(project)}`, {
@@ -24433,39 +24673,50 @@ function mergeAndDeduplicateByProject(liveData, paginatedData) {
24433
24673
  // src/ui/viewer/App.tsx
24434
24674
  var TYPE_FILTERS = ["file-write", "file-read", "command", "research", "delegation", "tool-use"];
24435
24675
  function App() {
24436
- const [currentFilter, setCurrentFilter] = (0, import_react10.useState)("");
24437
- const [currentView, setCurrentView] = (0, import_react10.useState)("feed");
24438
- const [activeTypes, setActiveTypes] = (0, import_react10.useState)(new Set(TYPE_FILTERS));
24439
- const [paginatedObservations, setPaginatedObservations] = (0, import_react10.useState)([]);
24440
- const [paginatedSummaries, setPaginatedSummaries] = (0, import_react10.useState)([]);
24441
- const [paginatedPrompts, setPaginatedPrompts] = (0, import_react10.useState)([]);
24442
- const [isLoadingMore, setIsLoadingMore] = (0, import_react10.useState)(false);
24443
- const [hasMore, setHasMore] = (0, import_react10.useState)(true);
24444
- const { observations, summaries, prompts, projects, isConnected } = useSSE();
24445
- const { resolvedTheme, setThemePreference } = useTheme();
24676
+ const [currentFilter, setCurrentFilter] = (0, import_react11.useState)("");
24677
+ const [currentView, setCurrentView] = (0, import_react11.useState)("feed");
24678
+ const [activeTypes, setActiveTypes] = (0, import_react11.useState)(new Set(TYPE_FILTERS));
24679
+ const [paginatedObservations, setPaginatedObservations] = (0, import_react11.useState)([]);
24680
+ const [paginatedSummaries, setPaginatedSummaries] = (0, import_react11.useState)([]);
24681
+ const [paginatedPrompts, setPaginatedPrompts] = (0, import_react11.useState)([]);
24682
+ const [isLoadingMore, setIsLoadingMore] = (0, import_react11.useState)(false);
24683
+ const [hasMore, setHasMore] = (0, import_react11.useState)({ observations: true, summaries: true, prompts: true });
24684
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = (0, import_react11.useState)(false);
24685
+ const { observations, summaries, prompts, projects, isConnected, lastEventTime } = useSSE();
24686
+ const { preference: themePreference, resolvedTheme, setThemePreference } = useTheme();
24446
24687
  const { getDisplayName, updateAlias } = useProjectAliases();
24447
- const allObservations = (0, import_react10.useMemo)(() => {
24688
+ const allObservations = (0, import_react11.useMemo)(() => {
24448
24689
  if (currentFilter) return paginatedObservations;
24449
24690
  return mergeAndDeduplicateByProject(observations, paginatedObservations);
24450
24691
  }, [observations, paginatedObservations, currentFilter]);
24451
- const allSummaries = (0, import_react10.useMemo)(() => {
24692
+ const allSummaries = (0, import_react11.useMemo)(() => {
24452
24693
  if (currentFilter) return paginatedSummaries;
24453
24694
  return mergeAndDeduplicateByProject(summaries, paginatedSummaries);
24454
24695
  }, [summaries, paginatedSummaries, currentFilter]);
24455
- const allPrompts = (0, import_react10.useMemo)(() => {
24696
+ const allPrompts = (0, import_react11.useMemo)(() => {
24456
24697
  if (currentFilter) return paginatedPrompts;
24457
24698
  return mergeAndDeduplicateByProject(prompts, paginatedPrompts);
24458
24699
  }, [prompts, paginatedPrompts, currentFilter]);
24459
- const filteredObservations = (0, import_react10.useMemo)(
24700
+ const filteredObservations = (0, import_react11.useMemo)(
24460
24701
  () => allObservations.filter((o) => activeTypes.has(o.type)),
24461
24702
  [allObservations, activeTypes]
24462
24703
  );
24463
- const stats = (0, import_react10.useMemo)(() => ({
24464
- observations: allObservations.length,
24465
- summaries: allSummaries.length,
24466
- prompts: allPrompts.length
24467
- }), [allObservations, allSummaries, allPrompts]);
24468
- const toggleType = (0, import_react10.useCallback)((type) => {
24704
+ const [stats, setStats] = (0, import_react11.useState)({ observations: 0, summaries: 0, prompts: 0, tokenEconomics: { discoveryTokens: 0, readTokens: 0, savings: 0 } });
24705
+ (0, import_react11.useEffect)(() => {
24706
+ const params = currentFilter ? `?project=${encodeURIComponent(currentFilter)}` : "";
24707
+ fetch(`/api/analytics/overview${params}`).then((r) => r.ok ? r.json() : null).then((data) => {
24708
+ if (data) {
24709
+ setStats({
24710
+ observations: data.observations || 0,
24711
+ summaries: data.summaries || 0,
24712
+ prompts: data.prompts || 0,
24713
+ tokenEconomics: data.tokenEconomics || { discoveryTokens: 0, readTokens: 0, savings: 0 }
24714
+ });
24715
+ }
24716
+ }).catch(() => {
24717
+ });
24718
+ }, [currentFilter, allObservations.length]);
24719
+ const toggleType = (0, import_react11.useCallback)((type) => {
24469
24720
  setActiveTypes((prev) => {
24470
24721
  const next = new Set(prev);
24471
24722
  if (next.has(type)) next.delete(type);
@@ -24473,7 +24724,7 @@ function App() {
24473
24724
  return next;
24474
24725
  });
24475
24726
  }, []);
24476
- const fetchForProject = (0, import_react10.useCallback)(async (project) => {
24727
+ const fetchForProject = (0, import_react11.useCallback)(async (project) => {
24477
24728
  setIsLoadingMore(true);
24478
24729
  try {
24479
24730
  const params = new URLSearchParams({
@@ -24495,63 +24746,64 @@ function App() {
24495
24746
  setIsLoadingMore(false);
24496
24747
  }
24497
24748
  }, []);
24498
- const handleLoadMore = (0, import_react10.useCallback)(async () => {
24749
+ const handleLoadMore = (0, import_react11.useCallback)(async () => {
24499
24750
  if (isLoadingMore) return;
24751
+ if (!hasMore.observations && !hasMore.summaries && !hasMore.prompts) return;
24500
24752
  setIsLoadingMore(true);
24501
24753
  try {
24502
- const offset = paginatedObservations.length;
24503
- const params = new URLSearchParams({
24504
- offset: String(offset),
24505
- limit: "20",
24506
- ...currentFilter && { project: currentFilter }
24507
- });
24508
- const [obsRes, sumRes, promptRes] = await Promise.all([
24509
- fetch(`/api/observations?${params}`),
24510
- fetch(`/api/summaries?${params}`),
24511
- fetch(`/api/prompts?${params}`)
24754
+ const limit = "20";
24755
+ const projectParam = currentFilter ? `&project=${encodeURIComponent(currentFilter)}` : "";
24756
+ const fetches = await Promise.all([
24757
+ hasMore.observations ? fetch(`/api/observations?offset=${paginatedObservations.length}&limit=${limit}${projectParam}`) : null,
24758
+ hasMore.summaries ? fetch(`/api/summaries?offset=${paginatedSummaries.length}&limit=${limit}${projectParam}`) : null,
24759
+ hasMore.prompts ? fetch(`/api/prompts?offset=${paginatedPrompts.length}&limit=${limit}${projectParam}`) : null
24512
24760
  ]);
24513
- let newItems = 0;
24514
- if (obsRes.ok) {
24761
+ const [obsRes, sumRes, promptRes] = fetches;
24762
+ const nextHasMore = { ...hasMore };
24763
+ if (obsRes?.ok) {
24515
24764
  const newObs = await obsRes.json();
24516
- newItems += newObs.length;
24517
- setPaginatedObservations((prev) => [...prev, ...newObs]);
24765
+ if (newObs.length === 0) nextHasMore.observations = false;
24766
+ else setPaginatedObservations((prev) => [...prev, ...newObs]);
24518
24767
  }
24519
- if (sumRes.ok) {
24768
+ if (sumRes?.ok) {
24520
24769
  const newSum = await sumRes.json();
24521
- newItems += newSum.length;
24522
- setPaginatedSummaries((prev) => [...prev, ...newSum]);
24770
+ if (newSum.length === 0) nextHasMore.summaries = false;
24771
+ else setPaginatedSummaries((prev) => [...prev, ...newSum]);
24523
24772
  }
24524
- if (promptRes.ok) {
24773
+ if (promptRes?.ok) {
24525
24774
  const newPrompts = await promptRes.json();
24526
- newItems += newPrompts.length;
24527
- setPaginatedPrompts((prev) => [...prev, ...newPrompts]);
24775
+ if (newPrompts.length === 0) nextHasMore.prompts = false;
24776
+ else setPaginatedPrompts((prev) => [...prev, ...newPrompts]);
24528
24777
  }
24529
- if (newItems === 0) setHasMore(false);
24778
+ setHasMore(nextHasMore);
24530
24779
  } catch (error) {
24531
24780
  console.error("Failed to load more data:", error);
24532
24781
  } finally {
24533
24782
  setIsLoadingMore(false);
24534
24783
  }
24535
- }, [currentFilter, paginatedObservations.length, isLoadingMore]);
24536
- (0, import_react10.useEffect)(() => {
24784
+ }, [currentFilter, paginatedObservations.length, paginatedSummaries.length, paginatedPrompts.length, isLoadingMore, hasMore]);
24785
+ (0, import_react11.useEffect)(() => {
24537
24786
  setPaginatedObservations([]);
24538
24787
  setPaginatedSummaries([]);
24539
24788
  setPaginatedPrompts([]);
24540
- setHasMore(true);
24789
+ setHasMore({ observations: true, summaries: true, prompts: true });
24541
24790
  if (currentFilter) {
24542
24791
  fetchForProject(currentFilter);
24543
24792
  }
24544
24793
  }, [currentFilter, fetchForProject]);
24545
- return /* @__PURE__ */ import_react10.default.createElement("div", { className: "h-screen overflow-hidden flex flex-col bg-surface-0" }, /* @__PURE__ */ import_react10.default.createElement(
24794
+ return /* @__PURE__ */ import_react11.default.createElement("div", { className: "h-screen overflow-hidden flex flex-col bg-surface-0" }, /* @__PURE__ */ import_react11.default.createElement(
24546
24795
  Header,
24547
24796
  {
24548
24797
  isConnected,
24798
+ lastEventTime,
24549
24799
  resolvedTheme,
24550
- onThemeToggle: () => setThemePreference(resolvedTheme === "dark" ? "light" : "dark"),
24800
+ themePreference,
24801
+ onThemeChange: setThemePreference,
24551
24802
  currentView,
24552
- onViewChange: setCurrentView
24803
+ onViewChange: setCurrentView,
24804
+ onMenuToggle: () => setIsMobileMenuOpen((prev) => !prev)
24553
24805
  }
24554
- ), /* @__PURE__ */ import_react10.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "hidden md:flex w-[260px] flex-shrink-0" }, /* @__PURE__ */ import_react10.default.createElement(
24806
+ ), /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "hidden md:flex w-[260px] flex-shrink-0" }, /* @__PURE__ */ import_react11.default.createElement(
24555
24807
  Sidebar,
24556
24808
  {
24557
24809
  projects,
@@ -24563,15 +24815,36 @@ function App() {
24563
24815
  getDisplayName,
24564
24816
  onRenameProject: updateAlias
24565
24817
  }
24566
- )), /* @__PURE__ */ import_react10.default.createElement("main", { className: "flex-1 overflow-y-auto bg-surface-0" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "max-w-3xl mx-auto px-6 py-6" }, currentFilter && /* @__PURE__ */ import_react10.default.createElement("div", { className: "flex items-center gap-3 mb-6 animate-fade-in" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "w-9 h-9 rounded-lg bg-accent-violet/15 flex items-center justify-center" }, /* @__PURE__ */ import_react10.default.createElement("span", { className: "text-xs font-bold text-accent-violet" }, getDisplayName(currentFilter).substring(0, 2).toUpperCase())), /* @__PURE__ */ import_react10.default.createElement("div", null, /* @__PURE__ */ import_react10.default.createElement("h2", { className: "text-lg font-bold text-zinc-100" }, getDisplayName(currentFilter)), currentFilter !== getDisplayName(currentFilter) && /* @__PURE__ */ import_react10.default.createElement("span", { className: "text-[11px] font-mono text-zinc-600" }, currentFilter))), /* @__PURE__ */ import_react10.default.createElement(
24818
+ )), isMobileMenuOpen && /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement(
24819
+ "div",
24820
+ {
24821
+ className: "fixed inset-0 bg-black/50 z-40 md:hidden",
24822
+ onClick: () => setIsMobileMenuOpen(false)
24823
+ }
24824
+ ), /* @__PURE__ */ import_react11.default.createElement("div", { className: "fixed inset-y-0 left-0 w-[280px] z-50 md:hidden animate-slide-in-left" }, /* @__PURE__ */ import_react11.default.createElement(
24825
+ Sidebar,
24826
+ {
24827
+ projects,
24828
+ currentFilter,
24829
+ onFilterChange: (p) => {
24830
+ setCurrentFilter(p);
24831
+ setIsMobileMenuOpen(false);
24832
+ },
24833
+ activeTypes,
24834
+ onToggleType: toggleType,
24835
+ stats,
24836
+ getDisplayName,
24837
+ onRenameProject: updateAlias
24838
+ }
24839
+ ))), /* @__PURE__ */ import_react11.default.createElement("main", { className: "flex-1 overflow-y-auto bg-surface-0" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "max-w-3xl mx-auto px-6 py-6" }, currentFilter && /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex items-center gap-3 mb-6 animate-fade-in" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "w-9 h-9 rounded-lg bg-accent-violet/15 flex items-center justify-center" }, /* @__PURE__ */ import_react11.default.createElement("span", { className: "text-xs font-bold text-accent-violet" }, getDisplayName(currentFilter).substring(0, 2).toUpperCase())), /* @__PURE__ */ import_react11.default.createElement("div", null, /* @__PURE__ */ import_react11.default.createElement("h2", { className: "text-lg font-bold text-zinc-100" }, getDisplayName(currentFilter)), currentFilter !== getDisplayName(currentFilter) && /* @__PURE__ */ import_react11.default.createElement("span", { className: "text-[11px] font-mono text-zinc-600" }, currentFilter))), /* @__PURE__ */ import_react11.default.createElement(
24567
24840
  "button",
24568
24841
  {
24569
24842
  onClick: () => setCurrentFilter(""),
24570
24843
  className: "ml-auto flex items-center gap-1.5 text-xs text-zinc-500 hover:text-zinc-300 transition-colors px-3 py-1.5 rounded-lg hover:bg-surface-2 border border-transparent hover:border-border"
24571
24844
  },
24572
- /* @__PURE__ */ import_react10.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react10.default.createElement("path", { d: "M18 6 6 18M6 6l12 12" })),
24845
+ /* @__PURE__ */ import_react11.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react11.default.createElement("path", { d: "M18 6 6 18M6 6l12 12" })),
24573
24846
  "Clear filter"
24574
- )), currentView === "feed" ? /* @__PURE__ */ import_react10.default.createElement(
24847
+ )), currentView === "feed" ? /* @__PURE__ */ import_react11.default.createElement(
24575
24848
  Feed,
24576
24849
  {
24577
24850
  observations: filteredObservations,
@@ -24579,10 +24852,16 @@ function App() {
24579
24852
  prompts: allPrompts,
24580
24853
  onLoadMore: handleLoadMore,
24581
24854
  isLoading: isLoadingMore,
24582
- hasMore,
24855
+ hasMore: hasMore.observations || hasMore.summaries || hasMore.prompts,
24856
+ getDisplayName
24857
+ }
24858
+ ) : currentView === "sessions" ? /* @__PURE__ */ import_react11.default.createElement(
24859
+ Sessions,
24860
+ {
24861
+ currentFilter,
24583
24862
  getDisplayName
24584
24863
  }
24585
- ) : /* @__PURE__ */ import_react10.default.createElement(
24864
+ ) : /* @__PURE__ */ import_react11.default.createElement(
24586
24865
  Analytics,
24587
24866
  {
24588
24867
  currentFilter,
@@ -24594,7 +24873,7 @@ function App() {
24594
24873
  // src/ui/viewer/index.tsx
24595
24874
  var root = import_client.default.createRoot(document.getElementById("root"));
24596
24875
  root.render(
24597
- /* @__PURE__ */ import_react11.default.createElement(import_react11.default.StrictMode, null, /* @__PURE__ */ import_react11.default.createElement(App, null))
24876
+ /* @__PURE__ */ import_react12.default.createElement(import_react12.default.StrictMode, null, /* @__PURE__ */ import_react12.default.createElement(App, null))
24598
24877
  );
24599
24878
  /*! Bundled license information:
24600
24879