kiro-memory 1.6.0 → 1.7.1

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;
@@ -23981,6 +24259,16 @@ function useSSE() {
23981
24259
  fetchPrompts();
23982
24260
  fetchProjects();
23983
24261
  };
24262
+ const onObservation = () => {
24263
+ fetchObservations();
24264
+ fetchProjects();
24265
+ };
24266
+ const onSummary = () => {
24267
+ fetchSummaries();
24268
+ };
24269
+ const onPrompt = () => {
24270
+ fetchPrompts();
24271
+ };
23984
24272
  let wasConnected = false;
23985
24273
  const connect = () => {
23986
24274
  if (!mountedRef.current) return;
@@ -23997,28 +24285,31 @@ function useSSE() {
23997
24285
  eventSource.onerror = () => {
23998
24286
  if (!mountedRef.current) return;
23999
24287
  setState((prev) => ({ ...prev, isConnected: false }));
24000
- 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
+ }
24001
24294
  eventSource = null;
24002
24295
  const delay = Math.min(1e3 * Math.pow(2, retryCount), MAX_RETRY_DELAY);
24003
24296
  retryCount++;
24004
24297
  retryTimeout = setTimeout(connect, delay);
24005
24298
  };
24006
- eventSource.addEventListener("observation-created", () => {
24007
- fetchObservations();
24008
- fetchProjects();
24009
- });
24010
- eventSource.addEventListener("summary-created", () => {
24011
- fetchSummaries();
24012
- });
24013
- eventSource.addEventListener("prompt-created", () => {
24014
- fetchPrompts();
24015
- });
24299
+ eventSource.addEventListener("observation-created", onObservation);
24300
+ eventSource.addEventListener("summary-created", onSummary);
24301
+ eventSource.addEventListener("prompt-created", onPrompt);
24016
24302
  };
24017
24303
  fetchAll();
24018
24304
  connect();
24019
24305
  return () => {
24020
24306
  mountedRef.current = false;
24021
- 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
+ }
24022
24313
  if (retryTimeout) clearTimeout(retryTimeout);
24023
24314
  };
24024
24315
  }, []);
@@ -24026,17 +24317,17 @@ function useSSE() {
24026
24317
  }
24027
24318
 
24028
24319
  // src/ui/viewer/hooks/useTheme.ts
24029
- var import_react6 = __toESM(require_react(), 1);
24320
+ var import_react8 = __toESM(require_react(), 1);
24030
24321
  function useTheme() {
24031
- const [preference, setPreference] = (0, import_react6.useState)("dark");
24032
- const [resolvedTheme, setResolvedTheme] = (0, import_react6.useState)("dark");
24033
- (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)(() => {
24034
24325
  const saved = localStorage.getItem("kiro-memory-theme");
24035
24326
  if (saved) {
24036
24327
  setPreference(saved);
24037
24328
  }
24038
24329
  }, []);
24039
- (0, import_react6.useEffect)(() => {
24330
+ (0, import_react8.useEffect)(() => {
24040
24331
  let resolved;
24041
24332
  if (preference === "system") {
24042
24333
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
@@ -24051,7 +24342,7 @@ function useTheme() {
24051
24342
  document.documentElement.classList.remove("dark");
24052
24343
  }
24053
24344
  }, [preference]);
24054
- (0, import_react6.useEffect)(() => {
24345
+ (0, import_react8.useEffect)(() => {
24055
24346
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
24056
24347
  const handler = (e) => {
24057
24348
  if (preference === "system") {
@@ -24075,11 +24366,11 @@ function useTheme() {
24075
24366
  }
24076
24367
 
24077
24368
  // src/ui/viewer/hooks/useProjectAliases.ts
24078
- var import_react7 = __toESM(require_react(), 1);
24369
+ var import_react9 = __toESM(require_react(), 1);
24079
24370
  function useProjectAliases() {
24080
- const [aliases, setAliases] = (0, import_react7.useState)({});
24081
- const [isLoading, setIsLoading] = (0, import_react7.useState)(false);
24082
- (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)(() => {
24083
24374
  const fetchAliases = async () => {
24084
24375
  try {
24085
24376
  const res = await fetch("/api/project-aliases");
@@ -24093,10 +24384,10 @@ function useProjectAliases() {
24093
24384
  };
24094
24385
  fetchAliases();
24095
24386
  }, []);
24096
- const getDisplayName = (0, import_react7.useCallback)((project) => {
24387
+ const getDisplayName = (0, import_react9.useCallback)((project) => {
24097
24388
  return aliases[project] || project;
24098
24389
  }, [aliases]);
24099
- const updateAlias = (0, import_react7.useCallback)(async (project, displayName) => {
24390
+ const updateAlias = (0, import_react9.useCallback)(async (project, displayName) => {
24100
24391
  setIsLoading(true);
24101
24392
  try {
24102
24393
  const res = await fetch(`/api/project-aliases/${encodeURIComponent(project)}`, {
@@ -24142,38 +24433,39 @@ function mergeAndDeduplicateByProject(liveData, paginatedData) {
24142
24433
  // src/ui/viewer/App.tsx
24143
24434
  var TYPE_FILTERS = ["file-write", "file-read", "command", "research", "delegation", "tool-use"];
24144
24435
  function App() {
24145
- const [currentFilter, setCurrentFilter] = (0, import_react8.useState)("");
24146
- const [activeTypes, setActiveTypes] = (0, import_react8.useState)(new Set(TYPE_FILTERS));
24147
- const [paginatedObservations, setPaginatedObservations] = (0, import_react8.useState)([]);
24148
- const [paginatedSummaries, setPaginatedSummaries] = (0, import_react8.useState)([]);
24149
- const [paginatedPrompts, setPaginatedPrompts] = (0, import_react8.useState)([]);
24150
- const [isLoadingMore, setIsLoadingMore] = (0, import_react8.useState)(false);
24151
- 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);
24152
24444
  const { observations, summaries, prompts, projects, isConnected } = useSSE();
24153
24445
  const { resolvedTheme, setThemePreference } = useTheme();
24154
24446
  const { getDisplayName, updateAlias } = useProjectAliases();
24155
- const allObservations = (0, import_react8.useMemo)(() => {
24447
+ const allObservations = (0, import_react10.useMemo)(() => {
24156
24448
  if (currentFilter) return paginatedObservations;
24157
24449
  return mergeAndDeduplicateByProject(observations, paginatedObservations);
24158
24450
  }, [observations, paginatedObservations, currentFilter]);
24159
- const allSummaries = (0, import_react8.useMemo)(() => {
24451
+ const allSummaries = (0, import_react10.useMemo)(() => {
24160
24452
  if (currentFilter) return paginatedSummaries;
24161
24453
  return mergeAndDeduplicateByProject(summaries, paginatedSummaries);
24162
24454
  }, [summaries, paginatedSummaries, currentFilter]);
24163
- const allPrompts = (0, import_react8.useMemo)(() => {
24455
+ const allPrompts = (0, import_react10.useMemo)(() => {
24164
24456
  if (currentFilter) return paginatedPrompts;
24165
24457
  return mergeAndDeduplicateByProject(prompts, paginatedPrompts);
24166
24458
  }, [prompts, paginatedPrompts, currentFilter]);
24167
- const filteredObservations = (0, import_react8.useMemo)(
24459
+ const filteredObservations = (0, import_react10.useMemo)(
24168
24460
  () => allObservations.filter((o) => activeTypes.has(o.type)),
24169
24461
  [allObservations, activeTypes]
24170
24462
  );
24171
- const stats = (0, import_react8.useMemo)(() => ({
24463
+ const stats = (0, import_react10.useMemo)(() => ({
24172
24464
  observations: allObservations.length,
24173
24465
  summaries: allSummaries.length,
24174
24466
  prompts: allPrompts.length
24175
24467
  }), [allObservations, allSummaries, allPrompts]);
24176
- const toggleType = (0, import_react8.useCallback)((type) => {
24468
+ const toggleType = (0, import_react10.useCallback)((type) => {
24177
24469
  setActiveTypes((prev) => {
24178
24470
  const next = new Set(prev);
24179
24471
  if (next.has(type)) next.delete(type);
@@ -24181,7 +24473,7 @@ function App() {
24181
24473
  return next;
24182
24474
  });
24183
24475
  }, []);
24184
- const fetchForProject = (0, import_react8.useCallback)(async (project) => {
24476
+ const fetchForProject = (0, import_react10.useCallback)(async (project) => {
24185
24477
  setIsLoadingMore(true);
24186
24478
  try {
24187
24479
  const params = new URLSearchParams({
@@ -24203,7 +24495,7 @@ function App() {
24203
24495
  setIsLoadingMore(false);
24204
24496
  }
24205
24497
  }, []);
24206
- const handleLoadMore = (0, import_react8.useCallback)(async () => {
24498
+ const handleLoadMore = (0, import_react10.useCallback)(async () => {
24207
24499
  if (isLoadingMore) return;
24208
24500
  setIsLoadingMore(true);
24209
24501
  try {
@@ -24241,7 +24533,7 @@ function App() {
24241
24533
  setIsLoadingMore(false);
24242
24534
  }
24243
24535
  }, [currentFilter, paginatedObservations.length, isLoadingMore]);
24244
- (0, import_react8.useEffect)(() => {
24536
+ (0, import_react10.useEffect)(() => {
24245
24537
  setPaginatedObservations([]);
24246
24538
  setPaginatedSummaries([]);
24247
24539
  setPaginatedPrompts([]);
@@ -24250,14 +24542,16 @@ function App() {
24250
24542
  fetchForProject(currentFilter);
24251
24543
  }
24252
24544
  }, [currentFilter, fetchForProject]);
24253
- 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(
24254
24546
  Header,
24255
24547
  {
24256
24548
  isConnected,
24257
24549
  resolvedTheme,
24258
- onThemeToggle: () => setThemePreference(resolvedTheme === "dark" ? "light" : "dark")
24550
+ onThemeToggle: () => setThemePreference(resolvedTheme === "dark" ? "light" : "dark"),
24551
+ currentView,
24552
+ onViewChange: setCurrentView
24259
24553
  }
24260
- ), /* @__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(
24261
24555
  Sidebar,
24262
24556
  {
24263
24557
  projects,
@@ -24269,15 +24563,15 @@ function App() {
24269
24563
  getDisplayName,
24270
24564
  onRenameProject: updateAlias
24271
24565
  }
24272
- )), /* @__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(
24273
24567
  "button",
24274
24568
  {
24275
24569
  onClick: () => setCurrentFilter(""),
24276
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"
24277
24571
  },
24278
- /* @__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" })),
24279
24573
  "Clear filter"
24280
- )), /* @__PURE__ */ import_react8.default.createElement(
24574
+ )), currentView === "feed" ? /* @__PURE__ */ import_react10.default.createElement(
24281
24575
  Feed,
24282
24576
  {
24283
24577
  observations: filteredObservations,
@@ -24288,13 +24582,19 @@ function App() {
24288
24582
  hasMore,
24289
24583
  getDisplayName
24290
24584
  }
24585
+ ) : /* @__PURE__ */ import_react10.default.createElement(
24586
+ Analytics,
24587
+ {
24588
+ currentFilter,
24589
+ getDisplayName
24590
+ }
24291
24591
  )))));
24292
24592
  }
24293
24593
 
24294
24594
  // src/ui/viewer/index.tsx
24295
24595
  var root = import_client.default.createRoot(document.getElementById("root"));
24296
24596
  root.render(
24297
- /* @__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))
24298
24598
  );
24299
24599
  /*! Bundled license information:
24300
24600