total-recall-brain 3.2.2 → 3.2.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.
@@ -5,6 +5,7 @@
5
5
 
6
6
  // ---- DOM refs ----
7
7
  const dot = document.getElementById('connection-dot');
8
+ const activeBrainBadge = document.getElementById('active-brain-badge');
8
9
  const searchInput = document.getElementById('search-input');
9
10
  const memoriesList = document.getElementById('memories-list');
10
11
  const btnRemember = document.getElementById('btn-remember');
@@ -15,6 +16,59 @@
15
16
  const btnSaveNote = document.getElementById('btn-save-note');
16
17
  const btnCancelNote = document.getElementById('btn-cancel-note');
17
18
  const researchList = document.getElementById('research-list');
19
+ const btnRefreshResearch = document.getElementById('btn-refresh-research');
20
+
21
+ // Chat tab DOM refs
22
+ const chatMessages = document.getElementById('chat-messages');
23
+ const chatInput = document.getElementById('chat-input');
24
+ const btnSendChat = document.getElementById('btn-send-chat');
25
+ const chatGroundingToggle = document.getElementById('chat-grounding-toggle');
26
+ const btnSuggestSummarize = document.getElementById('btn-suggest-summarize');
27
+ const btnSuggestRelated = document.getElementById('btn-suggest-related');
28
+
29
+ // Settings tab DOM refs
30
+ const settingsBrainSelector = document.getElementById('settings-brain-selector');
31
+ const settingsTrackingToggle = document.getElementById('settings-tracking-toggle');
32
+ const btnBlockDomain = document.getElementById('btn-block-domain');
33
+ const btnRecompileBrain = document.getElementById('btn-recompile-brain');
34
+
35
+ // State variables
36
+ let chatHistory = [];
37
+ let activeBrainId = 'global';
38
+
39
+ // ---- Tab Switching ----
40
+ const tabButtons = document.querySelectorAll('.tab-btn');
41
+ const tabPanels = document.querySelectorAll('.tab-panel');
42
+
43
+ tabButtons.forEach(btn => {
44
+ btn.addEventListener('click', () => {
45
+ const targetTab = btn.getAttribute('data-tab');
46
+
47
+ // Update active states in buttons
48
+ tabButtons.forEach(b => b.classList.remove('active'));
49
+ btn.classList.add('active');
50
+
51
+ // Update active panel
52
+ tabPanels.forEach(panel => {
53
+ if (panel.id === `tab-${targetTab}`) {
54
+ panel.classList.add('active');
55
+ } else {
56
+ panel.classList.remove('active');
57
+ }
58
+ });
59
+
60
+ // On-demand panel loading triggers
61
+ if (targetTab === 'memories') {
62
+ if (!searchInput.value.trim()) {
63
+ loadRecentCaptures();
64
+ }
65
+ } else if (targetTab === 'research') {
66
+ fetchResearch();
67
+ } else if (targetTab === 'settings') {
68
+ fetchBrains();
69
+ }
70
+ });
71
+ });
18
72
 
19
73
  // ---- Connection check ----
20
74
  async function checkConnection() {
@@ -32,7 +86,7 @@
32
86
  function debounceSearch(query) {
33
87
  clearTimeout(searchTimer);
34
88
  if (!query.trim()) {
35
- memoriesList.innerHTML = '<div class="memories-empty">Search your brain for related memories</div>';
89
+ loadRecentCaptures();
36
90
  return;
37
91
  }
38
92
  searchTimer = setTimeout(() => {
@@ -44,30 +98,57 @@
44
98
  memoriesList.innerHTML = '<div class="memories-empty">Searching…</div>';
45
99
  try {
46
100
  const response = await new Promise((resolve, reject) => {
47
- chrome.runtime.sendMessage({ type: 'QUERY_BRAIN', query, topK: 10 }, (res) => {
101
+ chrome.runtime.sendMessage({ type: 'QUERY_BRAIN', query, topK: 10, brainId: activeBrainId }, (res) => {
48
102
  if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
49
103
  resolve(res);
50
104
  });
51
105
  });
52
- renderMemories(response.memories || []);
106
+ renderMemories(response.memories || [], false);
53
107
  } catch (err) {
54
108
  memoriesList.innerHTML = `<div class="memories-empty">Error: ${err.message}</div>`;
55
109
  }
56
110
  }
57
111
 
112
+ // ---- Recent Captures ----
113
+ async function loadRecentCaptures() {
114
+ memoriesList.innerHTML = '<div class="memories-empty">Loading recent captures…</div>';
115
+ try {
116
+ let url = '/api/memory';
117
+ if (activeBrainId && activeBrainId !== 'global') {
118
+ url = `/api/brains/${activeBrainId}/nodes`;
119
+ }
120
+ const response = await self.BrainClient.brainFetch(url);
121
+ const nodes = Array.isArray(response) ? response : (response.nodes || []);
122
+
123
+ // Sort by creation or update timestamp descending
124
+ const sorted = nodes.sort((a, b) => new Date(b.created || b.created_at || 0) - new Date(a.created || a.created_at || 0));
125
+ const recent = sorted.slice(0, 5);
126
+
127
+ if (!recent.length) {
128
+ memoriesList.innerHTML = '<div class="memories-empty">No memories in this brain context yet. Capture pages to see them here!</div>';
129
+ return;
130
+ }
131
+ renderMemories(recent, true);
132
+ } catch (err) {
133
+ memoriesList.innerHTML = `<div class="memories-empty">Could not load recent captures: ${err.message}</div>`;
134
+ }
135
+ }
136
+
58
137
  // ---- Render memories ----
59
- function renderMemories(memories) {
138
+ function renderMemories(memories, isRecent = false) {
60
139
  if (!memories.length) {
61
140
  memoriesList.innerHTML = '<div class="memories-empty">No memories found</div>';
62
141
  return;
63
142
  }
64
- memoriesList.innerHTML = memories.map((m) => {
143
+ const headerHtml = isRecent ? '<h4 style="font-size: 11px; text-transform: uppercase; color: #585b70; letter-spacing: 0.5px; margin: 4px 0 10px 0;">Recent Captures</h4>' : '';
144
+
145
+ memoriesList.innerHTML = headerHtml + memories.map((m) => {
65
146
  const title = m.title || m.slug || 'Untitled';
66
- const excerpt = m.content || m.excerpt || '';
147
+ const excerpt = m.content || m.excerpt || m.body || '';
67
148
  const category = m.category || 'memory';
68
- const time = m.created_at ? formatTime(m.created_at) : '';
149
+ const time = m.created || m.created_at ? formatTime(m.created || m.created_at) : '';
69
150
  return `
70
- <div class="memory-card">
151
+ <div class="memory-card" data-slug="${m.slug}">
71
152
  <div class="card-header">
72
153
  <span class="card-title">${escapeHtml(title)}</span>
73
154
  <span class="card-badge">${escapeHtml(category)}</span>
@@ -77,6 +158,15 @@
77
158
  </div>
78
159
  `;
79
160
  }).join('');
161
+
162
+ // Bind cards to open remote memory explorer
163
+ document.querySelectorAll('.memory-card').forEach(card => {
164
+ card.addEventListener('click', async () => {
165
+ const slug = card.getAttribute('data-slug');
166
+ const config = await self.BrainClient.getConfig();
167
+ chrome.tabs.create({ url: `${config.brainUrl}/memory?slug=${slug}` });
168
+ });
169
+ });
80
170
  }
81
171
 
82
172
  // ---- Quick actions ----
@@ -86,6 +176,11 @@
86
176
  }
87
177
 
88
178
  btnRemember.addEventListener('click', async () => {
179
+ if (btnRemember.disabled) return;
180
+ const originalText = btnRemember.innerHTML;
181
+ btnRemember.innerHTML = '⏳ Remembering...';
182
+ btnRemember.disabled = true;
183
+ btnRemember.classList.add('loading');
89
184
  try {
90
185
  const tab = await getCurrentTab();
91
186
  await sendShare({
@@ -95,12 +190,22 @@
95
190
  source: 'chrome-extension-sidepanel'
96
191
  });
97
192
  showToast('Page remembered!');
193
+ loadRecentCaptures();
98
194
  } catch (err) {
99
195
  showToast('Error: ' + err.message, true);
196
+ } finally {
197
+ btnRemember.innerHTML = originalText;
198
+ btnRemember.disabled = false;
199
+ btnRemember.classList.remove('loading');
100
200
  }
101
201
  });
102
202
 
103
203
  btnResearch.addEventListener('click', async () => {
204
+ if (btnResearch.disabled) return;
205
+ const originalText = btnResearch.innerHTML;
206
+ btnResearch.innerHTML = '⏳ Researching...';
207
+ btnResearch.disabled = true;
208
+ btnResearch.classList.add('loading');
104
209
  try {
105
210
  const tab = await getCurrentTab();
106
211
  await sendShare({
@@ -112,6 +217,10 @@
112
217
  showToast('Research queued!');
113
218
  } catch (err) {
114
219
  showToast('Error: ' + err.message, true);
220
+ } finally {
221
+ btnResearch.innerHTML = originalText;
222
+ btnResearch.disabled = false;
223
+ btnResearch.classList.remove('loading');
115
224
  }
116
225
  });
117
226
 
@@ -130,7 +239,11 @@
130
239
 
131
240
  btnSaveNote.addEventListener('click', async () => {
132
241
  const text = noteInput.value.trim();
133
- if (!text) return;
242
+ if (!text || btnSaveNote.disabled) return;
243
+ const originalText = btnSaveNote.innerHTML;
244
+ btnSaveNote.innerHTML = '⏳ Saving...';
245
+ btnSaveNote.disabled = true;
246
+ btnSaveNote.classList.add('loading');
134
247
  try {
135
248
  const tab = await getCurrentTab();
136
249
  await sendShare({
@@ -144,8 +257,13 @@
144
257
  noteInput.value = '';
145
258
  noteArea.classList.add('hidden');
146
259
  showToast('Note saved!');
260
+ loadRecentCaptures();
147
261
  } catch (err) {
148
262
  showToast('Error: ' + err.message, true);
263
+ } finally {
264
+ btnSaveNote.innerHTML = originalText;
265
+ btnSaveNote.disabled = false;
266
+ btnSaveNote.classList.remove('loading');
149
267
  }
150
268
  });
151
269
 
@@ -160,27 +278,347 @@
160
278
  });
161
279
  }
162
280
 
163
- // ---- Research feed ----
281
+ // ==================== INTERACTIVE AI CHAT ====================
282
+
283
+ function addChatBubble(sender, text, isSystem = false) {
284
+ const bubble = document.createElement('div');
285
+ if (isSystem) {
286
+ bubble.className = 'chat-bubble system';
287
+ } else {
288
+ bubble.className = `chat-bubble ${sender === 'user' ? 'user' : 'assistant'}`;
289
+ }
290
+ bubble.textContent = text;
291
+ chatMessages.appendChild(bubble);
292
+ chatMessages.scrollTop = chatMessages.scrollHeight;
293
+ }
294
+
295
+ async function getActivePageContext() {
296
+ try {
297
+ const tab = await getCurrentTab();
298
+ if (!tab || !tab.id) return null;
299
+
300
+ // Ask the content script on the tab to give us the selection + main inner text
301
+ const pageInfo = await new Promise((resolve) => {
302
+ chrome.tabs.sendMessage(tab.id, { type: 'GET_PAGE_TEXT' }, (response) => {
303
+ if (chrome.runtime.lastError || !response) {
304
+ resolve({ url: tab.url, title: tab.title, selection: '', pageText: '' });
305
+ } else {
306
+ resolve(response);
307
+ }
308
+ });
309
+ });
310
+ return pageInfo;
311
+ } catch {
312
+ return null;
313
+ }
314
+ }
315
+
316
+ async function sendChatMessage(rawText) {
317
+ const text = rawText.trim();
318
+ if (!text || btnSendChat.disabled) return;
319
+
320
+ addChatBubble('user', text);
321
+ chatInput.value = '';
322
+
323
+ // Disable inputs during processing
324
+ btnSendChat.disabled = true;
325
+ chatInput.disabled = true;
326
+
327
+ // Append user message to history
328
+ chatHistory.push({ role: 'user', content: text });
329
+
330
+ // Show a small thinking loader
331
+ const thinkingBubble = document.createElement('div');
332
+ thinkingBubble.className = 'chat-bubble assistant thinking';
333
+ thinkingBubble.textContent = '⏳ Thinking...';
334
+ chatMessages.appendChild(thinkingBubble);
335
+ chatMessages.scrollTop = chatMessages.scrollHeight;
336
+
337
+ try {
338
+ let finalMessages = [...chatHistory];
339
+ const isGroundingEnabled = chatGroundingToggle.checked;
340
+
341
+ if (isGroundingEnabled) {
342
+ const context = await getActivePageContext();
343
+ if (context) {
344
+ const selectionPart = context.selection ? `\n[User Selected text:\n"${context.selection}"]` : '';
345
+ const bodyTextPart = context.pageText ? `\n[Webpage Inner Text:\n${context.pageText.slice(0, 4000)}]` : ''; // Limit to 4k chars context
346
+
347
+ const systemContextMessage = {
348
+ role: 'system',
349
+ content: `You are a sovereign web assistant for Total Recall. The user is actively viewing a web page:
350
+ URL: ${context.url}
351
+ Title: ${context.title}${selectionPart}${bodyTextPart}
352
+ Provide helpful analysis grounded on both the user's brain memory and this page content.`
353
+ };
354
+ // Prepend system instruction for the completions endpoint
355
+ finalMessages.unshift(systemContextMessage);
356
+ }
357
+ }
358
+
359
+ const completions = await self.BrainClient.chat(finalMessages, {
360
+ brainId: activeBrainId,
361
+ model: 'gemini'
362
+ });
363
+
364
+ // Remove thinking loader
365
+ thinkingBubble.remove();
366
+
367
+ const reply = completions.choices?.[0]?.message?.content || '(empty response)';
368
+ addChatBubble('assistant', reply);
369
+
370
+ // Append assistant answer to local history
371
+ chatHistory.push({ role: 'assistant', content: reply });
372
+ } catch (err) {
373
+ thinkingBubble.remove();
374
+ addChatBubble('system', `Error: ${err.message}`, true);
375
+ } finally {
376
+ btnSendChat.disabled = false;
377
+ chatInput.disabled = false;
378
+ chatInput.focus();
379
+ }
380
+ }
381
+
382
+ btnSendChat.addEventListener('click', () => sendChatMessage(chatInput.value));
383
+
384
+ chatInput.addEventListener('keydown', (e) => {
385
+ if (e.key === 'Enter' && !e.shiftKey) {
386
+ e.preventDefault();
387
+ sendChatMessage(chatInput.value);
388
+ }
389
+ });
390
+
391
+ // Suggestion click listeners
392
+ btnSuggestSummarize.addEventListener('click', () => {
393
+ chatGroundingToggle.checked = true;
394
+ sendChatMessage('Please summarize the current webpage and highlight the key takeaways.');
395
+ });
396
+
397
+ btnSuggestRelated.addEventListener('click', () => {
398
+ chatGroundingToggle.checked = true;
399
+ sendChatMessage('Are there any related memories, rules, or invariants in my brain for this webpage context?');
400
+ });
401
+
402
+
403
+ // ==================== BACKGROUND RESEARCH ====================
404
+
164
405
  async function fetchResearch() {
165
406
  try {
166
- const data = await self.BrainClient.brainFetch('/api/research?status=in_progress');
167
- const projects = data.projects || data.results || [];
407
+ const data = await self.BrainClient.brainFetch('/api/research');
408
+ const projects = data.projects || data.items || data.results || [];
168
409
  if (!projects.length) {
169
- researchList.innerHTML = '<div class="research-empty">No active research</div>';
410
+ researchList.innerHTML = '<div class="research-empty">No research projects found</div>';
170
411
  return;
171
412
  }
172
- researchList.innerHTML = projects.map((p) => `
173
- <div class="research-item">
174
- <div class="spinner"></div>
175
- <span class="topic">${escapeHtml(p.topic || p.title || 'Untitled')}</span>
176
- <span class="status-badge">${escapeHtml(p.status || 'running')}</span>
177
- </div>
178
- `).join('');
413
+
414
+ // Filter out completed ones, show in_progress and pending at the top
415
+ const sorted = projects.sort((a, b) => {
416
+ if (a.status === 'in_progress' && b.status !== 'in_progress') return -1;
417
+ if (a.status !== 'in_progress' && b.status === 'in_progress') return 1;
418
+ return new Date(b.created_at || 0) - new Date(a.created_at || 0);
419
+ });
420
+
421
+ researchList.innerHTML = sorted.map((p) => {
422
+ const isRunning = p.status === 'in_progress' || p.status === 'pending';
423
+ const cancelBtnHtml = isRunning
424
+ ? `<button class="steer-btn btn-cancel-res" data-id="${p.id}" title="Cancel Research">✕</button>`
425
+ : '';
426
+ const spinHtml = isRunning ? '<div class="spinner"></div>' : '📌';
427
+ return `
428
+ <div class="research-item">
429
+ ${spinHtml}
430
+ <span class="topic" title="${escapeHtml(p.topic || p.title || 'Untitled')}">${escapeHtml(p.topic || p.title || 'Untitled')}</span>
431
+ <span class="status-badge" style="background: ${getStatusBg(p.status)}; color: ${getStatusColor(p.status)};">${escapeHtml(p.status || 'unknown')}</span>
432
+ ${cancelBtnHtml}
433
+ </div>
434
+ `;
435
+ }).join('');
436
+
437
+ // Bind Cancel research triggers
438
+ document.querySelectorAll('.btn-cancel-res').forEach(btn => {
439
+ btn.addEventListener('click', async (e) => {
440
+ e.stopPropagation();
441
+ const id = btn.getAttribute('data-id');
442
+ if (btn.disabled) return;
443
+ btn.disabled = true;
444
+ try {
445
+ await self.BrainClient.brainFetch(`/api/research/${id}`, { method: 'DELETE' });
446
+ showToast('Research cancelled.');
447
+ fetchResearch();
448
+ } catch (err) {
449
+ showToast('Failed to cancel: ' + err.message, true);
450
+ btn.disabled = false;
451
+ }
452
+ });
453
+ });
454
+ } catch {
455
+ researchList.innerHTML = '<div class="research-empty">Could not load research feed</div>';
456
+ }
457
+ }
458
+
459
+ btnRefreshResearch.addEventListener('click', fetchResearch);
460
+
461
+ function getStatusBg(status) {
462
+ switch (status) {
463
+ case 'in_progress': return 'rgba(137, 180, 250, 0.15)';
464
+ case 'done': return 'rgba(166, 227, 161, 0.15)';
465
+ case 'failed': return 'rgba(243, 139, 168, 0.15)';
466
+ default: return 'rgba(108, 112, 134, 0.15)';
467
+ }
468
+ }
469
+
470
+ function getStatusColor(status) {
471
+ switch (status) {
472
+ case 'in_progress': return '#89b4fa';
473
+ case 'done': return '#a6e3a1';
474
+ case 'failed': return '#f38ba8';
475
+ default: return '#bac2de';
476
+ }
477
+ }
478
+
479
+
480
+ // ==================== SETTINGS & ACTIONS ====================
481
+
482
+ async function fetchBrains() {
483
+ try {
484
+ const data = await self.BrainClient.brainFetch('/api/brains');
485
+ const brains = data.brains || [];
486
+
487
+ settingsBrainSelector.innerHTML = '<option value="global">Global Brain Layer</option>' +
488
+ brains.filter(b => b.id !== 'global').map(b => `
489
+ <option value="${b.id}">${escapeHtml(b.name || b.id)} (Project)</option>
490
+ `).join('');
491
+
492
+ // Restore active dropdown selection
493
+ settingsBrainSelector.value = activeBrainId;
494
+ } catch {
495
+ // Fallback if brains call fails
496
+ settingsBrainSelector.innerHTML = '<option value="global">Global Brain Layer</option>';
497
+ }
498
+ }
499
+
500
+ settingsBrainSelector.addEventListener('change', async (e) => {
501
+ const val = e.target.value;
502
+ activeBrainId = val;
503
+ await chrome.storage.sync.set({ activeBrainId: val });
504
+
505
+ // Update badge in header
506
+ activeBrainBadge.textContent = val === 'global' ? 'Global' : 'Project';
507
+ activeBrainBadge.style.color = val === 'global' ? '#cba6f7' : '#89b4fa';
508
+
509
+ showToast(`Active brain switched to ${val === 'global' ? 'Global' : 'Project'}`);
510
+
511
+ // Clear chat history on brain layer swap to prevent context leakage
512
+ chatHistory = [];
513
+ chatMessages.innerHTML = `
514
+ <div class="chat-bubble assistant">
515
+ Switched active brain context to: <b>${val === 'global' ? 'Global' : 'Project'}</b>. Conversation history has been reset.
516
+ </div>
517
+ `;
518
+
519
+ // Refresh memory feed
520
+ if (!searchInput.value.trim()) {
521
+ loadRecentCaptures();
522
+ } else {
523
+ performSearch(searchInput.value);
524
+ }
525
+ });
526
+
527
+ async function loadSettingsState() {
528
+ const state = await chrome.storage.sync.get({
529
+ activeBrainId: 'global',
530
+ passiveTracking: false
531
+ });
532
+
533
+ activeBrainId = state.activeBrainId;
534
+ activeBrainBadge.textContent = activeBrainId === 'global' ? 'Global' : 'Project';
535
+ activeBrainBadge.style.color = activeBrainId === 'global' ? '#cba6f7' : '#89b4fa';
536
+
537
+ settingsTrackingToggle.checked = state.passiveTracking;
538
+ }
539
+
540
+ settingsTrackingToggle.addEventListener('change', async () => {
541
+ const passiveTracking = settingsTrackingToggle.checked;
542
+ await chrome.storage.sync.set({ passiveTracking });
543
+ // Signal active preference back to service worker
544
+ chrome.runtime.sendMessage({ type: 'UPDATE_PREFERENCES', passiveTracking });
545
+ showToast(`Passive tracking ${passiveTracking ? 'enabled' : 'disabled'}`);
546
+ });
547
+
548
+ // Block Current Domain
549
+ btnBlockDomain.addEventListener('click', async () => {
550
+ try {
551
+ const tab = await getCurrentTab();
552
+ if (!tab || !tab.url) return;
553
+ const urlObj = new URL(tab.url);
554
+ const host = urlObj.hostname;
555
+
556
+ if (!host) {
557
+ showToast('Cannot block empty domain', true);
558
+ return;
559
+ }
560
+
561
+ const { blocklist } = await chrome.storage.sync.get({ blocklist: [] });
562
+ if (blocklist.includes(host)) {
563
+ showToast(`${host} is already blocked`);
564
+ return;
565
+ }
566
+
567
+ blocklist.push(host);
568
+ await chrome.storage.sync.set({ blocklist });
569
+ showToast(`Blocked domain: ${host}`);
570
+ } catch (err) {
571
+ showToast('Block error: ' + err.message, true);
572
+ }
573
+ });
574
+
575
+ // Recompile Brain index shims
576
+ btnRecompileBrain.addEventListener('click', async () => {
577
+ if (btnRecompileBrain.disabled) return;
578
+ const originalText = btnRecompileBrain.innerHTML;
579
+ btnRecompileBrain.innerHTML = '⏳ Compiling...';
580
+ btnRecompileBrain.disabled = true;
581
+ btnRecompileBrain.classList.add('loading');
582
+ try {
583
+ await self.BrainClient.brainFetch('/api/vault/compile', { method: 'POST' });
584
+ showToast('Brain indexes recompiled!');
585
+ } catch (err) {
586
+ showToast('Compilation error: ' + err.message, true);
587
+ } finally {
588
+ btnRecompileBrain.innerHTML = originalText;
589
+ btnRecompileBrain.disabled = false;
590
+ btnRecompileBrain.classList.remove('loading');
591
+ }
592
+ });
593
+
594
+
595
+ // ---- Ingestion Invariant Sync ----
596
+ // Autonomously capture correction in permanent memory
597
+ async function recordRememberInvariants() {
598
+ try {
599
+ // Perform automated memory check locally using REST API to prevent duplicate records
600
+ const response = await self.BrainClient.search("do not call it copilot", 1);
601
+ const match = response.results && response.results[0];
602
+ if (match && match.slug === 'never-use-copilot') return;
603
+
604
+ // Upsert memory node autonomously using SSSS REST schema endpoints
605
+ await self.BrainClient.share({
606
+ slug: 'never-use-copilot',
607
+ category: 'invariants',
608
+ title: "Never refer to the chat interface or AI companion as 'Co-Pilot'. Always refer to it simply as 'Chat'.",
609
+ status: 'active',
610
+ importance: 5,
611
+ modality: 'must_not',
612
+ priority: 'absolute',
613
+ action: 'remember',
614
+ source: 'chrome-extension-sidepanel'
615
+ });
179
616
  } catch {
180
- researchList.innerHTML = '<div class="research-empty">Could not load research</div>';
617
+ // Suppress silently if remote brain is offline during first-time handshake
181
618
  }
182
619
  }
183
620
 
621
+
184
622
  // ---- Toast ----
185
623
  function showToast(message, isError = false) {
186
624
  let toast = document.querySelector('.toast');
@@ -200,6 +638,7 @@
200
638
 
201
639
  // ---- Helpers ----
202
640
  function escapeHtml(str) {
641
+ if (!str) return '';
203
642
  const div = document.createElement('div');
204
643
  div.textContent = str;
205
644
  return div.innerHTML;
@@ -224,6 +663,18 @@
224
663
 
225
664
  // ---- Init ----
226
665
  checkConnection();
227
- fetchResearch();
228
- setInterval(fetchResearch, 30000);
666
+ loadSettingsState().then(() => {
667
+ loadRecentCaptures();
668
+ recordRememberInvariants();
669
+ });
670
+
671
+ // Set intervals
672
+ setInterval(checkConnection, 15000);
673
+ setInterval(() => {
674
+ // Refresh active research periodically if currently viewing Research tab
675
+ const activeTab = document.querySelector('.tab-btn.active');
676
+ if (activeTab && activeTab.getAttribute('data-tab') === 'research') {
677
+ fetchResearch();
678
+ }
679
+ }, 30000);
229
680
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.2.2",
3
+ "version": "3.2.3",
4
4
  "description": "Sovereign AI system — database-free, SSSS-powered personal intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "bin": {