total-recall-brain 3.12.2 → 3.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +3 -1
- package/frontend/src/App.tsx +112 -111
- package/frontend/src/api.ts +700 -18
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +78 -0
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +614 -0
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +583 -0
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/ModelsPage.tsx +280 -23
- package/frontend/src/pages/OkfPage.tsx +535 -0
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +561 -0
- package/frontend/src/pages/SettingsPage.tsx +826 -740
- package/frontend/src/pages/SkillsPage.tsx +1259 -117
- package/frontend/src/pages/UsagePage.tsx +256 -11
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +173 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +19 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +28 -16
- package/src/server/auth.mjs +53 -2
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +405 -68
- package/src/server/route-manifest.json +168 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { fetchDesignDocs, fetchDesignDocContent } from '../api';
|
|
3
|
+
|
|
4
|
+
// ─── Types ──────────────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
interface DocItem {
|
|
7
|
+
name: string;
|
|
8
|
+
path: string;
|
|
9
|
+
icon: string;
|
|
10
|
+
category: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface KanbanCard {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
emoji: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface KanbanColumn {
|
|
20
|
+
id: string;
|
|
21
|
+
title: string;
|
|
22
|
+
accent: string;
|
|
23
|
+
cards: KanbanCard[];
|
|
24
|
+
count?: number;
|
|
25
|
+
collapsible?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Static Data ────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const CORE_DOCS: DocItem[] = [
|
|
31
|
+
{ name: 'ARCHITECTURE.md', path: 'docs/ARCHITECTURE.md', icon: '🏗️', category: 'Core Docs' },
|
|
32
|
+
{ name: 'SSSS.md', path: 'docs/SSSS.md', icon: '🔐', category: 'Core Docs' },
|
|
33
|
+
{ name: 'IDE_INTEGRATION.md', path: 'docs/IDE_INTEGRATION.md', icon: '🔌', category: 'Core Docs' },
|
|
34
|
+
{ name: 'CHANGELOG.md', path: 'docs/CHANGELOG.md', icon: '📋', category: 'Core Docs' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const DEV_GUIDES: DocItem[] = [
|
|
38
|
+
{ name: 'README.md', path: 'README.md', icon: '📖', category: 'Developer Guides' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const KANBAN_COLUMNS: KanbanColumn[] = [
|
|
42
|
+
{
|
|
43
|
+
id: 'in-progress',
|
|
44
|
+
title: 'In Progress',
|
|
45
|
+
accent: '#3b82f6',
|
|
46
|
+
cards: [
|
|
47
|
+
{ id: 'auto-update', name: 'Auto Update Feature', emoji: '🔄' },
|
|
48
|
+
{ id: 'compaction-cache', name: 'Compaction Cache Bypass', emoji: '⚡' },
|
|
49
|
+
{ id: 'memory-policy', name: 'Memory Policy Enforcement', emoji: '🛡️' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'planned',
|
|
54
|
+
title: 'Planned',
|
|
55
|
+
accent: '#74b9ff',
|
|
56
|
+
cards: [
|
|
57
|
+
{ id: 'system-resilience', name: 'System Resilience', emoji: '🧱' },
|
|
58
|
+
{ id: 'gpu-intelligence', name: 'GPU Intelligence Network', emoji: '🧠' },
|
|
59
|
+
{ id: 'living-memory', name: 'Living Memory Capsule (UltraChat)', emoji: '💊' },
|
|
60
|
+
{ id: 'expo-mobile', name: 'Expo Mobile (stub)', emoji: '📱' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'completed',
|
|
65
|
+
title: 'Completed',
|
|
66
|
+
accent: '#00cec9',
|
|
67
|
+
count: 35,
|
|
68
|
+
collapsible: true,
|
|
69
|
+
cards: [
|
|
70
|
+
{ id: 'okf-integration', name: 'OKF Integration', emoji: '✅' },
|
|
71
|
+
{ id: 'byom-architecture', name: 'BYOM Architecture', emoji: '✅' },
|
|
72
|
+
{ id: 'cli-performance', name: 'CLI Performance Optimization', emoji: '✅' },
|
|
73
|
+
{ id: 'ssss-portable-memory', name: 'SSSS Portable Memory', emoji: '✅' },
|
|
74
|
+
{ id: 'embedding-pipeline', name: 'Embedding Pipeline v2', emoji: '✅' },
|
|
75
|
+
{ id: 'surface-compiler', name: 'Surface Compiler', emoji: '✅' },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'archived',
|
|
80
|
+
title: 'Archived',
|
|
81
|
+
accent: '#636e72',
|
|
82
|
+
count: 3,
|
|
83
|
+
collapsible: true,
|
|
84
|
+
cards: [
|
|
85
|
+
{ id: 'legacy-sync', name: 'Legacy Sync Protocol', emoji: '📦' },
|
|
86
|
+
{ id: 'deprecated-cli', name: 'Deprecated CLI v1', emoji: '📦' },
|
|
87
|
+
{ id: 'old-auth', name: 'Old Auth Flow', emoji: '📦' },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// ─── Simple Markdown Renderer ───────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
function renderMarkdown(md: string): React.ReactNode {
|
|
95
|
+
if (!md) return <></>;
|
|
96
|
+
|
|
97
|
+
let html = md
|
|
98
|
+
.replace(/&/g, '&')
|
|
99
|
+
.replace(/</g, '<')
|
|
100
|
+
.replace(/>/g, '>');
|
|
101
|
+
|
|
102
|
+
// Tables (basic pipe tables) — process before other inline transforms
|
|
103
|
+
html = html.replace(
|
|
104
|
+
/^(\|.+\|)\n(\|[\s:|-]+\|)\n((?:\|.+\|\n?)+)/gm,
|
|
105
|
+
(_match, headerRow: string, _separator: string, bodyRows: string) => {
|
|
106
|
+
const headers = headerRow.split('|').filter((c: string) => c.trim());
|
|
107
|
+
const rows = bodyRows.trim().split('\n').map((r: string) =>
|
|
108
|
+
r.split('|').filter((c: string) => c.trim())
|
|
109
|
+
);
|
|
110
|
+
const thCells = headers.map((h: string) =>
|
|
111
|
+
`<th style="padding:8px 12px;text-align:left;border-bottom:2px solid var(--border-hover);font-weight:600;">${h.trim()}</th>`
|
|
112
|
+
).join('');
|
|
113
|
+
const tbodyRows = rows.map((row: string[]) => {
|
|
114
|
+
const cells = row.map((c: string) =>
|
|
115
|
+
`<td style="padding:8px 12px;border-bottom:1px solid var(--border);">${c.trim()}</td>`
|
|
116
|
+
).join('');
|
|
117
|
+
return `<tr>${cells}</tr>`;
|
|
118
|
+
}).join('');
|
|
119
|
+
return `<table style="width:100%;border-collapse:collapse;margin:16px 0;font-size:13px;"><thead><tr>${thCells}</tr></thead><tbody>${tbodyRows}</tbody></table>`;
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Headings
|
|
124
|
+
html = html.replace(/^#### (.*$)/gm, '<h4 style="margin:20px 0 8px;font-size:14px;color:var(--text-primary);">$1</h4>');
|
|
125
|
+
html = html.replace(/^### (.*$)/gm, '<h3 style="margin:24px 0 10px;font-size:16px;color:var(--text-primary);">$1</h3>');
|
|
126
|
+
html = html.replace(/^## (.*$)/gm, '<h2 style="margin:28px 0 12px;font-size:19px;color:var(--text-primary);border-bottom:1px solid var(--border);padding-bottom:8px;">$1</h2>');
|
|
127
|
+
html = html.replace(/^# (.*$)/gm, '<h1 style="margin:0 0 16px;font-size:24px;color:var(--text-primary);">$1</h1>');
|
|
128
|
+
|
|
129
|
+
// Code blocks (fenced)
|
|
130
|
+
html = html.replace(
|
|
131
|
+
/```[\w]*\n([\s\S]*?)```/g,
|
|
132
|
+
'<pre style="background:rgba(0,0,0,0.35);padding:14px 16px;border-radius:var(--radius-sm);border:1px solid var(--border);font-family:var(--font-mono);font-size:12.5px;overflow-x:auto;margin:12px 0;line-height:1.5;"><code>$1</code></pre>'
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Bold & Italic
|
|
136
|
+
html = html.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
137
|
+
html = html.replace(/\*\*(.*?)\*\*/g, '<strong style="color:var(--text-primary);">$1</strong>');
|
|
138
|
+
html = html.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
|
|
139
|
+
|
|
140
|
+
// Inline code
|
|
141
|
+
html = html.replace(
|
|
142
|
+
/`([^`]+)`/g,
|
|
143
|
+
'<code style="background:rgba(108,92,231,0.12);color:#b8b0f0;padding:2px 6px;border-radius:4px;font-family:var(--font-mono);font-size:12px;">$1</code>'
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Blockquotes
|
|
147
|
+
html = html.replace(
|
|
148
|
+
/^> (.*$)/gm,
|
|
149
|
+
'<blockquote style="border-left:3px solid var(--accent);padding:8px 14px;color:var(--text-secondary);margin:12px 0;background:rgba(108,92,231,0.05);border-radius:0 var(--radius-sm) var(--radius-sm) 0;">$1</blockquote>'
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Unordered lists
|
|
153
|
+
html = html.replace(
|
|
154
|
+
/^- (.*$)/gm,
|
|
155
|
+
'<li style="margin-left:20px;list-style-type:disc;margin-bottom:4px;">$1</li>'
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Ordered lists
|
|
159
|
+
html = html.replace(
|
|
160
|
+
/^\d+\. (.*$)/gm,
|
|
161
|
+
'<li style="margin-left:20px;list-style-type:decimal;margin-bottom:4px;">$1</li>'
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Line breaks
|
|
165
|
+
html = html.replace(/\n/g, '<br />');
|
|
166
|
+
|
|
167
|
+
return <div dangerouslySetInnerHTML={{ __html: html }} className="markdown-preview-block" />;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Component ──────────────────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
export default function DesignDocsPage() {
|
|
173
|
+
const [activeTab, setActiveTab] = useState<'architecture' | 'board'>('architecture');
|
|
174
|
+
|
|
175
|
+
// Architecture docs state
|
|
176
|
+
const [selectedDoc, setSelectedDoc] = useState<DocItem>(CORE_DOCS[0]);
|
|
177
|
+
const [docContent, setDocContent] = useState<string>('');
|
|
178
|
+
const [docLoading, setDocLoading] = useState(false);
|
|
179
|
+
const [docError, setDocError] = useState<string | null>(null);
|
|
180
|
+
const [, setDocsFromApi] = useState<Array<{ name: string; path: string; size: number; modified: string; category: string }>>([]);
|
|
181
|
+
|
|
182
|
+
// Kanban state
|
|
183
|
+
const [expandedColumns, setExpandedColumns] = useState<Record<string, boolean>>({});
|
|
184
|
+
|
|
185
|
+
// Load docs list on mount
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
async function loadDocsList() {
|
|
188
|
+
try {
|
|
189
|
+
const data = await fetchDesignDocs();
|
|
190
|
+
setDocsFromApi(data);
|
|
191
|
+
} catch {
|
|
192
|
+
// Silently fall back to static doc list
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
loadDocsList();
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
// Load selected doc content
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
if (activeTab !== 'architecture' || !selectedDoc) return;
|
|
201
|
+
let cancelled = false;
|
|
202
|
+
|
|
203
|
+
async function loadContent() {
|
|
204
|
+
setDocLoading(true);
|
|
205
|
+
setDocError(null);
|
|
206
|
+
try {
|
|
207
|
+
const data = await fetchDesignDocContent(selectedDoc.path);
|
|
208
|
+
if (!cancelled) setDocContent(data.content);
|
|
209
|
+
} catch {
|
|
210
|
+
if (!cancelled) {
|
|
211
|
+
setDocError(selectedDoc.path);
|
|
212
|
+
setDocContent('');
|
|
213
|
+
}
|
|
214
|
+
} finally {
|
|
215
|
+
if (!cancelled) setDocLoading(false);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
loadContent();
|
|
219
|
+
return () => { cancelled = true; };
|
|
220
|
+
}, [selectedDoc, activeTab]);
|
|
221
|
+
|
|
222
|
+
const toggleColumn = (colId: string) => {
|
|
223
|
+
setExpandedColumns(prev => ({ ...prev, [colId]: !prev[colId] }));
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// ─── Sidebar Section Renderer ───────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
const renderSidebarSection = (title: string, items: DocItem[]) => (
|
|
229
|
+
<div style={{ marginBottom: 20 }}>
|
|
230
|
+
<div
|
|
231
|
+
style={{
|
|
232
|
+
fontSize: 10,
|
|
233
|
+
fontWeight: 700,
|
|
234
|
+
textTransform: 'uppercase',
|
|
235
|
+
letterSpacing: '0.08em',
|
|
236
|
+
color: 'var(--text-tertiary)',
|
|
237
|
+
padding: '0 12px',
|
|
238
|
+
marginBottom: 8,
|
|
239
|
+
}}
|
|
240
|
+
>
|
|
241
|
+
{title}
|
|
242
|
+
</div>
|
|
243
|
+
{items.map(item => {
|
|
244
|
+
const isActive = selectedDoc.path === item.path;
|
|
245
|
+
return (
|
|
246
|
+
<button
|
|
247
|
+
id={`doc-nav-${item.name.replace(/\./g, '-').toLowerCase()}`}
|
|
248
|
+
key={item.path}
|
|
249
|
+
onClick={() => setSelectedDoc(item)}
|
|
250
|
+
style={{
|
|
251
|
+
display: 'flex',
|
|
252
|
+
alignItems: 'center',
|
|
253
|
+
gap: 10,
|
|
254
|
+
width: '100%',
|
|
255
|
+
textAlign: 'left',
|
|
256
|
+
padding: '9px 12px',
|
|
257
|
+
borderRadius: 'var(--radius-sm)',
|
|
258
|
+
background: isActive ? 'var(--accent-muted)' : 'transparent',
|
|
259
|
+
color: isActive ? 'var(--accent-hover)' : 'var(--text-secondary)',
|
|
260
|
+
border: 'none',
|
|
261
|
+
cursor: 'pointer',
|
|
262
|
+
transition: 'var(--transition-normal)',
|
|
263
|
+
fontSize: 13,
|
|
264
|
+
fontWeight: isActive ? 600 : 400,
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
<span style={{ fontSize: 15, flexShrink: 0 }}>{item.icon}</span>
|
|
268
|
+
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
269
|
+
{item.name}
|
|
270
|
+
</span>
|
|
271
|
+
</button>
|
|
272
|
+
);
|
|
273
|
+
})}
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// ─── Architecture Tab ───────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
const renderArchitectureTab = () => (
|
|
280
|
+
<div
|
|
281
|
+
id="architecture-panel"
|
|
282
|
+
style={{ display: 'flex', gap: 24, minHeight: '65vh' }}
|
|
283
|
+
>
|
|
284
|
+
{/* Sidebar */}
|
|
285
|
+
<div
|
|
286
|
+
id="docs-sidebar"
|
|
287
|
+
style={{
|
|
288
|
+
width: 220,
|
|
289
|
+
flexShrink: 0,
|
|
290
|
+
background: 'var(--bg-secondary)',
|
|
291
|
+
border: '1px solid var(--border)',
|
|
292
|
+
borderRadius: 'var(--radius-md)',
|
|
293
|
+
padding: '16px 8px',
|
|
294
|
+
overflowY: 'auto',
|
|
295
|
+
}}
|
|
296
|
+
>
|
|
297
|
+
{renderSidebarSection('Core Docs', CORE_DOCS)}
|
|
298
|
+
{renderSidebarSection('Developer Guides', DEV_GUIDES)}
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
{/* Main Viewer */}
|
|
302
|
+
<div
|
|
303
|
+
id="docs-viewer"
|
|
304
|
+
style={{
|
|
305
|
+
flex: 1,
|
|
306
|
+
background: 'var(--bg-secondary)',
|
|
307
|
+
border: '1px solid var(--border)',
|
|
308
|
+
borderRadius: 'var(--radius-md)',
|
|
309
|
+
padding: '24px 32px',
|
|
310
|
+
overflowY: 'auto',
|
|
311
|
+
maxHeight: '75vh',
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
{/* Breadcrumb */}
|
|
315
|
+
<div
|
|
316
|
+
id="docs-breadcrumb"
|
|
317
|
+
style={{
|
|
318
|
+
display: 'flex',
|
|
319
|
+
alignItems: 'center',
|
|
320
|
+
gap: 6,
|
|
321
|
+
fontSize: 12,
|
|
322
|
+
color: 'var(--text-tertiary)',
|
|
323
|
+
marginBottom: 20,
|
|
324
|
+
fontFamily: 'var(--font-mono)',
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
<span>Total Recall</span>
|
|
328
|
+
<span style={{ color: 'var(--text-tertiary)' }}>›</span>
|
|
329
|
+
<span>docs</span>
|
|
330
|
+
<span style={{ color: 'var(--text-tertiary)' }}>›</span>
|
|
331
|
+
<span style={{ color: 'var(--accent)' }}>{selectedDoc.name}</span>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{/* Content */}
|
|
335
|
+
{docLoading ? (
|
|
336
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
337
|
+
{[200, 120, 280, 80, 160].map((w, i) => (
|
|
338
|
+
<div
|
|
339
|
+
key={i}
|
|
340
|
+
className="skeleton"
|
|
341
|
+
style={{ height: 16, width: `${w}px`, maxWidth: '100%', borderRadius: 4 }}
|
|
342
|
+
/>
|
|
343
|
+
))}
|
|
344
|
+
</div>
|
|
345
|
+
) : docError ? (
|
|
346
|
+
<div
|
|
347
|
+
id="doc-fallback-message"
|
|
348
|
+
style={{
|
|
349
|
+
background: 'var(--warning-muted)',
|
|
350
|
+
border: '1px solid rgba(253, 203, 110, 0.2)',
|
|
351
|
+
borderRadius: 'var(--radius-sm)',
|
|
352
|
+
padding: '20px 24px',
|
|
353
|
+
color: 'var(--text-secondary)',
|
|
354
|
+
lineHeight: 1.7,
|
|
355
|
+
}}
|
|
356
|
+
>
|
|
357
|
+
<div style={{ marginBottom: 8, color: 'var(--warning)', fontWeight: 600 }}>
|
|
358
|
+
📄 Document not available via API
|
|
359
|
+
</div>
|
|
360
|
+
<div>
|
|
361
|
+
This document is available in your project at{' '}
|
|
362
|
+
<code
|
|
363
|
+
style={{
|
|
364
|
+
background: 'rgba(108,92,231,0.12)',
|
|
365
|
+
color: '#b8b0f0',
|
|
366
|
+
padding: '2px 6px',
|
|
367
|
+
borderRadius: 4,
|
|
368
|
+
fontFamily: 'var(--font-mono)',
|
|
369
|
+
fontSize: 12,
|
|
370
|
+
}}
|
|
371
|
+
>
|
|
372
|
+
{docError}
|
|
373
|
+
</code>
|
|
374
|
+
. Open it in your editor to view.
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
) : (
|
|
378
|
+
<div style={{ color: 'var(--text-primary)', lineHeight: 1.75, fontSize: 14 }}>
|
|
379
|
+
{renderMarkdown(docContent)}
|
|
380
|
+
</div>
|
|
381
|
+
)}
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// ─── Kanban Board Tab ─────────────────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
const renderKanbanCard = (card: KanbanCard) => (
|
|
389
|
+
<div
|
|
390
|
+
id={`kanban-card-${card.id}`}
|
|
391
|
+
key={card.id}
|
|
392
|
+
style={{
|
|
393
|
+
background: 'var(--bg-tertiary)',
|
|
394
|
+
border: '1px solid var(--border)',
|
|
395
|
+
borderRadius: 'var(--radius-sm)',
|
|
396
|
+
padding: '12px 14px',
|
|
397
|
+
cursor: 'default',
|
|
398
|
+
transition: 'var(--transition-normal)',
|
|
399
|
+
display: 'flex',
|
|
400
|
+
alignItems: 'center',
|
|
401
|
+
gap: 10,
|
|
402
|
+
}}
|
|
403
|
+
onMouseEnter={e => {
|
|
404
|
+
(e.currentTarget as HTMLElement).style.transform = 'translateY(-2px)';
|
|
405
|
+
(e.currentTarget as HTMLElement).style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
|
|
406
|
+
(e.currentTarget as HTMLElement).style.borderColor = 'var(--border-hover)';
|
|
407
|
+
}}
|
|
408
|
+
onMouseLeave={e => {
|
|
409
|
+
(e.currentTarget as HTMLElement).style.transform = 'translateY(0)';
|
|
410
|
+
(e.currentTarget as HTMLElement).style.boxShadow = 'none';
|
|
411
|
+
(e.currentTarget as HTMLElement).style.borderColor = 'var(--border)';
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
<span style={{ fontSize: 16, flexShrink: 0 }}>{card.emoji}</span>
|
|
415
|
+
<span style={{ fontSize: 13, fontWeight: 500, color: 'var(--text-primary)' }}>
|
|
416
|
+
{card.name}
|
|
417
|
+
</span>
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
const renderKanbanColumn = (col: KanbanColumn) => {
|
|
422
|
+
const isExpanded = expandedColumns[col.id] ?? !col.collapsible;
|
|
423
|
+
const visibleCards = isExpanded ? col.cards : [];
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<div
|
|
427
|
+
id={`kanban-col-${col.id}`}
|
|
428
|
+
key={col.id}
|
|
429
|
+
style={{
|
|
430
|
+
minWidth: 260,
|
|
431
|
+
flex: 1,
|
|
432
|
+
maxWidth: 320,
|
|
433
|
+
background: 'var(--bg-secondary)',
|
|
434
|
+
border: '1px solid var(--border)',
|
|
435
|
+
borderRadius: 'var(--radius-md)',
|
|
436
|
+
borderTop: `4px solid ${col.accent}`,
|
|
437
|
+
display: 'flex',
|
|
438
|
+
flexDirection: 'column',
|
|
439
|
+
overflow: 'hidden',
|
|
440
|
+
}}
|
|
441
|
+
>
|
|
442
|
+
{/* Column Header */}
|
|
443
|
+
<div
|
|
444
|
+
style={{
|
|
445
|
+
padding: '14px 16px 10px',
|
|
446
|
+
display: 'flex',
|
|
447
|
+
alignItems: 'center',
|
|
448
|
+
justifyContent: 'space-between',
|
|
449
|
+
}}
|
|
450
|
+
>
|
|
451
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
452
|
+
<span style={{ fontWeight: 600, fontSize: 14, color: 'var(--text-primary)' }}>
|
|
453
|
+
{col.title}
|
|
454
|
+
</span>
|
|
455
|
+
<span
|
|
456
|
+
className="badge"
|
|
457
|
+
style={{
|
|
458
|
+
background: `${col.accent}22`,
|
|
459
|
+
color: col.accent,
|
|
460
|
+
fontSize: 11,
|
|
461
|
+
padding: '2px 8px',
|
|
462
|
+
borderRadius: 10,
|
|
463
|
+
fontWeight: 600,
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
{col.count ?? col.cards.length}
|
|
467
|
+
</span>
|
|
468
|
+
</div>
|
|
469
|
+
{col.collapsible && (
|
|
470
|
+
<button
|
|
471
|
+
id={`kanban-toggle-${col.id}`}
|
|
472
|
+
className="btn btn-ghost"
|
|
473
|
+
onClick={() => toggleColumn(col.id)}
|
|
474
|
+
style={{
|
|
475
|
+
fontSize: 12,
|
|
476
|
+
padding: '4px 10px',
|
|
477
|
+
color: 'var(--text-secondary)',
|
|
478
|
+
background: 'transparent',
|
|
479
|
+
borderRadius: 'var(--radius-sm)',
|
|
480
|
+
}}
|
|
481
|
+
>
|
|
482
|
+
{isExpanded ? 'Collapse' : 'Expand'}
|
|
483
|
+
</button>
|
|
484
|
+
)}
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
{/* Cards */}
|
|
488
|
+
<div
|
|
489
|
+
style={{
|
|
490
|
+
padding: '0 12px 14px',
|
|
491
|
+
display: 'flex',
|
|
492
|
+
flexDirection: 'column',
|
|
493
|
+
gap: 8,
|
|
494
|
+
flex: 1,
|
|
495
|
+
}}
|
|
496
|
+
>
|
|
497
|
+
{col.collapsible && !isExpanded ? (
|
|
498
|
+
<div
|
|
499
|
+
style={{
|
|
500
|
+
color: 'var(--text-tertiary)',
|
|
501
|
+
fontSize: 12,
|
|
502
|
+
textAlign: 'center',
|
|
503
|
+
padding: '12px 0',
|
|
504
|
+
cursor: 'pointer',
|
|
505
|
+
}}
|
|
506
|
+
onClick={() => toggleColumn(col.id)}
|
|
507
|
+
>
|
|
508
|
+
{col.count ?? col.cards.length} projects — click to expand
|
|
509
|
+
</div>
|
|
510
|
+
) : (
|
|
511
|
+
visibleCards.map(card => renderKanbanCard(card))
|
|
512
|
+
)}
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
);
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const renderBoardTab = () => (
|
|
519
|
+
<div id="board-panel">
|
|
520
|
+
<div
|
|
521
|
+
id="kanban-board"
|
|
522
|
+
style={{
|
|
523
|
+
display: 'flex',
|
|
524
|
+
gap: 16,
|
|
525
|
+
overflowX: 'auto',
|
|
526
|
+
paddingBottom: 16,
|
|
527
|
+
}}
|
|
528
|
+
>
|
|
529
|
+
{KANBAN_COLUMNS.map(col => renderKanbanColumn(col))}
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// ─── Main Render ──────────────────────────────────────────────────────────────
|
|
535
|
+
|
|
536
|
+
const tabs: { key: typeof activeTab; label: string; icon: string }[] = [
|
|
537
|
+
{ key: 'architecture', label: 'Architecture', icon: '📐' },
|
|
538
|
+
{ key: 'board', label: 'Project Board', icon: '📋' },
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
return (
|
|
542
|
+
<div className="page" style={{ padding: '24px 28px', maxWidth: 1200, margin: '0 auto' }}>
|
|
543
|
+
{/* Page Header */}
|
|
544
|
+
<div className="page-header" style={{ marginBottom: 24 }}>
|
|
545
|
+
<h1 style={{ fontSize: 22, fontWeight: 700 }}>Design Docs & Projects</h1>
|
|
546
|
+
<p style={{ color: 'var(--text-secondary)', fontSize: 13, marginTop: 4 }}>
|
|
547
|
+
Architecture documentation, specifications, and project tracking board.
|
|
548
|
+
</p>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
{/* Tab Navigation */}
|
|
552
|
+
<div
|
|
553
|
+
id="design-docs-tabs"
|
|
554
|
+
style={{
|
|
555
|
+
display: 'inline-flex',
|
|
556
|
+
gap: 0,
|
|
557
|
+
marginBottom: 24,
|
|
558
|
+
borderBottom: '1px solid var(--border)',
|
|
559
|
+
}}
|
|
560
|
+
>
|
|
561
|
+
{tabs.map(tab => {
|
|
562
|
+
const isActive = activeTab === tab.key;
|
|
563
|
+
return (
|
|
564
|
+
<button
|
|
565
|
+
id={`tab-${tab.key}`}
|
|
566
|
+
key={tab.key}
|
|
567
|
+
onClick={() => setActiveTab(tab.key)}
|
|
568
|
+
style={{
|
|
569
|
+
display: 'flex',
|
|
570
|
+
alignItems: 'center',
|
|
571
|
+
gap: 7,
|
|
572
|
+
padding: '10px 20px',
|
|
573
|
+
background: 'transparent',
|
|
574
|
+
color: isActive ? 'var(--accent)' : 'var(--text-secondary)',
|
|
575
|
+
fontSize: 14,
|
|
576
|
+
fontWeight: isActive ? 600 : 400,
|
|
577
|
+
borderBottom: isActive ? '2px solid var(--accent)' : '2px solid transparent',
|
|
578
|
+
transition: 'var(--transition-normal)',
|
|
579
|
+
cursor: 'pointer',
|
|
580
|
+
marginBottom: -1,
|
|
581
|
+
}}
|
|
582
|
+
>
|
|
583
|
+
<span>{tab.icon}</span>
|
|
584
|
+
{tab.label}
|
|
585
|
+
</button>
|
|
586
|
+
);
|
|
587
|
+
})}
|
|
588
|
+
</div>
|
|
589
|
+
|
|
590
|
+
{/* Tab Content */}
|
|
591
|
+
{activeTab === 'architecture' ? renderArchitectureTab() : renderBoardTab()}
|
|
592
|
+
|
|
593
|
+
{/* ─── Responsive Styles ─────────────────────────────────────────────────── */}
|
|
594
|
+
<style>{`
|
|
595
|
+
@media (max-width: 768px) {
|
|
596
|
+
#architecture-panel {
|
|
597
|
+
flex-direction: column !important;
|
|
598
|
+
}
|
|
599
|
+
#docs-sidebar {
|
|
600
|
+
width: 100% !important;
|
|
601
|
+
max-height: 200px;
|
|
602
|
+
}
|
|
603
|
+
#kanban-board {
|
|
604
|
+
flex-direction: column !important;
|
|
605
|
+
}
|
|
606
|
+
#kanban-board > div {
|
|
607
|
+
max-width: 100% !important;
|
|
608
|
+
min-width: 100% !important;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
`}</style>
|
|
612
|
+
</div>
|
|
613
|
+
);
|
|
614
|
+
}
|
|
@@ -35,7 +35,11 @@ export default function FilesPage() {
|
|
|
35
35
|
listSkills()
|
|
36
36
|
])
|
|
37
37
|
setFiles(filesData)
|
|
38
|
-
|
|
38
|
+
// Skills API returns { skills, repos } catalog — flatten for this page
|
|
39
|
+
const flat = Array.isArray(skillsData)
|
|
40
|
+
? skillsData
|
|
41
|
+
: (skillsData?.skills || [])
|
|
42
|
+
setSkills(flat as import('../types').FileNode[])
|
|
39
43
|
setError("")
|
|
40
44
|
} catch (e) {
|
|
41
45
|
setError((e as Error).message)
|
|
@@ -121,10 +125,10 @@ export default function FilesPage() {
|
|
|
121
125
|
setScriptLoading(true)
|
|
122
126
|
try {
|
|
123
127
|
const defaultCode = name.endsWith(".py")
|
|
124
|
-
? "#!/usr/bin/env python3\nprint('Hello from
|
|
128
|
+
? "#!/usr/bin/env python3\nprint('Hello from Total Recall automation!')\n"
|
|
125
129
|
: name.endsWith(".sh")
|
|
126
|
-
? "#!/bin/bash\necho 'Hello from
|
|
127
|
-
: "/**\n *
|
|
130
|
+
? "#!/bin/bash\necho 'Hello from Total Recall automation!'\n"
|
|
131
|
+
: "/**\n * Total Recall skills automation script\n */\nconsole.log(\"Hello from Total Recall automation!\");\n"
|
|
128
132
|
|
|
129
133
|
await saveScript(name, defaultCode)
|
|
130
134
|
setNewScriptName("")
|
|
@@ -143,7 +147,7 @@ export default function FilesPage() {
|
|
|
143
147
|
<div className="page-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: 20 }}>
|
|
144
148
|
<div>
|
|
145
149
|
<h1>Storage & Automations</h1>
|
|
146
|
-
<p>Manage
|
|
150
|
+
<p>Manage local files, runtime skills, and automation scripts</p>
|
|
147
151
|
</div>
|
|
148
152
|
|
|
149
153
|
{/* Navigation Tabs */}
|
|
@@ -176,7 +180,7 @@ export default function FilesPage() {
|
|
|
176
180
|
|
|
177
181
|
{activeTab === "files" && (
|
|
178
182
|
<div className="health-log" style={{ background: "rgba(15,23,42,0.45)", backdropFilter: "blur(12px)", border: "1px solid var(--border)", borderRadius: 12, padding: 24 }}>
|
|
179
|
-
<h2 style={{ fontSize: 16, marginBottom: 12, borderBottom: "1px solid var(--border)", paddingBottom: 8, color: "#fff" }}>
|
|
183
|
+
<h2 style={{ fontSize: 16, marginBottom: 12, borderBottom: "1px solid var(--border)", paddingBottom: 8, color: "#fff" }}>Local Files (~/.agent/files/)</h2>
|
|
180
184
|
<table style={{ width: "100%", textAlign: "left", borderCollapse: "collapse" }}>
|
|
181
185
|
<thead>
|
|
182
186
|
<tr>
|
|
@@ -17,7 +17,7 @@ export default function GraphPage({ activeBrainId }: { activeBrainId?: string })
|
|
|
17
17
|
try {
|
|
18
18
|
const [nodes, researchRes, threadsRes] = await Promise.all([
|
|
19
19
|
listMemory(activeBrainId),
|
|
20
|
-
listResearch(
|
|
20
|
+
listResearch().catch(() => ({ items: [] })),
|
|
21
21
|
fetchChatThreads().catch(() => [])
|
|
22
22
|
])
|
|
23
23
|
setMemoryNodes(nodes)
|
|
@@ -35,8 +35,10 @@ export default function GraphPage({ activeBrainId }: { activeBrainId?: string })
|
|
|
35
35
|
return (
|
|
36
36
|
<div className="page" style={{ padding: 0, height: 'calc(100vh - 1px)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
|
37
37
|
<div style={{ padding: '16px 24px', background: 'rgba(18, 18, 26, 0.45)', backdropFilter: 'blur(16px)', borderBottom: '1px solid var(--border)', flexShrink: 0 }}>
|
|
38
|
-
<h1 style={{ fontSize: '18px', fontWeight: 700 }}>
|
|
39
|
-
<p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: '2px' }}>
|
|
38
|
+
<h1 style={{ fontSize: '18px', fontWeight: 700, letterSpacing: '-0.02em' }}>Memory Graph</h1>
|
|
39
|
+
<p style={{ fontSize: '12px', color: 'var(--text-secondary)', marginTop: '2px' }}>
|
|
40
|
+
3D vault map — facts (blue), rules (cyan/rose), research (amber), sessions (teal).
|
|
41
|
+
</p>
|
|
40
42
|
</div>
|
|
41
43
|
|
|
42
44
|
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|