total-recall-brain 3.21.2 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/frontend/dist/assets/index-C7_Xpk27.css +1 -0
  2. package/frontend/dist/assets/{index-D0XeeXhG.js → index-DJjuZ3t3.js} +106 -106
  3. package/frontend/dist/index.html +2 -2
  4. package/frontend/src/App.spec.tsx +1 -1
  5. package/frontend/src/api/_base.ts +3 -3
  6. package/frontend/src/api/embeddings.ts +46 -0
  7. package/frontend/src/api/mesh.ts +47 -0
  8. package/frontend/src/api/network.spec.ts +9 -9
  9. package/frontend/src/api/network.ts +4 -4
  10. package/frontend/src/api/notifications.ts +5 -4
  11. package/frontend/src/api/webhooks.ts +1 -1
  12. package/frontend/src/components/BrainSelector.spec.tsx +4 -4
  13. package/frontend/src/components/BrainSelector.tsx +1 -1
  14. package/frontend/src/components/ContextualHelp.spec.tsx +14 -14
  15. package/frontend/src/components/ContextualHelp.tsx +3 -1
  16. package/frontend/src/components/DocumentTable.spec.tsx +1 -1
  17. package/frontend/src/components/EmbeddingProviderPanel.spec.tsx +127 -0
  18. package/frontend/src/components/EmbeddingProviderPanel.tsx +177 -0
  19. package/frontend/src/components/Graph3D.spec.tsx +4 -4
  20. package/frontend/src/components/Graph3D.tsx +3 -3
  21. package/frontend/src/components/MobilePairing.spec.tsx +1 -1
  22. package/frontend/src/components/MobilePairing.tsx +1 -1
  23. package/frontend/src/components/UsageChart.spec.tsx +4 -4
  24. package/frontend/src/components/UsageChart.tsx +1 -1
  25. package/frontend/src/components/VoiceInput.spec.tsx +6 -6
  26. package/frontend/src/components/VoiceInput.tsx +19 -4
  27. package/frontend/src/components/brand/BrandMark.spec.tsx +3 -4
  28. package/frontend/src/index.css +6 -0
  29. package/frontend/src/pages/AutomationsPage.spec.tsx +4 -4
  30. package/frontend/src/pages/ChatPage.spec.tsx +3 -3
  31. package/frontend/src/pages/CollabPage.spec.tsx +1 -2
  32. package/frontend/src/pages/DesignDocsPage.spec.tsx +1 -1
  33. package/frontend/src/pages/FilesPage.spec.tsx +2 -2
  34. package/frontend/src/pages/GraphPage.spec.tsx +1 -1
  35. package/frontend/src/pages/HealthPage.spec.tsx +2 -2
  36. package/frontend/src/pages/HelpPage.spec.tsx +5 -5
  37. package/frontend/src/pages/InboxPage.spec.tsx +4 -4
  38. package/frontend/src/pages/InstructionsPage.spec.tsx +5 -5
  39. package/frontend/src/pages/IntegrationsPage.spec.tsx +7 -7
  40. package/frontend/src/pages/MemoryPage.spec.tsx +2 -2
  41. package/frontend/src/pages/MeshPage.spec.tsx +111 -2
  42. package/frontend/src/pages/MeshPage.tsx +95 -11
  43. package/frontend/src/pages/NetworkPage.spec.tsx +7 -8
  44. package/frontend/src/pages/NetworkPage.tsx +12 -12
  45. package/frontend/src/pages/NotificationsPage.tsx +9 -9
  46. package/frontend/src/pages/OnboardingPage.spec.tsx +7 -7
  47. package/frontend/src/pages/OpenWikiPage.tsx +3 -3
  48. package/frontend/src/pages/RulesPage.tsx +3 -3
  49. package/frontend/src/pages/SecretsPage.spec.tsx +5 -5
  50. package/frontend/src/pages/SecretsPage.tsx +13 -14
  51. package/frontend/src/pages/SettingsPage.spec.tsx +6 -6
  52. package/frontend/src/pages/SettingsPage.tsx +2 -2
  53. package/frontend/src/pages/SkillsPage.spec.tsx +1 -2
  54. package/frontend/src/pages/SkillsPage.tsx +3 -3
  55. package/frontend/src/pages/TasksPage.spec.tsx +3 -3
  56. package/frontend/src/pages/UsagePage.spec.tsx +2 -2
  57. package/frontend/src/pages/UsagePage.tsx +1 -1
  58. package/frontend/src/pages/WebhooksPage.spec.tsx +1 -1
  59. package/frontend/src/pages/WebhooksPage.tsx +19 -19
  60. package/frontend/src/setupTests.ts +4 -4
  61. package/package.json +1 -1
  62. package/scaffold/.agent/skills/total-recall/SKILL.md +92 -0
  63. package/scaffold/.agent/skills/total-recall/modules/agents/agents.yml +15 -13
  64. package/scaffold/.agent/skills/total-recall/research-queue.jsonl +0 -1
  65. package/src/cli/secret.mjs +93 -12
  66. package/src/core/browser-session.mjs +185 -0
  67. package/src/core/daemon-loop.mjs +28 -1
  68. package/src/core/embeddings.mjs +52 -10
  69. package/src/core/headscale-client.mjs +227 -0
  70. package/src/core/mesh-enroll.mjs +439 -0
  71. package/src/core/ollama-embeddings.mjs +336 -0
  72. package/src/core/provider-catalog.mjs +175 -6
  73. package/src/core/provider-rotation-recipes.mjs +299 -0
  74. package/src/core/rotation-capability.mjs +189 -0
  75. package/src/core/secrets-rotate.mjs +192 -0
  76. package/src/server/rest.mjs +2 -0
  77. package/src/server/route-manifest.json +20 -0
  78. package/src/server/routes/embeddings.mjs +111 -0
  79. package/src/server/routes/headscale.mjs +21 -81
  80. package/src/server/routes/mesh.mjs +41 -0
  81. package/frontend/dist/assets/index-B4RASWbV.css +0 -1
@@ -0,0 +1,177 @@
1
+ import { useCallback, useEffect, useState } from 'react'
2
+
3
+ import {
4
+ fetchEmbeddingProvider,
5
+ setEmbeddingModel,
6
+ rediscoverEmbeddingProvider,
7
+ type EmbeddingProviderStatus,
8
+ type EmbeddingCandidate,
9
+ } from '../api/embeddings'
10
+
11
+ function formatSize(bytes: number | null): string {
12
+ if (!bytes) return '—'
13
+ if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`
14
+ return `${Math.round(bytes / 1e6)} MB`
15
+ }
16
+
17
+ /**
18
+ * Explain why a model cannot be used, rather than hiding it. An operator who
19
+ * pulled the wrong model needs to see it listed with the reason attached.
20
+ */
21
+ function incompatibilityReason(c: EmbeddingCandidate, dims: number): string {
22
+ if (!c.embedding) return 'chat model — cannot embed'
23
+ if (c.dims !== dims) return `${c.dims ?? '?'} dims — index needs ${dims}`
24
+ return ''
25
+ }
26
+
27
+ export function EmbeddingProviderPanel() {
28
+ const [status, setStatus] = useState<EmbeddingProviderStatus | null>(null)
29
+ const [error, setError] = useState<string | null>(null)
30
+ const [busy, setBusy] = useState(false)
31
+
32
+ const load = useCallback(async () => {
33
+ try {
34
+ setStatus(await fetchEmbeddingProvider())
35
+ setError(null)
36
+ } catch (err) {
37
+ setError(err instanceof Error ? err.message : String(err))
38
+ }
39
+ }, [])
40
+
41
+ useEffect(() => {
42
+ void load()
43
+ }, [load])
44
+
45
+ const onPin = useCallback(
46
+ async (model: string | null) => {
47
+ setBusy(true)
48
+ try {
49
+ await setEmbeddingModel(model)
50
+ await load()
51
+ setError(null)
52
+ } catch (err) {
53
+ setError(err instanceof Error ? err.message : String(err))
54
+ } finally {
55
+ setBusy(false)
56
+ }
57
+ },
58
+ [load],
59
+ )
60
+
61
+ const onRediscover = useCallback(async () => {
62
+ setBusy(true)
63
+ try {
64
+ await rediscoverEmbeddingProvider()
65
+ await load()
66
+ setError(null)
67
+ } catch (err) {
68
+ setError(err instanceof Error ? err.message : String(err))
69
+ } finally {
70
+ setBusy(false)
71
+ }
72
+ }, [load])
73
+
74
+ if (error && !status) {
75
+ return (
76
+ <div className="panel">
77
+ <h3 style={{ marginTop: 0 }}>Embedding Provider</h3>
78
+ <p role="alert">{error}</p>
79
+ </div>
80
+ )
81
+ }
82
+
83
+ if (!status) {
84
+ return (
85
+ <div className="panel">
86
+ <h3 style={{ marginTop: 0 }}>Embedding Provider</h3>
87
+ <p>Loading…</p>
88
+ </div>
89
+ )
90
+ }
91
+
92
+ const { local, fallbacks, dims } = status
93
+
94
+ return (
95
+ <div className="panel">
96
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
97
+ <h3 style={{ margin: 0 }}>Embedding Provider</h3>
98
+ <span
99
+ data-testid="embed-availability"
100
+ style={{ color: local.available ? 'var(--ok, #2ea043)' : 'var(--warn, #d29922)' }}
101
+ >
102
+ {local.available ? '● local' : '○ hosted fallback'}
103
+ </span>
104
+ <button type="button" onClick={onRediscover} disabled={busy}>
105
+ Re-discover
106
+ </button>
107
+ </div>
108
+
109
+ <dl style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '4px 12px', margin: '12px 0' }}>
110
+ <dt>Endpoint</dt>
111
+ <dd data-testid="embed-endpoint" style={{ margin: 0 }}>
112
+ {local.endpoint ?? 'none reachable'}
113
+ </dd>
114
+ <dt>Model</dt>
115
+ <dd data-testid="embed-selected" style={{ margin: 0 }}>
116
+ {local.selected ?? '—'}
117
+ {local.preferred ? ' (pinned)' : local.selected ? ' (auto)' : ''}
118
+ </dd>
119
+ <dt>Vector width</dt>
120
+ <dd style={{ margin: 0 }}>{dims}</dd>
121
+ </dl>
122
+
123
+ {local.reason && <p role="status">{local.reason}</p>}
124
+ {error && <p role="alert">{error}</p>}
125
+
126
+ {local.candidates.length > 0 && (
127
+ <table style={{ width: '100%', borderCollapse: 'collapse' }}>
128
+ <thead>
129
+ <tr>
130
+ <th style={{ textAlign: 'left' }}>Model</th>
131
+ <th style={{ textAlign: 'left' }}>Size</th>
132
+ <th style={{ textAlign: 'left' }}>Status</th>
133
+ <th />
134
+ </tr>
135
+ </thead>
136
+ <tbody>
137
+ {local.candidates.map((c) => {
138
+ const reason = incompatibilityReason(c, dims)
139
+ const isSelected = c.name === local.selected
140
+ return (
141
+ <tr key={c.name}>
142
+ <td>{c.name}</td>
143
+ <td>{formatSize(c.size)}</td>
144
+ <td style={{ opacity: c.compatible ? 1 : 0.6 }}>
145
+ {c.compatible ? `${c.dims} dims` : reason}
146
+ </td>
147
+ <td style={{ textAlign: 'right' }}>
148
+ {c.compatible && !isSelected && (
149
+ <button type="button" onClick={() => onPin(c.name)} disabled={busy}>
150
+ Use
151
+ </button>
152
+ )}
153
+ {isSelected && local.preferred && (
154
+ <button type="button" onClick={() => onPin(null)} disabled={busy}>
155
+ Unpin
156
+ </button>
157
+ )}
158
+ </td>
159
+ </tr>
160
+ )
161
+ })}
162
+ </tbody>
163
+ </table>
164
+ )}
165
+
166
+ <div style={{ marginTop: 12, fontSize: '0.9em', opacity: 0.8 }}>
167
+ Fallback chain:{' '}
168
+ {fallbacks.map((f, i) => (
169
+ <span key={f.provider}>
170
+ {i > 0 && ' → '}
171
+ <span style={{ textDecoration: f.configured ? 'none' : 'line-through' }}>{f.provider}</span>
172
+ </span>
173
+ ))}
174
+ </div>
175
+ </div>
176
+ )
177
+ }
@@ -29,7 +29,7 @@ describe('Graph3D', () => {
29
29
  });
30
30
 
31
31
  // Mock requestAnimationFrame
32
- vi.spyOn(window, 'requestAnimationFrame').mockImplementation((_cb) => {
32
+ vi.spyOn(window, 'requestAnimationFrame').mockImplementation(() => {
33
33
  // Don't actually loop, just call once for coverage
34
34
  return 1;
35
35
  });
@@ -66,9 +66,9 @@ describe('Graph3D', () => {
66
66
  render(
67
67
  <Graph3D
68
68
  {...defaultProps}
69
- threads={[{ id: 't1', title: 'Thread 1', turns: 2, lastUpdated: new Date().toISOString() } as any]}
70
- memoryNodes={[{ slug: 'm1', title: 'Memory 1', category: 'facts', content: 'test', confidence: 0.9 } as any]}
71
- researchItems={[{ id: 'r1', topic: 'Research 1', status: 'active', created_at: new Date().toISOString() } as any]}
69
+ threads={[{ id: 't1', title: 'Thread 1', turns: 2, lastUpdated: new Date().toISOString() } as never]}
70
+ memoryNodes={[{ slug: 'm1', title: 'Memory 1', category: 'facts', content: 'test', confidence: 0.9 } as never]}
71
+ researchItems={[{ id: 'r1', topic: 'Research 1', status: 'active', created_at: new Date().toISOString() } as never]}
72
72
  />
73
73
  );
74
74
 
@@ -341,9 +341,9 @@ export default function Graph3D({
341
341
  const isFocus = !hoverNode || srcId === hoverNode?.id || tgtId === hoverNode?.id;
342
342
 
343
343
  const hex = link.colorHex || '#475569';
344
- let r = parseInt(hex.slice(1, 3), 16) || 71;
345
- let g = parseInt(hex.slice(3, 5), 16) || 85;
346
- let b = parseInt(hex.slice(5, 7), 16) || 105;
344
+ const r = parseInt(hex.slice(1, 3), 16) || 71;
345
+ const g = parseInt(hex.slice(3, 5), 16) || 85;
346
+ const b = parseInt(hex.slice(5, 7), 16) || 105;
347
347
 
348
348
  const baseOpacity = link.type === 'implicit' ? 0.2 : 0.6;
349
349
  const opacity = isFocus ? baseOpacity : 0.02;
@@ -42,7 +42,7 @@ describe('MobilePairing', () => {
42
42
  },
43
43
  ],
44
44
  }),
45
- } as any);
45
+ } as never);
46
46
 
47
47
  render(<MobilePairing />);
48
48
  await waitFor(() => {
@@ -74,7 +74,7 @@ export function MobilePairing() {
74
74
  };
75
75
 
76
76
  useEffect(() => {
77
- load();
77
+ setTimeout(() => { void load(); }, 0);
78
78
  }, []);
79
79
 
80
80
  const selected = info?.endpoints?.find((e) => e.url === selectedUrl) || null;
@@ -7,8 +7,8 @@ vi.mock('recharts', () => {
7
7
  const OriginalRecharts = vi.importActual('recharts');
8
8
  return {
9
9
  ...OriginalRecharts,
10
- ResponsiveContainer: ({ children }: any) => <div>{children}</div>,
11
- BarChart: ({ children, data }: any) => (
10
+ ResponsiveContainer: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
11
+ BarChart: ({ children, data }: { children: React.ReactNode, data: unknown }) => (
12
12
  <div data-testid="bar-chart" data-data={JSON.stringify(data)}>
13
13
  {children}
14
14
  </div>
@@ -39,7 +39,7 @@ describe('UsageChart', () => {
39
39
  const { container } = render(<UsageChart {...defaultProps} usageData={{
40
40
  breakdown: { gemini: { cost: 0 }, claude: { cost: 0 }, codex: { cost: 0 } },
41
41
  timeseries: {}
42
- } as any} />);
42
+ } as never} />);
43
43
  expect(container).toBeEmptyDOMElement();
44
44
  });
45
45
 
@@ -57,7 +57,7 @@ describe('UsageChart', () => {
57
57
  }
58
58
  };
59
59
 
60
- render(<UsageChart {...defaultProps} usageData={usageData as any} />);
60
+ render(<UsageChart {...defaultProps} usageData={usageData as never} />);
61
61
 
62
62
  expect(screen.getByText('API Costs Over Time')).toBeInTheDocument();
63
63
 
@@ -63,7 +63,7 @@ export const UsageChart: React.FC<UsageChartProps> = ({ usageData }) => {
63
63
  <Tooltip
64
64
  contentStyle={{ background: 'var(--bg-tertiary)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 12 }}
65
65
  itemStyle={{ fontSize: 12 }}
66
- formatter={(value: any) => [`$${Number(value).toFixed(4)}`, undefined]}
66
+ formatter={(value: unknown) => [`$${Number(value).toFixed(4)}`, undefined]}
67
67
  />
68
68
  <Legend wrapperStyle={{ fontSize: 11 }} />
69
69
  <Bar isAnimationActive={false} dataKey="gemini" name="Gemini" stackId="a" fill="#10b981" radius={[0,0,0,0]} />
@@ -1,16 +1,16 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
3
  import { VoiceInput } from './VoiceInput';
4
4
 
5
5
  describe('VoiceInput', () => {
6
- it('renders disabled button if SpeechRecognition is not supported', () => {
6
+ it('renders disabled button if SpeechRecognition is not supported', async () => {
7
7
  // Delete any mock to simulate unsupported browser
8
- delete (window as any).SpeechRecognition;
9
- delete (window as any).webkitSpeechRecognition;
8
+ delete (window as typeof window & { SpeechRecognition?: unknown }).SpeechRecognition;
9
+ delete (window as typeof window & { webkitSpeechRecognition?: unknown }).webkitSpeechRecognition;
10
10
 
11
11
  render(<VoiceInput onTranscript={() => {}} />);
12
12
  const button = screen.getByRole('button');
13
- expect(button).toBeDisabled();
13
+ await waitFor(() => expect(button).toBeDisabled());
14
14
  expect(button.title).toBe('Voice input not supported');
15
15
  });
16
16
 
@@ -25,7 +25,7 @@ describe('VoiceInput', () => {
25
25
  stop = mockStop;
26
26
  }
27
27
 
28
- (window as any).SpeechRecognition = MockSpeechRecognition;
28
+ (window as typeof window & { SpeechRecognition?: unknown }).SpeechRecognition = MockSpeechRecognition;
29
29
 
30
30
  render(<VoiceInput onTranscript={() => {}} />);
31
31
  const button = screen.getByRole('button');
@@ -5,20 +5,35 @@ interface VoiceInputProps {
5
5
  isRecording?: boolean;
6
6
  }
7
7
 
8
+ interface SpeechRecognitionObj {
9
+ continuous: boolean;
10
+ interimResults: boolean;
11
+ onresult: ((event: { results: { transcript: string }[][] }) => void) | null;
12
+ onerror: (() => void) | null;
13
+ onend: (() => void) | null;
14
+ start: () => void;
15
+ stop: () => void;
16
+ }
17
+
18
+ interface SpeechRecognitionConstructor {
19
+ new (): SpeechRecognitionObj;
20
+ }
21
+
8
22
  export function VoiceInput({ onTranscript, isRecording = false }: VoiceInputProps) {
9
23
  const [recording, setRecording] = useState(isRecording);
10
- const recognitionRef = useRef<any>(null);
24
+ const recognitionRef = useRef<SpeechRecognitionObj | null>(null);
11
25
  const [supported, setSupported] = useState(true);
12
26
 
13
27
  useEffect(() => {
14
28
  if (typeof window !== 'undefined') {
15
- const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
29
+ const win = window as unknown as { SpeechRecognition?: SpeechRecognitionConstructor, webkitSpeechRecognition?: SpeechRecognitionConstructor };
30
+ const SpeechRecognition = win.SpeechRecognition || win.webkitSpeechRecognition;
16
31
  if (SpeechRecognition) {
17
32
  recognitionRef.current = new SpeechRecognition();
18
33
  recognitionRef.current.continuous = false;
19
34
  recognitionRef.current.interimResults = false;
20
35
 
21
- recognitionRef.current.onresult = (event: any) => {
36
+ recognitionRef.current.onresult = (event) => {
22
37
  const transcript = event.results[0][0].transcript;
23
38
  onTranscript(transcript);
24
39
  setRecording(false);
@@ -32,7 +47,7 @@ export function VoiceInput({ onTranscript, isRecording = false }: VoiceInputProp
32
47
  setRecording(false);
33
48
  };
34
49
  } else {
35
- setSupported(false);
50
+ setTimeout(() => setSupported(false), 0);
36
51
  }
37
52
  }
38
53
  }, [onTranscript]);
@@ -1,5 +1,4 @@
1
- // @ts-nocheck
2
- import { describe, it, expect, vi } from 'vitest';
1
+ import { describe, it, expect } from 'vitest';
3
2
  import { render, screen } from '@testing-library/react';
4
3
  import BrandMark, { BrandChip } from './BrandMark';
5
4
 
@@ -58,7 +57,7 @@ describe('BrandMark', () => {
58
57
  describe('BrandChip', () => {
59
58
  it('renders with default size', () => {
60
59
  render(<BrandChip />);
61
- const chip = document.querySelector('.brand-chip');
60
+ const chip = document.querySelector('.brand-chip') as HTMLElement | null;
62
61
  expect(chip).toBeInTheDocument();
63
62
  expect(chip?.style.width).toBe('36px');
64
63
  expect(chip?.style.height).toBe('36px');
@@ -66,7 +65,7 @@ describe('BrandChip', () => {
66
65
 
67
66
  it('renders with custom size', () => {
68
67
  render(<BrandChip size={64} />);
69
- const chip = document.querySelector('.brand-chip');
68
+ const chip = document.querySelector('.brand-chip') as HTMLElement | null;
70
69
  expect(chip?.style.width).toBe('64px');
71
70
  expect(chip?.style.height).toBe('64px');
72
71
  });
@@ -377,6 +377,12 @@ code, pre { font-family: var(--font-mono); }
377
377
  color: var(--success);
378
378
  }
379
379
 
380
+ .alert-warning {
381
+ background: var(--warning-muted);
382
+ border-color: rgba(251, 191, 36, 0.25);
383
+ color: var(--warning);
384
+ }
385
+
380
386
  /* ─── Form Controls ──────────────────────────────────────────────────────────── */
381
387
 
382
388
  .input, .select {
@@ -1,4 +1,4 @@
1
- // @ts-nocheck
1
+
2
2
  import { describe, it, expect, vi, beforeEach } from 'vitest';
3
3
  import { render, screen, waitFor, fireEvent } from '@testing-library/react';
4
4
  import AutomationsPage from './AutomationsPage';
@@ -13,7 +13,7 @@ describe('AutomationsPage', () => {
13
13
  });
14
14
 
15
15
  it('renders correctly and triggers dream cycle', async () => {
16
- vi.mocked(api.triggerDream).mockResolvedValue({ status: 'completed' });
16
+ vi.mocked(api.triggerDream).mockResolvedValue({ status: 'completed' } as never);
17
17
 
18
18
  render(<AutomationsPage />);
19
19
 
@@ -29,7 +29,7 @@ describe('AutomationsPage', () => {
29
29
  });
30
30
 
31
31
  it('triggers recompilation', async () => {
32
- vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Brain surfaces recompiled successfully!' });
32
+ vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Brain surfaces recompiled successfully!' } as never);
33
33
 
34
34
  render(<AutomationsPage />);
35
35
 
@@ -43,7 +43,7 @@ describe('AutomationsPage', () => {
43
43
  });
44
44
 
45
45
  it('enqueues a task', async () => {
46
- vi.mocked(api.createTask).mockResolvedValue({} as any);
46
+ vi.mocked(api.createTask).mockResolvedValue({} as never);
47
47
 
48
48
  render(<AutomationsPage />);
49
49
 
@@ -14,10 +14,10 @@ describe('ChatPage', () => {
14
14
 
15
15
  it('renders correctly', async () => {
16
16
  vi.mocked(api.fetchChatThreads).mockResolvedValue([]);
17
- vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as any);
18
- vi.mocked(api.fetchHealth).mockResolvedValue({ cli_agents: ['antigravity', 'claude'] } as any);
17
+ vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as never);
18
+ vi.mocked(api.fetchHealth).mockResolvedValue({ cli_agents: ['antigravity', 'claude'] } as never);
19
19
  vi.mocked(api.fetchGeminiModels).mockResolvedValue([]);
20
- vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as any);
20
+ vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as never);
21
21
  vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: false, connected: false });
22
22
  vi.mocked(api.listMemory).mockResolvedValue([]);
23
23
  vi.mocked(api.fetchChatHistory).mockResolvedValue([]);
@@ -1,11 +1,10 @@
1
- // @ts-nocheck
2
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
3
2
  import { render, screen, waitFor, fireEvent } from '@testing-library/react';
4
3
  import CollabPage from './CollabPage';
5
4
 
6
5
  // Mock fetch globally
7
6
  const mockFetch = vi.fn();
8
- global.fetch = mockFetch;
7
+ globalThis.fetch = mockFetch as unknown as typeof fetch;
9
8
 
10
9
  describe('CollabPage', () => {
11
10
  beforeEach(() => {
@@ -13,7 +13,7 @@ describe('DesignDocsPage', () => {
13
13
  it('renders correctly', async () => {
14
14
  vi.mocked(api.fetchDesignDocs).mockResolvedValue([
15
15
  { name: 'Architecture Overview', path: 'docs/architecture.md', category: 'Core' }
16
- ] as any);
16
+ ] as never);
17
17
  vi.mocked(api.fetchDesignDocContent).mockResolvedValue({ content: '# Architecture Overview' });
18
18
 
19
19
  render(<DesignDocsPage />);
@@ -13,7 +13,7 @@ describe('FilesPage', () => {
13
13
  it('renders correctly and loads files', async () => {
14
14
  vi.mocked(api.listFiles).mockResolvedValue([
15
15
  { name: 'test.txt', size: 1024, modified: new Date().toISOString(), isDirectory: false }
16
- ] as any);
16
+ ] as never);
17
17
  vi.mocked(api.listSkills).mockResolvedValue([]);
18
18
 
19
19
  render(<FilesPage />);
@@ -31,7 +31,7 @@ describe('FilesPage', () => {
31
31
  vi.mocked(api.listSkills).mockResolvedValue([]);
32
32
  vi.mocked(api.listScripts).mockResolvedValue([
33
33
  { name: 'test-script.mjs', size: 500, modified: new Date().toISOString(), isDirectory: false }
34
- ] as any);
34
+ ] as never);
35
35
 
36
36
  render(<FilesPage />);
37
37
 
@@ -12,7 +12,7 @@ describe('GraphPage', () => {
12
12
 
13
13
  it('renders without crashing', async () => {
14
14
  vi.mocked(api.listMemory).mockResolvedValue([]);
15
- vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as any);
15
+ vi.mocked(api.listResearch).mockResolvedValue({ items: [] } as never);
16
16
  vi.mocked(api.fetchChatThreads).mockResolvedValue([]);
17
17
 
18
18
  render(<GraphPage activeBrainId="global" />);
@@ -16,8 +16,8 @@ describe('HealthPage', () => {
16
16
  version: '1.0.0',
17
17
  uptime_seconds: 3600,
18
18
  timestamp: new Date().toISOString()
19
- } as any);
20
- vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as any);
19
+ } as never);
20
+ vi.mocked(api.checkUpdate).mockResolvedValue({ updateAvailable: false } as never);
21
21
 
22
22
  render(<HealthPage />);
23
23
 
@@ -16,11 +16,11 @@ describe('HelpPage', () => {
16
16
  { id: 'cli', title: 'CLI Reference', description: 'Commands for CLI' },
17
17
  { id: 'vault', title: 'Vault Structure', description: 'How files are stored' }
18
18
  ]
19
- } as any);
19
+ } as never);
20
20
 
21
21
  vi.mocked(api.fetchHelpContent).mockResolvedValue({
22
22
  content: '# CLI Reference\n\nSome helpful text.'
23
- } as any);
23
+ } as never);
24
24
 
25
25
  await act(async () => {
26
26
  render(<HelpPage />);
@@ -39,13 +39,13 @@ describe('HelpPage', () => {
39
39
  { id: 'cli', title: 'CLI Reference', description: 'Commands for CLI' },
40
40
  { id: 'vault', title: 'Vault Structure', description: 'How files are stored' }
41
41
  ]
42
- } as any);
42
+ } as never);
43
43
 
44
44
  vi.mocked(api.fetchHelpContent).mockResolvedValueOnce({
45
45
  content: 'CLI content'
46
- } as any).mockResolvedValueOnce({
46
+ } as never).mockResolvedValueOnce({
47
47
  content: 'Vault content'
48
- } as any);
48
+ } as never);
49
49
 
50
50
  await act(async () => {
51
51
  render(<HelpPage />);
@@ -15,7 +15,7 @@ describe('InboxPage', () => {
15
15
  docs: [
16
16
  { path: 'proposals/p1.md', name: 'Proposal 1', type: 'proposal', status: 'pending_approval' }
17
17
  ]
18
- } as any);
18
+ } as never);
19
19
 
20
20
  render(<InboxPage />);
21
21
 
@@ -32,10 +32,10 @@ describe('InboxPage', () => {
32
32
  docs: [
33
33
  { path: 'proposals/p2.md', name: 'Proposal 2', type: 'proposal', status: 'pending_approval' }
34
34
  ]
35
- } as any);
35
+ } as never);
36
36
 
37
- vi.mocked(api.readDoc).mockResolvedValue({ raw: 'status: pending_approval' } as any);
38
- vi.mocked(api.postDecision).mockResolvedValue({} as any);
37
+ vi.mocked(api.readDoc).mockResolvedValue({ raw: 'status: pending_approval' } as never);
38
+ vi.mocked(api.postDecision).mockResolvedValue({} as never);
39
39
 
40
40
  render(<InboxPage />);
41
41
 
@@ -1,4 +1,4 @@
1
- // @ts-nocheck
1
+
2
2
  import { describe, it, expect, vi, beforeEach } from 'vitest';
3
3
  import { render, screen, waitFor, fireEvent } from '@testing-library/react';
4
4
  import InstructionsPage from './InstructionsPage';
@@ -18,9 +18,9 @@ describe('InstructionsPage', () => {
18
18
  ],
19
19
  lastCompileTimestamp: new Date().toISOString(),
20
20
  totalNodes: 10
21
- } as any);
21
+ } as never);
22
22
 
23
- vi.mocked(api.fetchInstructionContent).mockResolvedValue({ content: '# AGENTS\n\nRule 1' } as any);
23
+ vi.mocked(api.fetchInstructionContent).mockResolvedValue({ content: '# AGENTS\n\nRule 1' } as never);
24
24
 
25
25
  render(<InstructionsPage />);
26
26
 
@@ -36,9 +36,9 @@ describe('InstructionsPage', () => {
36
36
  surfaces: [],
37
37
  lastCompileTimestamp: new Date().toISOString(),
38
38
  totalNodes: 0
39
- } as any);
39
+ } as never);
40
40
 
41
- vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Success' });
41
+ vi.mocked(api.triggerRecompile).mockResolvedValue({ message: 'Success' } as never);
42
42
 
43
43
  render(<InstructionsPage />);
44
44
 
@@ -16,9 +16,9 @@ describe('IntegrationsPage', () => {
16
16
  keys: [
17
17
  { id: '1', name: 'Antigravity', token_preview: 'abc', created_at: '', last_used_at: '', hit_count: 1, revoked: false, scopes: [] }
18
18
  ]
19
- } as any);
20
- vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: ['antigravity'] } as any);
21
- vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: true, connected: true } as any);
19
+ } as never);
20
+ vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: ['antigravity'] } as never);
21
+ vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: true, connected: true } as never);
22
22
 
23
23
  await act(async () => {
24
24
  render(<IntegrationsPage />);
@@ -32,10 +32,10 @@ describe('IntegrationsPage', () => {
32
32
 
33
33
  it('handles connect', async () => {
34
34
  vi.mocked(api.getApiBase).mockReturnValue('http://localhost:3000');
35
- vi.mocked(api.listApiKeys).mockResolvedValue({ keys: [] } as any);
36
- vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: [] } as any);
37
- vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: false, connected: false } as any);
38
- vi.mocked(api.connectClient).mockResolvedValue({ success: true, message: 'Connected' } as any);
35
+ vi.mocked(api.listApiKeys).mockResolvedValue({ keys: [] } as never);
36
+ vi.mocked(api.fetchActiveIntegrations).mockResolvedValue({ success: true, active: [] } as never);
37
+ vi.mocked(api.fetchExtensionStatus).mockResolvedValue({ available: false, connected: false } as never);
38
+ vi.mocked(api.connectClient).mockResolvedValue({ success: true, message: 'Connected' } as never);
39
39
 
40
40
  await act(async () => {
41
41
  render(<IntegrationsPage />);
@@ -18,7 +18,7 @@ describe('MemoryPage', () => {
18
18
  it('renders correctly and loads nodes', async () => {
19
19
  vi.mocked(api.listMemory).mockResolvedValue([
20
20
  { slug: 'test-node', title: 'Test Node', category: 'invariants', content: 'test content' }
21
- ] as any);
21
+ ] as never);
22
22
 
23
23
  renderWithRouter(<MemoryPage />);
24
24
 
@@ -36,7 +36,7 @@ describe('MemoryPage', () => {
36
36
  { id: 'sess-1', brain_id: 'default', total_chunks: 5, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), status: 'active' }
37
37
  ],
38
38
  total: 1
39
- } as any);
39
+ } as never);
40
40
 
41
41
  renderWithRouter(<MemoryPage />);
42
42