dsh-memoir 0.4.3

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.
package/lib/client.js ADDED
@@ -0,0 +1,973 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-memoir",
3
+ factory: (require) => {
4
+ var module = { exports: {} }; var exports = module.exports;
5
+ "use strict";
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
+
24
+ // src/client/index.tsx
25
+ var index_exports = {};
26
+ __export(index_exports, {
27
+ apply: () => apply,
28
+ inject: () => inject
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/client/controller.ts
33
+ var PanelController = class {
34
+ state = { panelOpen: false };
35
+ listeners = /* @__PURE__ */ new Set();
36
+ /** The current immutable snapshot. */
37
+ getSnapshot() {
38
+ return this.state;
39
+ }
40
+ /** Subscribe to state changes; returns an unsubscribe function. */
41
+ subscribe(listener) {
42
+ this.listeners.add(listener);
43
+ return () => {
44
+ this.listeners.delete(listener);
45
+ };
46
+ }
47
+ open() {
48
+ this.patch({ panelOpen: true });
49
+ }
50
+ close() {
51
+ this.patch({ panelOpen: false });
52
+ }
53
+ toggle() {
54
+ this.patch({ panelOpen: !this.state.panelOpen });
55
+ }
56
+ /** Replace the snapshot; notifies only when the observable state changed. */
57
+ patch(next) {
58
+ const prev = this.state;
59
+ this.state = { ...prev, ...next };
60
+ if (this.state.panelOpen === prev.panelOpen) return;
61
+ for (const listener of [...this.listeners]) listener();
62
+ }
63
+ };
64
+
65
+ // src/client/api.ts
66
+ var MemoirApiError = class extends Error {
67
+ constructor(message) {
68
+ super(message);
69
+ this.name = "MemoirApiError";
70
+ }
71
+ };
72
+ async function readEnvelope(response) {
73
+ let body;
74
+ try {
75
+ body = await response.json();
76
+ } catch {
77
+ throw new MemoirApiError(`HTTP ${response.status}: invalid JSON response`);
78
+ }
79
+ if (typeof body !== "object" || body === null || body.ok !== true) {
80
+ const record = body;
81
+ const message = record && typeof record.error === "object" && record.error !== null && typeof record.error.message === "string" ? record.error.message : `HTTP ${response.status}`;
82
+ throw new MemoirApiError(message);
83
+ }
84
+ return body.value;
85
+ }
86
+ function query(params) {
87
+ const search = new URLSearchParams();
88
+ for (const [key, value] of Object.entries(params)) {
89
+ if (value !== void 0 && value !== "") search.set(key, String(value));
90
+ }
91
+ const text = search.toString();
92
+ return text === "" ? "" : "?" + text;
93
+ }
94
+ var MemoirApi = class {
95
+ fetchImpl;
96
+ /**
97
+ * @param fetchImpl - injectable fetch (defaults to globalThis.fetch).
98
+ */
99
+ constructor(fetchImpl) {
100
+ this.fetchImpl = fetchImpl ?? ((...args) => globalThis.fetch(...args));
101
+ }
102
+ /** Read one project's memory (empty project shape when unknown). */
103
+ async project(path, options = {}) {
104
+ const response = await this.fetchImpl("/api/dsh-memoir/project" + query({ path, ...options }));
105
+ return readEnvelope(response);
106
+ }
107
+ /** Read the cross-project global index. */
108
+ async global(options = {}) {
109
+ const response = await this.fetchImpl("/api/dsh-memoir/global" + query({ ...options }));
110
+ return readEnvelope(response);
111
+ }
112
+ /** Read runtime diagnostics (cache hit rates, snapshot/hot-memory stats). */
113
+ async diagnostics(path) {
114
+ const response = await this.fetchImpl("/api/dsh-memoir/diagnostics" + query({ path }));
115
+ return readEnvelope(response);
116
+ }
117
+ /**
118
+ * Ranked search over the host RetrievalEngine (v0.4.2) — the same ranking
119
+ * memoir_read uses, so the GUI search and the agent recall never diverge.
120
+ */
121
+ async search(options) {
122
+ const response = await this.fetchImpl("/api/dsh-memoir/search" + query({
123
+ scope: options.scope,
124
+ path: options.path,
125
+ section: options.section,
126
+ query: options.query,
127
+ limit: options.limit === void 0 ? void 0 : String(options.limit)
128
+ }));
129
+ return readEnvelope(response);
130
+ }
131
+ /** Hot-memory preview for one workspace (the inspector). */
132
+ async hotMemory(path) {
133
+ const response = await this.fetchImpl("/api/dsh-memoir/hot-memory" + query({ path }));
134
+ return readEnvelope(response);
135
+ }
136
+ /** Record one entry (host regenerates PROJECT_MEMORY.md). */
137
+ async record(payload) {
138
+ const response = await this.fetchImpl("/api/dsh-memoir/entries", {
139
+ method: "POST",
140
+ headers: { "content-type": "application/json" },
141
+ body: JSON.stringify(payload)
142
+ });
143
+ return readEnvelope(response);
144
+ }
145
+ /** Delete one entry by id. */
146
+ async remove(payload) {
147
+ const response = await this.fetchImpl("/api/dsh-memoir/entries", {
148
+ method: "DELETE",
149
+ headers: { "content-type": "application/json" },
150
+ body: JSON.stringify(payload)
151
+ });
152
+ return readEnvelope(response);
153
+ }
154
+ };
155
+
156
+ // src/client/cwd.ts
157
+ function readCwd(sessions) {
158
+ const snapshot = sessions.list.getSnapshot();
159
+ const id = snapshot.current;
160
+ const cwd = id === void 0 ? void 0 : snapshot.byId[id]?.cwd;
161
+ return typeof cwd === "string" && cwd !== "" ? cwd : "";
162
+ }
163
+ function createCwdTracker(sessions) {
164
+ let current = readCwd(sessions);
165
+ const listeners = /* @__PURE__ */ new Set();
166
+ const tracker = {
167
+ getSnapshot: () => current,
168
+ subscribe: (listener) => {
169
+ listeners.add(listener);
170
+ const dispose = sessions.list.subscribe(() => {
171
+ const next = readCwd(sessions);
172
+ if (next === current) return;
173
+ current = next;
174
+ for (const l of [...listeners]) l();
175
+ });
176
+ return () => {
177
+ listeners.delete(listener);
178
+ dispose();
179
+ };
180
+ }
181
+ };
182
+ return tracker;
183
+ }
184
+
185
+ // src/client/i18n.ts
186
+ var dictionaries = {
187
+ zh: {
188
+ "entry.label": "\u8BB0\u5FC6",
189
+ "entry.tooltip": "\u6253\u5F00\u8BB0\u5FC6\u9762\u677F\uFF08\u9879\u76EE\u8BB0\u5FC6 / \u5168\u5C40\u8BB0\u5FC6\uFF09",
190
+ "panel.title": "\u8BB0\u5FC6 Memoir",
191
+ "panel.close": "\u5173\u95ED",
192
+ "panel.refresh": "\u5237\u65B0",
193
+ "tab.project": "\u9879\u76EE\u8BB0\u5FC6",
194
+ "tab.global": "\u5168\u5C40\u8BB0\u5FC6",
195
+ "toolbar.search": "\u641C\u7D22\u6807\u9898\u4E0E\u6B63\u6587\u2026",
196
+ "toolbar.add": "\u6DFB\u52A0\u8BB0\u5FC6",
197
+ "toolbar.cancel": "\u53D6\u6D88",
198
+ "form.section": "\u5206\u7C7B",
199
+ "form.title": "\u6807\u9898\uFF08\u53EF\u9009\uFF09",
200
+ "form.content": "\u5185\u5BB9",
201
+ "form.submit": "\u8BB0\u5F55",
202
+ "form.placeholder.title": "\u4E00\u53E5\u8BDD\u6807\u9898\uFF0C\u5982\u300C\u4FEE\u590D pet \u60AC\u505C\u95EA\u9000\u300D",
203
+ "form.placeholder.content": "\u5177\u4F53\u505A\u4E86\u4EC0\u4E48\u3001\u7ED3\u8BBA\u3001\u6559\u8BAD\u6216\u4E0B\u4E00\u6B65\u600E\u4E48\u505A\u3002\u5EFA\u8BAE\u7CBE\u70BC\u3001\u53EF\u6267\u884C\u3002",
204
+ "empty.project": "\u672C\u9879\u76EE\u6682\u65E0\u6301\u4E45\u8BB0\u5FC6",
205
+ "empty.projectHint": "\u8BA9 agent \u7528 memoir_record \u6C89\u6DC0\u7ECF\u9A8C\uFF0C\u6216\u70B9\u300C\u6DFB\u52A0\u8BB0\u5FC6\u300D\u624B\u52A8\u8BB0\u5F55\u3002",
206
+ "empty.workspace": "\u672A\u6253\u5F00\u9879\u76EE\u4F1A\u8BDD",
207
+ "empty.workspaceHint": "\u6253\u5F00\u4E00\u4E2A\u9879\u76EE\u4F1A\u8BDD\u540E\uFF0C\u8FD9\u91CC\u663E\u793A\u8BE5\u9879\u76EE\u7684\u6301\u4E45\u8BB0\u5FC6\u3002",
208
+ "empty.global": "\u5168\u5C40\u7D22\u5F15\u4E3A\u7A7A",
209
+ "empty.globalHint": "\u5728\u4EFB\u4F55\u9879\u76EE\u91CC\u7528 memoir_record \u8BB0\u5F55\u540E\uFF0C\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u4F9B\u8DE8\u9879\u76EE\u68C0\u7D22\u3002",
210
+ "empty.search": "\u6CA1\u6709\u5339\u914D\u7684\u6761\u76EE",
211
+ "load.failed": "\u52A0\u8F7D\u5931\u8D25",
212
+ "record.failed": "\u8BB0\u5F55\u5931\u8D25",
213
+ "delete.failed": "\u5220\u9664\u5931\u8D25",
214
+ "delete.confirm": "\u5220\u9664\u8FD9\u6761\u8BB0\u5FC6\uFF1F",
215
+ "updated": "\u66F4\u65B0\u4E8E",
216
+ "entries": "\u6761",
217
+ "session": "\u4F1A\u8BDD",
218
+ "sections.work": "\u5DE5\u4F5C\u8BB0\u5F55",
219
+ "sections.lessons": "\u7ECF\u9A8C\u6559\u8BAD",
220
+ "sections.actions": "\u884C\u52A8\u6307\u5357",
221
+ "sections.note": "\u5907\u6CE8",
222
+ "diag.title": "Memory Diagnostics",
223
+ "diag.revision": "Store revision",
224
+ "diag.snapshot": "Session snapshots",
225
+ "diag.cache": "Store cache",
226
+ "diag.render": "Render cache",
227
+ "diag.hot": "Hot memory",
228
+ "diag.retrieval": "Retrieval \u7D22\u5F15",
229
+ "diag.qcache": "Query cache",
230
+ "diag.lastQuery": "\u6700\u8FD1\u67E5\u8BE2",
231
+ "diag.snapshotInfo": "\u4F1A\u8BDD\u5FEB\u7167",
232
+ "inspector.title": "Hot Memory \u9884\u89C8",
233
+ "inspector.empty": "\u672C\u5DE5\u4F5C\u533A\u6682\u65E0 Hot Memory",
234
+ "search.ranked": "\u6309\u76F8\u5173\u6027\u6392\u5E8F"
235
+ },
236
+ en: {
237
+ "entry.label": "Memory",
238
+ "entry.tooltip": "Open the memory panel (project / global)",
239
+ "panel.title": "Memory Memoir",
240
+ "panel.close": "Close",
241
+ "panel.refresh": "Refresh",
242
+ "tab.project": "Project",
243
+ "tab.global": "Global",
244
+ "toolbar.search": "Search titles and content\u2026",
245
+ "toolbar.add": "Add entry",
246
+ "toolbar.cancel": "Cancel",
247
+ "form.section": "Section",
248
+ "form.title": "Title (optional)",
249
+ "form.content": "Content",
250
+ "form.submit": "Record",
251
+ "form.placeholder.title": 'A one-line title, e.g. "Fix pet hover crash"',
252
+ "form.placeholder.content": "What was done, the conclusion, the lesson, or the next step. Concise and actionable.",
253
+ "empty.project": "No persistent memory in this project yet",
254
+ "empty.projectHint": "Ask the agent to distill via memoir_record, or add an entry manually.",
255
+ "empty.workspace": "No project session open",
256
+ "empty.workspaceHint": "Open a project session to see its persistent memory here.",
257
+ "empty.global": "The global index is empty",
258
+ "empty.globalHint": "Entries recorded via memoir_record in any project appear here for cross-project search.",
259
+ "empty.search": "No matching entries",
260
+ "load.failed": "Failed to load",
261
+ "record.failed": "Failed to record",
262
+ "delete.failed": "Failed to delete",
263
+ "delete.confirm": "Delete this entry?",
264
+ "updated": "Updated",
265
+ "entries": "entries",
266
+ "session": "session",
267
+ "sections.work": "Work Log",
268
+ "sections.lessons": "Lessons Learned",
269
+ "sections.actions": "Action Guide",
270
+ "sections.note": "Notes",
271
+ "diag.title": "Memory Diagnostics",
272
+ "diag.revision": "Store revision",
273
+ "diag.snapshot": "Session snapshots",
274
+ "diag.cache": "Store cache",
275
+ "diag.render": "Render cache",
276
+ "diag.hot": "Hot memory",
277
+ "diag.retrieval": "Retrieval index",
278
+ "diag.qcache": "Query cache",
279
+ "diag.lastQuery": "Last query",
280
+ "diag.snapshotInfo": "Session snapshot",
281
+ "inspector.title": "Hot Memory Inspector",
282
+ "inspector.empty": "No hot memory in this workspace yet",
283
+ "search.ranked": "Ranked by relevance"
284
+ }
285
+ };
286
+ var SECTION_KEYS = ["work", "lessons", "actions", "note"];
287
+ function detectLanguage(documentLike) {
288
+ const lang = documentLike?.documentElement?.lang;
289
+ return typeof lang === "string" && lang.startsWith("zh") ? "zh" : "en";
290
+ }
291
+ function translate(lang, key) {
292
+ const dict = dictionaries[lang] ?? dictionaries.en;
293
+ return Object.prototype.hasOwnProperty.call(dict, key) ? dict[key] : dictionaries.en[key] ?? key;
294
+ }
295
+ function makeT(documentLike) {
296
+ return (key) => translate(detectLanguage(documentLike), key);
297
+ }
298
+
299
+ // src/client/styles.ts
300
+ var PANEL_CSS = `
301
+ [data-dsh-memoir-view] {
302
+ display: none;
303
+ position: absolute;
304
+ inset: 0;
305
+ z-index: 20;
306
+ background: var(--bg-panel, #ffffff);
307
+ color: var(--text-primary, #1f2328);
308
+ font-size: 13px;
309
+ }
310
+ html[data-dsh-memoir-active] [data-dsh-memoir-view] {
311
+ display: flex;
312
+ flex-direction: column;
313
+ }
314
+ html[data-dsh-memoir-active] [data-pane="conversation"] > *:not([data-dsh-memoir-view]) {
315
+ display: none;
316
+ }
317
+
318
+ .memoir-panel { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
319
+ .memoir-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px 8px; }
320
+ .memoir-title { font-size: 15px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
321
+ .memoir-subtitle { font-size: 11px; opacity: .65; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
322
+ .memoir-iconbtn {
323
+ border: 1px solid transparent; background: transparent; color: inherit;
324
+ border-radius: 6px; padding: 4px 8px; cursor: pointer; font-size: 12px;
325
+ display: inline-flex; align-items: center; gap: 4px;
326
+ }
327
+ .memoir-iconbtn:hover { background: var(--bg-hover, rgba(0,0,0,.06)); }
328
+
329
+ .memoir-tabs { display: flex; gap: 4px; padding: 0 14px; border-bottom: 1px solid var(--border, rgba(0,0,0,.1)); }
330
+ .memoir-tab {
331
+ border: none; background: transparent; color: inherit; padding: 7px 12px;
332
+ cursor: pointer; font-size: 13px; border-bottom: 2px solid transparent; opacity: .75;
333
+ }
334
+ .memoir-tab:hover { opacity: 1; }
335
+ .memoir-tab[data-active="true"] { opacity: 1; border-bottom-color: var(--accent, #3b82f6); font-weight: 600; }
336
+
337
+ .memoir-toolbar { display: flex; gap: 8px; padding: 8px 14px; }
338
+ .memoir-search {
339
+ flex: 1; border: 1px solid var(--border, rgba(0,0,0,.15)); background: transparent;
340
+ color: inherit; border-radius: 6px; padding: 6px 10px; font-size: 13px; outline: none;
341
+ }
342
+ .memoir-search:focus { border-color: var(--accent, #3b82f6); }
343
+ .memoir-primary {
344
+ border: 1px solid transparent; background: var(--accent, #3b82f6); color: #fff;
345
+ border-radius: 6px; padding: 6px 12px; cursor: pointer; font-size: 13px;
346
+ }
347
+ .memoir-primary:hover { filter: brightness(1.05); }
348
+
349
+ .memoir-body { flex: 1; overflow-y: auto; padding: 4px 14px 16px; }
350
+ .memoir-section-title {
351
+ font-size: 12px; font-weight: 600; opacity: .8; margin: 14px 0 6px;
352
+ display: flex; align-items: center; gap: 6px;
353
+ }
354
+ .memoir-count { font-weight: 400; opacity: .6; }
355
+ .memoir-entry {
356
+ border: 1px solid var(--border, rgba(0,0,0,.08)); border-radius: 8px;
357
+ padding: 8px 10px; margin-bottom: 8px; background: var(--bg-card, rgba(0,0,0,.02));
358
+ }
359
+ .memoir-entry-meta { display: flex; align-items: center; gap: 6px; font-size: 11px; opacity: .7; margin-bottom: 3px; }
360
+ .memoir-chip {
361
+ border-radius: 999px; padding: 0 7px; font-size: 11px; line-height: 18px;
362
+ background: var(--chip-bg, rgba(59,130,246,.12)); color: var(--chip-fg, #2563eb);
363
+ }
364
+ .memoir-entry-title { font-weight: 600; margin-bottom: 2px; }
365
+ .memoir-entry-content { white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
366
+ .memoir-delete {
367
+ float: right; border: none; background: transparent; color: inherit; opacity: .45;
368
+ cursor: pointer; border-radius: 4px; padding: 2px 6px; font-size: 12px;
369
+ }
370
+ .memoir-delete:hover { opacity: 1; background: var(--danger-bg, rgba(220,38,38,.12)); color: var(--danger, #dc2626); }
371
+
372
+ .memoir-project-card { border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 10px; padding: 10px 12px; margin-bottom: 12px; }
373
+ .memoir-project-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
374
+ .memoir-project-title { font-weight: 600; font-size: 14px; }
375
+ .memoir-project-path { font-size: 11px; opacity: .6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
376
+ .memoir-project-meta { font-size: 11px; opacity: .6; margin-bottom: 6px; }
377
+
378
+ .memoir-empty { text-align: center; padding: 48px 20px; opacity: .7; }
379
+ .memoir-empty-title { font-size: 14px; font-weight: 600; margin-bottom: 6px; }
380
+ .memoir-empty-hint { font-size: 12px; }
381
+ .memoir-error { color: var(--danger, #dc2626); padding: 8px 14px; font-size: 12px; }
382
+
383
+ .memoir-form { border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 10px; padding: 12px; margin: 4px 14px 14px; }
384
+ .memoir-form-row { display: flex; gap: 8px; margin-bottom: 8px; }
385
+ .memoir-field { flex: 1; display: flex; flex-direction: column; gap: 4px; }
386
+ .memoir-field label { font-size: 11px; opacity: .7; }
387
+ .memoir-field input, .memoir-field select, .memoir-field textarea {
388
+ border: 1px solid var(--border, rgba(0,0,0,.15)); background: transparent; color: inherit;
389
+ border-radius: 6px; padding: 6px 8px; font-size: 13px; font-family: inherit; outline: none;
390
+ }
391
+ .memoir-field textarea { min-height: 72px; resize: vertical; }
392
+ .memoir-form-actions { display: flex; justify-content: flex-end; gap: 8px; }
393
+
394
+ /* Sidebar entry row (plain DOM, matches the shell's nav rows). */
395
+ .memoir-entry-row {
396
+ display: flex; align-items: center; gap: 8px; width: 100%;
397
+ border: none; background: transparent; color: inherit; cursor: pointer;
398
+ padding: 7px 10px; border-radius: 6px; font-size: 13px; text-align: left;
399
+ }
400
+ .memoir-entry-row:hover { background: var(--bg-hover, rgba(0,0,0,.06)); }
401
+ .memoir-entry-row[data-active="true"] { background: var(--bg-hover, rgba(0,0,0,.08)); }
402
+ .memoir-entry-icon { display: inline-flex; }
403
+
404
+ /* v0.4.2 ranked search */
405
+ .memoir-ranked-note { font-size: 11px; opacity: .65; margin: 4px 0 8px; }
406
+ .memoir-score {
407
+ float: right; border-radius: 999px; padding: 0 7px; font-size: 10px; line-height: 16px;
408
+ background: var(--chip-bg, rgba(59,130,246,.12)); color: var(--chip-fg, #2563eb); opacity: .8;
409
+ }
410
+
411
+ /* v0.4.2 Hot Memory Inspector */
412
+ .memoir-inspector { margin: 0 14px; border-top: 1px solid var(--border, rgba(0,0,0,.1)); padding-top: 8px; }
413
+ .memoir-inspector-body {
414
+ font-size: 11px; opacity: .85; margin: 6px 0 0; padding: 8px 10px;
415
+ border: 1px solid var(--border, rgba(0,0,0,.1)); border-radius: 8px;
416
+ background: var(--bg-card, rgba(0,0,0,.02)); white-space: pre-wrap; word-break: break-word;
417
+ font-family: inherit; max-height: 220px; overflow-y: auto;
418
+ }
419
+
420
+ body[data-ds-dark-theme] [data-dsh-memoir-view] {
421
+ background: var(--bg-panel, #16181d);
422
+ color: var(--text-primary, #e6e6e6);
423
+ }
424
+
425
+ /* Memory Diagnostics (observability strip, v0.4). */
426
+ .memoir-diagnostics { margin: 0 14px 14px; border-top: 1px solid var(--border, rgba(0,0,0,.1)); padding-top: 8px; }
427
+ .memoir-diagnostics-toggle {
428
+ border: none; background: transparent; color: inherit; cursor: pointer;
429
+ font-size: 12px; opacity: .75; padding: 2px 0;
430
+ }
431
+ .memoir-diagnostics-toggle:hover { opacity: 1; }
432
+ .memoir-diagnostics-body { font-size: 11px; opacity: .8; display: flex; flex-direction: column; gap: 3px; margin-top: 6px; }
433
+ `;
434
+
435
+ // src/client/sidebar-entry.ts
436
+ var ICON = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3.5c-1.6-1-3.6-1-5.2-.2-.5.3-.8.9-.8 1.4v8.6c0 .5.5.9 1 .7C4.6 13.4 6.4 13.5 8 14.5c1.6-1 3.4-1.1 5-.5.5.2 1-.2 1-.7V4.7c0-.5-.3-1.1-.8-1.4-1.6-.8-3.6-.8-5.2.2z"/><path d="M8 3.5v11"/></svg>';
437
+ var FAMILY_SELECTOR = "[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-memoir-entry]";
438
+ function sidebarRoot() {
439
+ const column = document.querySelector('[data-pane="sidebar"], [class*="sidebarCol"]');
440
+ if (column === null) return void 0;
441
+ const logoOwner = column.querySelector('[class*="logoRow"]')?.parentElement;
442
+ return logoOwner ?? column.firstElementChild;
443
+ }
444
+ function newSessionButton(root) {
445
+ const nested = root.querySelector('button[class*="newSession"]');
446
+ if (nested !== null) return nested;
447
+ for (const child of root.children) {
448
+ if (child.tagName === "BUTTON") return child;
449
+ }
450
+ return void 0;
451
+ }
452
+ function createEntry(controller, t) {
453
+ const entry = document.createElement("button");
454
+ entry.type = "button";
455
+ entry.dataset.dshMemoirEntry = "";
456
+ entry.className = "memoir-entry-row";
457
+ entry.setAttribute("aria-label", t("entry.label"));
458
+ entry.setAttribute("title", t("entry.tooltip"));
459
+ entry.innerHTML = '<span class="memoir-entry-icon">' + ICON + "</span><span>" + t("entry.label") + "</span>";
460
+ entry.addEventListener("click", () => {
461
+ controller.toggle();
462
+ });
463
+ return entry;
464
+ }
465
+ function placeEntry(root, entry) {
466
+ const button = newSessionButton(root);
467
+ if (button === void 0) return false;
468
+ if (entry.parentElement !== root) {
469
+ const row = button.closest('[class*="logoRow"]');
470
+ const base = row !== null && row.parentElement === root ? row : button;
471
+ const family = Array.from(root.children).filter(
472
+ (el) => el instanceof HTMLElement && el.matches(FAMILY_SELECTOR)
473
+ );
474
+ const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
475
+ root.insertBefore(entry, anchor);
476
+ }
477
+ return true;
478
+ }
479
+ function mountSidebarEntry(controller, t) {
480
+ const entry = createEntry(controller, t);
481
+ let root;
482
+ let placed = false;
483
+ const tryPlace = () => {
484
+ if (placed) return;
485
+ if (root !== void 0 && !root.isConnected) {
486
+ rootObserver.disconnect();
487
+ root = void 0;
488
+ }
489
+ root ??= sidebarRoot();
490
+ if (root === void 0) return;
491
+ placed = placeEntry(root, entry);
492
+ if (placed) {
493
+ rootObserver.observe(root, { childList: true, subtree: true });
494
+ waitObserver.disconnect();
495
+ }
496
+ };
497
+ const waitObserver = new MutationObserver(() => {
498
+ tryPlace();
499
+ });
500
+ waitObserver.observe(document.body, { childList: true, subtree: true });
501
+ const rootObserver = new MutationObserver(() => {
502
+ if (root === void 0 || !root.isConnected) {
503
+ placed = false;
504
+ tryPlace();
505
+ return;
506
+ }
507
+ if (!root.contains(entry)) {
508
+ placed = placeEntry(root, entry);
509
+ }
510
+ });
511
+ const unsubscribe = controller.subscribe(() => {
512
+ entry.dataset.active = controller.getSnapshot().panelOpen ? "true" : void 0;
513
+ });
514
+ entry.dataset.active = controller.getSnapshot().panelOpen ? "true" : void 0;
515
+ tryPlace();
516
+ return () => {
517
+ waitObserver.disconnect();
518
+ rootObserver.disconnect();
519
+ unsubscribe();
520
+ entry.remove();
521
+ };
522
+ }
523
+
524
+ // src/client/mount.tsx
525
+ var import_client = require("react-dom/client");
526
+
527
+ // src/client/panel.tsx
528
+ var import_react = require("react");
529
+ var import_jsx_runtime = require("react/jsx-runtime");
530
+ function EntryMeta({ entry, t }) {
531
+ const when = new Date(entry.time);
532
+ const pad = (n) => String(n).padStart(2, "0");
533
+ const timeText = `${when.getFullYear()}-${pad(when.getMonth() + 1)}-${pad(when.getDate())} ${pad(when.getHours())}:${pad(when.getMinutes())}`;
534
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-entry-meta", children: [
535
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: timeText }),
536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "memoir-chip", children: t("sections." + entry.section) }),
537
+ entry.sessionId !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { title: `${t("session")}: ${entry.sessionId}`, children: entry.sessionId.slice(0, 12) }) : null
538
+ ] });
539
+ }
540
+ function EntryCard({ entry, t, onDelete, score }) {
541
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-entry", children: [
542
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-delete", title: t("delete.confirm"), onClick: () => onDelete(entry), children: "\xD7" }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EntryMeta, { entry, t }),
544
+ score !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "memoir-score", title: t("search.ranked"), children: score.toFixed(3) }) : null,
545
+ entry.title !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-entry-title", children: entry.title }) : null,
546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-entry-content", children: entry.content })
547
+ ] });
548
+ }
549
+ function SectionedEntries({ entries, t, onDelete }) {
550
+ const groups = (0, import_react.useMemo)(
551
+ () => SECTION_KEYS.map((key) => ({ key, entries: entries.filter((e) => e.section === key) })).filter((g) => g.entries.length > 0),
552
+ [entries]
553
+ );
554
+ if (groups.length === 0) {
555
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-title", children: t("empty.search") }) });
556
+ }
557
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Fragment, { children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [
558
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-section-title", children: [
559
+ t("sections." + group.key),
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "memoir-count", children: [
561
+ "(",
562
+ group.entries.length,
563
+ ")"
564
+ ] })
565
+ ] }),
566
+ group.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EntryCard, { entry, t, onDelete }, entry.id))
567
+ ] }, group.key)) });
568
+ }
569
+ function RankedResults({ results, pending, grouped, t, onDelete }) {
570
+ if (pending) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty", children: "\u2026" });
571
+ if (results.length === 0) {
572
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-empty", children: [
573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-title", children: t("empty.search") }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-hint", children: t("search.ranked") })
575
+ ] });
576
+ }
577
+ if (!grouped) {
578
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [
579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-ranked-note", children: t("search.ranked") }),
580
+ results.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EntryCard, { entry: r.entry, score: r.score, t, onDelete: (entry) => onDelete(entry, r.projectPath) }, r.entry.id))
581
+ ] });
582
+ }
583
+ const groups = /* @__PURE__ */ new Map();
584
+ for (const result of results) {
585
+ const bucket = groups.get(result.projectPath) ?? [];
586
+ bucket.push(result);
587
+ groups.set(result.projectPath, bucket);
588
+ }
589
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Fragment, { children: [...groups.entries()].map(([path, bucket]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-project-card", children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-project-head", children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "memoir-project-title", children: path.split("/").filter(Boolean).pop() || path }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "memoir-project-path", children: path })
593
+ ] }),
594
+ bucket.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EntryCard, { entry: r.entry, score: r.score, t, onDelete: (entry) => onDelete(entry, path) }, r.entry.id))
595
+ ] }, path)) });
596
+ }
597
+ function AddForm({ t, onSubmit, onCancel }) {
598
+ const [section, setSection] = (0, import_react.useState)("lessons");
599
+ const [title, setTitle] = (0, import_react.useState)("");
600
+ const [content, setContent] = (0, import_react.useState)("");
601
+ const submit = () => {
602
+ if (content.trim() === "") return;
603
+ onSubmit({ section, title: title.trim() === "" ? void 0 : title.trim(), content: content.trim() });
604
+ };
605
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-form", children: [
606
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-form-row", children: [
607
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-field", children: [
608
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: t("form.section") }),
609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { value: section, onChange: (e) => setSection(e.target.value), children: SECTION_KEYS.map((key) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: key, children: t("sections." + key) }, key)) })
610
+ ] }),
611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-field", children: [
612
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: t("form.title") }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { value: title, placeholder: t("form.placeholder.title"), onChange: (e) => setTitle(e.target.value) })
614
+ ] })
615
+ ] }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-field", children: [
617
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { children: t("form.content") }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { value: content, placeholder: t("form.placeholder.content"), onChange: (e) => setContent(e.target.value) })
619
+ ] }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-form-actions", children: [
621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-iconbtn", onClick: onCancel, children: t("toolbar.cancel") }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-primary", onClick: submit, children: t("form.submit") })
623
+ ] })
624
+ ] });
625
+ }
626
+ function MemoirPanel({ controller, api, cwdTracker, t }) {
627
+ const cwd = (0, import_react.useSyncExternalStore)(cwdTracker.subscribe, cwdTracker.getSnapshot);
628
+ const [tab, setTab] = (0, import_react.useState)("project");
629
+ const [query2, setQuery] = (0, import_react.useState)("");
630
+ const [formOpen, setFormOpen] = (0, import_react.useState)(false);
631
+ const [refreshKey, setRefreshKey] = (0, import_react.useState)(0);
632
+ const [project, setProject] = (0, import_react.useState)(null);
633
+ const [projects, setProjects] = (0, import_react.useState)([]);
634
+ const [loading, setLoading] = (0, import_react.useState)(false);
635
+ const [error, setError] = (0, import_react.useState)(null);
636
+ const [busy, setBusy] = (0, import_react.useState)(false);
637
+ const [diag, setDiag] = (0, import_react.useState)(null);
638
+ const [diagOpen, setDiagOpen] = (0, import_react.useState)(false);
639
+ const [searchResults, setSearchResults] = (0, import_react.useState)(null);
640
+ const [hotMemory, setHotMemory] = (0, import_react.useState)(null);
641
+ const [inspectorOpen, setInspectorOpen] = (0, import_react.useState)(false);
642
+ const q = query2.trim().toLowerCase();
643
+ (0, import_react.useEffect)(() => {
644
+ let cancelled = false;
645
+ setError(null);
646
+ if (tab === "project") {
647
+ if (cwd === "") {
648
+ setProject(null);
649
+ return void 0;
650
+ }
651
+ setLoading(true);
652
+ api.project(cwd).then((value) => {
653
+ if (!cancelled) setProject(value.project);
654
+ }).catch((e) => {
655
+ if (!cancelled) setError(e.message);
656
+ }).finally(() => {
657
+ if (!cancelled) setLoading(false);
658
+ });
659
+ } else {
660
+ setLoading(true);
661
+ api.global().then((value) => {
662
+ if (!cancelled) setProjects(value.projects);
663
+ }).catch((e) => {
664
+ if (!cancelled) setError(e.message);
665
+ }).finally(() => {
666
+ if (!cancelled) setLoading(false);
667
+ });
668
+ }
669
+ api.diagnostics(cwd === "" ? void 0 : cwd).then((value) => {
670
+ if (!cancelled) setDiag(value);
671
+ }).catch(() => {
672
+ if (!cancelled) setDiag(null);
673
+ });
674
+ if (cwd === "") {
675
+ setHotMemory(null);
676
+ } else {
677
+ api.hotMemory(cwd).then((value) => {
678
+ if (!cancelled) setHotMemory(value.hotMemory);
679
+ }).catch(() => {
680
+ if (!cancelled) setHotMemory(null);
681
+ });
682
+ }
683
+ return () => {
684
+ cancelled = true;
685
+ };
686
+ }, [tab, cwd, refreshKey]);
687
+ (0, import_react.useEffect)(() => {
688
+ if (q === "") {
689
+ setSearchResults(null);
690
+ return void 0;
691
+ }
692
+ let cancelled = false;
693
+ const timer = setTimeout(() => {
694
+ const scope = tab === "project" ? "project" : "global";
695
+ api.search({ scope, path: tab === "project" && cwd !== "" ? cwd : void 0, query: q }).then((value) => {
696
+ if (!cancelled) setSearchResults(value.results);
697
+ }).catch((e) => {
698
+ if (!cancelled) {
699
+ setSearchResults([]);
700
+ setError(e.message);
701
+ }
702
+ });
703
+ }, 200);
704
+ return () => {
705
+ cancelled = true;
706
+ clearTimeout(timer);
707
+ };
708
+ }, [q, tab, cwd, refreshKey]);
709
+ const filterEntries = (entries) => q === "" ? entries : entries.filter((e) => `${e.title ?? ""} ${e.content}`.toLowerCase().includes(q));
710
+ const reload = () => setRefreshKey((k) => k + 1);
711
+ const onDelete = (entry, entryPath) => {
712
+ if (!window.confirm(t("delete.confirm"))) return;
713
+ setBusy(true);
714
+ api.remove({ path: entryPath, id: entry.id }).then(() => reload()).catch((e) => setError(`${t("delete.failed")}: ${e.message}`)).finally(() => setBusy(false));
715
+ };
716
+ const onRecord = (payload) => {
717
+ setBusy(true);
718
+ api.record({ path: cwd, ...payload }).then(() => {
719
+ setFormOpen(false);
720
+ reload();
721
+ }).catch((e) => setError(`${t("record.failed")}: ${e.message}`)).finally(() => setBusy(false));
722
+ };
723
+ const projectEntries = project === null ? [] : filterEntries(project.entries);
724
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-panel", children: [
725
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-header", children: [
726
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-title", children: [
727
+ t("panel.title"),
728
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-subtitle", children: tab === "project" ? cwd === "" ? t("empty.workspace") : cwd : t("tab.global") })
729
+ ] }),
730
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-iconbtn", title: t("panel.refresh"), onClick: reload, children: "\u27F3" }),
731
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-iconbtn", title: t("panel.close"), onClick: () => controller.close(), children: "\xD7" })
732
+ ] }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-tabs", children: [
734
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-tab", "data-active": tab === "project" ? "true" : void 0, onClick: () => setTab("project"), children: t("tab.project") }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-tab", "data-active": tab === "global" ? "true" : void 0, onClick: () => setTab("global"), children: t("tab.global") })
736
+ ] }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-toolbar", children: [
738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "memoir-search", placeholder: t("toolbar.search"), value: query2, onChange: (e) => setQuery(e.target.value) }),
739
+ tab === "project" && cwd !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "memoir-primary", onClick: () => setFormOpen((v) => !v), children: formOpen ? t("toolbar.cancel") : t("toolbar.add") }) : null
740
+ ] }),
741
+ formOpen && tab === "project" && cwd !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AddForm, { t, onSubmit: onRecord, onCancel: () => setFormOpen(false) }) : null,
742
+ error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-error", children: error }) : null,
743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-body", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty", children: "\u2026" }) : q !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
744
+ RankedResults,
745
+ {
746
+ results: searchResults ?? [],
747
+ pending: searchResults === null,
748
+ grouped: tab === "global",
749
+ t,
750
+ onDelete: (entry, path) => onDelete(entry, path)
751
+ }
752
+ ) : tab === "project" ? cwd === "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-empty", children: [
753
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-title", children: t("empty.workspace") }),
754
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-hint", children: t("empty.workspaceHint") })
755
+ ] }) : projectEntries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-empty", children: [
756
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-title", children: t("empty.project") }),
757
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-hint", children: t("empty.projectHint") })
758
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionedEntries, { entries: projectEntries, t, onDelete: (entry) => onDelete(entry, project?.path ?? cwd) }) : projects.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-empty", children: [
759
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-title", children: t("empty.global") }),
760
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty-hint", children: t("empty.globalHint") })
761
+ ] }) : projects.map((p) => {
762
+ const entries = filterEntries(p.entries);
763
+ if (entries.length === 0) return null;
764
+ const when = new Date(p.updatedAt);
765
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-project-card", children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-project-head", children: [
767
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "memoir-project-title", children: p.title }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "memoir-project-path", children: p.path })
769
+ ] }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-project-meta", children: `${t("updated")} ${when.toISOString().slice(0, 16).replace("T", " ")} \xB7 ${entries.length} ${t("entries")}` }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionedEntries, { entries, t, onDelete: (entry) => onDelete(entry, p.path) })
772
+ ] }, p.key);
773
+ }) }),
774
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-empty", children: "\u2026" }) : null,
775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-inspector", children: [
776
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "memoir-diagnostics-toggle", onClick: () => setInspectorOpen((v) => !v), children: [
777
+ t("inspector.title"),
778
+ " ",
779
+ inspectorOpen ? "\u25BE" : "\u25B8"
780
+ ] }),
781
+ inspectorOpen ? hotMemory === null || hotMemory.text === "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "memoir-inspector-body", children: t("inspector.empty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "memoir-inspector-body", children: hotMemory.text }) : null
782
+ ] }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-diagnostics", children: [
784
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "memoir-diagnostics-toggle", onClick: () => setDiagOpen((v) => !v), children: [
785
+ t("diag.title"),
786
+ " ",
787
+ diagOpen ? "\u25BE" : "\u25B8"
788
+ ] }),
789
+ diagOpen && diag !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "memoir-diagnostics-body", children: [
790
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
791
+ t("diag.revision"),
792
+ ": ",
793
+ diag.storeRevision,
794
+ " \xB7 ",
795
+ t("diag.snapshot"),
796
+ ": ",
797
+ diag.snapshotCount,
798
+ "/",
799
+ diag.snapshotMax
800
+ ] }),
801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
802
+ t("diag.cache"),
803
+ ": ",
804
+ diag.cache.hits,
805
+ "/",
806
+ diag.cache.loads,
807
+ " \u547D\u4E2D (",
808
+ Math.round(diag.cache.hitRate * 100),
809
+ "%) \xB7 ",
810
+ t("diag.render"),
811
+ ": ",
812
+ Math.round(diag.cache.renderHitRate * 100),
813
+ "%"
814
+ ] }),
815
+ diag.hotMemory !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
816
+ t("diag.hot"),
817
+ ": ",
818
+ diag.hotMemory.selected,
819
+ "/",
820
+ diag.hotMemory.total,
821
+ " \u6761 \xB7 ~",
822
+ diag.hotMemory.estimatedTokens,
823
+ " tokens\uFF08\u9884\u7B97 ",
824
+ diag.config.hotMemoryTokens,
825
+ "/",
826
+ diag.config.hotMemoryMaxTokens,
827
+ "\uFF09"
828
+ ] }) : null,
829
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
830
+ t("diag.retrieval"),
831
+ ": ",
832
+ diag.retrieval.index === null ? "\u2014" : `${diag.retrieval.index.docs} docs \xB7 ${diag.retrieval.index.terms} terms \xB7 epoch ${diag.retrieval.index.epoch}`
833
+ ] }),
834
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
835
+ t("diag.qcache"),
836
+ ": ",
837
+ diag.retrieval.cache.hits,
838
+ " hits / ",
839
+ diag.retrieval.cache.misses,
840
+ " misses (",
841
+ Math.round(diag.retrieval.cache.hitRate * 100),
842
+ "%) \xB7 ",
843
+ diag.retrieval.cache.size,
844
+ "/",
845
+ diag.retrieval.cache.capacity,
846
+ " \xB7 ",
847
+ diag.retrieval.cache.evictions,
848
+ " evicted"
849
+ ] }),
850
+ diag.retrieval.lastQuery !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
851
+ t("diag.lastQuery"),
852
+ ": ",
853
+ diag.retrieval.lastQuery.latencyMs.toFixed(1),
854
+ " ms \xB7 ",
855
+ diag.retrieval.lastQuery.returned,
856
+ "/",
857
+ diag.retrieval.lastQuery.candidates,
858
+ " returned"
859
+ ] }) : null,
860
+ diag.snapshot !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
861
+ t("diag.snapshotInfo"),
862
+ ": ",
863
+ diag.snapshot.hash,
864
+ " \xB7 rev ",
865
+ diag.snapshot.storeRevision
866
+ ] }) : null
867
+ ] }) : null
868
+ ] })
869
+ ] });
870
+ }
871
+
872
+ // src/client/mount.tsx
873
+ var import_jsx_runtime2 = require("react/jsx-runtime");
874
+ var CONVERSATION_COLUMN_SELECTOR = '[data-pane="conversation"]';
875
+ var ACTIVE_ATTR = "data-dsh-memoir-active";
876
+ var SIBLING_ATTRS = ["data-dsh-ssh-active", "data-dsh-taskboard-active"];
877
+ var ACTIVATE_EVENT = "dsh-panel-activate";
878
+ var PANEL_NAME = "memoir";
879
+ function conversationColumn() {
880
+ return document.querySelector(CONVERSATION_COLUMN_SELECTOR) ?? void 0;
881
+ }
882
+ function mountPanel(controller, api, cwdTracker, t) {
883
+ let root;
884
+ let container;
885
+ const ensure = () => {
886
+ if (container !== void 0) {
887
+ if (container.isConnected) return;
888
+ root?.unmount();
889
+ root = void 0;
890
+ container.remove();
891
+ container = void 0;
892
+ }
893
+ const column = conversationColumn();
894
+ if (column === void 0) return;
895
+ container = document.createElement("div");
896
+ container.dataset.dshMemoirView = "";
897
+ column.appendChild(container);
898
+ root = (0, import_client.createRoot)(container);
899
+ root.render(/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MemoirPanel, { controller, api, cwdTracker, t }));
900
+ };
901
+ const waitObserver = new MutationObserver(() => {
902
+ ensure();
903
+ });
904
+ waitObserver.observe(document.body, { childList: true, subtree: true });
905
+ const applyActive = () => {
906
+ if (controller.getSnapshot().panelOpen) {
907
+ for (const attr of SIBLING_ATTRS) document.documentElement.removeAttribute(attr);
908
+ document.documentElement.setAttribute(ACTIVE_ATTR, "");
909
+ document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }));
910
+ } else {
911
+ document.documentElement.removeAttribute(ACTIVE_ATTR);
912
+ }
913
+ };
914
+ const onOtherActivate = (event) => {
915
+ const detail = event.detail;
916
+ if ((detail === "ssh" || detail === "taskboard") && controller.getSnapshot().panelOpen) {
917
+ controller.close();
918
+ }
919
+ };
920
+ const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]';
921
+ const onClickSidebarRow = (event) => {
922
+ if (!controller.getSnapshot().panelOpen) return;
923
+ const target = event.target;
924
+ if (target === null || typeof target.closest !== "function") return;
925
+ if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close();
926
+ };
927
+ document.addEventListener("click", onClickSidebarRow, true);
928
+ document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
929
+ const unsubscribe = controller.subscribe(applyActive);
930
+ applyActive();
931
+ ensure();
932
+ return () => {
933
+ document.removeEventListener("click", onClickSidebarRow, true);
934
+ document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
935
+ waitObserver.disconnect();
936
+ unsubscribe();
937
+ document.documentElement.removeAttribute(ACTIVE_ATTR);
938
+ root?.unmount();
939
+ root = void 0;
940
+ container?.remove();
941
+ container = void 0;
942
+ };
943
+ }
944
+
945
+ // src/client/index.tsx
946
+ var inject = ["sessions"];
947
+ function injectStyles() {
948
+ if (document.querySelector('style[data-plugin="dsh-memoir"]') !== null) return;
949
+ const tag = document.createElement("style");
950
+ tag.dataset.plugin = "dsh-memoir";
951
+ tag.textContent = PANEL_CSS;
952
+ document.head.appendChild(tag);
953
+ }
954
+ function apply(ctx) {
955
+ const t = makeT(document);
956
+ const disposers = [];
957
+ try {
958
+ const controller = new PanelController();
959
+ const api = new MemoirApi();
960
+ const cwdTracker = createCwdTracker(ctx.sessions);
961
+ injectStyles();
962
+ disposers.push(mountSidebarEntry(controller, t));
963
+ disposers.push(mountPanel(controller, api, cwdTracker, t));
964
+ } catch (error) {
965
+ console.warn("[dsh-memoir] mount failed:", error);
966
+ }
967
+ ctx.effect(() => () => {
968
+ for (const dispose of disposers.splice(0)) dispose();
969
+ }, "dsh-memoir: ui mounts");
970
+ }
971
+ return module.exports;
972
+ } });
973
+ //# sourceMappingURL=client.js.map