dsh-mnemon 0.1.2 → 0.1.4

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.
Files changed (138) hide show
  1. package/README.md +103 -129
  2. package/README.zh-CN.md +173 -0
  3. package/SECURITY.md +2 -2
  4. package/docs/README.md +4 -4
  5. package/docs/assets/diagrams/en/memory-system-flow.png +0 -0
  6. package/docs/assets/diagrams/zh-CN/memory-system-flow.png +0 -0
  7. package/docs/assets/media/dsh-mnemon-memory-system-demo-poster.jpg +0 -0
  8. package/docs/assets/media/dsh-mnemon-memory-system-demo.gif +0 -0
  9. package/docs/assets/media/dsh-mnemon-memory-system-demo.mp4 +0 -0
  10. package/docs/assets/screenshots/conversation-memory-jump.png +0 -0
  11. package/docs/assets/screenshots/conversation-save-dialog.png +0 -0
  12. package/docs/assets/screenshots/conversation-turn-memory.png +0 -0
  13. package/docs/assets/screenshots/document-create-dialog.png +0 -0
  14. package/docs/assets/screenshots/documents-markdown.png +0 -0
  15. package/docs/assets/screenshots/entities-context.png +0 -0
  16. package/docs/assets/screenshots/memory-content.png +0 -0
  17. package/docs/assets/screenshots/overview-memory-graph.png +0 -0
  18. package/docs/assets/screenshots/recall-agent-answer.png +0 -0
  19. package/docs/assets/screenshots/remember-dialog.png +0 -0
  20. package/docs/assets/screenshots/runtime-memory-add.png +0 -0
  21. package/docs/assets/screenshots/runtime-memory.png +0 -0
  22. package/docs/assets/screenshots/settings-memory-system.png +0 -0
  23. package/docs/assets/screenshots/status-overview.png +0 -0
  24. package/docs/assets/screenshots/version-check.png +0 -0
  25. package/docs/en/README.md +40 -32
  26. package/docs/en/architecture.md +6 -2
  27. package/docs/en/configuration.md +15 -6
  28. package/docs/en/development.md +24 -4
  29. package/docs/en/getting-started.md +77 -87
  30. package/docs/en/interfaces.md +98 -90
  31. package/docs/en/operations.md +103 -93
  32. package/docs/en/project-overview.md +90 -194
  33. package/docs/en/storage-model.md +6 -0
  34. package/docs/en/ui-guide.md +156 -0
  35. package/docs/zh-CN/README.md +37 -29
  36. package/docs/zh-CN/architecture.md +6 -2
  37. package/docs/zh-CN/configuration.md +15 -6
  38. package/docs/zh-CN/development.md +24 -4
  39. package/docs/zh-CN/getting-started.md +70 -80
  40. package/docs/zh-CN/interfaces.md +84 -76
  41. package/docs/zh-CN/operations.md +96 -86
  42. package/docs/zh-CN/project-overview.md +86 -190
  43. package/docs/zh-CN/storage-model.md +6 -0
  44. package/docs/zh-CN/ui-guide.md +160 -0
  45. package/lib/client.js +2354 -1944
  46. package/lib/client.js.map +1 -1
  47. package/lib/index.js +446 -39
  48. package/lib/types/channels.d.ts +4 -0
  49. package/lib/types/channels.d.ts.map +1 -0
  50. package/lib/types/channels.js +4 -0
  51. package/lib/types/channels.js.map +1 -0
  52. package/lib/types/client/MnemonSaveAction.d.ts.map +1 -1
  53. package/lib/types/client/MnemonSaveAction.js +4 -55
  54. package/lib/types/client/MnemonSaveAction.js.map +1 -1
  55. package/lib/types/client/MnemonSettingsCard.d.ts.map +1 -1
  56. package/lib/types/client/MnemonSettingsCard.js +4 -5
  57. package/lib/types/client/MnemonSettingsCard.js.map +1 -1
  58. package/lib/types/client/MnemonTurnTail.d.ts +3 -0
  59. package/lib/types/client/MnemonTurnTail.d.ts.map +1 -1
  60. package/lib/types/client/MnemonTurnTail.js +15 -3
  61. package/lib/types/client/MnemonTurnTail.js.map +1 -1
  62. package/lib/types/client/MnemonView.d.ts +4 -0
  63. package/lib/types/client/MnemonView.d.ts.map +1 -1
  64. package/lib/types/client/MnemonView.js +424 -97
  65. package/lib/types/client/MnemonView.js.map +1 -1
  66. package/lib/types/client/MnemonViewAppearance.d.ts +21 -0
  67. package/lib/types/client/MnemonViewAppearance.d.ts.map +1 -0
  68. package/lib/types/client/MnemonViewAppearance.js +67 -0
  69. package/lib/types/client/MnemonViewAppearance.js.map +1 -0
  70. package/lib/types/client/anchor.d.ts +1 -1
  71. package/lib/types/client/anchor.js +1 -1
  72. package/lib/types/client/api.d.ts +4 -0
  73. package/lib/types/client/api.d.ts.map +1 -1
  74. package/lib/types/client/api.js +10 -1
  75. package/lib/types/client/api.js.map +1 -1
  76. package/lib/types/client/index.d.ts.map +1 -1
  77. package/lib/types/client/index.js +22 -25
  78. package/lib/types/client/index.js.map +1 -1
  79. package/lib/types/client/locales.d.ts +126 -294
  80. package/lib/types/client/locales.d.ts.map +1 -1
  81. package/lib/types/client/locales.js +140 -308
  82. package/lib/types/client/locales.js.map +1 -1
  83. package/lib/types/client/settings.d.ts +1 -1
  84. package/lib/types/client/settings.d.ts.map +1 -1
  85. package/lib/types/client/settings.js +1 -1
  86. package/lib/types/client/settings.js.map +1 -1
  87. package/lib/types/client/sidebar-entry.d.ts +1 -1
  88. package/lib/types/client/sidebar-entry.d.ts.map +1 -1
  89. package/lib/types/client/sidebar-entry.js +3 -1
  90. package/lib/types/client/sidebar-entry.js.map +1 -1
  91. package/lib/types/client/workspace-mount.d.ts.map +1 -1
  92. package/lib/types/client/workspace-mount.js +8 -5
  93. package/lib/types/client/workspace-mount.js.map +1 -1
  94. package/lib/types/config.d.ts +1 -3
  95. package/lib/types/config.d.ts.map +1 -1
  96. package/lib/types/config.js +10 -12
  97. package/lib/types/config.js.map +1 -1
  98. package/lib/types/index.d.ts +2 -1
  99. package/lib/types/index.d.ts.map +1 -1
  100. package/lib/types/index.js +2 -1
  101. package/lib/types/index.js.map +1 -1
  102. package/lib/types/lifecycle.js +3 -3
  103. package/lib/types/lifecycle.js.map +1 -1
  104. package/lib/types/memory-bodies.d.ts +1 -0
  105. package/lib/types/memory-bodies.d.ts.map +1 -1
  106. package/lib/types/memory-bodies.js +7 -0
  107. package/lib/types/memory-bodies.js.map +1 -1
  108. package/lib/types/rpc.d.ts +5 -7
  109. package/lib/types/rpc.d.ts.map +1 -1
  110. package/lib/types/rpc.js +28 -8
  111. package/lib/types/rpc.js.map +1 -1
  112. package/lib/types/service.d.ts +2 -0
  113. package/lib/types/service.d.ts.map +1 -1
  114. package/lib/types/service.js +4 -0
  115. package/lib/types/service.js.map +1 -1
  116. package/lib/types/settings.d.ts.map +1 -1
  117. package/lib/types/settings.js +1 -2
  118. package/lib/types/settings.js.map +1 -1
  119. package/lib/types/version-updates.d.ts +61 -0
  120. package/lib/types/version-updates.d.ts.map +1 -0
  121. package/lib/types/version-updates.js +377 -0
  122. package/lib/types/version-updates.js.map +1 -0
  123. package/package.json +23 -8
  124. package/README.en.md +0 -199
  125. package/docs/zh-CN/assets/screenshots/conversation-recall.png +0 -0
  126. package/docs/zh-CN/assets/screenshots/conversation-writeback.png +0 -0
  127. package/docs/zh-CN/assets/screenshots/documents-markdown.png +0 -0
  128. package/docs/zh-CN/assets/screenshots/entities-context.png +0 -0
  129. package/docs/zh-CN/assets/screenshots/memory-content.png +0 -0
  130. package/docs/zh-CN/assets/screenshots/overview-memory-graph.png +0 -0
  131. package/docs/zh-CN/assets/screenshots/recall-agent-answer.png +0 -0
  132. package/docs/zh-CN/assets/screenshots/runtime-memory.png +0 -0
  133. package/lib/types/client/MnemonToolviews.d.ts +0 -19
  134. package/lib/types/client/MnemonToolviews.d.ts.map +0 -1
  135. package/lib/types/client/MnemonToolviews.js +0 -172
  136. package/lib/types/client/MnemonToolviews.js.map +0 -1
  137. /package/docs/{en/assets → assets/diagrams/en}/project-architecture.svg +0 -0
  138. /package/docs/{zh-CN/assets → assets/diagrams/zh-CN}/project-architecture.svg +0 -0
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { createContext, Fragment, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
3
+ import { IconChevronLeftOutline14 } from '@deepseek-ai/dsh-client-ui-primitives';
3
4
  import { consumeMnemonAnchor, subscribeMnemonAnchor } from "./anchor.js";
4
5
  import Markdown from 'markdown-to-jsx';
5
- import { QODERWORK_REVIEW_POLICY } from "../review-activity.js";
6
6
  import { CATEGORIES, } from "../service.js";
7
7
  import { MnemonClient } from "./api.js";
8
8
  import { translateZh } from "./locales.js";
9
9
  import { MnemonLogo } from "./MnemonLogo.js";
10
+ import { appearanceClass, MnemonViewAppearanceProvider, resolveMnemonViewAppearance, useMnemonViewAppearance, } from "./MnemonViewAppearance.js";
10
11
  import css from './MnemonView.module.css';
11
12
  /** 系统 → 三层存储 → 读写工具;组间以分隔线呈现。 */
12
13
  const PAGE_NAV = [
@@ -34,6 +35,22 @@ const PAGE_NAV = [
34
35
  ],
35
36
  },
36
37
  ];
38
+ const SIDEBAR_PAGE_TABS = [
39
+ { id: 'status', label: 'nav.status', detail: 'nav.status.detail', glyph: '⌘' },
40
+ { id: 'runtime', label: 'nav.runtime', detail: 'nav.runtime.detail', glyph: '◫' },
41
+ { id: 'overview', label: 'nav.bodies', detail: 'nav.bodies.detail', glyph: '◇' },
42
+ { id: 'documents', label: 'nav.documents', detail: 'nav.documents.detail', glyph: '▤' },
43
+ ];
44
+ const MEMORY_PAGE_TABS = [
45
+ { id: 'overview', label: 'nav.overview' },
46
+ { id: 'explore', label: 'nav.search' },
47
+ { id: 'list', label: 'nav.content' },
48
+ { id: 'entities', label: 'nav.entities' },
49
+ ];
50
+ const MEMORY_PAGES = new Set(MEMORY_PAGE_TABS.map(item => item.id));
51
+ function isMemoryPage(page) {
52
+ return MEMORY_PAGES.has(page);
53
+ }
37
54
  const CATEGORY_KEYS = {
38
55
  decision: 'category.decision',
39
56
  preference: 'category.preference',
@@ -43,7 +60,9 @@ const CATEGORY_KEYS = {
43
60
  general: 'category.general',
44
61
  };
45
62
  const I18nContext = createContext(translateZh);
63
+ const LocaleContext = createContext('zh');
46
64
  function useT() { return useContext(I18nContext); }
65
+ function useLocale() { return useContext(LocaleContext) === 'en' ? 'en-US' : 'zh-CN'; }
47
66
  function categoryLabel(t, category) { return CATEGORY_KEYS[category] === undefined ? category : t(CATEGORY_KEYS[category]); }
48
67
  function humanBytes(bytes) {
49
68
  if (bytes < 1024)
@@ -62,11 +81,90 @@ function insightKey(insight) {
62
81
  return `${insight.memoryBodyId ?? 'memory'}:${insight.id}`;
63
82
  }
64
83
  function PageHeader(props) {
65
- return (_jsxs("div", { className: css.pageHeader, children: [_jsxs("div", { children: [_jsx("h2", { children: props.title }), _jsx("p", { children: props.description })] }), _jsxs("div", { className: css.pageHeaderMeta, children: [props.meta !== undefined && _jsx("code", { children: props.meta }), props.action] })] }));
84
+ const appearance = useMnemonViewAppearance();
85
+ return (_jsxs("div", { className: appearanceClass(css.pageHeader, appearance.classes.pageHeader), children: [_jsxs("div", { children: [_jsx("h2", { children: props.title }), _jsx("p", { children: props.description })] }), _jsxs("div", { className: css.pageHeaderMeta, children: [props.meta !== undefined && _jsx("code", { children: props.meta }), props.action] })] }));
86
+ }
87
+ function ProgressiveFooter(props) {
88
+ const t = useT();
89
+ if (props.total === 0)
90
+ return null;
91
+ const remaining = Math.max(0, props.total - props.visible);
92
+ return _jsxs("div", { className: props.compact === true ? css.compactListProgress : css.listProgress, children: [_jsx("span", { children: t('common.showing', { visible: props.visible, total: props.total }) }), remaining > 0 && _jsx("button", { type: "button", className: css.secondaryButton, onClick: props.onMore, children: t('common.showMore', { count: Math.min(props.pageSize, remaining) }) })] });
93
+ }
94
+ /** DSH-style action dialog shared by Sidebar add/write flows. */
95
+ function SidebarModal(props) {
96
+ const t = useT();
97
+ const appearance = useMnemonViewAppearance();
98
+ const dialogRef = useRef(null);
99
+ const returnFocusRef = useRef(null);
100
+ const close = useCallback(() => { if (props.busy !== true)
101
+ props.onClose(); }, [props.busy, props.onClose]);
102
+ useLayoutEffect(() => {
103
+ returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
104
+ const firstControl = dialogRef.current?.querySelector('[data-autofocus]')
105
+ ?? dialogRef.current?.querySelector('input:not(:disabled), textarea:not(:disabled), select:not(:disabled)')
106
+ ?? dialogRef.current?.querySelector('div:last-child button:not(:disabled)');
107
+ firstControl?.focus({ preventScroll: true });
108
+ return () => { if (returnFocusRef.current?.isConnected === true)
109
+ returnFocusRef.current.focus({ preventScroll: true }); };
110
+ }, []);
111
+ useEffect(() => {
112
+ const onKey = (event) => {
113
+ if (event.key === 'Escape') {
114
+ event.preventDefault();
115
+ close();
116
+ return;
117
+ }
118
+ if (event.key !== 'Tab')
119
+ return;
120
+ const controls = Array.from(dialogRef.current?.querySelectorAll('button:not(:disabled), input:not(:disabled), textarea:not(:disabled), select:not(:disabled), a[href], [tabindex]:not([tabindex="-1"])') ?? []).filter(control => control.getAttribute('aria-hidden') !== 'true');
121
+ const first = controls[0];
122
+ const last = controls.at(-1);
123
+ if (first === undefined || last === undefined) {
124
+ event.preventDefault();
125
+ return;
126
+ }
127
+ const active = document.activeElement;
128
+ if (event.shiftKey && (active === first || !dialogRef.current?.contains(active))) {
129
+ event.preventDefault();
130
+ last.focus();
131
+ }
132
+ else if (!event.shiftKey && (active === last || !dialogRef.current?.contains(active))) {
133
+ event.preventDefault();
134
+ first.focus();
135
+ }
136
+ };
137
+ window.addEventListener('keydown', onKey);
138
+ return () => window.removeEventListener('keydown', onKey);
139
+ }, [close]);
140
+ return (_jsx("div", { className: appearanceClass(css.modalBackdrop, appearance.classes.modalBackdrop), onPointerDown: event => { if (event.target === event.currentTarget)
141
+ close(); }, children: _jsxs("section", { ref: dialogRef, className: appearanceClass(css.modal, appearance.classes.modal), role: "dialog", "aria-modal": "true", "aria-label": props.title, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h2", { children: props.title }), props.description !== undefined && _jsx("p", { children: props.description })] }), _jsx("button", { type: "button", className: css.iconButton, disabled: props.busy, onClick: close, "aria-label": t('common.cancel'), children: "\u00D7" })] }), _jsx("div", { children: props.children })] }) }));
66
142
  }
67
143
  function EmptyState(props) {
68
144
  return (_jsxs("div", { className: css.emptyState, children: [_jsx("div", { className: css.emptyGlyph, "aria-hidden": "true", children: _jsx("span", { children: props.glyph }) }), _jsxs("div", { children: [_jsx("h3", { children: props.title }), _jsx("p", { children: props.children })] })] }));
69
145
  }
146
+ /** Sidebar mirrors the SSH panel's flat tab model; Buildin keeps the grouped navigation unchanged. */
147
+ function WorkspaceNavigation(props) {
148
+ const t = useT();
149
+ const appearance = useMnemonViewAppearance();
150
+ return (_jsxs("div", { className: appearanceClass(css.topNavigation, appearance.classes.topNavigation), children: [appearance.surface === 'sidebar'
151
+ ? _jsx("div", { className: appearanceClass(css.nav, appearance.classes.nav), role: "tablist", "aria-label": t('nav.aria'), children: SIDEBAR_PAGE_TABS.map(item => {
152
+ const active = item.id === 'overview' ? isMemoryPage(props.page) : props.page === item.id;
153
+ return _jsx("button", { type: "button", role: "tab", "aria-selected": active, "data-active": active ? '' : undefined, onClick: () => props.onSelect(item.id), children: t(item.label) }, item.id);
154
+ }) })
155
+ : _jsx("nav", { className: appearanceClass(css.nav, appearance.classes.nav), "aria-label": t('nav.aria'), children: PAGE_NAV.map((group, groupIndex) => _jsxs(Fragment, { children: [_jsx("div", { className: appearanceClass(css.navGroup, appearance.classes.navGroup), role: "group", "aria-label": t(group.aria), children: group.entries.map(item => _jsxs("button", { type: "button", "aria-current": props.page === item.id ? 'page' : undefined, onClick: () => props.onSelect(item.id), children: [appearance.showNavigationGlyphs && _jsx("span", { className: css.navGlyph, "aria-hidden": "true", children: item.glyph }), _jsxs("span", { children: [_jsx("strong", { children: t(item.label) }), appearance.showNavigationDetails && _jsx("small", { children: t(item.detail) })] })] }, item.id)) }), appearance.showNavigationDividers && groupIndex < PAGE_NAV.length - 1 && _jsx("span", { className: css.navGroupDivider, "aria-hidden": "true" })] }, group.aria)) }), appearance.showSpaceSummary && _jsxs("div", { className: css.spaceSummary, children: [_jsx("span", { children: t('sidebar.activeSpaces') }), _jsx("code", { children: props.catalogKnown ? `${props.activeBodies} / ${props.bodyCount}` : '— / —' }), _jsx("small", { children: props.writeEnabled ? t('common.agentSupervised') : t('common.readOnly') })] })] }));
156
+ }
157
+ /** Memory tools become a focused second-level tab set on the Sidebar surface. */
158
+ function MemoryNavigation(props) {
159
+ const t = useT();
160
+ const appearance = useMnemonViewAppearance();
161
+ if (appearance.surface !== 'sidebar' || !isMemoryPage(props.page))
162
+ return null;
163
+ return (_jsxs("section", { className: appearance.classes.memoryWorkspace, children: [_jsx(PageHeader, { title: t('nav.bodies'), description: t('overview.description'), action: _jsx("button", { type: "button", className: appearanceClass(css.primaryButton, appearance.classes.memoryWriteButton), disabled: !props.writeEnabled, onClick: props.onRemember, children: t('nav.rememberAction') }) }), _jsx("div", { className: appearance.classes.memoryNavigation, children: _jsx("div", { className: appearance.classes.memoryTabs, role: "tablist", "aria-label": t('nav.memory.aria'), children: MEMORY_PAGE_TABS.map(item => {
164
+ const active = props.page === item.id;
165
+ return _jsx("button", { type: "button", role: "tab", "aria-selected": active, "data-active": active ? '' : undefined, onClick: () => props.onSelect(item.id), children: t(item.label) }, item.id);
166
+ }) }) })] }));
167
+ }
70
168
  /** Full-text popup for a selected graph node whose inspector preview is clamped. */
71
169
  function ContentPreview(props) {
72
170
  const t = useT();
@@ -107,9 +205,17 @@ function DocumentMarkdown(props) {
107
205
  }
108
206
  function InsightCard(props) {
109
207
  const t = useT();
208
+ const appearance = useMnemonViewAppearance();
110
209
  const [confirming, setConfirming] = useState(false);
111
210
  const [forgetting, setForgetting] = useState(false);
112
211
  const { insight } = props;
212
+ const neutralActionClass = appearance.surface === 'sidebar'
213
+ ? appearanceClass(css.ghostButton, appearance.classes.itemActionButton)
214
+ : css.ghostButton;
215
+ const forgetActionClass = appearance.surface === 'sidebar'
216
+ ? appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction))
217
+ : css.dangerButton;
218
+ const inlineConfirming = appearance.surface === 'buildin' && confirming;
113
219
  const meta = [
114
220
  insight.memoryBodyName,
115
221
  insight.category !== undefined ? categoryLabel(t, insight.category) : undefined,
@@ -127,7 +233,7 @@ function InsightCard(props) {
127
233
  setConfirming(false);
128
234
  }
129
235
  };
130
- return (_jsxs("article", { className: css.insightCard, children: [_jsxs("div", { className: css.cardTop, children: [_jsx("div", { className: css.badges, children: meta.map(entry => _jsx("span", { className: css.badge, children: entry }, entry)) }), _jsx("code", { className: css.id, title: insight.id, children: insight.id.slice(0, 8) })] }), _jsx("p", { className: css.content, children: insight.content }), (insight.tags?.length ?? 0) > 0 && _jsx("div", { className: css.tags, children: insight.tags.map(tag => _jsxs("span", { children: ["#", tag] }, tag)) }), (insight.entities?.length ?? 0) > 0 && _jsx("div", { className: css.entities, children: insight.entities.map(entity => _jsx("span", { children: entity }, entity)) }), _jsx("div", { className: css.cardActions, children: confirming ? (_jsxs("div", { className: css.confirmBar, role: "group", "aria-label": t('card.confirmAria'), children: [_jsx("span", { children: t('card.confirmText') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: forgetting, onClick: () => void forget(), children: forgetting ? t('card.processing') : t('card.confirmForget') }), _jsx("button", { type: "button", className: css.ghostButton, disabled: forgetting, onClick: () => setConfirming(false), children: t('common.cancel') })] })) : (_jsxs(_Fragment, { children: [props.onRelated !== undefined && _jsx("button", { type: "button", className: css.ghostButton, onClick: () => props.onRelated?.(insight), children: t('card.related') }), props.onClone !== undefined && _jsx("button", { type: "button", className: css.ghostButton, onClick: () => props.onClone?.(insight), children: t('card.clone') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => void navigator.clipboard?.writeText(insight.id), children: t('common.copyId') }), props.writeEnabled && _jsx("button", { type: "button", className: css.dangerButton, onClick: () => setConfirming(true), children: t('card.forget') })] })) })] }));
236
+ return (_jsxs(_Fragment, { children: [_jsxs("article", { className: css.insightCard, children: [_jsxs("div", { className: css.cardTop, children: [_jsx("div", { className: css.badges, children: meta.map(entry => _jsx("span", { className: css.badge, children: entry }, entry)) }), _jsx("code", { className: css.id, title: insight.id, children: insight.id.slice(0, 8) })] }), _jsx("p", { className: css.content, children: insight.content }), (insight.tags?.length ?? 0) > 0 && _jsx("div", { className: css.tags, children: insight.tags.map(tag => _jsxs("span", { children: ["#", tag] }, tag)) }), (insight.entities?.length ?? 0) > 0 && _jsx("div", { className: css.entities, children: insight.entities.map(entity => _jsx("span", { children: entity }, entity)) }), _jsx("div", { className: css.cardActions, children: inlineConfirming ? (_jsxs("div", { className: css.confirmBar, role: "group", "aria-label": t('card.confirmAria'), children: [_jsx("span", { children: t('card.confirmText') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: forgetting, onClick: () => void forget(), children: forgetting ? t('card.processing') : t('card.confirmForget') }), _jsx("button", { type: "button", className: css.ghostButton, disabled: forgetting, onClick: () => setConfirming(false), children: t('common.cancel') })] })) : (_jsxs(_Fragment, { children: [props.onRelated !== undefined && _jsx("button", { type: "button", className: neutralActionClass, onClick: () => props.onRelated?.(insight), children: t('card.related') }), props.onClone !== undefined && _jsx("button", { type: "button", className: neutralActionClass, onClick: () => props.onClone?.(insight), children: t('card.clone') }), _jsx("button", { type: "button", className: neutralActionClass, onClick: () => void navigator.clipboard?.writeText(insight.id), children: t('common.copyId') }), props.writeEnabled && _jsx("button", { type: "button", className: forgetActionClass, onClick: () => setConfirming(true), children: t('card.forget') })] })) })] }), appearance.surface === 'sidebar' && confirming && _jsx(SidebarModal, { title: t('card.confirmText'), description: `${insight.memoryBodyName ?? insight.memoryBodyId ?? ''}${insight.memoryBodyName === undefined && insight.memoryBodyId === undefined ? '' : ' · '}${insight.id}`, busy: forgetting, onClose: () => setConfirming(false), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("p", { className: css.bodyDeleteContent, children: insight.content }), _jsx("span", { children: meta.join(' · ') })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: forgetting, onClick: () => setConfirming(false), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: forgetting, onClick: () => void forget(), children: forgetting ? t('card.processing') : t('card.confirmForget') })] })] }) })] }));
131
237
  }
132
238
  const GRAPH_WIDTH = 930;
133
239
  const GRAPH_HEIGHT = 520;
@@ -545,6 +651,8 @@ function MemoryGraph(props) {
545
651
  }
546
652
  function OverviewPage(props) {
547
653
  const t = useT();
654
+ const locale = useLocale();
655
+ const appearance = useMnemonViewAppearance();
548
656
  const [graph, setGraph] = useState(null);
549
657
  const [catalog, setCatalog] = useState(null);
550
658
  const [selected, setSelected] = useState(null);
@@ -552,6 +660,7 @@ function OverviewPage(props) {
552
660
  const [error, setError] = useState(null);
553
661
  const [changing, setChanging] = useState(null);
554
662
  const [creating, setCreating] = useState(false);
663
+ const [creatingBodyOpen, setCreatingBodyOpen] = useState(false);
555
664
  const [bodyName, setBodyName] = useState('');
556
665
  const [bodyDescription, setBodyDescription] = useState('');
557
666
  const [catalogUnavailable, setCatalogUnavailable] = useState(false);
@@ -559,6 +668,8 @@ function OverviewPage(props) {
559
668
  const [editName, setEditName] = useState('');
560
669
  const [editDescription, setEditDescription] = useState('');
561
670
  const [savingBody, setSavingBody] = useState(null);
671
+ const [confirmingDeleteBody, setConfirmingDeleteBody] = useState(null);
672
+ const [deletingBody, setDeletingBody] = useState(null);
562
673
  const [preview, setPreview] = useState(null);
563
674
  const load = useCallback(async (quiet = false) => {
564
675
  if (!quiet)
@@ -645,6 +756,8 @@ function OverviewPage(props) {
645
756
  await props.client.createBody({ name: bodyName, description: bodyDescription });
646
757
  setBodyName('');
647
758
  setBodyDescription('');
759
+ if (appearance.surface === 'sidebar')
760
+ setCreatingBodyOpen(false);
648
761
  await load(true);
649
762
  props.onMutate();
650
763
  }
@@ -655,12 +768,39 @@ function OverviewPage(props) {
655
768
  setCreating(false);
656
769
  }
657
770
  };
658
- const generated = graph === null ? t('overview.waitingSnapshot') : t('overview.updatedAt', { time: new Date(graph.generatedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) });
771
+ const deleteBody = async (body) => {
772
+ setDeletingBody(body.id);
773
+ setError(null);
774
+ try {
775
+ await props.client.deleteBody(body.id);
776
+ setConfirmingDeleteBody(null);
777
+ await load(true);
778
+ props.onMutate();
779
+ }
780
+ catch (reason) {
781
+ setError(message(reason));
782
+ }
783
+ finally {
784
+ setDeletingBody(null);
785
+ }
786
+ };
787
+ const generated = graph === null ? t('overview.waitingSnapshot') : t('overview.updatedAt', { time: new Date(graph.generatedAt).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', second: '2-digit' }) });
659
788
  const graphSpaces = graph?.nodes.filter(node => graphNodeKind(node) === 'space').length ?? 0;
660
789
  const graphEntities = graph?.nodes.filter(node => graphNodeKind(node) === 'entity').length ?? 0;
661
790
  const graphMemories = graph?.nodes.filter(node => graphNodeKind(node) === 'memory').length ?? 0;
662
791
  const selectedKind = selected === null ? null : graphNodeKind(selected);
663
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('overview.title'), description: t('overview.description'), meta: t('overview.interval'), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: loading ? t('overview.syncing') : t('overview.syncNow') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), _jsxs("section", { className: css.bodyDirectory, "aria-label": t('overview.directory'), children: [_jsxs("div", { className: css.bodyDirectoryHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('overview.directory') }), _jsx("p", { children: t('overview.directory.description') }), _jsx("code", { className: css.bodyDirectoryPath, children: catalogUnavailable ? t('overview.directory.unsynced') : catalog?.directory || props.fallbackDirectory || t('overview.directory.waiting') })] }), _jsx("strong", { children: catalogUnavailable ? t('overview.directory.unsyncedBadge') : `${catalog?.activeCount ?? '—'} / ${catalog?.total ?? '—'} ${t('common.active')}` })] }), _jsxs("div", { className: css.bodyGrid, children: [catalog?.items.map(body => (_jsx("article", { className: css.bodyCard, "data-active": body.active || undefined, "data-healthy": body.healthy || undefined, "data-editing": editingBody === body.id || undefined, title: body.error, children: editingBody === body.id ? (_jsxs("form", { className: css.bodyEdit, onSubmit: event => void saveEdit(event, body), children: [_jsxs("label", { children: [t('overview.editName'), _jsx("input", { "aria-label": t('overview.editName'), value: editName, onChange: event => setEditName(event.target.value), maxLength: 100, required: true })] }), _jsxs("label", { children: [t('overview.editDescription'), _jsx("textarea", { "aria-label": t('overview.editDescription'), value: editDescription, onChange: event => setEditDescription(event.target.value), rows: 3, maxLength: 1000 })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "submit", className: css.primaryButton, disabled: savingBody === body.id || editName.trim() === '', children: savingBody === body.id ? t('overview.savingBody') : t('overview.saveBody') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditingBody(null), children: t('common.cancel') })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.bodyCardTop, children: [_jsx("span", { className: css.bodySignal }), _jsxs("div", { children: [_jsx("strong", { children: body.name }), _jsx("code", { children: body.id }), _jsx("small", { className: css.bodyHealth, children: body.healthy ? t('overview.storageHealthy') : t('overview.storageUnhealthy') })] }), _jsxs("div", { className: css.bodyCardActions, children: [_jsxs("button", { type: "button", className: css.bodySwitch, role: "switch", "aria-checked": body.active, "aria-label": t('overview.toggleAria', { name: body.name }), disabled: !props.writeEnabled || changing === body.id, onClick: () => void toggle(body), children: [_jsx("span", { className: css.bodySwitchTrack, "aria-hidden": "true", children: _jsx("i", {}) }), _jsx("span", { children: changing === body.id ? t('overview.toggling') : body.active ? t('common.active') : t('common.inactive') })] }), _jsx("button", { type: "button", className: css.bodyEditButton, "aria-label": t('overview.editBodyAria', { name: body.name }), title: t('overview.editBody'), disabled: !props.writeEnabled, onClick: () => beginEdit(body), children: "\u270E" })] })] }), _jsx("p", { children: body.description || t('overview.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: t('common.memories', { count: body.stats?.totalInsights ?? 0 }) }), _jsx("span", { children: t('common.edges', { count: body.stats?.edgeCount ?? 0 }) }), _jsx("span", { children: humanBytes(body.stats?.dbSizeBytes ?? 0) })] })] })) }, body.id))), catalog?.total === 0 && _jsxs("div", { className: css.bodyDirectoryEmpty, children: [_jsx("span", { children: "\u25C7" }), _jsxs("div", { children: [_jsx("strong", { children: catalogUnavailable ? t('overview.unsyncedTitle') : t('overview.emptyTitle') }), _jsx("p", { children: catalogUnavailable ? t('overview.unsyncedShort') : t('overview.emptyShort') })] })] })] }), props.writeEnabled && !catalogUnavailable && _jsxs("details", { className: css.bodyCreate, open: catalog?.total === 0 ? true : undefined, children: [_jsx("summary", { children: t('overview.create') }), _jsxs("form", { onSubmit: event => void create(event), children: [_jsx("input", { "aria-label": t('overview.createName'), value: bodyName, onChange: event => setBodyName(event.target.value), placeholder: t('overview.createNamePlaceholder'), required: true }), _jsx("input", { "aria-label": t('overview.createDescription'), value: bodyDescription, onChange: event => setBodyDescription(event.target.value), placeholder: t('overview.createDescriptionPlaceholder'), required: true }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: creating, children: creating ? t('overview.creating') : t('overview.createAction') })] })] })] }), !catalogUnavailable && graph !== null && graph.nodes.length > 0 ? (_jsxs("div", { className: css.graphLayout, children: [_jsxs("section", { className: css.graphPanel, children: [_jsxs("div", { className: css.graphToolbar, children: [_jsxs("div", { children: [_jsx("span", { className: css.liveDot }), t('overview.snapshot'), " ", _jsx("small", { children: generated })] }), _jsxs("div", { className: css.graphLegend, children: [_jsx("span", { "data-edge": "scope", children: t('overview.edgeScope') }), _jsx("span", { "data-edge": "temporal", children: t('overview.edgeTemporal') }), _jsx("span", { "data-edge": "semantic", children: t('overview.edgeSemantic') }), _jsx("span", { "data-edge": "causal", children: t('overview.edgeCausal') }), _jsx("span", { "data-edge": "entity", children: t('overview.edgeEntity') })] })] }), _jsx("div", { className: css.graphViewport, children: _jsx(MemoryGraph, { graph: graph, selectedId: selected === null ? undefined : graphNodeKey(selected), onSelect: setSelected }) }), _jsxs("div", { className: css.graphFooter, children: [_jsx("span", { children: t('overview.graphComposition', { spaces: graphSpaces, memories: graphMemories, entities: graphEntities }) }), _jsxs("span", { children: [t('overview.graphCount', { visible: Math.min(graph.nodes.length, 60), total: graph.nodes.length }), " \u00B7 ", t('overview.graphEdges', { count: graph.edges.length })] })] })] }), _jsx("aside", { className: css.graphInspector, "data-empty": selected === null || undefined, children: selected === null ? (_jsxs("div", { className: css.inspectorEmpty, children: [_jsx(MnemonLogo, { className: css.inspectorLogo, title: t('overview.inspector') }), _jsx("h3", { children: t('overview.selectNode') }), _jsx("p", { children: t('overview.selectNodeText') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.inspectorHeading, children: [_jsx("span", { children: t(selectedKind === 'space' ? 'overview.inspectorSpace' : selectedKind === 'entity' ? 'overview.inspectorEntity' : 'overview.inspector') }), _jsx("button", { type: "button", onClick: () => setSelected(null), "aria-label": t('overview.closeInspector'), children: "\u00D7" })] }), _jsx("span", { className: css.categoryChip, children: graphKindLabel(t, selected) }), _jsxs("div", { className: css.inspectorTitleRow, children: [_jsx("h3", { className: css.inspectorTitle, children: selected.content }), selectedKind === 'memory' && selected.content.length > 140 && _jsx("button", { type: "button", className: css.inspectorEye, onClick: () => setPreview(selected), "aria-label": t('overview.previewAria'), title: t('overview.previewAria'), children: _jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [_jsx("path", { d: "M1 8s2.6-4.4 7-4.4S15 8 15 8s-2.6 4.4-7 4.4S1 8 1 8z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("circle", { cx: "8", cy: "8", r: "2.1", fill: "currentColor" })] }) })] }), selectedKind === 'space'
792
+ const editingBodyView = editingBody === null ? undefined : catalog?.items.find(body => body.id === editingBody);
793
+ const deletingBodyView = confirmingDeleteBody === null ? undefined : catalog?.items.find(body => body.id === confirmingDeleteBody);
794
+ const bodyEditForm = (body) => _jsxs("form", { className: css.bodyEdit, onSubmit: event => void saveEdit(event, body), children: [_jsxs("label", { children: [t('overview.editName'), _jsx("input", { "aria-label": t('overview.editName'), value: editName, onChange: event => setEditName(event.target.value), maxLength: 100, required: true })] }), _jsxs("label", { children: [t('overview.editDescription'), _jsx("textarea", { "aria-label": t('overview.editDescription'), value: editDescription, onChange: event => setEditDescription(event.target.value), rows: 4, maxLength: 1000 })] }), _jsxs("div", { className: css.bodyEditActions, children: [appearance.surface === 'sidebar' && _jsx("button", { type: "button", className: css.ghostButton, disabled: savingBody === body.id, onClick: () => setEditingBody(null), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: savingBody === body.id || editName.trim() === '', children: savingBody === body.id ? t('overview.savingBody') : t('overview.saveBody') }), appearance.surface === 'buildin' && _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditingBody(null), children: t('common.cancel') })] })] });
795
+ const bodyCreateForm = _jsxs("form", { className: css.bodyEdit, onSubmit: event => void create(event), children: [_jsxs("label", { children: [t('overview.createName'), _jsx("input", { "aria-label": t('overview.createName'), value: bodyName, onChange: event => setBodyName(event.target.value), placeholder: t('overview.createNamePlaceholder'), maxLength: 100, required: true })] }), _jsxs("label", { children: [t('overview.createDescription'), _jsx("textarea", { "aria-label": t('overview.createDescription'), value: bodyDescription, onChange: event => setBodyDescription(event.target.value), placeholder: t('overview.createDescriptionPlaceholder'), rows: 5, maxLength: 1000, required: true })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: creating, onClick: () => setCreatingBodyOpen(false), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: creating || bodyName.trim() === '' || bodyDescription.trim() === '', children: creating ? t('overview.creating') : t('overview.createAction') })] })] });
796
+ const bodyToggle = (body) => _jsxs("button", { type: "button", className: css.bodySwitch, role: "switch", "aria-checked": body.active, "aria-label": t('overview.toggleAria', { name: body.name }), disabled: !props.writeEnabled || changing === body.id || deletingBody === body.id, onClick: () => void toggle(body), children: [_jsx("span", { className: css.bodySwitchTrack, "aria-hidden": "true", children: _jsx("i", {}) }), _jsx("span", { children: changing === body.id ? t('overview.toggling') : body.active ? t('common.active') : t('common.inactive') })] });
797
+ const bodyEditActionClass = appearanceClass(css.ghostButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemEditAction));
798
+ const bodyDeleteActionClass = appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction));
799
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: appearance.surface === 'sidebar' ? t('nav.overview') : t('overview.title'), description: t(appearance.surface === 'sidebar' ? 'overview.pageDescription' : 'overview.description'), meta: t('overview.interval'), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: loading ? t('overview.syncing') : t('overview.syncNow') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), _jsxs("section", { className: css.bodyDirectory, "aria-label": t('overview.directory'), children: [_jsxs("div", { className: css.bodyDirectoryHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('overview.directory') }), _jsx("p", { children: t('overview.directory.description') }), _jsx("code", { className: css.bodyDirectoryPath, children: catalogUnavailable ? t('overview.directory.unsynced') : catalog?.directory || props.fallbackDirectory || t('overview.directory.waiting') })] }), appearance.surface === 'sidebar' ? _jsxs("div", { className: appearance.classes.bodyDirectoryActions, children: [_jsx("strong", { children: catalogUnavailable ? t('overview.directory.unsyncedBadge') : `${catalog?.activeCount ?? '—'} / ${catalog?.total ?? '—'} ${t('common.active')}` }), props.writeEnabled && !catalogUnavailable && _jsx("button", { type: "button", className: bodyEditActionClass, onClick: () => setCreatingBodyOpen(true), children: t('overview.createTitle') })] }) : _jsx("strong", { children: catalogUnavailable ? t('overview.directory.unsyncedBadge') : `${catalog?.activeCount ?? '—'} / ${catalog?.total ?? '—'} ${t('common.active')}` })] }), _jsxs("div", { className: css.bodyGrid, children: [catalog?.items.map(body => (_jsx("article", { className: css.bodyCard, "data-active": body.active || undefined, "data-healthy": body.healthy || undefined, "data-editing": (appearance.surface === 'buildin' && editingBody === body.id) || undefined, title: body.error, children: appearance.surface === 'buildin'
800
+ ? editingBody === body.id
801
+ ? bodyEditForm(body)
802
+ : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.bodyCardTop, children: [_jsx("span", { className: css.bodySignal }), _jsxs("div", { children: [_jsx("strong", { children: body.name }), _jsx("code", { children: body.id }), _jsx("small", { className: css.bodyHealth, children: body.healthy ? t('overview.storageHealthy') : t('overview.storageUnhealthy') })] }), _jsxs("div", { className: css.bodyCardActions, children: [bodyToggle(body), _jsx("button", { type: "button", className: css.bodyEditButton, "aria-label": t('overview.editBodyAria', { name: body.name }), title: t('overview.editBody'), disabled: !props.writeEnabled, onClick: () => beginEdit(body), children: "\u270E" })] })] }), _jsx("p", { children: body.description || t('overview.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: t('common.memories', { count: body.stats?.totalInsights ?? 0 }) }), _jsx("span", { children: t('common.edges', { count: body.stats?.edgeCount ?? 0 }) }), _jsx("span", { children: humanBytes(body.stats?.dbSizeBytes ?? 0) })] })] })
803
+ : _jsxs(_Fragment, { children: [_jsxs("div", { className: appearance.classes.bodyCardHeader, children: [_jsxs("div", { className: appearance.classes.bodyCardIdentity, children: [_jsx("span", { className: css.bodySignal }), _jsxs("div", { children: [_jsx("strong", { children: body.name }), _jsxs("div", { className: appearance.classes.bodyCardMeta, children: [_jsx("code", { children: body.id }), _jsx("small", { className: css.bodyHealth, children: body.healthy ? t('overview.storageHealthy') : t('overview.storageUnhealthy') })] })] })] }), bodyToggle(body)] }), _jsx("p", { title: body.description || t('overview.noDescription'), children: body.description || t('overview.noDescription') }), _jsxs("footer", { className: appearance.classes.bodyCardFooter, children: [_jsxs("div", { className: appearance.classes.bodyCardStats, children: [_jsx("span", { children: t('common.memories', { count: body.stats?.totalInsights ?? 0 }) }), _jsx("span", { children: t('common.edges', { count: body.stats?.edgeCount ?? 0 }) }), _jsx("span", { children: humanBytes(body.stats?.dbSizeBytes ?? 0) })] }), _jsxs("div", { className: css.bodyCardActions, children: [_jsx("button", { type: "button", className: bodyEditActionClass, "aria-label": t('overview.editBodyAria', { name: body.name }), disabled: !props.writeEnabled || deletingBody === body.id, onClick: () => beginEdit(body), children: t('overview.editBody') }), _jsx("button", { type: "button", className: bodyDeleteActionClass, "aria-label": t('overview.deleteBodyAria', { name: body.name }), disabled: !props.writeEnabled || deletingBody === body.id, onClick: () => setConfirmingDeleteBody(body.id), children: t('overview.deleteBody') })] })] })] }) }, body.id))), catalog?.total === 0 && _jsxs("div", { className: css.bodyDirectoryEmpty, children: [_jsx("span", { children: "\u25C7" }), _jsxs("div", { children: [_jsx("strong", { children: catalogUnavailable ? t('overview.unsyncedTitle') : t('overview.emptyTitle') }), _jsx("p", { children: catalogUnavailable ? t('overview.unsyncedShort') : t('overview.emptyShort') })] })] })] }), appearance.surface === 'buildin' && props.writeEnabled && !catalogUnavailable && _jsxs("details", { className: css.bodyCreate, open: catalog?.total === 0 ? true : undefined, children: [_jsx("summary", { children: t('overview.create') }), _jsxs("form", { onSubmit: event => void create(event), children: [_jsx("input", { "aria-label": t('overview.createName'), value: bodyName, onChange: event => setBodyName(event.target.value), placeholder: t('overview.createNamePlaceholder'), required: true }), _jsx("input", { "aria-label": t('overview.createDescription'), value: bodyDescription, onChange: event => setBodyDescription(event.target.value), placeholder: t('overview.createDescriptionPlaceholder'), required: true }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: creating, children: creating ? t('overview.creating') : t('overview.createAction') })] })] })] }), appearance.surface === 'sidebar' && creatingBodyOpen && _jsx(SidebarModal, { title: t('overview.createTitle'), description: catalog?.directory || props.fallbackDirectory || t('overview.directory.waiting'), busy: creating, onClose: () => setCreatingBodyOpen(false), children: bodyCreateForm }), appearance.surface === 'sidebar' && editingBodyView !== undefined && _jsx(SidebarModal, { title: t('overview.editBodyAria', { name: editingBodyView.name }), description: editingBodyView.id, busy: savingBody === editingBodyView.id, onClose: () => setEditingBody(null), children: bodyEditForm(editingBodyView) }), appearance.surface === 'sidebar' && deletingBodyView !== undefined && _jsx(SidebarModal, { title: t('overview.deleteTitle', { name: deletingBodyView.name }), description: deletingBodyView.id, busy: deletingBody === deletingBodyView.id, onClose: () => setConfirmingDeleteBody(null), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsx("p", { children: t('overview.deleteWarning') }), _jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("strong", { children: deletingBodyView.name }), _jsxs("span", { children: [t('common.memories', { count: deletingBodyView.stats?.totalInsights ?? 0 }), " \u00B7 ", t('common.edges', { count: deletingBodyView.stats?.edgeCount ?? 0 }), " \u00B7 ", humanBytes(deletingBodyView.stats?.dbSizeBytes ?? 0)] })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: deletingBody === deletingBodyView.id, onClick: () => setConfirmingDeleteBody(null), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: deletingBody === deletingBodyView.id, onClick: () => void deleteBody(deletingBodyView), children: deletingBody === deletingBodyView.id ? t('overview.deletingBody') : t('overview.deleteAction') })] })] }) }), !catalogUnavailable && graph !== null && graph.nodes.length > 0 ? (_jsxs("div", { className: css.graphLayout, children: [_jsxs("section", { className: css.graphPanel, children: [_jsxs("div", { className: css.graphToolbar, children: [_jsxs("div", { children: [_jsx("span", { className: css.liveDot }), t('overview.snapshot'), " ", _jsx("small", { children: generated })] }), _jsxs("div", { className: css.graphLegend, children: [_jsx("span", { "data-edge": "scope", children: t('overview.edgeScope') }), _jsx("span", { "data-edge": "temporal", children: t('overview.edgeTemporal') }), _jsx("span", { "data-edge": "semantic", children: t('overview.edgeSemantic') }), _jsx("span", { "data-edge": "causal", children: t('overview.edgeCausal') }), _jsx("span", { "data-edge": "entity", children: t('overview.edgeEntity') })] })] }), _jsx("div", { className: css.graphViewport, children: _jsx(MemoryGraph, { graph: graph, selectedId: selected === null ? undefined : graphNodeKey(selected), onSelect: setSelected }) }), _jsxs("div", { className: css.graphFooter, children: [_jsx("span", { children: t('overview.graphComposition', { spaces: graphSpaces, memories: graphMemories, entities: graphEntities }) }), _jsxs("span", { children: [t('overview.graphCount', { visible: Math.min(graph.nodes.length, 60), total: graph.nodes.length }), " \u00B7 ", t('overview.graphEdges', { count: graph.edges.length })] })] })] }), _jsx("aside", { className: css.graphInspector, "data-empty": selected === null || undefined, children: selected === null ? (_jsxs("div", { className: css.inspectorEmpty, children: [appearance.showLogo ? _jsx(MnemonLogo, { className: css.inspectorLogo, title: t('overview.inspector') }) : _jsx("span", { className: appearanceClass(css.inspectorLogo, appearance.classes.inspectorGlyph), "aria-hidden": "true", children: "\u25C7" }), _jsx("h3", { children: t('overview.selectNode') }), _jsx("p", { children: t('overview.selectNodeText') })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.inspectorHeading, children: [_jsx("span", { children: t(selectedKind === 'space' ? 'overview.inspectorSpace' : selectedKind === 'entity' ? 'overview.inspectorEntity' : 'overview.inspector') }), _jsx("button", { type: "button", onClick: () => setSelected(null), "aria-label": t('overview.closeInspector'), children: "\u00D7" })] }), _jsx("span", { className: css.categoryChip, children: graphKindLabel(t, selected) }), _jsxs("div", { className: css.inspectorTitleRow, children: [_jsx("h3", { className: css.inspectorTitle, children: selected.content }), selectedKind === 'memory' && selected.content.length > 140 && _jsx("button", { type: "button", className: css.inspectorEye, onClick: () => setPreview(selected), "aria-label": t('overview.previewAria'), title: t('overview.previewAria'), children: _jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", "aria-hidden": "true", children: [_jsx("path", { d: "M1 8s2.6-4.4 7-4.4S15 8 15 8s-2.6 4.4-7 4.4S1 8 1 8z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("circle", { cx: "8", cy: "8", r: "2.1", fill: "currentColor" })] }) })] }), selectedKind === 'space'
664
804
  ? _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('overview.spaceId') }), _jsx("dd", { children: _jsx("code", { children: selected.memoryBodyId ?? selected.id }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('overview.containedMemories') }), _jsx("dd", { children: selected.occurrenceCount ?? 0 })] })] })
665
805
  : selectedKind === 'entity'
666
806
  ? _jsxs("dl", { className: css.inspectorMeta, children: [_jsxs("div", { children: [_jsx("dt", { children: t('overview.entityMentions') }), _jsx("dd", { children: selected.occurrenceCount ?? 0 })] }), _jsxs("div", { children: [_jsx("dt", { children: t('term.spaces') }), _jsx("dd", { children: selected.memoryBodyNames?.join(' · ') || '—' })] })] })
@@ -674,6 +814,8 @@ function OverviewPage(props) {
674
814
  }
675
815
  function ExplorePage(props) {
676
816
  const t = useT();
817
+ const appearance = useMnemonViewAppearance();
818
+ const pageSize = appearance.surface === 'sidebar' ? 6 : Number.MAX_SAFE_INTEGER;
677
819
  const [query, setQuery] = useState(props.seed);
678
820
  const [mode, setMode] = useState('smart');
679
821
  const [category, setCategory] = useState('');
@@ -685,6 +827,8 @@ function ExplorePage(props) {
685
827
  const [relatedTo, setRelatedTo] = useState(null);
686
828
  const [related, setRelated] = useState([]);
687
829
  const [relatedLoading, setRelatedLoading] = useState(false);
830
+ const [visibleResultLimit, setVisibleResultLimit] = useState(pageSize);
831
+ const [visibleRelatedLimit, setVisibleRelatedLimit] = useState(pageSize);
688
832
  useEffect(() => { if (props.seed !== '')
689
833
  setQuery(props.seed); }, [props.seed]);
690
834
  const runSearch = async (withAgent) => {
@@ -695,6 +839,8 @@ function ExplorePage(props) {
695
839
  setError(null);
696
840
  setRelatedTo(null);
697
841
  setAgentAnswer(null);
842
+ setVisibleResultLimit(pageSize);
843
+ setVisibleRelatedLimit(pageSize);
698
844
  try {
699
845
  const request = { query, mode, ...(category === '' ? {} : { category }), limit: props.status?.defaultRecallLimit ?? 10 };
700
846
  if (withAgent) {
@@ -722,6 +868,7 @@ function ExplorePage(props) {
722
868
  setRelated([]);
723
869
  setRelatedLoading(true);
724
870
  setError(null);
871
+ setVisibleRelatedLimit(pageSize);
725
872
  if (typeof window.requestAnimationFrame === 'function')
726
873
  window.requestAnimationFrame(() => document.getElementById('mnemon-related-pane')?.scrollIntoView({ block: 'nearest', behavior: 'smooth' }));
727
874
  try {
@@ -741,17 +888,27 @@ function ExplorePage(props) {
741
888
  if (relatedTo !== null && insightKey(relatedTo) === insightKey(insight))
742
889
  setRelatedTo(null);
743
890
  };
744
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('search.title'), description: t('search.description'), meta: t('search.maxResults', { count: props.status?.defaultRecallLimit ?? '—' }) }), _jsxs("form", { className: css.searchBar, onSubmit: event => void search(event), children: [_jsxs("div", { className: css.queryField, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { value: query, onChange: event => setQuery(event.target.value), placeholder: t('search.placeholder'), "aria-label": t('search.queryAria') }), _jsx("kbd", { children: "\u21B5" })] }), _jsxs("div", { className: css.searchControls, children: [_jsxs("label", { children: [t('common.category'), _jsxs("select", { value: category, onChange: event => setCategory(event.target.value), "aria-label": t('search.categoryAria'), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] })] }), _jsxs("label", { children: [t('search.strategy'), _jsxs("select", { value: mode, onChange: event => setMode(event.target.value), "aria-label": t('search.modeAria'), children: [_jsx("option", { value: "smart", children: t('search.modeSmart') }), _jsx("option", { value: "keyword", children: t('search.modeKeyword') }), _jsx("option", { value: "basic", children: t('search.modeBasic') })] })] }), _jsxs("div", { className: css.searchActions, children: [_jsx("button", { type: "submit", className: css.secondaryButton, disabled: searching || query.trim() === '', children: searchKind === 'direct' ? t('search.searching') : t('search.action') }), _jsx("button", { type: "button", className: css.primaryButton, disabled: searching || query.trim() === '' || props.status?.lifecycle?.sessionAvailable !== true, onClick: () => void runSearch(true), children: searchKind === 'agent' ? t('search.agentSearching') : t('search.agentAction') })] })] })] }), agentAnswer !== null && _jsxs("section", { className: css.agentAnswer, "aria-label": t('search.agentAnswer'), children: [_jsxs("div", { className: css.agentAnswerHeading, children: [_jsxs("div", { children: [_jsx("span", { children: t('search.agentAnswerHint') }), _jsx("h3", { children: t('search.agentAnswer') })] }), _jsx("code", { children: agentAnswer.runId.slice(0, 8) })] }), _jsx("p", { children: agentAnswer.answer }), agentAnswer.citations.length > 0 && _jsx("div", { className: css.agentCitations, children: agentAnswer.citations.map(citation => _jsx("code", { children: citation }, citation)) })] }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !searched && _jsx(EmptyState, { glyph: "\u2315", title: t('search.startTitle'), children: t('search.startText') }), searched && !searching && results.length === 0 && error === null && _jsx(EmptyState, { glyph: "0", title: t('search.emptyTitle'), children: t('search.emptyText') }), results.length > 0 && (_jsxs("div", { className: relatedTo === null ? css.singleColumn : css.resultLayout, children: [_jsxs("section", { className: css.results, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.results') }) }), _jsx("strong", { children: results.length })] }), results.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight)))] }), relatedTo !== null && _jsxs("aside", { id: "mnemon-related-pane", className: css.relatedPane, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.related') }) }), _jsx("button", { type: "button", onClick: () => setRelatedTo(null), "aria-label": t('search.closeRelated'), children: "\u00D7" })] }), _jsx("p", { className: css.relatedSource, children: relatedTo.content }), relatedLoading && _jsx("div", { className: css.loading, children: t('search.traversing') }), !relatedLoading && related.length === 0 && _jsx("div", { className: css.muted, children: t('search.noRelated') }), related.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight)))] })] }))] }));
891
+ const visibleResults = results.slice(0, visibleResultLimit);
892
+ const visibleRelated = related.slice(0, visibleRelatedLimit);
893
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('search.title'), description: t('search.description'), meta: t('search.maxResults', { count: props.status?.defaultRecallLimit ?? '—' }) }), _jsxs("form", { className: css.searchBar, onSubmit: event => void search(event), children: [_jsxs("div", { className: css.queryField, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { value: query, onChange: event => setQuery(event.target.value), placeholder: t('search.placeholder'), "aria-label": t('search.queryAria') }), _jsx("kbd", { children: "\u21B5" })] }), _jsxs("div", { className: css.searchControls, children: [_jsxs("label", { children: [t('common.category'), _jsxs("select", { value: category, onChange: event => setCategory(event.target.value), "aria-label": t('search.categoryAria'), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] })] }), _jsxs("label", { children: [t('search.strategy'), _jsxs("select", { value: mode, onChange: event => setMode(event.target.value), "aria-label": t('search.modeAria'), children: [_jsx("option", { value: "smart", children: t('search.modeSmart') }), _jsx("option", { value: "keyword", children: t('search.modeKeyword') }), _jsx("option", { value: "basic", children: t('search.modeBasic') })] })] }), _jsxs("div", { className: css.searchActions, children: [_jsx("button", { type: "submit", className: css.secondaryButton, disabled: searching || query.trim() === '', children: searchKind === 'direct' ? t('search.searching') : t('search.action') }), _jsx("button", { type: "button", className: css.primaryButton, disabled: searching || query.trim() === '' || props.status?.lifecycle?.sessionAvailable !== true, onClick: () => void runSearch(true), children: searchKind === 'agent' ? t('search.agentSearching') : t('search.agentAction') })] })] })] }), agentAnswer !== null && _jsxs("section", { className: css.agentAnswer, "aria-label": t('search.agentAnswer'), children: [_jsxs("div", { className: css.agentAnswerHeading, children: [_jsxs("div", { children: [_jsx("span", { children: t('search.agentAnswerHint') }), _jsx("h3", { children: t('search.agentAnswer') })] }), _jsx("code", { children: agentAnswer.runId.slice(0, 8) })] }), _jsx("p", { children: agentAnswer.answer }), agentAnswer.citations.length > 0 && _jsx("div", { className: css.agentCitations, children: agentAnswer.citations.map(citation => _jsx("code", { children: citation }, citation)) })] }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !searched && _jsx(EmptyState, { glyph: "\u2315", title: t('search.startTitle'), children: t('search.startText') }), searched && !searching && results.length === 0 && error === null && _jsx(EmptyState, { glyph: "0", title: t('search.emptyTitle'), children: t('search.emptyText') }), results.length > 0 && (_jsxs("div", { className: relatedTo === null ? css.singleColumn : css.resultLayout, children: [_jsxs("section", { className: css.results, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.results') }) }), _jsx("strong", { children: results.length })] }), visibleResults.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight))), appearance.surface === 'sidebar' && _jsx(ProgressiveFooter, { visible: visibleResults.length, total: results.length, pageSize: pageSize, onMore: () => setVisibleResultLimit(value => value + pageSize) })] }), relatedTo !== null && _jsxs("aside", { id: "mnemon-related-pane", className: css.relatedPane, children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('search.related') }) }), _jsx("button", { type: "button", onClick: () => setRelatedTo(null), "aria-label": t('search.closeRelated'), children: "\u00D7" })] }), _jsx("p", { className: css.relatedSource, children: relatedTo.content }), relatedLoading && _jsx("div", { className: css.loading, children: t('search.traversing') }), !relatedLoading && related.length === 0 && _jsx("div", { className: css.muted, children: t('search.noRelated') }), visibleRelated.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onRelated: item => void showRelated(item) }, insightKey(insight))), appearance.surface === 'sidebar' && !relatedLoading && _jsx(ProgressiveFooter, { visible: visibleRelated.length, total: related.length, pageSize: pageSize, onMore: () => setVisibleRelatedLimit(value => value + pageSize) })] })] }))] }));
745
894
  }
746
895
  function EntitiesPage(props) {
747
896
  const t = useT();
897
+ const appearance = useMnemonViewAppearance();
898
+ const entityPageSize = appearance.surface === 'sidebar' ? 10 : Number.MAX_SAFE_INTEGER;
899
+ const insightPageSize = appearance.surface === 'sidebar' ? 6 : Number.MAX_SAFE_INTEGER;
748
900
  const [view, setView] = useState({ items: [], insights: [] });
749
901
  const [entity, setEntity] = useState('');
750
902
  const [loading, setLoading] = useState(true);
751
903
  const [error, setError] = useState(null);
904
+ const [visibleEntityLimit, setVisibleEntityLimit] = useState(entityPageSize);
905
+ const [visibleInsightLimit, setVisibleInsightLimit] = useState(insightPageSize);
752
906
  const load = useCallback(async (selected) => {
753
907
  setLoading(true);
754
908
  setError(null);
909
+ setVisibleInsightLimit(insightPageSize);
910
+ if (selected === undefined)
911
+ setVisibleEntityLimit(entityPageSize);
755
912
  try {
756
913
  setView(await props.client.entities(selected, 20));
757
914
  }
@@ -761,14 +918,19 @@ function EntitiesPage(props) {
761
918
  finally {
762
919
  setLoading(false);
763
920
  }
764
- }, [props.client]);
921
+ }, [entityPageSize, insightPageSize, props.client]);
765
922
  useEffect(() => { void load(); }, [load, props.revision]);
766
923
  const submit = (event) => { event.preventDefault(); if (entity.trim() !== '')
767
924
  void load(entity); };
768
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('entities.title'), description: t('entities.description'), meta: t('entities.count', { count: view.items.length }) }), _jsxs("div", { className: css.entityLayout, children: [_jsxs("aside", { className: css.entityRail, children: [_jsxs("form", { className: css.entitySearch, onSubmit: submit, children: [_jsx("input", { "aria-label": t('entities.nameAria'), value: entity, onChange: event => setEntity(event.target.value), placeholder: t('entities.placeholder') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading || entity.trim() === '', children: t('entities.action') })] }), _jsxs("div", { className: css.entityHeading, children: [_jsx("span", { children: t('entities.top') }), _jsx("small", { children: t('entities.frequency') })] }), _jsx("div", { className: css.entityList, children: view.items.map(item => _jsxs("button", { type: "button", "aria-pressed": view.selected === item.entity, onClick: () => { setEntity(item.entity); void load(item.entity); }, children: [_jsx("span", { children: item.entity }), _jsx("strong", { children: item.count })] }, item.entity)) }), !loading && view.items.length === 0 && _jsx("p", { className: css.muted, children: t('entities.emptyRail') })] }), _jsxs("section", { className: css.entityResults, children: [error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), loading && _jsx("div", { className: css.loadingPanel, children: t('entities.loading') }), !loading && view.selected === undefined && _jsx(EmptyState, { glyph: "\u25CE", title: t('entities.selectTitle'), children: t('entities.selectText') }), !loading && view.selected !== undefined && _jsxs(_Fragment, { children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: view.selected }) }), _jsx("strong", { children: view.insights.length })] }), view.insights.length === 0 ? _jsx(EmptyState, { glyph: "0", title: t('entities.emptyTitle'), children: t('entities.emptyText') }) : view.insights.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: props.onForget, onRelated: () => props.onExplore(insight.content) }, insightKey(insight)))] })] })] })] }));
925
+ const visibleEntities = view.items.slice(0, visibleEntityLimit);
926
+ const visibleInsights = view.insights.slice(0, visibleInsightLimit);
927
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('entities.title'), description: t('entities.description'), meta: t('entities.count', { count: view.items.length }) }), _jsxs("div", { className: css.entityLayout, children: [_jsxs("aside", { className: css.entityRail, children: [_jsxs("form", { className: css.entitySearch, onSubmit: submit, children: [_jsx("input", { "aria-label": t('entities.nameAria'), value: entity, onChange: event => setEntity(event.target.value), placeholder: t('entities.placeholder') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading || entity.trim() === '', children: t('entities.action') })] }), _jsxs("div", { className: css.entityHeading, children: [_jsx("span", { children: t('entities.top') }), _jsx("small", { children: t('entities.frequency') })] }), _jsx("div", { className: css.entityList, children: visibleEntities.map(item => _jsxs("button", { type: "button", "aria-pressed": view.selected === item.entity, onClick: () => { setEntity(item.entity); void load(item.entity); }, children: [_jsx("span", { children: item.entity }), _jsx("strong", { children: item.count })] }, item.entity)) }), appearance.surface === 'sidebar' && _jsx(ProgressiveFooter, { compact: true, visible: visibleEntities.length, total: view.items.length, pageSize: entityPageSize, onMore: () => setVisibleEntityLimit(value => value + entityPageSize) }), !loading && view.items.length === 0 && _jsx("p", { className: css.muted, children: t('entities.emptyRail') })] }), _jsxs("section", { className: css.entityResults, children: [error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), loading && _jsx("div", { className: css.loadingPanel, children: t('entities.loading') }), !loading && view.selected === undefined && _jsx(EmptyState, { glyph: "\u25CE", title: t('entities.selectTitle'), children: t('entities.selectText') }), !loading && view.selected !== undefined && _jsxs(_Fragment, { children: [_jsxs("div", { className: css.sectionHeading, children: [_jsx("div", { children: _jsx("h3", { children: view.selected }) }), _jsx("strong", { children: view.insights.length })] }), view.insights.length === 0 ? _jsx(EmptyState, { glyph: "0", title: t('entities.emptyTitle'), children: t('entities.emptyText') }) : _jsxs(_Fragment, { children: [visibleInsights.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: props.onForget, onRelated: () => props.onExplore(insight.content) }, insightKey(insight))), appearance.surface === 'sidebar' && _jsx(ProgressiveFooter, { visible: visibleInsights.length, total: view.insights.length, pageSize: insightPageSize, onMore: () => setVisibleInsightLimit(value => value + insightPageSize) })] })] })] })] })] }));
769
928
  }
770
929
  function RuntimePage(props) {
771
930
  const t = useT();
931
+ const locale = useLocale();
932
+ const appearance = useMnemonViewAppearance();
933
+ const pageSize = 10;
772
934
  const [snapshot, setSnapshot] = useState(null);
773
935
  const [loading, setLoading] = useState(true);
774
936
  const [error, setError] = useState(null);
@@ -781,6 +943,10 @@ function RuntimePage(props) {
781
943
  const [editContent, setEditContent] = useState('');
782
944
  const [editImportance, setEditImportance] = useState('normal');
783
945
  const [removing, setRemoving] = useState(null);
946
+ const [adding, setAdding] = useState(false);
947
+ const [filterTarget, setFilterTarget] = useState('all');
948
+ const [filterQuery, setFilterQuery] = useState('');
949
+ const [visibleLimit, setVisibleLimit] = useState(pageSize);
784
950
  const load = useCallback(async () => {
785
951
  setLoading(true);
786
952
  setError(null);
@@ -795,6 +961,7 @@ function RuntimePage(props) {
795
961
  }
796
962
  }, [props.client]);
797
963
  useEffect(() => { void load(); }, [load, props.revision]);
964
+ useEffect(() => { setVisibleLimit(pageSize); }, [filterQuery, filterTarget]);
798
965
  const entryKey = (entry) => `${entry.target}:${entry.created_at}:${entry.content}`;
799
966
  const mutate = async (request) => {
800
967
  setNotice(null);
@@ -816,6 +983,8 @@ function RuntimePage(props) {
816
983
  try {
817
984
  await mutate({ action: 'add', target, content, importance });
818
985
  setContent('');
986
+ if (appearance.surface === 'sidebar')
987
+ setAdding(false);
819
988
  }
820
989
  catch (reason) {
821
990
  setError(message(reason));
@@ -858,18 +1027,43 @@ function RuntimePage(props) {
858
1027
  setSaving(false);
859
1028
  }
860
1029
  };
1030
+ const runtimeEditActionClass = appearance.surface === 'sidebar'
1031
+ ? appearanceClass(css.ghostButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemEditAction))
1032
+ : css.ghostButton;
1033
+ const runtimeRemoveActionClass = appearance.surface === 'sidebar'
1034
+ ? appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction))
1035
+ : css.dangerButton;
1036
+ const runtimeEntry = (entry, showTarget = false) => {
1037
+ const key = entryKey(entry);
1038
+ const isEditing = editing === key;
1039
+ const isInlineEditing = appearance.surface === 'buildin' && isEditing;
1040
+ const isRemoving = removing === key;
1041
+ const isInlineRemoving = appearance.surface === 'buildin' && isRemoving;
1042
+ return _jsxs("article", { className: css.runtimeEntry, "data-importance": entry.importance, "data-target": entry.target, children: [_jsxs("div", { className: css.runtimeEntryMeta, children: [showTarget ? _jsxs("div", { className: css.runtimeEntryBadges, children: [_jsx("span", { className: css.runtimeEntryTarget, children: entry.target === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("span", { children: t(`runtime.importance.${entry.importance}`) })] }) : _jsx("span", { children: t(`runtime.importance.${entry.importance}`) }), _jsx("time", { dateTime: entry.updated_at, children: new Date(entry.updated_at).toLocaleString(locale) })] }), isInlineEditing ? _jsx("textarea", { "aria-label": t('runtime.editContent'), value: editContent, onChange: event => setEditContent(event.target.value), rows: 4 }) : _jsx("p", { children: entry.content }), isInlineEditing && _jsxs("select", { "aria-label": t('runtime.importance'), value: editImportance, onChange: event => setEditImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] }), _jsx("footer", { children: isInlineRemoving ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('runtime.removeConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void remove(entry), children: t('runtime.removeAction') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setRemoving(null), children: t('common.cancel') })] }) : isInlineEditing ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.primaryButton, disabled: saving || editContent.trim() === '', onClick: () => void replace(entry), children: t('runtime.saveEdit') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditing(null), children: t('common.cancel') })] }) : props.writeEnabled ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: runtimeEditActionClass, disabled: saving && isRemoving, onClick: () => beginEdit(entry), children: t('runtime.editAction') }), _jsx("button", { type: "button", className: runtimeRemoveActionClass, disabled: saving && isRemoving, onClick: () => { setRemoving(key); setEditing(null); }, children: t('runtime.removeAction') })] }) : null })] }, key);
1043
+ };
861
1044
  const targetPanel = (value) => {
862
1045
  const view = snapshot?.targets[value];
863
1046
  const entries = snapshot?.entries.filter(entry => entry.target === value) ?? [];
864
1047
  const percentage = view === undefined || view.limit === 0 ? 0 : Math.min(100, Math.round(view.used / view.limit * 100));
865
- return (_jsxs("section", { className: css.runtimeTarget, "aria-label": t(`runtime.target.${value}`), children: [_jsxs("header", { className: css.runtimeTargetHeader, children: [_jsxs("div", { children: [_jsx("span", { children: value === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("h3", { children: t(`runtime.target.${value}`) })] }), _jsx("strong", { children: view?.entryCount ?? 0 })] }), _jsxs("div", { className: css.capacityLine, children: [_jsx("div", { children: _jsx("i", { style: { width: `${percentage}%` } }) }), _jsx("span", { children: view === undefined ? '—' : `${humanBytes(view.used)} / ${humanBytes(view.limit)}` })] }), _jsx("p", { className: css.runtimeTargetDescription, children: t(`runtime.target.${value}.description`) }), _jsxs("div", { className: css.runtimeEntries, children: [entries.map(entry => {
866
- const key = entryKey(entry);
867
- const isEditing = editing === key;
868
- const isRemoving = removing === key;
869
- return _jsxs("article", { className: css.runtimeEntry, "data-importance": entry.importance, children: [_jsxs("div", { className: css.runtimeEntryMeta, children: [_jsx("span", { children: t(`runtime.importance.${entry.importance}`) }), _jsx("time", { dateTime: entry.updated_at, children: new Date(entry.updated_at).toLocaleString() })] }), isEditing ? _jsx("textarea", { "aria-label": t('runtime.editContent'), value: editContent, onChange: event => setEditContent(event.target.value), rows: 4 }) : _jsx("p", { children: entry.content }), isEditing && _jsxs("select", { "aria-label": t('runtime.importance'), value: editImportance, onChange: event => setEditImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] }), _jsx("footer", { children: isRemoving ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('runtime.removeConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void remove(entry), children: t('runtime.removeAction') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setRemoving(null), children: t('common.cancel') })] }) : isEditing ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.primaryButton, disabled: saving || editContent.trim() === '', onClick: () => void replace(entry), children: t('runtime.saveEdit') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditing(null), children: t('common.cancel') })] }) : props.writeEnabled ? _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.ghostButton, onClick: () => beginEdit(entry), children: t('runtime.editAction') }), _jsx("button", { type: "button", className: css.dangerButton, onClick: () => { setRemoving(key); setEditing(null); }, children: t('runtime.removeAction') })] }) : null })] }, key);
870
- }), !loading && entries.length === 0 && _jsxs("div", { className: css.runtimeEmpty, children: [_jsx("span", { children: "\u25CB" }), _jsx("p", { children: t('runtime.empty') })] })] })] }));
1048
+ return (_jsxs("section", { className: css.runtimeTarget, "aria-label": t(`runtime.target.${value}`), children: [_jsxs("header", { className: css.runtimeTargetHeader, children: [_jsxs("div", { children: [_jsx("span", { children: value === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("h3", { children: t(`runtime.target.${value}`) })] }), _jsx("strong", { children: view?.entryCount ?? 0 })] }), _jsxs("div", { className: css.capacityLine, children: [_jsx("div", { children: _jsx("i", { style: { width: `${percentage}%` } }) }), _jsx("span", { children: view === undefined ? '—' : `${humanBytes(view.used)} / ${humanBytes(view.limit)}` })] }), _jsx("p", { className: css.runtimeTargetDescription, children: t(`runtime.target.${value}.description`) }), _jsxs("div", { className: css.runtimeEntries, children: [entries.map(entry => runtimeEntry(entry)), !loading && entries.length === 0 && _jsxs("div", { className: css.runtimeEmpty, children: [_jsx("span", { children: "\u25CB" }), _jsx("p", { children: t('runtime.empty') })] })] })] }));
1049
+ };
1050
+ const targetSummary = (value) => {
1051
+ const view = snapshot?.targets[value];
1052
+ const percentage = view === undefined || view.limit === 0 ? 0 : Math.min(100, Math.round(view.used / view.limit * 100));
1053
+ return _jsxs("section", { className: css.runtimeSummaryCard, "aria-label": t(`runtime.target.${value}`), children: [_jsxs("header", { className: css.runtimeTargetHeader, children: [_jsxs("div", { children: [_jsx("span", { children: value === 'user' ? 'USER.md' : 'MEMORY.md' }), _jsx("h3", { children: t(`runtime.target.${value}`) })] }), _jsx("strong", { children: view?.entryCount ?? 0 })] }), _jsxs("div", { className: css.capacityLine, children: [_jsx("div", { children: _jsx("i", { style: { width: `${percentage}%` } }) }), _jsx("span", { children: view === undefined ? '—' : `${humanBytes(view.used)} / ${humanBytes(view.limit)}` })] }), _jsx("p", { className: css.runtimeTargetDescription, children: t(`runtime.target.${value}.description`) })] });
871
1054
  };
872
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('runtime.title'), description: t('runtime.description'), meta: snapshot === null ? t('common.loading') : t('runtime.total', { count: snapshot.entries.length }), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: t('runtime.refresh') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), props.writeEnabled && _jsxs("form", { className: css.runtimeComposer, onSubmit: event => void add(event), children: [_jsxs("div", { className: css.runtimeComposerHeading, children: [_jsxs("div", { children: [_jsx("h3", { children: t('runtime.addTitle') }), _jsx("p", { children: t('runtime.addDescription') })] }), _jsx("span", { children: t('runtime.hotContext') })] }), _jsx("textarea", { "aria-label": t('runtime.content'), value: content, onChange: event => setContent(event.target.value), rows: 3, placeholder: t('runtime.placeholder') }), _jsxs("div", { className: css.runtimeComposerActions, children: [_jsxs("label", { children: [t('runtime.target'), _jsxs("select", { value: target, onChange: event => setTarget(event.target.value), children: [_jsx("option", { value: "memory", children: t('runtime.target.memory') }), _jsx("option", { value: "user", children: t('runtime.target.user') })] })] }), _jsxs("label", { children: [t('runtime.importance'), _jsxs("select", { value: importance, onChange: event => setImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] })] }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || content.trim() === '', children: saving ? t('runtime.saving') : t('runtime.addAction') })] })] }), !props.writeEnabled && _jsx("div", { className: css.runtimeReadOnly, children: t('runtime.readOnly') }), _jsxs("div", { className: css.runtimeGrid, children: [targetPanel('user'), targetPanel('memory')] }), _jsx("p", { className: css.runtimeFootnote, children: t('runtime.footnote') })] }));
1055
+ const normalizedQuery = filterQuery.trim().toLocaleLowerCase();
1056
+ const filteredEntries = (snapshot?.entries ?? []).filter(entry => (filterTarget === 'all' || entry.target === filterTarget) && (normalizedQuery === '' || entry.content.toLocaleLowerCase().includes(normalizedQuery)));
1057
+ const visibleEntries = filteredEntries.slice(0, visibleLimit);
1058
+ const closeComposer = () => {
1059
+ setContent('');
1060
+ setAdding(false);
1061
+ };
1062
+ const composer = _jsxs("form", { className: css.runtimeComposer, onSubmit: event => void add(event), children: [_jsxs("div", { className: css.runtimeComposerHeading, children: [_jsxs("div", { children: [_jsx("h3", { children: t('runtime.addTitle') }), _jsx("p", { children: t('runtime.addDescription') })] }), _jsx("span", { children: t('runtime.hotContext') })] }), _jsx("textarea", { "aria-label": t('runtime.content'), value: content, onChange: event => setContent(event.target.value), rows: 3, placeholder: t('runtime.placeholder') }), _jsxs("div", { className: css.runtimeComposerActions, children: [_jsxs("label", { children: [t('runtime.target'), _jsxs("select", { value: target, onChange: event => setTarget(event.target.value), children: [_jsx("option", { value: "memory", children: t('runtime.target.memory') }), _jsx("option", { value: "user", children: t('runtime.target.user') })] })] }), _jsxs("label", { children: [t('runtime.importance'), _jsxs("select", { value: importance, onChange: event => setImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] })] }), appearance.surface === 'sidebar' && _jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: closeComposer, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || content.trim() === '', children: saving ? t('runtime.saving') : t('runtime.addAction') })] })] });
1063
+ const editingEntry = editing === null ? undefined : snapshot?.entries.find(entry => entryKey(entry) === editing);
1064
+ const removingEntry = removing === null ? undefined : snapshot?.entries.find(entry => entryKey(entry) === removing);
1065
+ const editForm = editingEntry === undefined ? null : _jsxs("form", { className: css.bodyEdit, onSubmit: event => { event.preventDefault(); void replace(editingEntry); }, children: [_jsxs("label", { children: [t('runtime.editContent'), _jsx("textarea", { "aria-label": t('runtime.editContent'), value: editContent, onChange: event => setEditContent(event.target.value), rows: 7 })] }), _jsxs("label", { children: [t('runtime.importance'), _jsxs("select", { "aria-label": t('runtime.importance'), value: editImportance, onChange: event => setEditImportance(event.target.value), children: [_jsx("option", { value: "critical", children: t('runtime.importance.critical') }), _jsx("option", { value: "normal", children: t('runtime.importance.normal') }), _jsx("option", { value: "low", children: t('runtime.importance.low') })] })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: () => setEditing(null), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || editContent.trim() === '', children: t('runtime.saveEdit') })] })] });
1066
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('runtime.title'), description: t('runtime.description'), meta: snapshot === null ? t('common.loading') : t('runtime.total', { count: snapshot.entries.length }), action: _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void load(), children: t('runtime.refresh') }), appearance.surface === 'sidebar' && props.writeEnabled && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => setAdding(true), children: t('runtime.addButton') })] }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), props.writeEnabled && appearance.surface === 'buildin' && composer, !props.writeEnabled && _jsx("div", { className: css.runtimeReadOnly, children: t('runtime.readOnly') }), appearance.surface === 'buildin' ? _jsxs("div", { className: css.runtimeGrid, children: [targetPanel('user'), targetPanel('memory')] }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.runtimeSummaryGrid, children: [targetSummary('user'), targetSummary('memory')] }), _jsxs("section", { className: css.runtimeBrowser, "aria-label": t('runtime.entriesAria'), children: [_jsxs("div", { className: css.runtimeBrowserToolbar, children: [_jsxs("div", { className: css.runtimeScopeFilter, role: "group", "aria-label": t('runtime.scopeAria'), children: [_jsxs("button", { type: "button", "data-active": filterTarget === 'all' || undefined, onClick: () => setFilterTarget('all'), children: [t('runtime.scopeAll'), " ", _jsx("b", { children: snapshot?.entries.length ?? 0 })] }), _jsxs("button", { type: "button", "data-active": filterTarget === 'user' || undefined, onClick: () => setFilterTarget('user'), children: [t('runtime.target.user'), " ", _jsx("b", { children: snapshot?.targets.user.entryCount ?? 0 })] }), _jsxs("button", { type: "button", "data-active": filterTarget === 'memory' || undefined, onClick: () => setFilterTarget('memory'), children: [t('runtime.target.memory'), " ", _jsx("b", { children: snapshot?.targets.memory.entryCount ?? 0 })] })] }), _jsxs("div", { className: css.runtimeFilterQuery, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { "aria-label": t('runtime.filterAria'), value: filterQuery, onChange: event => setFilterQuery(event.target.value), placeholder: t('runtime.filterPlaceholder') })] })] }), _jsxs("div", { className: css.runtimeUnifiedList, children: [visibleEntries.map(entry => runtimeEntry(entry, true)), !loading && filteredEntries.length === 0 && _jsxs("div", { className: css.runtimeEmpty, children: [_jsx("span", { children: "\u25CB" }), _jsx("p", { children: t('runtime.noMatch') })] })] }), !loading && _jsx(ProgressiveFooter, { visible: visibleEntries.length, total: filteredEntries.length, pageSize: pageSize, onMore: () => setVisibleLimit(value => value + pageSize) })] })] }), _jsx("p", { className: css.runtimeFootnote, children: t('runtime.footnote') }), appearance.surface === 'sidebar' && adding && _jsx(SidebarModal, { title: t('runtime.addTitle'), description: t('runtime.addDescription'), busy: saving, onClose: closeComposer, children: composer }), appearance.surface === 'sidebar' && editingEntry !== undefined && _jsx(SidebarModal, { title: t('runtime.editContent'), description: t(`runtime.target.${editingEntry.target}`), busy: saving, onClose: () => setEditing(null), children: editForm }), appearance.surface === 'sidebar' && removingEntry !== undefined && _jsx(SidebarModal, { title: t('runtime.removeTitle'), description: t(`runtime.target.${removingEntry.target}`), busy: saving, onClose: () => setRemoving(null), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsx("p", { children: t('runtime.removeWarning') }), _jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("p", { className: css.bodyDeleteContent, children: removingEntry.content }), _jsx("span", { children: t(`runtime.importance.${removingEntry.importance}`) })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: saving, onClick: () => setRemoving(null), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void remove(removingEntry), children: t('runtime.removeAction') })] })] }) })] }));
873
1067
  }
874
1068
  function RememberPage(props) {
875
1069
  const t = useT();
@@ -897,8 +1091,11 @@ function RememberPage(props) {
897
1091
  try {
898
1092
  const response = await props.client.supervise(content);
899
1093
  setResult(`${t(response.action === 'skipped' ? 'remember.skipped' : 'remember.completed')}${response.memoryBodyIds.length === 0 ? '' : ` · ${response.memoryBodyIds.join(', ')}`}${response.summary === '' ? '' : ` · ${response.summary}`}`);
900
- setContent('');
901
1094
  props.onMutate();
1095
+ if (response.action !== 'skipped') {
1096
+ setContent('');
1097
+ props.onComplete?.();
1098
+ }
902
1099
  }
903
1100
  catch (reason) {
904
1101
  setResult(t('remember.dispatchFailed', { error: message(reason) }));
@@ -923,6 +1120,7 @@ function RememberPage(props) {
923
1120
  setTags('');
924
1121
  setEntities('');
925
1122
  props.onMutate();
1123
+ props.onComplete?.();
926
1124
  }
927
1125
  }
928
1126
  catch (reason) {
@@ -932,17 +1130,22 @@ function RememberPage(props) {
932
1130
  setSaving(false);
933
1131
  }
934
1132
  };
935
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('remember.title'), description: t('remember.description'), meta: props.writeEnabled ? t('remember.worker') : t('common.readOnly') }), !props.writeEnabled ? _jsx(EmptyState, { glyph: "\u2298", title: t('remember.readOnlyTitle'), children: t('remember.readOnlyText') }) : (_jsxs("div", { className: css.writebackLayout, children: [_jsxs("aside", { className: css.writeGuide, children: [_jsx("h3", { children: t('remember.flowTitle') }), _jsxs("ol", { children: [_jsxs("li", { children: [_jsx("strong", { children: t('remember.routeTitle') }), _jsx("span", { children: t('remember.routeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.dedupeTitle') }), _jsx("span", { children: t('remember.dedupeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.writeTitle') }), _jsx("span", { children: t('remember.writeText') })] })] }), _jsx("p", { children: t('remember.flowText') })] }), _jsxs("section", { className: css.supervisedComposer, children: [_jsxs("form", { className: css.supervisedForm, onSubmit: event => void supervise(event), children: [_jsxs("div", { className: css.supervisedHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('remember.delegateTitle') }) }), _jsx("span", { className: props.sessionId === undefined ? css.sessionMissing : css.sessionReady, children: props.sessionId === undefined ? t('remember.noSession') : t('remember.ready') })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.candidate'), _jsx("textarea", { "aria-label": t('remember.candidateAria'), value: content, onChange: event => setContent(event.target.value), maxLength: 8000, rows: 8, placeholder: t('remember.placeholder') })] }), props.sessionId === undefined && _jsx("p", { className: css.sessionHint, children: t('remember.sessionHint') }), _jsxs("div", { className: css.formActions, children: [_jsx("button", { type: "submit", className: css.primaryButton, disabled: supervising || content.trim() === '' || props.sessionId === undefined, children: supervising ? t('remember.processing') : t('remember.action') }), result !== null && _jsx("span", { role: "status", children: result })] })] }), _jsxs("details", { className: css.advancedWrite, children: [_jsxs("summary", { children: [_jsxs("span", { children: [_jsx("strong", { children: t('remember.advanced') }), _jsx("small", { children: t('remember.advancedHint') })] }), _jsx("span", { children: t('remember.expand') })] }), _jsxs("form", { className: css.manualForm, onSubmit: event => void manualSave(event), children: [_jsxs("div", { className: css.formGrid, children: [_jsxs("label", { className: css.fieldWide, children: [t('remember.target'), _jsx("select", { "aria-label": t('remember.target'), value: memoryBodyId, onChange: event => setMemoryBodyId(event.target.value), children: props.memoryBodies.map(body => _jsxs("option", { value: body.id, children: [body.name, " \u00B7 ", body.id, body.active ? ` · ${t('common.active')}` : ''] }, body.id)) })] }), _jsxs("label", { children: [t('common.category'), _jsx("select", { value: category, onChange: event => setCategory(event.target.value), children: CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value)) })] }), _jsxs("label", { children: [t('common.importanceLabel'), _jsx("select", { value: importance, onChange: event => setImportance(Number(event.target.value)), children: [1, 2, 3, 4, 5].map(value => _jsxs("option", { value: value, children: [value, " / 5"] }, value)) })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.entities'), _jsx("input", { value: entities, onChange: event => setEntities(event.target.value), placeholder: "SQLite, DSH" })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.tags'), _jsx("input", { value: tags, onChange: event => setTags(event.target.value), placeholder: "architecture, local-first" })] })] }), _jsxs("div", { className: css.manualActions, children: [_jsx("p", { children: t('remember.advancedText') }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: saving || content.trim() === '' || props.sessionId === undefined || memoryBodyId === '', children: saving ? t('remember.saving') : t('remember.advancedAction') })] })] })] })] })] }))] }));
1133
+ const composer = _jsxs("section", { className: css.supervisedComposer, children: [_jsxs("form", { className: css.supervisedForm, onSubmit: event => void supervise(event), children: [_jsxs("div", { className: css.supervisedHeading, children: [_jsx("div", { children: _jsx("h3", { children: t('remember.delegateTitle') }) }), _jsx("span", { className: props.sessionId === undefined ? css.sessionMissing : css.sessionReady, children: props.sessionId === undefined ? t('remember.noSession') : t('remember.ready') })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.candidate'), _jsx("textarea", { "aria-label": t('remember.candidateAria'), value: content, onChange: event => setContent(event.target.value), maxLength: 8000, rows: 8, placeholder: t('remember.placeholder') })] }), props.sessionId === undefined && _jsx("p", { className: css.sessionHint, children: t('remember.sessionHint') }), _jsxs("div", { className: css.formActions, children: [props.onClose !== undefined && _jsx("button", { type: "button", className: css.ghostButton, disabled: supervising || saving, onClick: props.onClose, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: supervising || content.trim() === '' || props.sessionId === undefined, children: supervising ? t('remember.processing') : t('remember.action') }), result !== null && _jsx("span", { role: "status", children: result })] })] }), _jsxs("details", { className: css.advancedWrite, children: [_jsxs("summary", { children: [_jsxs("span", { children: [_jsx("strong", { children: t('remember.advanced') }), _jsx("small", { children: t('remember.advancedHint') })] }), _jsx("span", { children: t('remember.expand') })] }), _jsxs("form", { className: css.manualForm, onSubmit: event => void manualSave(event), children: [_jsxs("div", { className: css.formGrid, children: [_jsxs("label", { className: css.fieldWide, children: [t('remember.target'), _jsx("select", { "aria-label": t('remember.target'), value: memoryBodyId, onChange: event => setMemoryBodyId(event.target.value), children: props.memoryBodies.map(body => _jsxs("option", { value: body.id, children: [body.name, " \u00B7 ", body.id, body.active ? ` · ${t('common.active')}` : ''] }, body.id)) })] }), _jsxs("label", { children: [t('common.category'), _jsx("select", { value: category, onChange: event => setCategory(event.target.value), children: CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value)) })] }), _jsxs("label", { children: [t('common.importanceLabel'), _jsx("select", { value: importance, onChange: event => setImportance(Number(event.target.value)), children: [1, 2, 3, 4, 5].map(value => _jsxs("option", { value: value, children: [value, " / 5"] }, value)) })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.entities'), _jsx("input", { value: entities, onChange: event => setEntities(event.target.value), placeholder: "SQLite, DSH" })] }), _jsxs("label", { className: css.fieldWide, children: [t('remember.tags'), _jsx("input", { value: tags, onChange: event => setTags(event.target.value), placeholder: "architecture, local-first" })] })] }), _jsxs("div", { className: css.manualActions, children: [_jsx("p", { children: t('remember.advancedText') }), _jsx("button", { type: "submit", className: css.secondaryButton, disabled: saving || content.trim() === '' || props.sessionId === undefined || memoryBodyId === '', children: saving ? t('remember.saving') : t('remember.advancedAction') })] })] })] })] });
1134
+ if (props.onClose !== undefined) {
1135
+ return _jsx(SidebarModal, { title: t('remember.title'), description: t('remember.description'), busy: supervising || saving, onClose: props.onClose, children: props.writeEnabled ? composer : _jsx(EmptyState, { glyph: "\u2298", title: t('remember.readOnlyTitle'), children: t('remember.readOnlyText') }) });
1136
+ }
1137
+ return _jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('remember.title'), description: t('remember.description'), meta: props.writeEnabled ? t('remember.worker') : t('common.readOnly') }), !props.writeEnabled ? _jsx(EmptyState, { glyph: "\u2298", title: t('remember.readOnlyTitle'), children: t('remember.readOnlyText') }) : _jsxs("div", { className: css.writebackLayout, children: [_jsxs("aside", { className: css.writeGuide, children: [_jsx("h3", { children: t('remember.flowTitle') }), _jsxs("ol", { children: [_jsxs("li", { children: [_jsx("strong", { children: t('remember.routeTitle') }), _jsx("span", { children: t('remember.routeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.dedupeTitle') }), _jsx("span", { children: t('remember.dedupeText') })] }), _jsxs("li", { children: [_jsx("strong", { children: t('remember.writeTitle') }), _jsx("span", { children: t('remember.writeText') })] })] }), _jsx("p", { children: t('remember.flowText') })] }), composer] })] });
936
1138
  }
937
1139
  function ListPage(props) {
938
- const PAGE_SIZE = 48;
939
1140
  const t = useT();
1141
+ const appearance = useMnemonViewAppearance();
1142
+ const pageSize = appearance.surface === 'sidebar' ? 12 : 48;
940
1143
  const [query, setQuery] = useState('');
941
1144
  const [category, setCategory] = useState('');
942
1145
  const [view, setView] = useState(null);
943
1146
  const [loading, setLoading] = useState(true);
944
1147
  const [error, setError] = useState(null);
945
- const [visibleLimit, setVisibleLimit] = useState(PAGE_SIZE);
1148
+ const [visibleLimit, setVisibleLimit] = useState(pageSize);
946
1149
  const load = useCallback(async () => {
947
1150
  setLoading(true);
948
1151
  setError(null);
@@ -956,17 +1159,21 @@ function ListPage(props) {
956
1159
  setLoading(false);
957
1160
  }
958
1161
  }, [category, props.client, query]);
959
- useEffect(() => { void load(); }, [props.revision]);
960
- const submit = (event) => { event.preventDefault(); setVisibleLimit(PAGE_SIZE); void load(); };
1162
+ useEffect(() => { setVisibleLimit(pageSize); void load(); }, [pageSize, props.revision]);
1163
+ const submit = (event) => { event.preventDefault(); setVisibleLimit(pageSize); void load(); };
961
1164
  const forget = async (insight) => { await props.onForget(insight); setView(current => current === null ? current : { ...current, total: Math.max(0, current.total - 1), items: current.items.filter(item => insightKey(item) !== insightKey(insight)) }); };
962
1165
  const visibleItems = view?.items.slice(0, visibleLimit) ?? [];
963
- const remaining = Math.max(0, (view?.items.length ?? 0) - visibleItems.length);
964
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('content.title'), description: t('content.description'), meta: t('content.count', { count: view?.total ?? '—' }) }), _jsxs("form", { className: css.listToolbar, onSubmit: submit, children: [_jsx("input", { "aria-label": t('content.filterAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('content.filterPlaceholder') }), _jsxs("select", { "aria-label": t('content.categoryAria'), value: category, onChange: event => setCategory(event.target.value), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading, children: loading ? t('common.loading') : t('content.apply') })] }), _jsx("div", { className: css.listNotice, children: t('content.notice') }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !loading && view?.items.length === 0 && _jsx(EmptyState, { glyph: "\u2261", title: t('content.emptyTitle'), children: t('content.emptyText') }), _jsx("div", { className: css.memoryList, children: visibleItems.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onClone: props.onClone, onRelated: () => props.onExplore(insight.content) }, insightKey(insight))) }), view !== null && view.items.length > 0 && _jsxs("div", { className: css.listProgress, children: [_jsx("span", { children: t('content.showing', { visible: visibleItems.length, total: view.items.length }) }), remaining > 0 && _jsx("button", { type: "button", className: css.secondaryButton, onClick: () => setVisibleLimit(value => value + PAGE_SIZE), children: t('content.showMore', { count: Math.min(PAGE_SIZE, remaining) }) })] })] }));
1166
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('content.title'), description: t('content.description'), meta: t('content.count', { count: view?.total ?? '—' }) }), _jsxs("form", { className: css.listToolbar, onSubmit: submit, children: [_jsx("input", { "aria-label": t('content.filterAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('content.filterPlaceholder') }), _jsxs("select", { "aria-label": t('content.categoryAria'), value: category, onChange: event => setCategory(event.target.value), children: [_jsx("option", { value: "", children: t('common.allCategories') }), CATEGORIES.map(value => _jsx("option", { value: value, children: categoryLabel(t, value) }, value))] }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: loading, children: loading ? t('common.loading') : t('content.apply') })] }), _jsx("div", { className: css.listNotice, children: t('content.notice') }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), !loading && view?.items.length === 0 && _jsx(EmptyState, { glyph: "\u2261", title: t('content.emptyTitle'), children: t('content.emptyText') }), _jsx("div", { className: css.memoryList, children: visibleItems.map(insight => _jsx(InsightCard, { insight: insight, writeEnabled: props.writeEnabled, onForget: forget, onClone: props.onClone, onRelated: () => props.onExplore(insight.content) }, insightKey(insight))) }), view !== null && _jsx(ProgressiveFooter, { visible: visibleItems.length, total: view.items.length, pageSize: pageSize, onMore: () => setVisibleLimit(value => value + pageSize) })] }));
965
1167
  }
966
1168
  function DocumentsPage(props) {
967
1169
  const t = useT();
1170
+ const locale = useLocale();
1171
+ const appearance = useMnemonViewAppearance();
1172
+ const pageSize = appearance.surface === 'sidebar' ? 8 : Number.MAX_SAFE_INTEGER;
1173
+ const readerRef = useRef(null);
968
1174
  const [snapshot, setSnapshot] = useState(null);
969
1175
  const [items, setItems] = useState([]);
1176
+ const [visibleLimit, setVisibleLimit] = useState(pageSize);
970
1177
  const [selectedId, setSelectedId] = useState(null);
971
1178
  const [selected, setSelected] = useState(null);
972
1179
  const [status, setStatus] = useState('active');
@@ -985,6 +1192,7 @@ function DocumentsPage(props) {
985
1192
  const display = useCallback(async (nextQuery, nextStatus) => {
986
1193
  setLoading(true);
987
1194
  setError(null);
1195
+ setVisibleLimit(pageSize);
988
1196
  try {
989
1197
  const current = await props.client.documents();
990
1198
  const records = nextQuery.trim() === ''
@@ -1004,7 +1212,7 @@ function DocumentsPage(props) {
1004
1212
  finally {
1005
1213
  setLoading(false);
1006
1214
  }
1007
- }, [props.client]);
1215
+ }, [pageSize, props.client]);
1008
1216
  useEffect(() => { void display(query, status); }, [display, props.revision, status]);
1009
1217
  useEffect(() => {
1010
1218
  setSelected(null);
@@ -1016,7 +1224,19 @@ function DocumentsPage(props) {
1016
1224
  setError(message(reason)); });
1017
1225
  return () => { active = false; };
1018
1226
  }, [props.client, selectedId, props.revision]);
1227
+ useLayoutEffect(() => {
1228
+ if (appearance.surface === 'sidebar' && readerRef.current !== null)
1229
+ readerRef.current.scrollTop = 0;
1230
+ }, [appearance.surface, selectedId]);
1231
+ useEffect(() => {
1232
+ if (appearance.surface !== 'sidebar' || selectedId === null)
1233
+ return;
1234
+ const index = items.findIndex(item => item.id === selectedId);
1235
+ if (index >= visibleLimit)
1236
+ setVisibleLimit(Math.ceil((index + 1) / pageSize) * pageSize);
1237
+ }, [appearance.surface, items, pageSize, selectedId, visibleLimit]);
1019
1238
  const resetComposer = () => { setTitle(''); setDescription(''); setContent(''); setSources(''); setComposing(false); };
1239
+ const startComposer = () => { setTitle(''); setDescription(''); setContent(''); setSources(''); setEditing(false); setComposing(true); };
1020
1240
  const sourcePaths = (value) => value.split(/\r?\n|,/gu).map(path => path.trim()).filter(Boolean);
1021
1241
  const create = async (event) => {
1022
1242
  event.preventDefault();
@@ -1099,40 +1319,124 @@ function DocumentsPage(props) {
1099
1319
  const usage = snapshot === null ? 0 : Math.min(100, snapshot.activeBytes / snapshot.limitBytes * 100);
1100
1320
  const activeCount = snapshot?.activeCount ?? 0;
1101
1321
  const archivedCount = snapshot?.archivedCount ?? 0;
1102
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('documents.title'), description: t('documents.description'), meta: snapshot === null ? t('common.loading') : t('documents.capacity', { used: humanBytes(snapshot.activeBytes), limit: humanBytes(snapshot.limitBytes) }), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void display(query, status), children: t('documents.refresh') }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), _jsxs("section", { className: css.documentSummary, "aria-label": t('documents.summary'), children: [_jsxs("article", { children: [_jsx("span", { children: t('documents.active') }), _jsx("strong", { children: activeCount }), _jsx("small", { children: t('documents.activeHint') })] }), _jsxs("article", { children: [_jsx("span", { children: t('documents.archivedCount') }), _jsx("strong", { children: archivedCount }), _jsx("small", { children: t('documents.archivedHint') })] }), _jsxs("article", { className: css.documentCapacity, children: [_jsx("span", { children: t('documents.activeCapacity') }), _jsx("strong", { children: snapshot === null ? '—' : `${usage.toFixed(1)}%` }), _jsx("div", { children: _jsx("i", { style: { width: `${usage}%` } }) }), _jsx("small", { children: t('documents.capacityHint') })] })] }), _jsxs("section", { className: css.documentToolbar, children: [_jsxs("form", { onSubmit: event => { event.preventDefault(); void display(query, status); }, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { "aria-label": t('documents.searchAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('documents.searchPlaceholder') }), _jsx("button", { type: "submit", className: css.secondaryButton, children: t('documents.search') })] }), _jsxs("div", { role: "group", "aria-label": t('documents.scope'), children: [_jsxs("button", { type: "button", "data-active": status === 'active' || undefined, onClick: () => setStatus('active'), children: [t('documents.active'), " ", _jsx("b", { children: activeCount })] }), _jsxs("button", { type: "button", "data-active": status === 'archived' || undefined, onClick: () => setStatus('archived'), children: [t('documents.archivedCount'), " ", _jsx("b", { children: archivedCount })] })] }), props.writeEnabled && props.sessionId !== undefined && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => { setComposing(value => !value); setEditing(false); }, children: composing ? t('common.cancel') : t('documents.new') })] }), composing && _jsxs("form", { className: css.documentEditor, onSubmit: event => void create(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.newTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("span", { children: t('documents.managedCopy') })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value), placeholder: t('documents.sourcesPlaceholder') })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 10, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, onClick: resetComposer, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || title.trim() === '' || content.trim() === '', children: saving ? t('documents.saving') : t('documents.create') })] })] }), _jsxs("div", { className: css.documentWorkspace, children: [_jsxs("aside", { className: css.documentList, "aria-label": t('documents.list'), children: [_jsxs("header", { children: [_jsx("span", { children: status === 'active' ? t('documents.activeList') : t('documents.archiveList') }), _jsx("code", { children: items.length })] }), items.map(document => _jsxs("button", { type: "button", "data-selected": selectedId === document.id || undefined, onClick: () => { setSelected(null); setSelectedId(document.id); setEditing(false); setConfirmArchive(false); }, children: [_jsxs("div", { children: [_jsx("strong", { children: document.title }), _jsx("time", { dateTime: document.updatedAt, children: new Date(document.updatedAt).toLocaleDateString() })] }), _jsx("p", { children: document.description || document.excerpt || t('documents.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: humanBytes(document.sizeBytes) }), _jsx("code", { children: document.id.slice(0, 8) }), document.healthy === false && _jsx("em", { children: t('documents.missing') })] })] }, document.id)), !loading && items.length === 0 && _jsxs("div", { className: css.documentListEmpty, children: [_jsx("span", { children: "\u25A4" }), _jsx("strong", { children: status === 'active' ? t('documents.emptyActive') : t('documents.emptyArchived') }), _jsx("p", { children: status === 'active' ? t('documents.emptyActiveText') : t('documents.emptyArchivedText') })] }), loading && _jsx("div", { className: css.loading, children: t('common.loading') })] }), _jsx("section", { className: css.documentReader, "aria-label": t('documents.reader'), children: selected === null ? _jsx(EmptyState, { glyph: "\u25A4", title: t('documents.selectTitle'), children: t('documents.selectText') }) : editing ? _jsxs("form", { className: css.documentEditor, onSubmit: event => void update(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.editTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("code", { children: selected.id })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value) })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 18, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, onClick: () => setEditing(false), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving, children: saving ? t('documents.saving') : t('documents.save') })] })] }) : _jsxs("article", { className: css.documentDetail, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", { children: selected.status === 'active' ? t('documents.active') : t('documents.coldArchive') }), _jsx("h3", { children: selected.title }), _jsx("p", { children: selected.description || t('documents.noDescription') })] }), _jsx("div", { children: props.writeEnabled && selected.status === 'active' && _jsx("button", { type: "button", className: css.secondaryButton, onClick: beginEdit, children: t('documents.edit') }) })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: t('documents.path') }), _jsx("dd", { children: _jsx("code", { children: selected.relativePath }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.revision') }), _jsx("dd", { children: selected.revision })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.hash') }), _jsx("dd", { children: _jsx("code", { children: selected.contentHash.slice(0, 16) }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.size') }), _jsx("dd", { children: humanBytes(selected.sizeBytes) })] })] }), selected.sourcePaths.length > 0 && _jsxs("div", { className: css.documentSources, children: [_jsx("span", { children: t('documents.sources') }), selected.sourcePaths.map(path => _jsx("code", { children: path }, path))] }), selected.status === 'archived' && _jsxs("div", { className: css.documentArchiveReceipt, children: [_jsx("strong", { children: t('documents.archiveReceipt') }), _jsx("p", { children: selected.archiveSummary }), _jsx("div", { children: selected.memoryBodyIds.map(id => _jsx("code", { children: id }, id)) })] }), _jsx(DocumentMarkdown, { content: selected.content }), props.writeEnabled && selected.status === 'active' && _jsx("footer", { className: css.documentDanger, children: confirmArchive ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('documents.archiveConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void archive(), children: saving ? t('documents.archiving') : t('documents.archiveNow') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setConfirmArchive(false), children: t('common.cancel') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("strong", { children: t('documents.archiveTitle') }), _jsx("p", { children: t('documents.archiveDescription') })] }), _jsx("button", { type: "button", className: css.dangerButton, onClick: () => setConfirmArchive(true), children: t('documents.archive') })] }) })] }) })] }), _jsx("p", { className: css.runtimeFootnote, children: t('documents.footnote') })] }));
1322
+ const composer = _jsxs("form", { className: css.documentEditor, onSubmit: event => void create(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.newTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("span", { children: t('documents.managedCopy') })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value), placeholder: t('documents.sourcesPlaceholder') })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 10, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: resetComposer, children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving || title.trim() === '' || content.trim() === '', children: saving ? t('documents.saving') : t('documents.create') })] })] });
1323
+ const editComposer = selected === null ? null : _jsxs("form", { className: css.documentEditor, onSubmit: event => void update(event), children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("h3", { children: t('documents.editTitle') }), _jsx("p", { children: t('documents.editorHint') })] }), _jsx("code", { children: selected.id })] }), _jsxs("div", { className: css.documentEditorMeta, children: [_jsxs("label", { children: [t('documents.name'), _jsx("input", { value: title, onChange: event => setTitle(event.target.value), required: true })] }), _jsxs("label", { children: [t('documents.routing'), _jsx("input", { value: description, onChange: event => setDescription(event.target.value) })] })] }), _jsxs("label", { children: [t('documents.sources'), _jsx("input", { value: sources, onChange: event => setSources(event.target.value) })] }), _jsxs("label", { children: [t('documents.markdown'), _jsx("textarea", { value: content, onChange: event => setContent(event.target.value), rows: 18, required: true })] }), _jsxs("footer", { children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: saving, onClick: () => setEditing(false), children: t('common.cancel') }), _jsx("button", { type: "submit", className: css.primaryButton, disabled: saving, children: saving ? t('documents.saving') : t('documents.save') })] })] });
1324
+ const documentEditActionClass = appearance.surface === 'sidebar'
1325
+ ? appearanceClass(css.ghostButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemEditAction))
1326
+ : css.secondaryButton;
1327
+ const documentArchiveActionClass = appearance.surface === 'sidebar'
1328
+ ? appearanceClass(css.dangerButton, appearanceClass(appearance.classes.itemActionButton, appearance.classes.itemDangerAction))
1329
+ : css.dangerButton;
1330
+ const visibleItems = items.slice(0, visibleLimit);
1331
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('documents.title'), description: t('documents.description'), meta: snapshot === null ? t('common.loading') : t('documents.capacity', { used: humanBytes(snapshot.activeBytes), limit: humanBytes(snapshot.limitBytes) }), action: _jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.secondaryButton, disabled: loading, onClick: () => void display(query, status), children: t('documents.refresh') }), appearance.surface === 'sidebar' && props.writeEnabled && props.sessionId !== undefined && _jsx("button", { type: "button", className: css.primaryButton, onClick: startComposer, children: t('documents.new') })] }) }), error !== null && _jsx("div", { className: css.inlineError, role: "alert", children: error }), notice !== null && _jsx("div", { className: css.runtimeNotice, role: "status", children: notice }), _jsxs("section", { className: css.documentSummary, "aria-label": t('documents.summary'), children: [_jsxs("article", { children: [_jsx("span", { children: t('documents.active') }), _jsx("strong", { children: activeCount }), _jsx("small", { children: t('documents.activeHint') })] }), _jsxs("article", { children: [_jsx("span", { children: t('documents.archivedCount') }), _jsx("strong", { children: archivedCount }), _jsx("small", { children: t('documents.archivedHint') })] }), _jsxs("article", { className: css.documentCapacity, children: [_jsx("span", { children: t('documents.activeCapacity') }), _jsx("strong", { children: snapshot === null ? '—' : `${usage.toFixed(1)}%` }), _jsx("div", { children: _jsx("i", { style: { width: `${usage}%` } }) }), _jsx("small", { children: t('documents.capacityHint') })] })] }), _jsxs("section", { className: css.documentToolbar, children: [_jsxs("form", { onSubmit: event => { event.preventDefault(); void display(query, status); }, children: [_jsx("span", { "aria-hidden": "true", children: "\u2315" }), _jsx("input", { "aria-label": t('documents.searchAria'), value: query, onChange: event => setQuery(event.target.value), placeholder: t('documents.searchPlaceholder') }), _jsx("button", { type: "submit", className: css.secondaryButton, children: t('documents.search') })] }), _jsxs("div", { role: "group", "aria-label": t('documents.scope'), children: [_jsxs("button", { type: "button", "data-active": status === 'active' || undefined, onClick: () => setStatus('active'), children: [t('documents.active'), " ", _jsx("b", { children: activeCount })] }), _jsxs("button", { type: "button", "data-active": status === 'archived' || undefined, onClick: () => setStatus('archived'), children: [t('documents.archivedCount'), " ", _jsx("b", { children: archivedCount })] })] }), appearance.surface === 'buildin' && props.writeEnabled && props.sessionId !== undefined && _jsx("button", { type: "button", className: css.primaryButton, onClick: () => { if (composing)
1332
+ resetComposer();
1333
+ else
1334
+ startComposer(); }, children: composing ? t('common.cancel') : t('documents.new') })] }), composing && appearance.surface === 'buildin' && composer, _jsxs("div", { className: css.documentWorkspace, children: [_jsxs("aside", { className: css.documentList, "aria-label": t('documents.list'), children: [_jsxs("header", { children: [_jsx("span", { children: status === 'active' ? t('documents.activeList') : t('documents.archiveList') }), _jsx("code", { children: items.length })] }), visibleItems.map(document => _jsxs("button", { type: "button", "data-selected": selectedId === document.id || undefined, onClick: () => { setSelected(null); setSelectedId(document.id); setEditing(false); setConfirmArchive(false); }, children: [_jsxs("div", { children: [_jsx("strong", { children: document.title }), _jsx("time", { dateTime: document.updatedAt, children: new Date(document.updatedAt).toLocaleDateString(locale) })] }), _jsx("p", { children: document.description || document.excerpt || t('documents.noDescription') }), _jsxs("footer", { children: [_jsx("span", { children: humanBytes(document.sizeBytes) }), _jsx("code", { children: document.id.slice(0, 8) }), document.healthy === false && _jsx("em", { children: t('documents.missing') })] })] }, document.id)), appearance.surface === 'sidebar' && !loading && _jsx(ProgressiveFooter, { compact: true, visible: visibleItems.length, total: items.length, pageSize: pageSize, onMore: () => setVisibleLimit(value => value + pageSize) }), !loading && items.length === 0 && _jsxs("div", { className: css.documentListEmpty, children: [_jsx("span", { children: "\u25A4" }), _jsx("strong", { children: status === 'active' ? t('documents.emptyActive') : t('documents.emptyArchived') }), _jsx("p", { children: status === 'active' ? t('documents.emptyActiveText') : t('documents.emptyArchivedText') })] }), loading && _jsx("div", { className: css.loading, children: t('common.loading') })] }), _jsx("section", { ref: readerRef, className: css.documentReader, "aria-label": t('documents.reader'), "data-scroll-region": appearance.surface === 'sidebar' ? '' : undefined, children: selected === null ? _jsx(EmptyState, { glyph: "\u25A4", title: t('documents.selectTitle'), children: t('documents.selectText') }) : editing && appearance.surface === 'buildin' ? editComposer : _jsxs("article", { className: css.documentDetail, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", { children: selected.status === 'active' ? t('documents.active') : t('documents.coldArchive') }), _jsx("h3", { children: selected.title }), _jsx("p", { children: selected.description || t('documents.noDescription') })] }), _jsx("div", { children: props.writeEnabled && selected.status === 'active' && _jsx("button", { type: "button", className: documentEditActionClass, onClick: beginEdit, children: t('documents.edit') }) })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: t('documents.path') }), _jsx("dd", { children: _jsx("code", { children: selected.relativePath }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.revision') }), _jsx("dd", { children: selected.revision })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.hash') }), _jsx("dd", { children: _jsx("code", { children: selected.contentHash.slice(0, 16) }) })] }), _jsxs("div", { children: [_jsx("dt", { children: t('documents.size') }), _jsx("dd", { children: humanBytes(selected.sizeBytes) })] })] }), selected.sourcePaths.length > 0 && _jsxs("div", { className: css.documentSources, children: [_jsx("span", { children: t('documents.sources') }), selected.sourcePaths.map(path => _jsx("code", { children: path }, path))] }), selected.status === 'archived' && _jsxs("div", { className: css.documentArchiveReceipt, children: [_jsx("strong", { children: t('documents.archiveReceipt') }), _jsx("p", { children: selected.archiveSummary }), _jsx("div", { children: selected.memoryBodyIds.map(id => _jsx("code", { children: id }, id)) })] }), _jsx(DocumentMarkdown, { content: selected.content }), props.writeEnabled && selected.status === 'active' && _jsx("footer", { className: css.documentDanger, children: appearance.surface === 'buildin' && confirmArchive ? _jsxs(_Fragment, { children: [_jsx("span", { children: t('documents.archiveConfirm') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void archive(), children: saving ? t('documents.archiving') : t('documents.archiveNow') }), _jsx("button", { type: "button", className: css.ghostButton, onClick: () => setConfirmArchive(false), children: t('common.cancel') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("strong", { children: t('documents.archiveTitle') }), _jsx("p", { children: t('documents.archiveDescription') })] }), _jsx("button", { type: "button", className: documentArchiveActionClass, onClick: () => setConfirmArchive(true), children: t('documents.archive') })] }) })] }) })] }), _jsx("p", { className: css.runtimeFootnote, children: t('documents.footnote') }), composing && appearance.surface === 'sidebar' && _jsx(SidebarModal, { title: t('documents.newTitle'), description: t('documents.editorHint'), busy: saving, onClose: resetComposer, children: composer }), editing && appearance.surface === 'sidebar' && selected !== null && _jsx(SidebarModal, { title: t('documents.editTitle'), description: selected.title, busy: saving, onClose: () => setEditing(false), children: editComposer }), confirmArchive && appearance.surface === 'sidebar' && selected !== null && _jsx(SidebarModal, { title: t('documents.archiveConfirm'), description: selected.title, busy: saving, onClose: () => setConfirmArchive(false), children: _jsxs("div", { className: css.bodyDeleteConfirm, children: [_jsx("p", { children: t('documents.archiveDescription') }), _jsxs("div", { className: css.bodyDeleteSummary, children: [_jsx("strong", { children: selected.title }), _jsxs("span", { children: [selected.relativePath, " \u00B7 ", humanBytes(selected.sizeBytes)] })] }), _jsxs("div", { className: css.bodyEditActions, children: [_jsx("button", { type: "button", "data-autofocus": true, className: css.ghostButton, disabled: saving, onClick: () => setConfirmArchive(false), children: t('common.cancel') }), _jsx("button", { type: "button", className: css.dangerSolidButton, disabled: saving, onClick: () => void archive(), children: saving ? t('documents.archiving') : t('documents.archiveNow') })] })] }) })] }));
1335
+ }
1336
+ function versionModeLabel(t, mode) {
1337
+ if (mode === 'homebrew')
1338
+ return t('versions.modeHomebrew');
1339
+ if (mode === 'go')
1340
+ return t('versions.modeGo');
1341
+ if (mode === 'npm')
1342
+ return t('versions.modeNpm');
1343
+ if (mode === 'link')
1344
+ return t('versions.modeLink');
1345
+ if (mode === 'missing')
1346
+ return t('versions.modeMissing');
1347
+ return t('versions.modeManual');
1348
+ }
1349
+ function versionHint(t, component) {
1350
+ if (component.checkError !== undefined)
1351
+ return t('versions.latestUnavailable');
1352
+ if (component.updateHint === 'brew')
1353
+ return t('versions.hintHomebrew');
1354
+ if (component.updateHint === 'brew-missing')
1355
+ return t('versions.hintBrewMissing');
1356
+ if (component.updateHint === 'go')
1357
+ return t('versions.hintGo');
1358
+ if (component.updateHint === 'pnpm')
1359
+ return t('versions.hintPnpm');
1360
+ if (component.updateHint === 'pnpm-missing')
1361
+ return t('versions.hintPnpmMissing');
1362
+ if (component.updateHint === 'link')
1363
+ return t('versions.hintLink');
1364
+ if (component.updateHint === 'install')
1365
+ return t('versions.hintInstall');
1366
+ return t('versions.hintManual');
1367
+ }
1368
+ function VersionDialog(props) {
1369
+ const t = useT();
1370
+ const [snapshot, setSnapshot] = useState(null);
1371
+ const [checking, setChecking] = useState(true);
1372
+ const [updating, setUpdating] = useState(null);
1373
+ const [error, setError] = useState(null);
1374
+ const [result, setResult] = useState(null);
1375
+ const check = useCallback(async () => {
1376
+ setChecking(true);
1377
+ setError(null);
1378
+ let timeout;
1379
+ try {
1380
+ const deadline = new Promise((_resolve, reject) => { timeout = setTimeout(() => reject(new Error(t('versions.timeout'))), 15_000); });
1381
+ setSnapshot(await Promise.race([props.client.versions(), deadline]));
1382
+ }
1383
+ catch (reason) {
1384
+ setError(message(reason));
1385
+ }
1386
+ finally {
1387
+ if (timeout !== undefined)
1388
+ clearTimeout(timeout);
1389
+ setChecking(false);
1390
+ }
1391
+ }, [props.client, t]);
1392
+ useEffect(() => { void check(); }, [check]);
1393
+ const update = async (component) => {
1394
+ setUpdating(component.id);
1395
+ setError(null);
1396
+ setResult(null);
1397
+ try {
1398
+ const next = await props.client.updateVersion(component.id);
1399
+ setResult(next);
1400
+ await check();
1401
+ props.onRefreshStatus();
1402
+ }
1403
+ catch (reason) {
1404
+ setError(message(reason));
1405
+ }
1406
+ finally {
1407
+ setUpdating(null);
1408
+ }
1409
+ };
1410
+ const busy = checking || updating !== null;
1411
+ return _jsx(SidebarModal, { title: t('versions.title'), description: t('versions.description'), busy: busy, onClose: props.onClose, children: _jsxs("div", { className: css.versionDialogBody, children: [checking && snapshot === null && _jsxs("div", { className: css.versionChecking, role: "status", children: [_jsx("span", {}), t('versions.checking')] }), error !== null && _jsxs("div", { className: css.versionError, role: "alert", children: [_jsx("strong", { children: t('versions.failed') }), _jsx("p", { children: error })] }), result !== null && _jsxs("div", { className: css.versionResult, role: "status", children: [_jsx("strong", { children: result.updated ? t('versions.updated', { name: result.component === 'mnemon' ? 'Mnemon CLI' : 'dsh-mnemon' }) : t('versions.alreadyCurrent') }), result.restartRequired && _jsx("p", { children: t('versions.restartRequired') })] }), snapshot !== null && _jsx("div", { className: css.versionList, children: snapshot.components.map(component => {
1412
+ const canUpdate = component.outdated && component.updateSupported && component.checkError === undefined;
1413
+ const state = component.checkError !== undefined ? t('versions.unknown') : component.outdated ? t('versions.available') : t('versions.current');
1414
+ return _jsxs("article", { "data-outdated": component.outdated || undefined, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("strong", { children: component.name }), _jsx("span", { children: versionModeLabel(t, component.installMode) })] }), _jsx("em", { children: state })] }), _jsxs("div", { className: css.versionNumbers, children: [_jsxs("div", { children: [_jsx("small", { children: t('versions.installed') }), _jsx("code", { children: component.current ?? '—' })] }), _jsx("span", { children: "\u2192" }), _jsxs("div", { children: [_jsx("small", { children: t('versions.latest') }), _jsx("code", { children: component.latest ?? '—' })] })] }), _jsxs("footer", { children: [_jsx("p", { children: versionHint(t, component) }), canUpdate && _jsx("button", { type: "button", className: css.primaryButton, disabled: busy, onClick: () => void update(component), children: updating === component.id ? t('versions.updating') : t('versions.update') })] })] }, component.id);
1415
+ }) }), _jsxs("div", { className: css.versionDialogActions, children: [_jsx("span", { children: snapshot === null ? '' : t('versions.checkedAt', { time: new Date(snapshot.checkedAt).toLocaleTimeString() }) }), _jsxs("div", { children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: busy, onClick: props.onClose, children: t('common.cancel') }), _jsx("button", { type: "button", "data-autofocus": true, className: css.secondaryButton, disabled: busy, onClick: () => void check(), children: checking ? t('versions.checkingShort') : t('versions.recheck') })] })] })] }) });
1103
1416
  }
1104
1417
  function StatusPage(props) {
1105
1418
  const t = useT();
1419
+ const [versionsOpen, setVersionsOpen] = useState(false);
1106
1420
  const status = props.status;
1107
- const lifecycle = status?.lifecycle;
1108
- const current = lifecycle?.current;
1109
- const workers = lifecycle?.subagents;
1110
1421
  const documents = status?.documents;
1111
- const reviewActivity = current?.reviewActivity;
1112
- const reviewThreshold = reviewActivity?.threshold ?? QODERWORK_REVIEW_POLICY.reviewThreshold;
1113
1422
  const catalogKnown = status?.memoryBodies !== undefined;
1114
1423
  const memoryBodies = useMemo(() => status?.memoryBodies ?? [], [status]);
1115
1424
  const activeBodies = memoryBodies.filter(body => body.active).length;
1116
- const phase = current?.lastPhase === undefined || current.lastPhase === 'idle' ? t('status.phaseIdle') : current.lastPhase === 'supervised' ? t('status.phaseSupervised') : current.lastPhase === 'error' ? t('status.phaseError') : current.lastPhase === 'prime' ? t('status.prime') : current.lastPhase === 'recall' ? t('status.recallWorker') : current.lastPhase === 'review' ? t('status.phaseReview') : t('status.writeWorker');
1117
- const reviewState = current?.reviewRunning === true
1118
- ? t('status.reviewRunning')
1119
- : current?.idleReviewPending === true
1120
- ? t('status.reviewPending')
1121
- : reviewActivity?.eligible === true
1122
- ? t('status.reviewQualified')
1123
- : t('status.reviewAccumulating');
1124
- const lastReview = current?.lastReviewAt === undefined ? t('status.noReview') : t('status.reviewAt', { time: new Date(current.lastReviewAt).toLocaleTimeString(), action: current.lastReviewAction ?? '—', score: current.lastReviewScore ?? '—' });
1125
1425
  const storage = status?.storage;
1126
1426
  const selectedScopeKind = storage?.activeKind ?? 'global';
1127
1427
  const selectedScope = storage?.scopes.find(scope => scope.kind === selectedScopeKind);
1128
1428
  const runtimeArea = selectedScope?.areas.find(area => area.kind === 'runtime');
1129
1429
  const runtimeUserEntries = runtimeArea === undefined ? 0 : Number(runtimeArea.details.userEntries ?? 0);
1130
1430
  const runtimeMemoryEntries = runtimeArea === undefined ? 0 : Number(runtimeArea.details.memoryEntries ?? 0);
1131
- return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('status.title'), description: t('status.description'), meta: status === null && props.loading ? t('common.loading') : status?.healthy === true && lifecycle?.sessionAvailable === true ? t('status.nominal') : t('status.checkRequired'), action: _jsx("button", { type: "button", className: css.secondaryButton, disabled: props.loading, onClick: props.onRefresh, children: props.loading ? t('status.rechecking') : t('status.recheck') }) }), _jsxs("section", { className: css.healthStrip, "aria-label": t('status.aria'), children: [_jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${status === null ? css.healthMuted : status.healthy ? css.healthGood : css.healthBad}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.engine') }), _jsx("strong", { children: status === null ? t('status.engineChecking') : status.healthy ? t('status.engineConnected') : t('status.engineUnavailable') }), _jsx("p", { children: status?.version === undefined ? t('status.versionWaiting') : `CLI ${status.version}` })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${runtimeArea === undefined ? css.healthMuted : runtimeArea.status === 'invalid' ? css.healthBad : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.runtime') }), _jsx("strong", { children: runtimeArea === undefined ? t('status.runtimeWaiting') : t('status.runtimeRatio', { user: runtimeUserEntries, memory: runtimeMemoryEntries }) }), _jsx("p", { children: runtimeArea === undefined ? t('status.runtimeWaitingDetail') : t('status.runtimeBytes', { bytes: humanBytes(runtimeArea.bytes) }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${activeBodies > 0 ? css.healthGood : css.healthMuted}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.spaces') }), _jsx("strong", { children: catalogKnown ? t('status.activeRatio', { active: activeBodies, total: memoryBodies.length }) : t('status.directoryUnsynced') }), _jsx("p", { children: t('status.activeMemories', { count: status?.stats?.totalInsights ?? 0 }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${documents === undefined ? css.healthMuted : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.documents') }), _jsx("strong", { children: documents === undefined ? t('status.documentsWaiting') : t('status.documentRatio', { active: documents.activeCount, archived: documents.archivedCount }) }), _jsx("p", { children: documents === undefined ? t('status.documentsSession') : t('status.documentUsage', { used: humanBytes(documents.activeBytes), limit: humanBytes(documents.limitBytes) }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${lifecycle === undefined ? css.healthMuted : lifecycle.sessionAvailable ? css.healthGood : css.healthBad}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.router') }), _jsx("strong", { children: lifecycle === undefined ? t('status.routerChecking') : lifecycle.sessionAvailable ? t('status.routerReady') : t('status.sessionMissing') }), _jsx("p", { children: workers === undefined ? t('status.orchestrationWaiting') : t('status.workerSummary', { recalls: workers.recalls, reviews: workers.reviews ?? 0, writes: workers.writes, failures: workers.failures ?? 0 }) })] })] })] }), _jsx(MemorySystemFlow, { status: status, reviewState: reviewState }), _jsx(StorageDomains, { catalog: storage, selected: selectedScope, selectedKind: selectedScopeKind }), _jsxs("section", { className: css.lifecyclePanel, children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.lifecycle') }), _jsx("p", { children: t('status.lifecycleText') })] }), _jsx("span", { className: css.phaseBadge, children: phase })] }), _jsxs("div", { className: css.lifecycleFlow, children: [_jsxs("article", { children: [_jsx("span", { children: "01" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.prime') }), _jsx("p", { children: t('status.primeText') })] }), _jsx("code", { children: lifecycle?.counters.primes ?? 0 })] }), _jsxs("article", { "data-disabled": lifecycle?.recallMode === 'off' || undefined, children: [_jsx("span", { children: "02" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.recallWorker') }), _jsx("p", { children: lifecycle?.recallMode === 'guided' ? t('status.recallText') : t('status.recallOff') })] }), _jsx("code", { children: workers?.recalls ?? 0 })] }), _jsxs("article", { "data-disabled": lifecycle?.writebackMode === 'off' || undefined, children: [_jsx("span", { children: "03" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.writeWorker') }), _jsx("p", { children: lifecycle?.writebackMode === 'guided' ? t('status.writeText', { threshold: reviewThreshold, seconds: Math.round((lifecycle.idleReviewMs ?? 0) / 1000) }) : t('status.writeOff') })] }), _jsx("code", { children: workers?.reviews ?? 0 })] })] }), _jsxs("div", { className: css.lifecycleFoot, children: [_jsxs("span", { children: [t('status.activitySignals'), " ", _jsx("strong", { children: t('status.activitySignalValues', { chars: reviewActivity?.totalUserTextLength ?? 0, turns: reviewActivity?.turnCount ?? 0, tools: reviewActivity?.toolCallCount ?? 0, unique: reviewActivity?.uniqueToolCount ?? 0 }) })] }), _jsxs("span", { children: [t('status.lastReview'), " ", _jsx("strong", { children: lastReview })] }), _jsxs("span", { children: [t('status.workerFailures'), " ", _jsx("strong", { children: workers?.failures ?? 0 })] })] }), current?.lastError !== undefined && _jsxs("div", { className: css.inlineError, role: "alert", children: ["Lifecycle\uFF1A", current.lastError] })] })] }));
1431
+ return (_jsxs("div", { className: css.page, children: [_jsx(PageHeader, { title: t('status.title'), description: t('status.description'), meta: status === null && props.loading ? t('common.loading') : status?.healthy === true ? t('status.nominal') : t('status.checkRequired'), action: _jsxs("div", { className: css.statusHeaderActions, children: [_jsx("button", { type: "button", className: css.ghostButton, disabled: props.loading, onClick: props.onRefresh, children: props.loading ? t('status.rechecking') : t('status.recheck') }), _jsx("button", { type: "button", className: css.secondaryButton, onClick: () => setVersionsOpen(true), children: t('versions.checkAction') })] }) }), _jsxs("section", { className: css.healthStrip, "aria-label": t('status.aria'), children: [_jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${status === null ? css.healthMuted : status.healthy ? css.healthGood : css.healthBad}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.engine') }), _jsx("strong", { children: status === null ? t('status.engineChecking') : status.healthy ? status.version === undefined ? t('status.engineConnected') : `Mnemon ${status.version}` : t('status.engineUnavailable') }), _jsx("p", { children: status?.dshMnemonVersion === undefined ? t('status.versionWaiting') : `dsh-mnemon ${status.dshMnemonVersion}` })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${runtimeArea === undefined ? css.healthMuted : runtimeArea.status === 'invalid' ? css.healthBad : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.runtime') }), _jsx("strong", { children: runtimeArea === undefined ? t('status.runtimeWaiting') : t('status.runtimeRatio', { user: runtimeUserEntries, memory: runtimeMemoryEntries }) }), _jsx("p", { children: runtimeArea === undefined ? t('status.runtimeWaitingDetail') : t('status.runtimeBytes', { bytes: humanBytes(runtimeArea.bytes) }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${activeBodies > 0 ? css.healthGood : css.healthMuted}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.spaces') }), _jsx("strong", { children: catalogKnown ? t('status.activeRatio', { active: activeBodies, total: memoryBodies.length }) : t('status.directoryUnsynced') }), _jsx("p", { children: t('status.activeMemories', { count: status?.stats?.totalInsights ?? 0 }) })] })] }), _jsxs("article", { children: [_jsx("span", { className: `${css.healthIndicator} ${documents === undefined ? css.healthMuted : css.healthGood}` }), _jsxs("div", { children: [_jsx("small", { children: t('status.documents') }), _jsx("strong", { children: documents === undefined ? t('status.documentsWaiting') : t('status.documentRatio', { active: documents.activeCount, archived: documents.archivedCount }) }), _jsx("p", { children: documents === undefined ? t('status.documentsSession') : t('status.documentUsage', { used: humanBytes(documents.activeBytes), limit: humanBytes(documents.limitBytes) }) })] })] })] }), _jsx(StorageDomains, { catalog: storage, selected: selectedScope, selectedKind: selectedScopeKind }), versionsOpen && _jsx(VersionDialog, { client: props.client, onClose: () => setVersionsOpen(false), onRefreshStatus: props.onRefresh })] }));
1132
1432
  }
1133
1433
  function storageScopeLabel(t, kind) {
1134
1434
  return t(kind === 'global' ? 'status.storageGlobal' : kind === 'workspace' ? 'status.storageWorkspace' : 'status.storageCustom');
1135
1435
  }
1436
+ /** Resolve the configured scope before the first status round-trip to keep the Sidebar header stable. */
1437
+ function configuredStorageScope(config) {
1438
+ return config?.storageScope ?? (config?.dataDir?.trim() ? 'custom' : 'global');
1439
+ }
1136
1440
  function storageAreaLabel(t, kind) {
1137
1441
  return t(kind === 'runtime' ? 'status.storageRuntime' : kind === 'memory-bodies' ? 'status.storageBodies' : kind === 'documents' ? 'status.storageDocuments' : 'status.storageState');
1138
1442
  }
@@ -1148,74 +1452,78 @@ function storageAreaDetails(t, area) {
1148
1452
  function StorageDomains(props) {
1149
1453
  const t = useT();
1150
1454
  const areaStatus = (status) => t(status === 'ready' ? 'status.storageReady' : status === 'empty' ? 'status.storageEmpty' : status === 'missing' ? 'status.storageMissing' : 'status.storageInvalid');
1151
- return (_jsxs("section", { className: css.storageDomains, "aria-label": t('status.storageDomains'), children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.storageDomains') }), _jsx("p", { children: t('status.storageDomainsText') })] }), _jsx("span", { className: css.phaseBadge, children: storageScopeLabel(t, props.selectedKind) })] }), props.catalog === undefined ? _jsx("div", { className: css.storageUnavailable, children: t('status.storageWaiting') }) : props.selected?.root === undefined ? _jsxs("div", { className: css.storageUnavailable, children: [_jsx("strong", { children: storageScopeLabel(t, props.selectedKind) }), _jsx("p", { children: props.selectedKind === 'custom' ? t('status.storageCustomUnset') : t('status.storageWorkspaceUnavailable') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.storageRoot, children: [_jsxs("div", { children: [_jsxs("span", { children: [storageScopeLabel(t, props.selectedKind), " \u00B7 ", t('status.storageActiveRoot')] }), _jsx("code", { children: props.selected.root })] }), _jsxs("div", { children: [_jsx("strong", { children: humanBytes(props.selected.totalBytes) }), _jsx("small", { children: props.selected.available ? t('status.storageAvailable') : t('status.storageNotCreated') })] })] }), _jsx("div", { className: css.storageAreaGrid, children: props.selected.areas.map(area => _jsxs("article", { "data-status": area.status, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", {}), " ", _jsx("strong", { children: storageAreaLabel(t, area.kind) })] }), _jsx("em", { children: areaStatus(area.status) })] }), _jsxs("div", { className: css.storageAreaMetric, children: [_jsx("strong", { children: area.itemCount }), _jsx("span", { children: t('status.storageItems') }), _jsx("code", { children: humanBytes(area.bytes) })] }), _jsx("p", { children: storageAreaDetails(t, area) }), _jsx("code", { className: css.storagePath, children: area.path }), area.issue !== undefined && _jsx("small", { children: area.issue })] }, area.kind)) })] }), props.catalog !== undefined && _jsx("p", { className: css.storageFootnote, children: t('status.storageFootnote', { root: props.catalog.activeRoot }) })] }));
1152
- }
1153
- function MemorySystemFlow(props) {
1154
- const t = useT();
1155
- const status = props.status;
1156
- const activeScope = status?.storage?.scopes.find(scope => scope.active);
1157
- const runtime = activeScope?.areas.find(area => area.kind === 'runtime');
1158
- const documentArea = activeScope?.areas.find(area => area.kind === 'documents');
1159
- const documents = status?.documents;
1160
- const bodies = status?.memoryBodies ?? [];
1161
- const activeBodies = bodies.filter(body => body.active).length;
1162
- const score = status?.lifecycle?.current?.reviewActivity?.score ?? 0;
1163
- const threshold = status?.lifecycle?.current?.reviewActivity?.threshold ?? QODERWORK_REVIEW_POLICY.reviewThreshold;
1164
- const idleSeconds = Math.round((status?.lifecycle?.idleReviewMs ?? 30_000) / 1000);
1165
- const hostReady = status?.lifecycle?.sessionAvailable === true;
1166
- const flowActive = status?.healthy === true && hostReady;
1167
- const areaState = (area) => area?.status === 'invalid' ? 'error' : area?.status === 'ready' ? 'ready' : 'idle';
1168
- const current = status?.lifecycle?.current;
1169
- const workers = status?.lifecycle?.subagents;
1170
- const lastWorkerOperation = workers?.lastOperation;
1171
- const spawnRecent = lastWorkerOperation !== undefined && lastWorkerOperation !== 'review';
1172
- const archiveRecent = lastWorkerOperation === 'document-archive' || lastWorkerOperation === 'migration';
1173
- const reviewState = current?.reviewRunning === true ? 'running' : current?.idleReviewPending === true ? 'pending' : 'idle';
1174
- const rootRunning = current?.status === 'running';
1175
- const semanticActive = rootRunning && (current?.lastPhase === 'recall' || current?.lastPhase === 'supervised');
1176
- const runtimeCount = runtime?.itemCount ?? 0;
1177
- const insights = status?.stats?.totalInsights ?? 0;
1178
- const flowNode = (title, meta, state = 'idle', kind = 'host', emphasis) => (_jsxs("article", { className: css.flowArchitectureNode, "data-state": state, "data-kind": kind, "data-emphasis": emphasis, children: [_jsx("span", { className: css.flowArchitectureSignal, "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("strong", { children: title }), _jsx("small", { children: meta })] })] }));
1179
- const connector = (label, kind = 'host', active = false) => _jsxs("div", { className: css.flowConnector, "data-kind": kind, "data-active": active || undefined, children: [_jsx("span", { children: label }), _jsx("i", { "aria-hidden": "true" })] });
1180
- return (_jsxs("section", { className: css.memoryFlow, "aria-label": t('status.flowTitle'), children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.flowTitle') }), _jsx("p", { children: t('status.flowDescription') })] }), _jsx("span", { className: `${css.runtimeBadge} ${status === null ? css.runtimePending : flowActive ? css.runtimeOnline : css.runtimeOffline}`, children: status === null ? t('common.loading') : flowActive ? t('status.flowLive') : t('status.flowDegraded') })] }), _jsx("div", { className: css.memoryFlowCanvas, children: _jsxs("div", { className: css.flowArchitecture, role: "img", "aria-label": `${t('status.flowTitle')}:${t('status.flowDescription')}`, children: [_jsxs("section", { className: css.flowLane, "data-lane": "context", children: [_jsxs("header", { children: [_jsx("span", { children: "01" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.flowLaneContext') }), _jsx("small", { children: t('status.flowLaneContextDetail') })] })] }), _jsxs("div", { className: css.flowTrack, children: [flowNode(t('status.storageRuntime'), t('status.flowRuntimeMeta', { count: runtimeCount }), areaState(runtime), 'store'), connector(t('status.flowPromptAssembly'), 'host'), flowNode(t('status.flowSupervisor'), t('status.flowRootMeta'), hostReady ? 'ready' : 'idle', 'host', rootRunning ? 'active' : undefined), connector('↔', 'host', rootRunning), flowNode(t('status.flowConversation'), t('status.flowTurns'), hostReady ? 'ready' : 'idle', 'task')] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowSecondaryTrack}`, children: [flowNode(t('status.flowSupervisor'), t('status.flowRootMeta'), hostReady ? 'ready' : 'idle', 'host'), connector(t('status.flowOnDemand'), 'host'), flowNode(t('status.flowDocumentSearch'), t('status.flowOnDemand'), areaState(documentArea), 'host'), connector('→', 'host'), flowNode(t('status.storageDocuments'), t('status.flowDocuments', { active: documents?.activeCount ?? 0, archived: documents?.archivedCount ?? 0 }), areaState(documentArea), 'store'), connector('↩', 'host'), flowNode(t('status.flowContext'), t('status.flowEvidenceMeta'), hostReady ? 'ready' : 'idle', 'task')] })] }), _jsxs("section", { className: css.flowLane, "data-lane": "semantic", children: [_jsxs("header", { children: [_jsx("span", { children: "02" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.flowLaneSemantic') }), _jsx("small", { children: t('status.flowLaneSemanticDetail') })] })] }), _jsxs("div", { className: css.flowTrack, children: [flowNode(t('status.flowSupervisor'), t('status.flowSemanticRequest'), hostReady ? 'ready' : 'idle', 'task'), connector('→', 'worker', semanticActive), flowNode(t('status.flowSpawnWorker'), `${t('status.flowSpawnMeta')}${spawnRecent ? ` · ${t('status.flowRecent')}` : ''}`, hostReady ? 'ready' : 'idle', 'worker', semanticActive ? 'active' : spawnRecent ? 'recent' : undefined), connector('→', 'worker', semanticActive), flowNode(t('status.flowHostBridge'), t('status.flowHostBridgeMeta'), status?.healthy === false ? 'error' : status?.healthy === true ? 'ready' : 'idle', 'host'), connector('↔', 'host', semanticActive), flowNode(t('term.spaces'), t('status.flowSpacesMeta', { active: activeBodies, total: bodies.length, insights }), status?.healthy === false ? 'error' : activeBodies > 0 ? 'ready' : 'idle', 'store'), connector('↩', 'host', semanticActive), flowNode(t('status.flowContext'), t('status.flowEvidenceMeta'), hostReady ? 'ready' : 'idle', 'task')] })] }), _jsxs("section", { className: css.flowLane, "data-lane": "maintenance", children: [_jsxs("header", { children: [_jsx("span", { children: "03" }), _jsxs("div", { children: [_jsx("strong", { children: t('status.flowLaneMaintenance') }), _jsx("small", { children: t('status.flowLaneMaintenanceDetail') })] })] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowMaintenanceTrack}`, children: [flowNode(t('status.flowDeterministicWrites'), t('status.flowDeterministicWritesMeta'), hostReady ? 'ready' : 'idle', 'task'), connector('→', 'host'), flowNode(t('status.flowControlPlane'), t('status.flowControlPlaneMeta'), areaState(runtime), 'host'), connector('→', 'host'), flowNode(t('status.storageRuntime'), t('status.storageDocuments'), areaState(documentArea), 'store')] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowReviewTrack}`, children: [flowNode(t('status.flowReviewGate'), t('status.flowReviewGateMeta', { score, threshold, seconds: idleSeconds }), current?.reviewActivity?.eligible === true ? 'ready' : 'idle', 'task', reviewState === 'pending' ? 'pending' : undefined), connector('→', 'worker', reviewState === 'running'), flowNode(t('status.flowForkReview'), t('status.flowForkReviewMeta', { state: props.reviewState }), current?.lastError === undefined ? hostReady ? 'ready' : 'idle' : 'error', 'worker', reviewState === 'running' ? 'active' : reviewState === 'pending' ? 'pending' : undefined), connector('→', 'host', reviewState === 'running'), flowNode(t('status.flowReviewTargets'), t('status.flowReviewTargetsMeta'), areaState(documentArea), 'host'), connector('↩', 'host', reviewState === 'running'), flowNode(t('status.flowContext'), t('status.flowEvidenceMeta'), hostReady ? 'ready' : 'idle', 'task')] }), _jsxs("div", { className: `${css.flowTrack} ${css.flowArchiveTrack}`, children: [flowNode(t('status.storageDocuments'), t('status.flowDocuments', { active: documents?.activeCount ?? 0, archived: documents?.archivedCount ?? 0 }), areaState(documentArea), 'store'), connector('→', 'worker'), flowNode(t('status.flowColdArchive'), `${t('status.flowColdArchiveMeta')}${archiveRecent ? ` · ${t('status.flowRecent')}` : ''}`, hostReady ? 'ready' : 'idle', 'worker', archiveRecent ? 'recent' : undefined), connector('→', 'worker'), flowNode(t('status.flowHostBridge'), t('status.flowHostBridgeMeta'), status?.healthy === false ? 'error' : status?.healthy === true ? 'ready' : 'idle', 'host'), connector('→', 'host'), flowNode(t('status.flowIndexFirst'), t('term.spaces'), status?.healthy === false ? 'error' : activeBodies > 0 ? 'ready' : 'idle', 'store'), connector('→', 'host'), flowNode(t('status.flowRevisionMove'), t('status.flowRevisionMoveMeta', { archived: documents?.archivedCount ?? 0 }), areaState(documentArea), 'store')] })] })] }) }), _jsxs("div", { className: css.flowLegend, children: [_jsxs("span", { children: [_jsx("i", {}), t('status.flowReadWrite')] }), _jsxs("span", { children: [_jsx("i", {}), t('status.flowArchive')] }), _jsxs("span", { children: [_jsx("b", {}), t('status.flowLegendRecent')] }), _jsx("span", { children: t('status.flowCurrentScope', { scope: storageScopeLabel(t, status?.storage?.activeKind ?? 'global') }) })] })] }));
1455
+ return (_jsxs("section", { className: css.storageDomains, "aria-label": t('status.storageDomains'), children: [_jsxs("div", { className: css.statusSectionHeader, children: [_jsxs("div", { children: [_jsx("h3", { children: t('status.storageDomains') }), _jsx("p", { children: t('status.storageDomainsText') })] }), _jsx("span", { className: css.phaseBadge, children: storageScopeLabel(t, props.selectedKind) })] }), props.catalog === undefined ? _jsx("div", { className: css.storageUnavailable, children: t('status.storageWaiting') }) : props.selected?.root === undefined ? _jsxs("div", { className: css.storageUnavailable, children: [_jsx("strong", { children: storageScopeLabel(t, props.selectedKind) }), _jsx("p", { children: props.selectedKind === 'custom' ? t('status.storageCustomUnset') : t('status.storageWorkspaceUnavailable') })] }) : _jsxs(_Fragment, { children: [_jsxs("div", { className: css.storageRoot, children: [_jsxs("div", { children: [_jsxs("span", { children: [storageScopeLabel(t, props.selectedKind), " \u00B7 ", t('status.storageActiveRoot')] }), _jsx("code", { children: props.selected.root })] }), _jsxs("div", { children: [_jsx("strong", { children: humanBytes(props.selected.totalBytes) }), _jsx("small", { children: props.selected.available ? t('status.storageAvailable') : t('status.storageNotCreated') })] })] }), _jsx("div", { className: css.storageAreaGrid, children: props.selected.areas.filter(area => area.kind !== 'state').map(area => _jsxs("article", { "data-status": area.status, children: [_jsxs("header", { children: [_jsxs("div", { children: [_jsx("span", {}), " ", _jsx("strong", { children: storageAreaLabel(t, area.kind) })] }), _jsx("em", { children: areaStatus(area.status) })] }), _jsxs("div", { className: css.storageAreaMetric, children: [_jsx("strong", { children: area.itemCount }), _jsx("span", { children: t('status.storageItems') }), _jsx("code", { children: humanBytes(area.bytes) })] }), _jsx("p", { children: storageAreaDetails(t, area) }), _jsx("code", { className: css.storagePath, children: area.path }), area.issue !== undefined && _jsx("small", { children: area.issue })] }, area.kind)) })] }), props.catalog !== undefined && _jsx("p", { className: css.storageFootnote, children: t('status.storageFootnote', { root: props.catalog.activeRoot }) })] }));
1181
1456
  }
1182
1457
  export function MnemonView(props) {
1183
- return _jsx(I18nContext.Provider, { value: props.t ?? translateZh, children: _jsx(MnemonWorkspace, { ...props }) });
1458
+ const t = props.t ?? translateZh;
1459
+ const appearance = resolveMnemonViewAppearance(props.surface ?? 'buildin', t);
1460
+ return _jsx(I18nContext.Provider, { value: t, children: _jsx(LocaleContext.Provider, { value: props.locale ?? 'zh', children: _jsx(MnemonViewAppearanceProvider, { value: appearance, children: _jsx(MnemonWorkspace, { ...props }) }) }) });
1184
1461
  }
1185
- function MnemonWorkspace({ connection, sessionId, workspaceId, workspaceSelection }) {
1462
+ function MnemonWorkspace({ connection, settingsScope, sessionId, workspaceId, workspaceSelection, onClose }) {
1186
1463
  const t = useT();
1464
+ const appearance = useMnemonViewAppearance();
1465
+ const settingsSnapshot = useSyncExternalStore(settingsScope.subscribe, settingsScope.getSnapshot, settingsScope.getSnapshot);
1187
1466
  const client = useMemo(() => new MnemonClient(connection, sessionId, workspaceId), [connection, sessionId, workspaceId]);
1467
+ const clientContextKey = `${sessionId ?? ''}\u0000${workspaceId ?? ''}`;
1468
+ const viewContextKey = `${clientContextKey}\u0000${settingsSnapshot.revision ?? 'loading'}`;
1188
1469
  const [page, setPage] = useState('status');
1470
+ const lastMemoryPage = useRef('overview');
1189
1471
  const canvasRef = useRef(null);
1472
+ const selectPage = useCallback((next) => {
1473
+ if (isMemoryPage(next))
1474
+ lastMemoryPage.current = next;
1475
+ setPage(next);
1476
+ }, []);
1477
+ const selectPrimaryPage = useCallback((next) => {
1478
+ selectPage(appearance.surface === 'sidebar' && next === 'overview' ? lastMemoryPage.current : next);
1479
+ }, [appearance.surface, selectPage]);
1190
1480
  /** Pages share one plugin-owned scroll container; never mutate DSH ancestor scrollports. */
1191
1481
  const resetViewportScroll = useCallback(() => {
1192
1482
  const canvas = canvasRef.current;
1193
1483
  if (canvas !== null)
1194
1484
  canvas.scrollTop = 0;
1195
1485
  }, []);
1196
- useEffect(() => { resetViewportScroll(); }, [page, resetViewportScroll]);
1197
- useEffect(() => {
1198
- // Clear only this view's offset after layout; the host owns every ancestor.
1199
- const frame = window.requestAnimationFrame(() => resetViewportScroll());
1200
- return () => window.cancelAnimationFrame(frame);
1201
- }, [resetViewportScroll]);
1202
- const [status, setStatus] = useState(null);
1203
- const [statusLoading, setStatusLoading] = useState(true);
1204
- const [statusError, setStatusError] = useState(null);
1486
+ // Reset before paint so a newly selected page never flashes at the previous
1487
+ // page's scroll offset for one frame. The host still owns every ancestor.
1488
+ useLayoutEffect(() => { resetViewportScroll(); }, [viewContextKey, page, resetViewportScroll]);
1489
+ const [statusState, setStatusState] = useState(() => ({ contextKey: viewContextKey, value: null, loading: true, error: null }));
1490
+ const currentStatusState = statusState.contextKey === viewContextKey
1491
+ ? statusState
1492
+ : { contextKey: viewContextKey, value: null, loading: true, error: null };
1493
+ const status = currentStatusState.value;
1494
+ const statusLoading = currentStatusState.loading;
1495
+ const statusError = currentStatusState.error;
1205
1496
  const statusRequest = useRef(0);
1206
1497
  const [revision, setRevision] = useState(0);
1207
1498
  const [searchSeed, setSearchSeed] = useState('');
1208
1499
  const [rememberSeed, setRememberSeed] = useState('');
1500
+ const [rememberOpen, setRememberOpen] = useState(false);
1501
+ // A newly inspected workspace must never inherit visible cards, open editors,
1502
+ // search seeds, or scroll position from the previous workspace.
1503
+ useLayoutEffect(() => {
1504
+ setRememberOpen(false);
1505
+ setRememberSeed('');
1506
+ setSearchSeed('');
1507
+ }, [viewContextKey]);
1508
+ const openRemember = useCallback((seed = '') => {
1509
+ setRememberSeed(seed);
1510
+ setRememberOpen(true);
1511
+ }, []);
1209
1512
  /** Conversation surfaces ask this view to open a page (optionally with a seed). */
1210
1513
  const applyAnchor = useCallback((anchor) => {
1514
+ if (anchor.page === 'remember' && appearance.surface === 'sidebar') {
1515
+ openRemember(anchor.seed ?? '');
1516
+ selectPage(lastMemoryPage.current);
1517
+ return;
1518
+ }
1211
1519
  if (anchor.seed !== undefined && anchor.seed !== '') {
1212
1520
  if (anchor.page === 'explore')
1213
1521
  setSearchSeed(anchor.seed);
1214
1522
  if (anchor.page === 'remember')
1215
1523
  setRememberSeed(anchor.seed);
1216
1524
  }
1217
- setPage(anchor.page);
1218
- }, []);
1525
+ selectPage(anchor.page);
1526
+ }, [appearance.surface, openRemember, selectPage]);
1219
1527
  useEffect(() => {
1220
1528
  const held = consumeMnemonAnchor(sessionId);
1221
1529
  if (held !== null)
@@ -1224,27 +1532,29 @@ function MnemonWorkspace({ connection, sessionId, workspaceId, workspaceSelectio
1224
1532
  }, [sessionId, applyAnchor]);
1225
1533
  const loadStatus = useCallback(async () => {
1226
1534
  const request = ++statusRequest.current;
1227
- setStatusLoading(true);
1228
- setStatusError(null);
1535
+ setStatusState(current => ({ contextKey: viewContextKey, value: current.contextKey === viewContextKey ? current.value : null, loading: true, error: null }));
1229
1536
  try {
1230
1537
  const next = await client.status();
1231
1538
  if (request === statusRequest.current)
1232
- setStatus(next);
1539
+ setStatusState({ contextKey: viewContextKey, value: next, loading: false, error: null });
1233
1540
  }
1234
1541
  catch (reason) {
1235
1542
  if (request === statusRequest.current)
1236
- setStatusError(message(reason));
1237
- }
1238
- finally {
1239
- if (request === statusRequest.current)
1240
- setStatusLoading(false);
1543
+ setStatusState({ contextKey: viewContextKey, value: null, loading: false, error: message(reason) });
1241
1544
  }
1242
- }, [client]);
1545
+ }, [client, viewContextKey]);
1243
1546
  useEffect(() => { void loadStatus(); }, [loadStatus]);
1244
1547
  const mutate = useCallback(() => { setRevision(value => value + 1); void loadStatus(); }, [loadStatus]);
1245
1548
  const forget = useCallback(async (insight) => { await client.forget(insight.id, insight.memoryBodyId); mutate(); }, [client, mutate]);
1246
- const explore = useCallback((query) => { setSearchSeed(query); setPage('explore'); }, []);
1247
- const clone = useCallback((insight) => { setRememberSeed(insight.content); setPage('remember'); }, []);
1549
+ const explore = useCallback((query) => { setSearchSeed(query); selectPage('explore'); }, [selectPage]);
1550
+ const clone = useCallback((insight) => {
1551
+ if (appearance.surface === 'sidebar')
1552
+ openRemember(insight.content);
1553
+ else {
1554
+ setRememberSeed(insight.content);
1555
+ selectPage('remember');
1556
+ }
1557
+ }, [appearance.surface, openRemember, selectPage]);
1248
1558
  const refreshAll = () => { setRevision(value => value + 1); void loadStatus(); };
1249
1559
  const writeEnabled = status?.writeEnabled === true;
1250
1560
  const stats = status?.stats;
@@ -1252,7 +1562,24 @@ function MnemonWorkspace({ connection, sessionId, workspaceId, workspaceSelectio
1252
1562
  const memoryBodies = useMemo(() => status?.memoryBodies ?? [], [status]);
1253
1563
  const activeBodies = memoryBodies.filter(body => body.active).length;
1254
1564
  const workspaceContext = status?.workspaceContext;
1255
- const showWorkspacePicker = workspaceContext?.mode === 'workspace' && workspaceSelection !== undefined && workspaceSelection.options.length > 0;
1256
- return (_jsxs("main", { className: css.shell, children: [_jsxs("header", { className: css.masthead, children: [_jsxs("div", { className: css.brand, children: [_jsx(MnemonLogo, { className: css.brandLogo }), _jsx("h1", { children: "Mnemon" })] }), _jsxs("section", { className: css.telemetry, "aria-label": t('telemetry.aria'), children: [_jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.memories') }), _jsx("strong", { children: stats?.totalInsights ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.graph') }), _jsx("strong", { children: stats?.edgeCount ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.entities') }), _jsx("strong", { children: stats?.topEntities.length ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.spaces') }), _jsx("strong", { children: status === null || !catalogKnown ? '—' : activeBodies })] })] }), _jsxs("div", { className: css.headerActions, children: [showWorkspacePicker && _jsxs("label", { className: css.workspacePicker, children: [_jsx("span", { children: t('workspace.viewing') }), _jsx("select", { "aria-label": t('workspace.selectorAria'), value: workspaceSelection.selectedWorkspaceId ?? '', onChange: event => workspaceSelection.onSelect(event.target.value), children: workspaceSelection.options.map(workspace => _jsx("option", { value: workspace.id, children: workspace.title }, workspace.id)) })] }), _jsxs("div", { className: css.statusCluster, children: [_jsx("span", { className: `${css.statusDot} ${statusLoading && status === null ? css.checking : status?.healthy === true ? css.online : css.offline}` }), _jsx("span", { children: statusLoading ? t('header.checking') : status?.healthy === true ? catalogKnown ? t('header.connected', { count: activeBodies }) : t('header.directoryPending') : t('header.unavailable') }), _jsx("button", { type: "button", className: css.iconButton, disabled: statusLoading, onClick: refreshAll, "aria-label": t('common.refresh'), children: "\u21BB" })] })] })] }), (statusError !== null || status?.healthy === false) && _jsxs("div", { className: css.alert, role: "alert", children: [_jsx("strong", { children: t('header.notReady') }), _jsx("span", { children: statusError ?? status?.error })] }), workspaceContext?.mode === 'workspace' && !workspaceContext.aligned && _jsxs("div", { className: css.workspaceMismatch, role: "status", children: [_jsxs("div", { children: [_jsx("strong", { children: t('workspace.mismatchTitle') }), _jsx("span", { children: t('workspace.mismatchDescription') }), _jsxs("div", { children: [_jsx("code", { children: t('workspace.selectedRoot', { root: workspaceContext.selectedRoot }) }), _jsx("code", { children: t('workspace.effectiveRoot', { root: workspaceContext.effectiveRoot }) })] })] }), workspaceSelection?.effectiveWorkspaceId !== undefined && _jsx("button", { type: "button", className: css.secondaryButton, onClick: workspaceSelection.onAlign, children: t('workspace.align') })] }), _jsxs("div", { className: css.workspace, children: [_jsxs("div", { className: css.topNavigation, children: [_jsx("nav", { className: css.nav, "aria-label": t('nav.aria'), children: PAGE_NAV.map((group, groupIndex) => _jsxs(Fragment, { children: [_jsx("div", { className: css.navGroup, role: "group", "aria-label": t(group.aria), children: group.entries.map(item => _jsxs("button", { type: "button", "aria-current": page === item.id ? 'page' : undefined, onClick: () => setPage(item.id), children: [_jsx("span", { className: css.navGlyph, "aria-hidden": "true", children: item.glyph }), _jsxs("span", { children: [_jsx("strong", { children: t(item.label) }), _jsx("small", { children: t(item.detail) })] })] }, item.id)) }), groupIndex < PAGE_NAV.length - 1 && _jsx("span", { className: css.navGroupDivider, "aria-hidden": "true" })] }, group.aria)) }), _jsxs("div", { className: css.spaceSummary, children: [_jsx("span", { children: t('sidebar.activeSpaces') }), _jsx("code", { children: catalogKnown ? `${activeBodies} / ${memoryBodies.length}` : '— / —' }), _jsx("small", { children: writeEnabled ? t('common.agentSupervised') : t('common.readOnly') })] })] }), _jsxs("section", { className: css.canvas, ref: canvasRef, "data-testid": "mnemon-canvas", children: [page === 'overview' && _jsx(OverviewPage, { client: client, revision: revision, writeEnabled: writeEnabled, fallbackBodies: memoryBodies, fallbackDirectory: status?.memoryBodyDirectory, catalogKnown: catalogKnown, onMutate: mutate, onExplore: explore }), page === 'runtime' && _jsx(RuntimePage, { client: client, revision: revision, writeEnabled: writeEnabled, onMutate: mutate }), page === 'documents' && _jsx(DocumentsPage, { client: client, revision: revision, writeEnabled: writeEnabled, ...(sessionId === undefined ? {} : { sessionId }), onMutate: mutate }), page === 'explore' && _jsx(ExplorePage, { client: client, status: status, seed: searchSeed, writeEnabled: writeEnabled, onForget: forget }), page === 'entities' && _jsx(EntitiesPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onExplore: explore }), page === 'remember' && _jsx(RememberPage, { client: client, sessionId: sessionId, memoryBodies: status?.memoryBodies ?? [], writeEnabled: writeEnabled, seed: rememberSeed, onMutate: mutate }), page === 'list' && _jsx(ListPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onClone: clone, onExplore: explore }), page === 'status' && _jsx(StatusPage, { status: status, loading: statusLoading, onRefresh: () => void loadStatus() })] })] })] }));
1565
+ const storageMode = workspaceContext?.mode ?? status?.storage?.activeKind ?? configuredStorageScope(settingsSnapshot.value);
1566
+ const storageModeText = storageScopeLabel(t, storageMode);
1567
+ const showWorkspacePicker = storageMode === 'workspace' && workspaceSelection !== undefined && workspaceSelection.options.length > 0;
1568
+ const workspaceDiverged = workspaceContext?.mode === 'workspace' && !workspaceContext.aligned;
1569
+ const canAlignWorkspace = workspaceDiverged && workspaceSelection?.effectiveWorkspaceId !== undefined;
1570
+ const workspaceDifference = workspaceContext === undefined
1571
+ ? ''
1572
+ : `${t('workspace.selectedRoot', { root: workspaceContext.selectedRoot })}; ${t('workspace.effectiveRoot', { root: workspaceContext.effectiveRoot })}`;
1573
+ const workspacePicker = showWorkspacePicker && _jsxs("label", { className: appearanceClass(css.workspacePicker, appearance.classes.workspacePicker), children: [_jsx("span", { children: t('workspace.viewing') }), _jsx("select", { "aria-label": t('workspace.selectorAria'), value: workspaceSelection.selectedWorkspaceId ?? '', onChange: event => workspaceSelection.onSelect(event.target.value), children: workspaceSelection.options.map(workspace => _jsx("option", { value: workspace.id, children: workspace.title }, workspace.id)) })] });
1574
+ const connectionLabel = statusLoading
1575
+ ? t('header.checking')
1576
+ : status?.healthy !== true
1577
+ ? t('header.unavailable')
1578
+ : appearance.surface === 'sidebar'
1579
+ ? t('header.connected')
1580
+ : catalogKnown
1581
+ ? t('header.connectedWithCount', { count: activeBodies })
1582
+ : t('header.directoryPending');
1583
+ return (_jsxs("main", { className: appearanceClass(css.shell, appearance.classes.shell), "data-mnemon-surface": appearance.surface, children: [_jsxs("header", { className: appearanceClass(css.masthead, appearance.classes.masthead), children: [appearance.surface === 'sidebar' && onClose !== undefined && _jsxs("button", { type: "button", className: appearanceClass(css.ghostButton, css.backButton), onClick: onClose, "aria-label": t('header.backToConversation'), children: [_jsx(IconChevronLeftOutline14, { size: 14 }), _jsx("span", { children: t('header.backToConversation') })] }), _jsxs("div", { className: appearanceClass(css.brand, appearance.classes.brand), children: [appearance.showLogo && _jsx(MnemonLogo, { className: css.brandLogo }), _jsx("h1", { children: appearance.title }), appearance.surface === 'sidebar' && _jsxs("span", { className: css.storageMode, "aria-label": t('workspace.storageModeAria', { mode: storageModeText }), children: [_jsx("span", { children: t('workspace.storageMode') }), _jsx("strong", { children: storageModeText })] }), appearance.surface === 'sidebar' && workspacePicker, appearance.surface === 'sidebar' && canAlignWorkspace && _jsxs("div", { className: appearanceClass(css.workspaceMismatch, appearance.classes.workspaceMismatch), role: "status", "aria-label": `${t('workspace.mismatchTitle')}. ${workspaceDifference}`, title: workspaceDifference, children: [_jsx("span", { children: t('workspace.mismatchShort') }), _jsx("button", { type: "button", onClick: workspaceSelection.onAlign, children: t('workspace.align') })] })] }), appearance.showTelemetry && _jsxs("section", { className: css.telemetry, "aria-label": t('telemetry.aria'), children: [_jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.memories') }), _jsx("strong", { children: stats?.totalInsights ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.graph') }), _jsx("strong", { children: stats?.edgeCount ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.entities') }), _jsx("strong", { children: stats?.topEntities.length ?? '—' })] }), _jsxs("div", { className: css.telemetryMetric, children: [_jsx("span", { children: t('telemetry.spaces') }), _jsx("strong", { children: status === null || !catalogKnown ? '—' : activeBodies })] })] }), _jsxs("div", { className: appearanceClass(css.headerActions, appearance.classes.headerActions), children: [appearance.surface === 'buildin' && workspacePicker, _jsxs("div", { className: appearanceClass(css.statusCluster, appearance.classes.statusCluster), children: [_jsx("span", { className: `${css.statusDot} ${statusLoading && status === null ? css.checking : status?.healthy === true ? css.online : css.offline}` }), _jsx("span", { children: connectionLabel }), _jsx("button", { type: "button", className: css.iconButton, disabled: statusLoading, onClick: refreshAll, "aria-label": t('common.refresh'), children: "\u21BB" })] })] })] }), (statusError !== null || status?.healthy === false) && _jsxs("div", { className: css.alert, role: "alert", children: [_jsx("strong", { children: t('header.notReady') }), _jsx("span", { children: statusError ?? status?.error })] }), appearance.surface === 'buildin' && workspaceDiverged && _jsxs("div", { className: css.workspaceMismatch, role: "status", children: [_jsxs("div", { children: [_jsx("strong", { children: t('workspace.mismatchTitle') }), _jsx("span", { children: t('workspace.mismatchDescription') }), _jsxs("div", { children: [_jsx("code", { children: t('workspace.selectedRoot', { root: workspaceContext.selectedRoot }) }), _jsx("code", { children: t('workspace.effectiveRoot', { root: workspaceContext.effectiveRoot }) })] })] }), canAlignWorkspace && _jsx("button", { type: "button", className: css.secondaryButton, onClick: workspaceSelection.onAlign, children: t('workspace.align') })] }), _jsxs("div", { className: css.workspace, children: [_jsx(WorkspaceNavigation, { page: page, onSelect: selectPrimaryPage, activeBodies: activeBodies, bodyCount: memoryBodies.length, catalogKnown: catalogKnown, writeEnabled: writeEnabled }), _jsx(MemoryNavigation, { page: page, writeEnabled: writeEnabled, onSelect: selectPage, onRemember: () => openRemember() }), _jsxs("section", { className: appearanceClass(css.canvas, appearance.classes.canvas), ref: canvasRef, "data-testid": "mnemon-canvas", "data-lock-page-header": !isMemoryPage(page) ? '' : undefined, children: [page === 'overview' && _jsx(OverviewPage, { client: client, revision: revision, writeEnabled: writeEnabled, fallbackBodies: memoryBodies, fallbackDirectory: status?.memoryBodyDirectory, catalogKnown: catalogKnown, onMutate: mutate, onExplore: explore }), page === 'runtime' && _jsx(RuntimePage, { client: client, revision: revision, writeEnabled: writeEnabled, onMutate: mutate }), page === 'documents' && _jsx(DocumentsPage, { client: client, revision: revision, writeEnabled: writeEnabled, ...(sessionId === undefined ? {} : { sessionId }), onMutate: mutate }), page === 'explore' && _jsx(ExplorePage, { client: client, status: status, seed: searchSeed, writeEnabled: writeEnabled, onForget: forget }), page === 'entities' && _jsx(EntitiesPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onExplore: explore }), page === 'remember' && appearance.surface === 'buildin' && _jsx(RememberPage, { client: client, sessionId: sessionId, memoryBodies: memoryBodies, writeEnabled: writeEnabled, seed: rememberSeed, onMutate: mutate }), page === 'list' && _jsx(ListPage, { client: client, revision: revision, writeEnabled: writeEnabled, onForget: forget, onClone: clone, onExplore: explore }), page === 'status' && _jsx(StatusPage, { client: client, status: status, loading: statusLoading, onRefresh: () => void loadStatus() })] }, viewContextKey), appearance.surface === 'sidebar' && rememberOpen && _jsx(RememberPage, { client: client, sessionId: sessionId, memoryBodies: memoryBodies, writeEnabled: writeEnabled, seed: rememberSeed, onMutate: mutate, onClose: () => setRememberOpen(false), onComplete: () => setRememberOpen(false) })] })] }));
1257
1584
  }
1258
1585
  //# sourceMappingURL=MnemonView.js.map