Changeset 0.25.0 (#160)

This commit is contained in:
2026-03-08 16:54:17 +00:00
parent 937be26578
commit 2b01d540d6
63 changed files with 6942 additions and 2773 deletions

View File

@@ -32,7 +32,7 @@
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
Routing
</a>
<a href="{{$base}}/admin/settings" class="admin-cat-btn{{if eq $section "settings"}} active{{else if eq $section "storage"}} active{{else if eq $section "extensions"}} active{{end}}" data-cat="system">
<a href="{{$base}}/admin/settings" class="admin-cat-btn{{if eq $section "settings"}} active{{else if eq $section "storage"}} active{{else if eq $section "extensions"}} active{{else if eq $section "channels"}} active{{else if eq $section "surfaces"}} active{{end}}" data-cat="system">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09"/></svg>
System
</a>
@@ -241,4 +241,5 @@
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-scaffold.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-surfaces.js?v={{.Version}}"></script>
{{end}}

View File

@@ -177,45 +177,20 @@ window.addEventListener('unhandledrejection', function(e) {
{{/* User / bottom */}}
<div class="sidebar-bottom">
{{if .SurfaceEnabled "notes"}}
<button id="notesBtn" class="sb-btn sidebar-notes-btn">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
<span class="sb-label">Notes</span>
</button>
{{/* Editor shortcut */}}
{{end}}
{{if .SurfaceEnabled "editor"}}
<a href="{{.BasePath}}/editor" class="sb-btn sidebar-editor-btn" title="Editor">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
<span class="sb-label">Editor</span>
</a>
{{end}}
<div class="sidebar-bottom-divider"></div>
<button id="userMenuBtn" class="user-btn">
<div id="userAvatar" class="user-avatar">
<span id="avatarLetter">?</span>
</div>
<span id="userName" class="sb-label">User</span>
</button>
<div id="userFlyout" class="user-flyout">
<button id="menuSettings" class="flyout-item">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.32 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span>Settings</span>
</button>
<button id="menuAdmin" class="flyout-item" style="display:none;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<span>Admin</span>
</button>
<button id="menuTeamAdmin" class="flyout-item" style="display:none;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<span>Team Admin</span>
</button>
<button id="menuDebug" class="flyout-item" style="display:none;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/><path d="M12 6V12L16 14"/></svg>
<span>Debug Log</span>
</button>
<hr class="flyout-divider">
<button id="menuSignout" class="flyout-item flyout-danger">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
<span>Sign Out</span>
</button>
</div>
{{template "user-menu" dict "ID" ""}}
</div>
</div>
@@ -233,16 +208,7 @@ window.addEventListener('unhandledrejection', function(e) {
{{/* Chat header: model selector + caps + tokens + panel + notif */}}
<div class="chat-header">
<div id="modelDropdown" class="model-dropdown">
<button id="modelDropdownBtn" class="model-dropdown-btn">
<span id="modelDropdownLabel" class="model-dropdown-label">Select model…</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div id="modelDropdownMenu" class="model-dropdown-menu">
{{/* Populated by UI.updateModelSelector() */}}
</div>
</div>
<span id="modelCaps" class="model-caps"></span>
{{template "model-selector" dict "ID" ""}}
<div class="chat-header-right">
<span id="chatTokenCount" class="token-count" title="Estimated token count"></span>
<span id="summarizedHistory" class="summarized-badge" style="display:none;" title="Earlier messages are summarized">📋 Summarized</span>

View File

@@ -1,127 +1,104 @@
{{/*
Editor surface — matches switchboard-prototype-editor.jsx.
IDE-like layout: topbar + file tree + tabs + code + chat pane.
editor-mode.js builds CodeMirror, file tree, etc.
Editor surface — v0.25.0 rebuild.
Three-pane layout: files | code-editor | <chat, notes>
Uses PaneContainer for resizable splits and tabbed right pane.
Components are SERVER-RENDERED via Go template partials into hidden
containers (#editorComponents). The boot script moves them into
pane slots created by PaneContainer. This ensures full DOM parity
with the chat surface — same buttons, same features, same behavior.
*/}}
{{define "surface-editor"}}
<div class="surface-editor">
<div class="surface-editor" id="editorSurface">
{{/* Top Bar */}}
<div class="editor-topbar">
<a href="{{.BasePath}}/" class="editor-topbar-back">
{{/* ── Top Bar ─────────────────────────── */}}
<div class="editor-topbar" id="editorTopbar">
<a href="{{.BasePath}}/" class="editor-topbar-back" title="Back to chat">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
Back
</a>
<div style="width:1px;height:18px;background:var(--border);"></div>
<div class="editor-topbar-sep"></div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--accent);"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
<span style="font-size:13px;font-weight:600;" id="editorWorkspaceName">
{{if .Data}}{{.Data.WorkspaceName}}{{else}}Editor{{end}}
</span>
<div style="display:flex;align-items:center;gap:4px;background:var(--purple-dim);padding:2px 8px;border-radius:4px;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" stroke-width="2"><circle cx="12" cy="18" r="3"/><circle cx="12" cy="6" r="3"/><line x1="12" y1="9" x2="12" y2="15"/></svg>
<span style="font-size:11px;font-weight:600;color:var(--purple);font-family:var(--mono);">main</span>
</div>
<div style="flex:1;"></div>
<button class="icon-btn" id="editorNewFileBtn" title="New File">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<button class="icon-btn" id="editorRefreshBtn" title="Refresh">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
</button>
<button class="icon-btn" id="editorSearchBtn" title="Search">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</button>
<div style="width:1px;height:18px;background:var(--border);"></div>
<button class="icon-btn" id="editorToggleTree" title="Toggle file tree">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg>
</button>
<button class="icon-btn" id="editorToggleChat" title="Toggle chat">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="15" y1="3" x2="15" y2="21"/></svg>
</button>
</div>
{{/* Body: tree + editor + chat */}}
<div class="editor-body" id="editorBody">
{{/* File Tree */}}
<div class="editor-tree" id="editorFileTree">
<div class="editor-tree-header">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
<span style="font-size:11px;font-weight:600;color:var(--text-2);text-transform:uppercase;letter-spacing:0.4px;flex:1;">Files</span>
<button class="icon-btn" id="treeNewFileBtn" title="New file">
{{/* Workspace selector dropdown */}}
<div class="editor-ws-selector" id="editorWsSelector">
<button class="editor-ws-selector-btn" id="editorWsSelectorBtn">
<span id="editorWorkspaceName">{{if .Data}}{{.Data.WorkspaceName}}{{else}}Editor{{end}}</span>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
</button>
<div class="editor-ws-dropdown" id="editorWsDropdown">
<div id="editorWsList" class="editor-ws-list">
{{/* Populated by JS */}}
</div>
<div class="editor-ws-dropdown-divider"></div>
<button class="editor-ws-dropdown-item editor-ws-new" id="editorWsNewBtn">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
New Workspace
</button>
</div>
<div class="editor-tree-files" id="editorTreeFiles">
{{/* Populated by editor-mode.js */}}
</div>
</div>
{{/* Main Editor Area */}}
<div class="editor-main" id="editorMain">
{{/* Tabs */}}
<div class="editor-tabs" id="editorTabs">
{{/* Populated by editor-mode.js */}}
</div>
{{/* Code Content */}}
<div class="editor-content" id="editorContent">
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-3);">
<div style="text-align:center;">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="opacity:0.12;display:inline-block;margin-bottom:10px;"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>
<p style="margin:0;font-size:14px;">Open a file to start editing</p>
<p style="margin:6px 0 0;font-size:12px;">Select from the file tree or Ctrl+P</p>
</div>
</div>
</div>
{{/* Status Bar */}}
<div class="editor-statusbar" id="editorStatusbar">
<span>Ready</span>
</div>
<div class="editor-topbar-branch" id="editorBranchBadge" style="display:none;">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" stroke-width="2"><circle cx="12" cy="18" r="3"/><circle cx="12" cy="6" r="3"/><line x1="12" y1="9" x2="12" y2="15"/></svg>
<span id="editorBranchName" class="editor-topbar-branch-text">main</span>
</div>
<div style="flex:1;"></div>
<button class="icon-btn" id="editorRefreshBtn" title="Refresh files">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
</button>
{{template "user-menu" dict "ID" ""}}
</div>
{{/* Chat Pane (resizable) */}}
<div id="editorSplitHandle" style="width:5px;background:var(--border);cursor:col-resize;flex-shrink:0;display:none;"></div>
<div class="editor-chat" id="editorChatPane" style="display:none;">
<div class="editor-chat-header">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
<span style="font-size:12px;font-weight:600;flex:1;">AI Chat</span>
<span class="badge badge-accent" id="editorChatModel">claude-sonnet-4-5</span>
{{/* ── Pane Body ───────────────────────── */}}
<div class="editor-body" id="editorBody"></div>
{{/* ── Bootstrap (no workspace) ────────── */}}
<div class="editor-bootstrap" id="editorBootstrap" style="display:none;">
<div class="editor-bootstrap-card">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="1.5" style="opacity:0.6;margin-bottom:12px;">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
</svg>
<h3 style="margin:0 0 16px;font-size:16px;">Open a Workspace</h3>
<div id="editorBootstrapList" style="margin-bottom:16px;">
<div style="font-size:12px;color:var(--text-3);">Loading workspaces…</div>
</div>
<div class="editor-chat-messages" id="editorChatMessages">
{{/* Populated by editor-mode.js */}}
</div>
<div class="editor-chat-input">
<div style="display:flex;gap:8px;align-items:flex-end;">
<textarea id="editorChatInput" placeholder="Ask about this code…" rows="2"
style="flex:1;background:var(--input-bg,var(--bg));border:1px solid var(--border);color:var(--text);padding:8px 10px;border-radius:8px;font-size:12px;font-family:inherit;outline:none;resize:none;"></textarea>
<button id="editorChatSendBtn" style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;">Send</button>
</div>
<div style="display:flex;gap:6px;margin-top:6px;font-size:10px;color:var(--text-3);">
<span id="editorChatContext">Context: none</span>
<span>&middot;</span>
<span>Cmd+L to focus</span>
</div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="flex:1;height:1px;background:var(--border);"></div>
<span style="font-size:11px;color:var(--text-3);text-transform:uppercase;">or create new</span>
<div style="flex:1;height:1px;background:var(--border);"></div>
</div>
<input type="text" id="editorBootstrapName" class="editor-bootstrap-input" placeholder="Workspace name" value="workspace">
<button id="editorBootstrapBtn" class="editor-bootstrap-btn">Create Workspace</button>
</div>
</div>
{{/* ── Server-Rendered Components ──────── */}}
{{/* Hidden until moved into pane slots by editor-surface.js.
Full DOM from Go template partials = feature parity with chat surface. */}}
<div id="editorComponents" style="display:none;">
{{template "file-tree" dict "ID" "ed"}}
{{template "code-editor" dict "ID" "ed"}}
{{template "chat-pane" dict "ID" "ed"}}
{{template "note-editor" dict "ID" "edNotes"}}
</div>
</div>
<div id="toastContainer" class="toast-container"></div>
{{end}}
{{/* CSS: loaded via base.html (variables, layout, primitives, modals, chat, panels, surfaces, splash) */}}
{{/* ── CSS ─────────────────────────────────── */}}
{{define "css-editor"}}
<link rel="stylesheet" href="{{.BasePath}}/css/editor-mode.css?v={{.Version}}">
<link rel="stylesheet" href="{{.BasePath}}/css/editor-surface.css?v={{.Version}}">
{{end}}
{{/* Scripts */}}
{{/* ── Scripts ─────────────────────────────── */}}
{{define "scripts-editor"}}
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/vendor/marked.min.js"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/vendor/purify.min.js"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/vendor/codemirror/codemirror.bundle.js?v={{.Version}}" onerror="console.warn('[CM6] Bundle not available')"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-format.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/editor-mode.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/chat-pane.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/editor-surface.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app.js?v={{.Version}}"></script>
{{end}}