diff --git a/web/backend/dist/index.html b/web/backend/dist/index.html
new file mode 100644
index 000000000..15af737cc
--- /dev/null
+++ b/web/backend/dist/index.html
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+ PicoClaw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/backend/dist/sessions.js b/web/backend/dist/sessions.js
new file mode 100644
index 000000000..1e1967469
--- /dev/null
+++ b/web/backend/dist/sessions.js
@@ -0,0 +1,32 @@
+// web/backend/dist/sessions.js
+// Session History Controller (~4KB)
+function loadSessionHistory() {
+ const history = document.getElementById('session-history');
+ if (!history) return;
+
+ fetch('/api/sessions/search?limit=20')
+ .then(r => r.json())
+ .then(sessions => {
+ history.innerHTML = 'Recent Sessions
';
+ sessions.forEach(session => {
+ const div = document.createElement('div');
+ div.className = 'session-item';
+ div.innerHTML = `
+ ${session.title || 'Untitled'}
+ ${(session.tags || []).map(t => `#${t}`).join(' ')}
+ ${new Date(session.timestamp).toLocaleDateString()}
+ `;
+ div.onclick = () => loadSessionContext(session.id);
+ history.appendChild(div);
+ });
+ })
+ .catch(e => console.error('Failed to load sessions:', e));
+}
+
+function loadSessionContext(sessionId) {
+ // TODO: load session context into chat
+ console.log('Load session:', sessionId);
+}
+
+// Expose
+window.loadSessionHistory = loadSessionHistory;
diff --git a/web/backend/dist/styles/vault.css b/web/backend/dist/styles/vault.css
new file mode 100644
index 000000000..eb1c44868
--- /dev/null
+++ b/web/backend/dist/styles/vault.css
@@ -0,0 +1,82 @@
+/* web/backend/dist/styles/vault.css */
+.sidebar {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 300px;
+ height: 100%;
+ background: var(--background-primary, #1e1e1e);
+ color: var(--text-normal, #dcddde);
+ padding: 10px;
+ overflow-y: auto;
+ z-index: 1000;
+}
+
+.sidebar.hidden {
+ display: none;
+}
+
+.sidebar-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+}
+
+.sidebar-header h3 {
+ margin: 0;
+}
+
+.tree-item {
+ padding: 5px 10px;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.tree-item:hover {
+ background: var(--background-secondary, #2d2d2d);
+}
+
+.tag-badge {
+ display: inline-block;
+ background: var(--background-secondary, #2d2d2d);
+ border-radius: 12px;
+ padding: 2px 8px;
+ margin: 2px;
+ cursor: pointer;
+ font-size: 0.9em;
+}
+
+.tag-badge:hover {
+ background: var(--interactive-accent, #5865f2);
+ color: white;
+}
+
+.skill-item {
+ padding: 8px;
+ border-bottom: 1px solid var(--background-secondary, #2d2d2d);
+}
+
+.skill-name {
+ font-weight: bold;
+}
+
+.skill-usage {
+ font-size: 0.9em;
+ color: var(--text-muted, #72767d);
+}
+
+.session-item {
+ padding: 8px;
+ border-bottom: 1px solid var(--background-secondary, #2d2d2d);
+ cursor: pointer;
+}
+
+.session-title {
+ font-weight: bold;
+}
+
+.session-tags {
+ font-size: 0.9em;
+ color: var(--text-muted, #72767d);
+}
diff --git a/web/backend/dist/tags.js b/web/backend/dist/tags.js
new file mode 100644
index 000000000..91e53d3d3
--- /dev/null
+++ b/web/backend/dist/tags.js
@@ -0,0 +1,36 @@
+// web/backend/dist/tags.js
+// Tag Cloud Controller (~3KB)
+function loadTagCloud() {
+ const cloud = document.getElementById('tag-cloud');
+ if (!cloud) return;
+
+ fetch('/api/vault/tags')
+ .then(r => r.json())
+ .then(tags => {
+ cloud.innerHTML = 'Tags
';
+ tags.forEach(tag => {
+ const badge = document.createElement('span');
+ badge.className = 'tag-badge';
+ badge.textContent = `#${tag.name} (${tag.count})`;
+ badge.onclick = () => filterByTag(tag.name);
+ cloud.appendChild(badge);
+ });
+ })
+ .catch(e => console.error('Failed to load tags:', e));
+}
+
+function filterByTag(tagName) {
+ fetch(`/api/sessions/search?tag=${encodeURIComponent(tagName)}`)
+ .then(r => r.json())
+ .then(data => displaySearchResults(data))
+ .catch(e => console.error('Failed to filter by tag:', e));
+}
+
+function displaySearchResults(data) {
+ // TODO: render search results in UI
+ console.log('Search results:', data);
+}
+
+// Expose
+window.loadTagCloud = loadTagCloud;
+window.filterByTag = filterByTag;
diff --git a/web/backend/dist/tools-skills.js b/web/backend/dist/tools-skills.js
new file mode 100644
index 000000000..c12105c46
--- /dev/null
+++ b/web/backend/dist/tools-skills.js
@@ -0,0 +1,42 @@
+// web/backend/dist/tools-skills.js
+// Tool Skills Panel Controller (~3KB)
+function loadToolSkills() {
+ const panel = document.getElementById('tool-skills-panel');
+ if (!panel) return;
+
+ fetch('/api/tools/skills')
+ .then(r => r.json())
+ .then(skills => {
+ panel.innerHTML = 'Tool Skills
';
+ skills.forEach(skill => {
+ const div = document.createElement('div');
+ div.className = 'skill-item';
+ div.innerHTML = `
+ ${skill.name || 'Unknown'}
+ Used ${skill.usage_count || 0} times
+ ${(skill.tags || []).map(t => `#${t}`).join(' ')}
+ `;
+ panel.appendChild(div);
+ });
+ })
+ .catch(e => console.error('Failed to load tool skills:', e));
+}
+
+async function searchCommunityRegistry(query) {
+ try {
+ const response = await fetch(`/api/tools/registry?q=${encodeURIComponent(query || '')}`);
+ const results = await response.json();
+ displayRegistryResults(results);
+ } catch (e) {
+ console.error('Failed to search registry:', e);
+ }
+}
+
+function displayRegistryResults(results) {
+ console.log('Registry results:', results);
+ // TODO: render results in UI
+}
+
+// Expose
+window.loadToolSkills = loadToolSkills;
+window.searchCommunityRegistry = searchCommunityRegistry;
diff --git a/web/backend/dist/vault.js b/web/backend/dist/vault.js
new file mode 100644
index 000000000..e22903ac3
--- /dev/null
+++ b/web/backend/dist/vault.js
@@ -0,0 +1,64 @@
+// web/backend/dist/vault.js
+// Vault Sidebar Controller (Vanilla JS, ~5KB)
+document.addEventListener('DOMContentLoaded', () => {
+ const sidebar = document.getElementById('vault-sidebar');
+ const toggleBtn = document.getElementById('toggle-sidebar');
+ const closeBtn = document.getElementById('close-sidebar');
+
+ if (toggleBtn) {
+ toggleBtn.addEventListener('click', () => {
+ sidebar.classList.toggle('hidden');
+ if (!sidebar.classList.contains('hidden')) {
+ loadVaultTree();
+ }
+ });
+ }
+
+ if (closeBtn) {
+ closeBtn.addEventListener('click', () => {
+ sidebar.classList.add('hidden');
+ });
+ }
+
+ async function loadVaultTree() {
+ try {
+ const response = await fetch('/api/vault/list');
+ const data = await response.json();
+ renderTree(data.notes || []);
+ } catch (e) {
+ console.error('Failed to load vault tree:', e);
+ }
+ }
+
+ function renderTree(items) {
+ const tree = document.getElementById('vault-tree');
+ if (!tree) return;
+ tree.innerHTML = '';
+ items.forEach(item => {
+ const div = document.createElement('div');
+ div.className = 'tree-item';
+ div.textContent = item.name || item.path || 'Untitled';
+ div.onclick = () => loadNote(item.path || item.name);
+ tree.appendChild(div);
+ });
+ }
+
+ async function loadNote(path) {
+ try {
+ const response = await fetch(`/api/vault/note?path=${encodeURIComponent(path)}`);
+ const data = await response.json();
+ showNoteModal(data);
+ } catch (e) {
+ console.error('Failed to load note:', e);
+ }
+ }
+
+ function showNoteModal(noteData) {
+ // Simple modal implementation
+ alert(`Note: ${noteData.title || 'Note'}\n\n${noteData.content || ''}`);
+ }
+
+ // Expose functions for other scripts
+ window.loadVaultTree = loadVaultTree;
+ window.loadNote = loadNote;
+});