kiro-memory 1.5.0 → 1.7.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 useState8(initialState) {
1086
+ function useState10(initialState) {
1087
1087
  var dispatcher = resolveDispatcher();
1088
1088
  return dispatcher.useState(initialState);
1089
1089
  }
@@ -1091,11 +1091,11 @@ var require_react_development = __commonJS({
1091
1091
  var dispatcher = resolveDispatcher();
1092
1092
  return dispatcher.useReducer(reducer, initialArg, init);
1093
1093
  }
1094
- function useRef4(initialValue) {
1094
+ function useRef5(initialValue) {
1095
1095
  var dispatcher = resolveDispatcher();
1096
1096
  return dispatcher.useRef(initialValue);
1097
1097
  }
1098
- function useEffect7(create, deps) {
1098
+ function useEffect8(create, deps) {
1099
1099
  var dispatcher = resolveDispatcher();
1100
1100
  return dispatcher.useEffect(create, deps);
1101
1101
  }
@@ -1107,7 +1107,7 @@ var require_react_development = __commonJS({
1107
1107
  var dispatcher = resolveDispatcher();
1108
1108
  return dispatcher.useLayoutEffect(create, deps);
1109
1109
  }
1110
- function useCallback4(callback, deps) {
1110
+ function useCallback5(callback, deps) {
1111
1111
  var dispatcher = resolveDispatcher();
1112
1112
  return dispatcher.useCallback(callback, deps);
1113
1113
  }
@@ -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 = useCallback4;
1877
+ exports.useCallback = useCallback5;
1878
1878
  exports.useContext = useContext;
1879
1879
  exports.useDebugValue = useDebugValue;
1880
1880
  exports.useDeferredValue = useDeferredValue;
1881
- exports.useEffect = useEffect7;
1881
+ exports.useEffect = useEffect8;
1882
1882
  exports.useId = useId;
1883
1883
  exports.useImperativeHandle = useImperativeHandle;
1884
1884
  exports.useInsertionEffect = useInsertionEffect;
1885
1885
  exports.useLayoutEffect = useLayoutEffect;
1886
1886
  exports.useMemo = useMemo2;
1887
1887
  exports.useReducer = useReducer;
1888
- exports.useRef = useRef4;
1889
- exports.useState = useState8;
1888
+ exports.useRef = useRef5;
1889
+ exports.useState = useState10;
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 React7 = require_react();
2385
+ var React8 = require_react();
2386
2386
  var Scheduler = require_scheduler();
2387
- var ReactSharedInternals = React7.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
2387
+ var ReactSharedInternals = React8.__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
- React7.Children.forEach(props.children, function(child) {
3994
+ React8.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_react9 = __toESM(require_react(), 1);
23584
+ var import_react11 = __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_react8 = __toESM(require_react(), 1);
23588
+ var import_react10 = __toESM(require_react(), 1);
23589
23589
 
23590
23590
  // src/ui/viewer/components/Header.tsx
23591
23591
  var import_react2 = __toESM(require_react(), 1);
@@ -23601,7 +23601,11 @@ function getTypeBadgeClasses(type) {
23601
23601
  "command": { bg: "bg-amber-500/10 dark:bg-amber-500/10", text: "text-amber-600 dark:text-amber-400", dot: "bg-amber-500" },
23602
23602
  "research": { bg: "bg-blue-500/10 dark:bg-blue-500/10", text: "text-blue-600 dark:text-blue-400", dot: "bg-blue-500" },
23603
23603
  "delegation": { bg: "bg-violet-500/10 dark:bg-violet-500/10", text: "text-violet-600 dark:text-violet-400", dot: "bg-violet-500" },
23604
- "tool-use": { bg: "bg-zinc-500/10 dark:bg-zinc-500/10", text: "text-zinc-600 dark:text-zinc-400", dot: "bg-zinc-500" }
23604
+ "tool-use": { bg: "bg-zinc-500/10 dark:bg-zinc-500/10", text: "text-zinc-600 dark:text-zinc-400", dot: "bg-zinc-500" },
23605
+ "constraint": { bg: "bg-red-500/10 dark:bg-red-500/10", text: "text-red-600 dark:text-red-400", dot: "bg-red-500" },
23606
+ "decision": { bg: "bg-orange-500/10 dark:bg-orange-500/10", text: "text-orange-600 dark:text-orange-400", dot: "bg-orange-500" },
23607
+ "heuristic": { bg: "bg-indigo-500/10 dark:bg-indigo-500/10", text: "text-indigo-600 dark:text-indigo-400", dot: "bg-indigo-500" },
23608
+ "rejected": { bg: "bg-slate-500/10 dark:bg-slate-500/10", text: "text-slate-600 dark:text-slate-400", dot: "bg-slate-500" }
23605
23609
  };
23606
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" };
23607
23611
  }
@@ -23712,8 +23716,26 @@ function SearchBar() {
23712
23716
  }
23713
23717
 
23714
23718
  // src/ui/viewer/components/Header.tsx
23715
- function Header({ isConnected, resolvedTheme, onThemeToggle }) {
23716
- 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(
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(
23721
+ "button",
23722
+ {
23723
+ onClick: () => onViewChange("feed"),
23724
+ 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"
23726
+ },
23727
+ /* @__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
+ "Feed"
23729
+ ), /* @__PURE__ */ import_react2.default.createElement(
23730
+ "button",
23731
+ {
23732
+ onClick: () => onViewChange("analytics"),
23733
+ 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"
23735
+ },
23736
+ /* @__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
+ "Analytics"
23738
+ )), /* @__PURE__ */ import_react2.default.createElement(
23717
23739
  "button",
23718
23740
  {
23719
23741
  onClick: onThemeToggle,
@@ -23732,7 +23754,11 @@ var TYPE_CONFIG = {
23732
23754
  "command": { color: "bg-accent-amber", label: "Commands" },
23733
23755
  "research": { color: "bg-accent-blue", label: "Research" },
23734
23756
  "delegation": { color: "bg-accent-violet", label: "Delegations" },
23735
- "tool-use": { color: "bg-zinc-400", label: "Tool usage" }
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" }
23736
23762
  };
23737
23763
  var PROJECT_COLORS = [
23738
23764
  { bg: "bg-accent-violet/15", text: "text-accent-violet", ring: "ring-accent-violet/30" },
@@ -23858,7 +23884,11 @@ var TYPE_STYLES = {
23858
23884
  "command": { border: "border-l-amber-500", bg: "bg-amber-500/10", text: "text-amber-400", dot: "bg-amber-500", label: "command" },
23859
23885
  "research": { border: "border-l-blue-500", bg: "bg-blue-500/10", text: "text-blue-400", dot: "bg-blue-500", label: "research" },
23860
23886
  "delegation": { border: "border-l-violet-500", bg: "bg-violet-500/10", text: "text-violet-400", dot: "bg-violet-500", label: "delegation" },
23861
- "tool-use": { border: "border-l-zinc-500", bg: "bg-zinc-500/10", text: "text-zinc-400", dot: "bg-zinc-500", label: "tool-use" }
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" }
23862
23892
  };
23863
23893
  function getTypeStyle(type) {
23864
23894
  return TYPE_STYLES[type] || TYPE_STYLES["tool-use"];
@@ -23914,18 +23944,266 @@ function PromptCard({ prompt, getDisplayName }) {
23914
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)));
23915
23945
  }
23916
23946
 
23917
- // src/ui/viewer/hooks/useSSE.ts
23947
+ // src/ui/viewer/components/Analytics.tsx
23948
+ var import_react6 = __toESM(require_react(), 1);
23949
+
23950
+ // src/ui/viewer/hooks/useAnalytics.ts
23918
23951
  var import_react5 = __toESM(require_react(), 1);
23952
+ function useAnalytics(project) {
23953
+ const [data, setData] = (0, import_react5.useState)({
23954
+ overview: null,
23955
+ timeline: [],
23956
+ typeDistribution: [],
23957
+ sessionStats: null,
23958
+ isLoading: true
23959
+ });
23960
+ const mountedRef = (0, import_react5.useRef)(true);
23961
+ const debounceRef = (0, import_react5.useRef)(null);
23962
+ const fetchAnalytics = (0, import_react5.useCallback)(async () => {
23963
+ if (!mountedRef.current) return;
23964
+ const params = project ? `?project=${encodeURIComponent(project)}` : "";
23965
+ try {
23966
+ const [overviewRes, timelineRes, typesRes, sessionsRes] = await Promise.all([
23967
+ fetch(`/api/analytics/overview${params}`),
23968
+ fetch(`/api/analytics/timeline${params}`),
23969
+ fetch(`/api/analytics/types${params}`),
23970
+ fetch(`/api/analytics/sessions${params}`)
23971
+ ]);
23972
+ if (!mountedRef.current) return;
23973
+ const overview = overviewRes.ok ? await overviewRes.json() : null;
23974
+ const timeline = timelineRes.ok ? await timelineRes.json() : [];
23975
+ const typeDistribution = typesRes.ok ? await typesRes.json() : [];
23976
+ const sessionStats = sessionsRes.ok ? await sessionsRes.json() : null;
23977
+ setData({ overview, timeline, typeDistribution, sessionStats, isLoading: false });
23978
+ } catch (err) {
23979
+ console.error("Analytics fetch failed:", err);
23980
+ if (mountedRef.current) {
23981
+ setData((prev) => ({ ...prev, isLoading: false }));
23982
+ }
23983
+ }
23984
+ }, [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
+ (0, import_react5.useEffect)(() => {
23992
+ mountedRef.current = true;
23993
+ setData((prev) => ({ ...prev, isLoading: true }));
23994
+ 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);
24000
+ return () => {
24001
+ 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();
24007
+ };
24008
+ }, [fetchAnalytics, debouncedRefresh]);
24009
+ return data;
24010
+ }
24011
+
24012
+ // src/ui/viewer/components/Analytics.tsx
24013
+ var TYPE_COLORS = {
24014
+ "file-write": { bar: "bg-emerald-500", text: "text-emerald-400" },
24015
+ "file-read": { bar: "bg-cyan-500", text: "text-cyan-400" },
24016
+ "command": { bar: "bg-amber-500", text: "text-amber-400" },
24017
+ "research": { bar: "bg-blue-500", text: "text-blue-400" },
24018
+ "delegation": { bar: "bg-violet-500", text: "text-violet-400" },
24019
+ "tool-use": { bar: "bg-zinc-500", text: "text-zinc-400" },
24020
+ "constraint": { bar: "bg-red-500", text: "text-red-400" },
24021
+ "decision": { bar: "bg-orange-500", text: "text-orange-400" },
24022
+ "heuristic": { bar: "bg-indigo-500", text: "text-indigo-400" },
24023
+ "rejected": { bar: "bg-slate-500", text: "text-slate-400" }
24024
+ };
24025
+ function getTypeColor(type) {
24026
+ return TYPE_COLORS[type] || { bar: "bg-zinc-500", text: "text-zinc-400" };
24027
+ }
24028
+ function Analytics({ currentFilter, getDisplayName }) {
24029
+ const { overview, timeline, typeDistribution, sessionStats, isLoading } = useAnalytics(currentFilter);
24030
+ if (isLoading) {
24031
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col items-center justify-center py-20" }, /* @__PURE__ */ import_react6.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_react6.default.createElement("p", { className: "text-sm text-zinc-500" }, "Loading analytics..."));
24032
+ }
24033
+ if (!overview) {
24034
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ import_react6.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_react6.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_react6.default.createElement("line", { x1: "18", y1: "20", x2: "18", y2: "10" }), /* @__PURE__ */ import_react6.default.createElement("line", { x1: "12", y1: "20", x2: "12", y2: "4" }), /* @__PURE__ */ import_react6.default.createElement("line", { x1: "6", y1: "20", x2: "6", y2: "14" }))), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-base font-semibold text-zinc-300 mb-2" }, "No data available"), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-sm text-zinc-500 max-w-xs leading-relaxed" }, "Start a coding session to begin collecting analytics data."));
24035
+ }
24036
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "grid grid-cols-2 lg:grid-cols-4 gap-3" }, /* @__PURE__ */ import_react6.default.createElement(
24037
+ StatCard,
24038
+ {
24039
+ label: "Observations",
24040
+ value: overview.observations,
24041
+ sub: `${overview.observationsToday} today`,
24042
+ color: "text-accent-violet"
24043
+ }
24044
+ ), /* @__PURE__ */ import_react6.default.createElement(
24045
+ StatCard,
24046
+ {
24047
+ label: "This Week",
24048
+ value: overview.observationsThisWeek,
24049
+ sub: "observations",
24050
+ color: "text-accent-blue"
24051
+ }
24052
+ ), /* @__PURE__ */ import_react6.default.createElement(
24053
+ StatCard,
24054
+ {
24055
+ label: "Sessions",
24056
+ value: sessionStats?.total || overview.sessions,
24057
+ sub: sessionStats ? `${sessionStats.avgDurationMinutes}m avg` : "",
24058
+ color: "text-accent-cyan"
24059
+ }
24060
+ ), /* @__PURE__ */ import_react6.default.createElement(
24061
+ StatCard,
24062
+ {
24063
+ label: "Knowledge",
24064
+ value: overview.knowledgeCount,
24065
+ sub: `${overview.staleCount} stale`,
24066
+ color: "text-accent-amber"
24067
+ }
24068
+ )), /* @__PURE__ */ import_react6.default.createElement("div", { className: "grid grid-cols-3 gap-3" }, /* @__PURE__ */ import_react6.default.createElement(MiniStat, { label: "Summaries", value: overview.summaries, color: "text-accent-cyan" }), /* @__PURE__ */ import_react6.default.createElement(MiniStat, { label: "Prompts", value: overview.prompts, color: "text-accent-rose" }), /* @__PURE__ */ import_react6.default.createElement(
24069
+ MiniStat,
24070
+ {
24071
+ label: "Completion",
24072
+ value: sessionStats ? `${sessionStats.total > 0 ? Math.round(sessionStats.completed / sessionStats.total * 100) : 0}%` : "\u2014",
24073
+ color: "text-accent-green"
24074
+ }
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 })));
24076
+ }
24077
+ function StatCard({ label, value, sub, color }) {
24078
+ 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));
24079
+ }
24080
+ function MiniStat({ label, value, color }) {
24081
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "rounded-lg bg-surface-1 border border-border px-3 py-3 text-center" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: `text-lg font-bold tabular-nums ${color}` }, typeof value === "number" ? value.toLocaleString() : value), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[10px] uppercase tracking-wider text-zinc-600 mt-0.5" }, label));
24082
+ }
24083
+ function TimelineChart({ entries }) {
24084
+ const [hoveredIndex, setHoveredIndex] = (0, import_react6.useState)(null);
24085
+ 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
+ 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",
24149
+ {
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
+ ));
24160
+ }
24161
+ function TypeDistributionChart({ entries }) {
24162
+ const total = entries.reduce((sum, e) => sum + e.count, 0);
24163
+ const maxCount = Math.max(...entries.map((e) => e.count), 1);
24164
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-2" }, entries.map((entry) => {
24165
+ const pct = total > 0 ? Math.round(entry.count / total * 100) : 0;
24166
+ const widthPct = Math.max(2, entry.count / maxCount * 100);
24167
+ const colors = getTypeColor(entry.type);
24168
+ return /* @__PURE__ */ import_react6.default.createElement("div", { key: entry.type, className: "flex items-center gap-3" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "w-24 text-right" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: `text-xs font-medium ${colors.text}` }, entry.type)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex-1 h-5 bg-surface-2 rounded-md overflow-hidden" }, /* @__PURE__ */ import_react6.default.createElement(
24169
+ "div",
24170
+ {
24171
+ className: `h-full rounded-md ${colors.bar} transition-all duration-500`,
24172
+ style: { width: `${widthPct}%` }
24173
+ }
24174
+ )), /* @__PURE__ */ import_react6.default.createElement("div", { className: "w-16 text-right" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-xs text-zinc-400 font-mono tabular-nums" }, entry.count), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-[10px] text-zinc-600 ml-1" }, pct, "%")));
24175
+ }));
24176
+ }
24177
+ function SessionStatsPanel({ stats }) {
24178
+ const completionRate = stats.total > 0 ? Math.round(stats.completed / stats.total * 100) : 0;
24179
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ import_react6.default.createElement("div", null, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-center justify-between mb-2" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-xs text-zinc-400" }, "Completion rate"), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-xs font-bold text-accent-green tabular-nums" }, completionRate, "%")), /* @__PURE__ */ import_react6.default.createElement("div", { className: "h-2.5 bg-surface-2 rounded-full overflow-hidden" }, /* @__PURE__ */ import_react6.default.createElement(
24180
+ "div",
24181
+ {
24182
+ className: "h-full bg-accent-green rounded-full transition-all duration-500",
24183
+ style: { width: `${completionRate}%` }
24184
+ }
24185
+ )), /* @__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
+ }
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`;
24193
+ }
24194
+
24195
+ // src/ui/viewer/hooks/useSSE.ts
24196
+ var import_react7 = __toESM(require_react(), 1);
23919
24197
  function useSSE() {
23920
- const [state, setState] = (0, import_react5.useState)({
24198
+ const [state, setState] = (0, import_react7.useState)({
23921
24199
  observations: [],
23922
24200
  summaries: [],
23923
24201
  prompts: [],
23924
24202
  projects: [],
23925
24203
  isConnected: false
23926
24204
  });
23927
- const mountedRef = (0, import_react5.useRef)(true);
23928
- (0, import_react5.useEffect)(() => {
24205
+ const mountedRef = (0, import_react7.useRef)(true);
24206
+ (0, import_react7.useEffect)(() => {
23929
24207
  mountedRef.current = true;
23930
24208
  let eventSource = null;
23931
24209
  let retryTimeout = null;
@@ -23975,42 +24253,63 @@ function useSSE() {
23975
24253
  console.error("Failed to fetch projects:", err);
23976
24254
  }
23977
24255
  };
24256
+ const fetchAll = () => {
24257
+ fetchObservations();
24258
+ fetchSummaries();
24259
+ fetchPrompts();
24260
+ fetchProjects();
24261
+ };
24262
+ const onObservation = () => {
24263
+ fetchObservations();
24264
+ fetchProjects();
24265
+ };
24266
+ const onSummary = () => {
24267
+ fetchSummaries();
24268
+ };
24269
+ const onPrompt = () => {
24270
+ fetchPrompts();
24271
+ };
24272
+ let wasConnected = false;
23978
24273
  const connect = () => {
23979
24274
  if (!mountedRef.current) return;
23980
24275
  eventSource = new EventSource("/events");
23981
24276
  eventSource.onopen = () => {
23982
24277
  if (!mountedRef.current) return;
24278
+ if (wasConnected) {
24279
+ fetchAll();
24280
+ }
24281
+ wasConnected = true;
23983
24282
  retryCount = 0;
23984
24283
  setState((prev) => ({ ...prev, isConnected: true }));
23985
24284
  };
23986
24285
  eventSource.onerror = () => {
23987
24286
  if (!mountedRef.current) return;
23988
24287
  setState((prev) => ({ ...prev, isConnected: false }));
23989
- eventSource?.close();
24288
+ if (eventSource) {
24289
+ eventSource.removeEventListener("observation-created", onObservation);
24290
+ eventSource.removeEventListener("summary-created", onSummary);
24291
+ eventSource.removeEventListener("prompt-created", onPrompt);
24292
+ eventSource.close();
24293
+ }
23990
24294
  eventSource = null;
23991
24295
  const delay = Math.min(1e3 * Math.pow(2, retryCount), MAX_RETRY_DELAY);
23992
24296
  retryCount++;
23993
24297
  retryTimeout = setTimeout(connect, delay);
23994
24298
  };
23995
- eventSource.addEventListener("observation-created", () => {
23996
- fetchObservations();
23997
- fetchProjects();
23998
- });
23999
- eventSource.addEventListener("summary-created", () => {
24000
- fetchSummaries();
24001
- });
24002
- eventSource.addEventListener("prompt-created", () => {
24003
- fetchPrompts();
24004
- });
24299
+ eventSource.addEventListener("observation-created", onObservation);
24300
+ eventSource.addEventListener("summary-created", onSummary);
24301
+ eventSource.addEventListener("prompt-created", onPrompt);
24005
24302
  };
24006
- fetchObservations();
24007
- fetchSummaries();
24008
- fetchPrompts();
24009
- fetchProjects();
24303
+ fetchAll();
24010
24304
  connect();
24011
24305
  return () => {
24012
24306
  mountedRef.current = false;
24013
- eventSource?.close();
24307
+ if (eventSource) {
24308
+ eventSource.removeEventListener("observation-created", onObservation);
24309
+ eventSource.removeEventListener("summary-created", onSummary);
24310
+ eventSource.removeEventListener("prompt-created", onPrompt);
24311
+ eventSource.close();
24312
+ }
24014
24313
  if (retryTimeout) clearTimeout(retryTimeout);
24015
24314
  };
24016
24315
  }, []);
@@ -24018,17 +24317,17 @@ function useSSE() {
24018
24317
  }
24019
24318
 
24020
24319
  // src/ui/viewer/hooks/useTheme.ts
24021
- var import_react6 = __toESM(require_react(), 1);
24320
+ var import_react8 = __toESM(require_react(), 1);
24022
24321
  function useTheme() {
24023
- const [preference, setPreference] = (0, import_react6.useState)("dark");
24024
- const [resolvedTheme, setResolvedTheme] = (0, import_react6.useState)("dark");
24025
- (0, import_react6.useEffect)(() => {
24322
+ const [preference, setPreference] = (0, import_react8.useState)("dark");
24323
+ const [resolvedTheme, setResolvedTheme] = (0, import_react8.useState)("dark");
24324
+ (0, import_react8.useEffect)(() => {
24026
24325
  const saved = localStorage.getItem("kiro-memory-theme");
24027
24326
  if (saved) {
24028
24327
  setPreference(saved);
24029
24328
  }
24030
24329
  }, []);
24031
- (0, import_react6.useEffect)(() => {
24330
+ (0, import_react8.useEffect)(() => {
24032
24331
  let resolved;
24033
24332
  if (preference === "system") {
24034
24333
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
@@ -24043,7 +24342,7 @@ function useTheme() {
24043
24342
  document.documentElement.classList.remove("dark");
24044
24343
  }
24045
24344
  }, [preference]);
24046
- (0, import_react6.useEffect)(() => {
24345
+ (0, import_react8.useEffect)(() => {
24047
24346
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
24048
24347
  const handler = (e) => {
24049
24348
  if (preference === "system") {
@@ -24067,11 +24366,11 @@ function useTheme() {
24067
24366
  }
24068
24367
 
24069
24368
  // src/ui/viewer/hooks/useProjectAliases.ts
24070
- var import_react7 = __toESM(require_react(), 1);
24369
+ var import_react9 = __toESM(require_react(), 1);
24071
24370
  function useProjectAliases() {
24072
- const [aliases, setAliases] = (0, import_react7.useState)({});
24073
- const [isLoading, setIsLoading] = (0, import_react7.useState)(false);
24074
- (0, import_react7.useEffect)(() => {
24371
+ const [aliases, setAliases] = (0, import_react9.useState)({});
24372
+ const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
24373
+ (0, import_react9.useEffect)(() => {
24075
24374
  const fetchAliases = async () => {
24076
24375
  try {
24077
24376
  const res = await fetch("/api/project-aliases");
@@ -24085,10 +24384,10 @@ function useProjectAliases() {
24085
24384
  };
24086
24385
  fetchAliases();
24087
24386
  }, []);
24088
- const getDisplayName = (0, import_react7.useCallback)((project) => {
24387
+ const getDisplayName = (0, import_react9.useCallback)((project) => {
24089
24388
  return aliases[project] || project;
24090
24389
  }, [aliases]);
24091
- const updateAlias = (0, import_react7.useCallback)(async (project, displayName) => {
24390
+ const updateAlias = (0, import_react9.useCallback)(async (project, displayName) => {
24092
24391
  setIsLoading(true);
24093
24392
  try {
24094
24393
  const res = await fetch(`/api/project-aliases/${encodeURIComponent(project)}`, {
@@ -24134,38 +24433,39 @@ function mergeAndDeduplicateByProject(liveData, paginatedData) {
24134
24433
  // src/ui/viewer/App.tsx
24135
24434
  var TYPE_FILTERS = ["file-write", "file-read", "command", "research", "delegation", "tool-use"];
24136
24435
  function App() {
24137
- const [currentFilter, setCurrentFilter] = (0, import_react8.useState)("");
24138
- const [activeTypes, setActiveTypes] = (0, import_react8.useState)(new Set(TYPE_FILTERS));
24139
- const [paginatedObservations, setPaginatedObservations] = (0, import_react8.useState)([]);
24140
- const [paginatedSummaries, setPaginatedSummaries] = (0, import_react8.useState)([]);
24141
- const [paginatedPrompts, setPaginatedPrompts] = (0, import_react8.useState)([]);
24142
- const [isLoadingMore, setIsLoadingMore] = (0, import_react8.useState)(false);
24143
- const [hasMore, setHasMore] = (0, import_react8.useState)(true);
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);
24144
24444
  const { observations, summaries, prompts, projects, isConnected } = useSSE();
24145
24445
  const { resolvedTheme, setThemePreference } = useTheme();
24146
24446
  const { getDisplayName, updateAlias } = useProjectAliases();
24147
- const allObservations = (0, import_react8.useMemo)(() => {
24447
+ const allObservations = (0, import_react10.useMemo)(() => {
24148
24448
  if (currentFilter) return paginatedObservations;
24149
24449
  return mergeAndDeduplicateByProject(observations, paginatedObservations);
24150
24450
  }, [observations, paginatedObservations, currentFilter]);
24151
- const allSummaries = (0, import_react8.useMemo)(() => {
24451
+ const allSummaries = (0, import_react10.useMemo)(() => {
24152
24452
  if (currentFilter) return paginatedSummaries;
24153
24453
  return mergeAndDeduplicateByProject(summaries, paginatedSummaries);
24154
24454
  }, [summaries, paginatedSummaries, currentFilter]);
24155
- const allPrompts = (0, import_react8.useMemo)(() => {
24455
+ const allPrompts = (0, import_react10.useMemo)(() => {
24156
24456
  if (currentFilter) return paginatedPrompts;
24157
24457
  return mergeAndDeduplicateByProject(prompts, paginatedPrompts);
24158
24458
  }, [prompts, paginatedPrompts, currentFilter]);
24159
- const filteredObservations = (0, import_react8.useMemo)(
24459
+ const filteredObservations = (0, import_react10.useMemo)(
24160
24460
  () => allObservations.filter((o) => activeTypes.has(o.type)),
24161
24461
  [allObservations, activeTypes]
24162
24462
  );
24163
- const stats = (0, import_react8.useMemo)(() => ({
24463
+ const stats = (0, import_react10.useMemo)(() => ({
24164
24464
  observations: allObservations.length,
24165
24465
  summaries: allSummaries.length,
24166
24466
  prompts: allPrompts.length
24167
24467
  }), [allObservations, allSummaries, allPrompts]);
24168
- const toggleType = (0, import_react8.useCallback)((type) => {
24468
+ const toggleType = (0, import_react10.useCallback)((type) => {
24169
24469
  setActiveTypes((prev) => {
24170
24470
  const next = new Set(prev);
24171
24471
  if (next.has(type)) next.delete(type);
@@ -24173,7 +24473,7 @@ function App() {
24173
24473
  return next;
24174
24474
  });
24175
24475
  }, []);
24176
- const fetchForProject = (0, import_react8.useCallback)(async (project) => {
24476
+ const fetchForProject = (0, import_react10.useCallback)(async (project) => {
24177
24477
  setIsLoadingMore(true);
24178
24478
  try {
24179
24479
  const params = new URLSearchParams({
@@ -24195,7 +24495,7 @@ function App() {
24195
24495
  setIsLoadingMore(false);
24196
24496
  }
24197
24497
  }, []);
24198
- const handleLoadMore = (0, import_react8.useCallback)(async () => {
24498
+ const handleLoadMore = (0, import_react10.useCallback)(async () => {
24199
24499
  if (isLoadingMore) return;
24200
24500
  setIsLoadingMore(true);
24201
24501
  try {
@@ -24233,7 +24533,7 @@ function App() {
24233
24533
  setIsLoadingMore(false);
24234
24534
  }
24235
24535
  }, [currentFilter, paginatedObservations.length, isLoadingMore]);
24236
- (0, import_react8.useEffect)(() => {
24536
+ (0, import_react10.useEffect)(() => {
24237
24537
  setPaginatedObservations([]);
24238
24538
  setPaginatedSummaries([]);
24239
24539
  setPaginatedPrompts([]);
@@ -24242,14 +24542,16 @@ function App() {
24242
24542
  fetchForProject(currentFilter);
24243
24543
  }
24244
24544
  }, [currentFilter, fetchForProject]);
24245
- return /* @__PURE__ */ import_react8.default.createElement("div", { className: "h-screen overflow-hidden flex flex-col bg-surface-0" }, /* @__PURE__ */ import_react8.default.createElement(
24545
+ return /* @__PURE__ */ import_react10.default.createElement("div", { className: "h-screen overflow-hidden flex flex-col bg-surface-0" }, /* @__PURE__ */ import_react10.default.createElement(
24246
24546
  Header,
24247
24547
  {
24248
24548
  isConnected,
24249
24549
  resolvedTheme,
24250
- onThemeToggle: () => setThemePreference(resolvedTheme === "dark" ? "light" : "dark")
24550
+ onThemeToggle: () => setThemePreference(resolvedTheme === "dark" ? "light" : "dark"),
24551
+ currentView,
24552
+ onViewChange: setCurrentView
24251
24553
  }
24252
- ), /* @__PURE__ */ import_react8.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "hidden md:flex w-[260px] flex-shrink-0" }, /* @__PURE__ */ import_react8.default.createElement(
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(
24253
24555
  Sidebar,
24254
24556
  {
24255
24557
  projects,
@@ -24261,15 +24563,15 @@ function App() {
24261
24563
  getDisplayName,
24262
24564
  onRenameProject: updateAlias
24263
24565
  }
24264
- )), /* @__PURE__ */ import_react8.default.createElement("main", { className: "flex-1 overflow-y-auto bg-surface-0" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "max-w-3xl mx-auto px-6 py-6" }, currentFilter && /* @__PURE__ */ import_react8.default.createElement("div", { className: "flex items-center gap-3 mb-6 animate-fade-in" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "w-9 h-9 rounded-lg bg-accent-violet/15 flex items-center justify-center" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "text-xs font-bold text-accent-violet" }, getDisplayName(currentFilter).substring(0, 2).toUpperCase())), /* @__PURE__ */ import_react8.default.createElement("div", null, /* @__PURE__ */ import_react8.default.createElement("h2", { className: "text-lg font-bold text-zinc-100" }, getDisplayName(currentFilter)), currentFilter !== getDisplayName(currentFilter) && /* @__PURE__ */ import_react8.default.createElement("span", { className: "text-[11px] font-mono text-zinc-600" }, currentFilter))), /* @__PURE__ */ import_react8.default.createElement(
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(
24265
24567
  "button",
24266
24568
  {
24267
24569
  onClick: () => setCurrentFilter(""),
24268
24570
  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"
24269
24571
  },
24270
- /* @__PURE__ */ import_react8.default.createElement("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ import_react8.default.createElement("path", { d: "M18 6 6 18M6 6l12 12" })),
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" })),
24271
24573
  "Clear filter"
24272
- )), /* @__PURE__ */ import_react8.default.createElement(
24574
+ )), currentView === "feed" ? /* @__PURE__ */ import_react10.default.createElement(
24273
24575
  Feed,
24274
24576
  {
24275
24577
  observations: filteredObservations,
@@ -24280,13 +24582,19 @@ function App() {
24280
24582
  hasMore,
24281
24583
  getDisplayName
24282
24584
  }
24585
+ ) : /* @__PURE__ */ import_react10.default.createElement(
24586
+ Analytics,
24587
+ {
24588
+ currentFilter,
24589
+ getDisplayName
24590
+ }
24283
24591
  )))));
24284
24592
  }
24285
24593
 
24286
24594
  // src/ui/viewer/index.tsx
24287
24595
  var root = import_client.default.createRoot(document.getElementById("root"));
24288
24596
  root.render(
24289
- /* @__PURE__ */ import_react9.default.createElement(import_react9.default.StrictMode, null, /* @__PURE__ */ import_react9.default.createElement(App, null))
24597
+ /* @__PURE__ */ import_react11.default.createElement(import_react11.default.StrictMode, null, /* @__PURE__ */ import_react11.default.createElement(App, null))
24290
24598
  );
24291
24599
  /*! Bundled license information:
24292
24600