Changeset 0.28.3.2 (#189)

This commit is contained in:
2026-03-14 16:46:16 +00:00
parent 205a770c74
commit fa6b04434a
66 changed files with 304 additions and 320 deletions

View File

@@ -1,5 +1,114 @@
# Changelog # Changelog
## [0.28.3.5] — 2026-03-14
### Summary
Frontend decomposition Phase 4: ES module conversion. IIFE wrappers
removed from all 47 JS files, `<script type="module">` applied across
all templates. `sb.js` and vendor libs stay as classic scripts to
guarantee availability before modules execute.
### Changed
#### Phase 4 — IIFE Removal
- Removed `(function() { 'use strict'; ... })();` wrappers from 45
standard-pattern JS files (3 lines per file: open, strict, close).
- Removed `'use strict';` from 2 arrow-IIFE files (`knowledge-ui.js`,
`tools-toggle.js`) — arrow IIFE retained (const binding pattern,
harmless in module scope).
- Removed leftover `'use strict';` from `persona-kb.js` (not caught
by the main script due to blank line before directive).
- Inner IIFEs preserved: `projects-ui.js` (visibility timer),
`ui-primitives.js` (Providers/Roles factory constructors).
- All 48 JS files pass `node --check` syntax validation.
#### Phase 4 — Template Module Conversion
- All `<script src=".../js/*.js">` tags converted to
`<script type="module" src="...">` across 6 template files:
`base.html`, `surfaces/chat.html`, `surfaces/admin.html`,
`surfaces/editor.html`, `surfaces/notes.html`,
`surfaces/settings.html`.
- Classic scripts retained for: `sb.js` (must be globally available
before any module), vendor libs (`marked.min.js`, `purify.min.js`,
`codemirror.bundle.js`), early theme script, `__BASE__`/`__VERSION__`
hydration.
- 3 inline scripts in `base.html` converted to `type="module"`:
`API.loadTokens()`, Theme/appearance init + `handleLogout` fallback,
UserMenu hydration.
- `handleLogout` fallback: added `sb.register('handleLogout', handleLogout)`
since function is now module-scoped (no longer on `window`).
- UserMenu hydration: `handleLogout()``sb.call('handleLogout')`,
`openDebugModal()``sb.call('openDebugModal')`.
- Extension surface `main.js``type="module"`.
- DOMContentLoaded inline scripts in surface templates stay classic
(safe — modules execute before DOMContentLoaded fires).
### Fixed
- CI: frontend test harness (`helpers.js`) now loads `sb.js` into VM
context before other source files. Fixes 55 extension test failures
(`ReferenceError: sb is not defined` at `events.js:334`).
- `extensions.test.js`, `extensions-builtin.test.js`: `loadExtensions()`
prepends `sb.js` load. Result: 219/219 tests pass.
### Not Changed
- `sb.register()`/`sb.ns()` dual-write to `window[name]` stays —
cross-file JS references (`API.listProjects()`, `UI.toast()`) still
go through `window.*`. Removal requires `import`/`export` statements
(Phase 5, future).
- `login.html`, `workflow.html`, `workflow-landing.html` unchanged
(standalone pages, no `sb.js`).
## [0.28.3.3] — 2026-03-14
### Summary
CI fix: frontend test harness missing `sb.js` in VM context. All 55
extension test failures were the same root cause — `events.js` calls
`sb.ns('Events', Events)` but the test VM never loaded `sb.js`.
### Fixed
- `helpers.js`: `loadAppModules()` now loads `sb.js` before `app-state.js`
(defensive — not currently broken but would fail if any passing test
switched to using `loadAppModules` with source files that call `sb.*`).
- `extensions.test.js`: `loadExtensions()` loads `sb.js` before `events.js`.
- `extensions-builtin.test.js`: same fix.
**Result:** 219/219 tests pass (was 164/219).
## [0.28.3.2] — 2026-03-14
### Summary
Frontend decomposition Phase 3b: Go template `onclick``sb.call()`
migration. All server-rendered onclick handlers in templates that load
`sb.js` (via `base.html`) now route through the action registry. This
clears the template gate for Phase 4 (ES module conversion).
### Changed
#### Phase 3b — Go Template onclick → sb.call()
- Converted 72 `onclick="fn(args)"` handlers across 10 Go template
files to use `sb.call('fn', args)` or `sb.callEvent(event, 'fn', args)`.
- Templates converted: `base.html` (10), `surfaces/chat.html` (28),
`surfaces/admin.html` (14), `surfaces/settings.html` (3),
`admin/providers.html` (5), `admin/users.html` (3),
`admin/roles.html` (1), `admin/routing.html` (3),
`admin/teams.html` (4), `admin/settings.html` (1).
- 4 onclick handlers intentionally left unconverted (inline DOM
manipulation or Go template variable in element ID):
- `this.parentElement.style.display='none'` (crash banner dismiss)
- `event.stopPropagation()` (bare container click guard)
- `document.getElementById('settingsTeamAddMember').style.display='none'`
- `document.getElementById('{{.FieldName}}Input').click()`
- 3 standalone pages excluded (no `sb.js`): `login.html`,
`workflow.html`, `workflow-landing.html`.
- Defensive `typeof` guards in `settings.html` onclick handlers
(e.g. `if(typeof UI!=='undefined')UI.saveAppearance?.()`) replaced
with clean `sb.call()` — registry silently logs unresolved actions.
- `sb.js` comments updated: template gate cleared, dual-write removal
deferred to Phase 4 (cross-file JS references still use `window.*`).
## [0.28.3.1] — 2026-03-14 ## [0.28.3.1] — 2026-03-14
### Summary ### Summary

View File

@@ -1 +1 @@
0.28.3.1 0.28.3.5

View File

@@ -25,7 +25,7 @@ v0.9.xv0.27.5 Foundation → Extensions → Surfaces → Auth ✅
v0.28.0 Platform Polish v0.28.0 Platform Polish
├─ v0.28.1 Surfaces ICD audit ✅ ├─ v0.28.1 Surfaces ICD audit ✅
├─ v0.28.2 ICD audit — all domains ✅ ├─ v0.28.2 ICD audit — all domains ✅
├─ v0.28.3 ICD close-out + FE decomp prep ├─ v0.28.3 ICD close-out + FE decomp
├─ v0.28.4 Security tier (red team) ├─ v0.28.4 Security tier (red team)
├─ v0.28.5 Infrastructure ├─ v0.28.5 Infrastructure
│ (virtual scroll, KB auto-inject, │ (virtual scroll, KB auto-inject,
@@ -179,8 +179,14 @@ decomposition groundwork.
- [x] Phase 3: Action registry (`sb.js`) — 248 `window.*` exports replaced with - [x] Phase 3: Action registry (`sb.js`) — 248 `window.*` exports replaced with
`sb.register()`/`sb.ns()`. `sb.resolve()` centralized dispatch. `sb.register()`/`sb.ns()`. `sb.resolve()` centralized dispatch.
`sb.call()` template bridge ready for Go template migration. `sb.call()` template bridge ready for Go template migration.
- [ ] Phase 3b: Go template onclick → `sb.call()` migration (~50 handlers) - [x] Phase 3b: Go template onclick → `sb.call()` migration (72 handlers
- [ ] Phase 4: ES module conversion (remove IIFE wrappers, add import/export) across 10 templates, 4 unconvertible inline DOM ops, 3 standalone
pages excluded)
- [x] Phase 4: ES module conversion — IIFE wrappers removed from all 47
files, `<script type="module">` across all templates. `sb.js` +
vendor stay classic. CI test harness fixed (sb.js in VM context).
- [ ] Phase 5 (future): `import`/`export` statements, remove `window[name]`
dual-write from `sb.register()`/`sb.ns()`
### v0.28.4 — Security Tier (ICD Runner Red Team) ### v0.28.4 — Security Tier (ICD Runner Red Team)
New `security` tier in ICD test runner. Multi-user fixtures already exist. New `security` tier in ICD test runner. Multi-user fixtures already exist.

View File

@@ -6,7 +6,7 @@
<p class="section-hint">AI provider configurations. Each provider connects to an API endpoint.</p> <p class="section-hint">AI provider configurations. Each provider connects to an API endpoint.</p>
<div class="admin-toolbar"> <div class="admin-toolbar">
<button class="btn-small btn-primary" onclick="Pages.showProviderForm()">+ Add Provider</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.showProviderForm')">+ Add Provider</button>
</div> </div>
<div id="providerFormWrap" style="display:none;"> <div id="providerFormWrap" style="display:none;">
@@ -34,8 +34,8 @@
<td>{{.ModelCount}}</td> <td>{{.ModelCount}}</td>
<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-secondary);">{{.Endpoint}}</td> <td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-secondary);">{{.Endpoint}}</td>
<td> <td>
<button class="btn-small" onclick="Pages.syncProvider('{{.ID}}')">Sync</button> <button class="btn-small" onclick="sb.call('Pages.syncProvider','{{.ID}}')">Sync</button>
<button class="btn-small" onclick="Pages.editProvider('{{.ID}}')">Edit</button> <button class="btn-small" onclick="sb.call('Pages.editProvider','{{.ID}}')">Edit</button>
</td> </td>
</tr> </tr>
{{end}} {{end}}
@@ -90,8 +90,8 @@
</div> </div>
</div> </div>
<div style="display:flex;gap:8px;margin-top:8px;"> <div style="display:flex;gap:8px;margin-top:8px;">
<button class="btn-small btn-primary" onclick="Pages.saveProvider()">Save</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.saveProvider')">Save</button>
<button class="btn-small" onclick="Pages.hideProviderForm()">Cancel</button> <button class="btn-small" onclick="sb.call('Pages.hideProviderForm')">Cancel</button>
</div> </div>
</div> </div>
{{end}} {{end}}

View File

@@ -86,7 +86,7 @@
</div> </div>
</div> </div>
<button class="btn-small btn-primary" onclick="Pages.saveRole('{{$roleName}}')">Save</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.saveRole','{{$roleName}}')">Save</button>
</div> </div>
</section> </section>
{{end}} {{end}}

View File

@@ -8,7 +8,7 @@
<p class="section-hint">Define rules for model selection, cost limits, and team-specific routing.</p> <p class="section-hint">Define rules for model selection, cost limits, and team-specific routing.</p>
<div style="margin-bottom:12px"> <div style="margin-bottom:12px">
<button class="btn-small btn-primary" onclick="Pages.showRoutingForm()">+ New Policy</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.showRoutingForm')">+ New Policy</button>
</div> </div>
<div id="routingPolicyForm" style="display:none" class="admin-inline-form"> <div id="routingPolicyForm" style="display:none" class="admin-inline-form">
@@ -66,8 +66,8 @@
</label> </label>
</div> </div>
<div class="form-row" style="gap:8px;margin-top:8px"> <div class="form-row" style="gap:8px;margin-top:8px">
<button class="btn-small btn-primary" onclick="Pages.saveRoutingPolicy()">Save</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.saveRoutingPolicy')">Save</button>
<button class="btn-small" onclick="Pages.hideRoutingForm()">Cancel</button> <button class="btn-small" onclick="sb.call('Pages.hideRoutingForm')">Cancel</button>
</div> </div>
</div> </div>

View File

@@ -77,7 +77,7 @@
</div> </div>
<div style="margin-top:16px;"> <div style="margin-top:16px;">
<button class="btn-small btn-primary" onclick="Pages.saveSettings()">Save Settings</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.saveSettings')">Save Settings</button>
</div> </div>
</div> </div>
{{end}} {{end}}

View File

@@ -6,7 +6,7 @@
<p class="section-hint">Organize users into teams for scoped access to providers, personas, and policies.</p> <p class="section-hint">Organize users into teams for scoped access to providers, personas, and policies.</p>
<div class="admin-toolbar"> <div class="admin-toolbar">
<button class="btn-small btn-primary" onclick="Pages.showTeamForm()">+ Create Team</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.showTeamForm')">+ Create Team</button>
</div> </div>
<div id="teamFormWrap" style="display:none;"> <div id="teamFormWrap" style="display:none;">
@@ -24,8 +24,8 @@
</div> </div>
</div> </div>
<div style="display:flex;gap:8px;margin-top:8px;"> <div style="display:flex;gap:8px;margin-top:8px;">
<button class="btn-small btn-primary" onclick="Pages.saveTeam()">Save</button> <button class="btn-small btn-primary" onclick="sb.call('Pages.saveTeam')">Save</button>
<button class="btn-small" onclick="Pages.hideTeamForm()">Cancel</button> <button class="btn-small" onclick="sb.call('Pages.hideTeamForm')">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@@ -49,7 +49,7 @@
<td>{{.MemberCount}}</td> <td>{{.MemberCount}}</td>
<td><span class="admin-badge admin-badge-{{if .IsActive}}active{{else}}inactive{{end}}">{{if .IsActive}}active{{else}}inactive{{end}}</span></td> <td><span class="admin-badge admin-badge-{{if .IsActive}}active{{else}}inactive{{end}}">{{if .IsActive}}active{{else}}inactive{{end}}</span></td>
<td> <td>
<button class="btn-small" onclick="Pages.editTeam('{{.ID}}','{{.Name}}','{{.Description}}')">Edit</button> <button class="btn-small" onclick="sb.call('Pages.editTeam','{{.ID}}','{{.Name}}','{{.Description}}')">Edit</button>
</td> </td>
</tr> </tr>
{{end}} {{end}}

View File

@@ -30,11 +30,11 @@
<td><span class="admin-badge admin-badge-{{.Role}}">{{.Role}}</span></td> <td><span class="admin-badge admin-badge-{{.Role}}">{{.Role}}</span></td>
<td><span class="admin-badge admin-badge-{{if .IsActive}}active{{else}}inactive{{end}}">{{if .IsActive}}active{{else}}inactive{{end}}</span></td> <td><span class="admin-badge admin-badge-{{if .IsActive}}active{{else}}inactive{{end}}">{{if .IsActive}}active{{else}}inactive{{end}}</span></td>
<td> <td>
<button class="btn-small" onclick="Pages.editUserRole('{{.ID}}','{{.Username}}','{{.Role}}')">Role</button> <button class="btn-small" onclick="sb.call('Pages.editUserRole','{{.ID}}','{{.Username}}','{{.Role}}')">Role</button>
{{if .IsActive}} {{if .IsActive}}
<button class="btn-small" onclick="Pages.toggleUser('{{.ID}}',false)">Disable</button> <button class="btn-small" onclick="sb.call('Pages.toggleUser','{{.ID}}',false)">Disable</button>
{{else}} {{else}}
<button class="btn-small" onclick="Pages.toggleUser('{{.ID}}',true)">Enable</button> <button class="btn-small" onclick="sb.call('Pages.toggleUser','{{.ID}}',true)">Enable</button>
{{end}} {{end}}
</td> </td>
</tr> </tr>

View File

@@ -92,30 +92,30 @@
</script> </script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/sb.js?v={{.Version}}"></script> <script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/sb.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app-state.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app-state.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/api.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/api.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}"> <script type="module" nonce="{{.CSPNonce}}">
// Hydrate auth tokens for all surfaces (chat surface also calls this in init) // Hydrate auth tokens for all surfaces (chat surface also calls this in init)
API.loadTokens(); API.loadTokens();
</script> </script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/events.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/events.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-primitives.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-primitives.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/vendor/marked.min.js"></script> <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/purify.min.js"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-format.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-format.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-core.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-core.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/pages.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/pages.js?v={{.Version}}"></script>
{{/* v0.25.0: Component scripts — available on all surfaces */}} {{/* v0.25.0: Component scripts — available on all surfaces */}}
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/user-menu.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/user-menu.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/model-selector.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/model-selector.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/file-tree.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/file-tree.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/code-editor.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/code-editor.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/note-editor.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/note-editor.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/drag-resize.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/drag-resize.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/pane-container.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/pane-container.js?v={{.Version}}"></script>
{{/* ── Universal init: theme + appearance for ALL surfaces ── */}} {{/* ── Universal init: theme + appearance for ALL surfaces ── */}}
<script nonce="{{.CSPNonce}}"> <script type="module" nonce="{{.CSPNonce}}">
// Theme must init on every surface, not just chat. // Theme must init on every surface, not just chat.
if (typeof Theme !== 'undefined') Theme.init(); if (typeof Theme !== 'undefined') Theme.init();
// Appearance (zoom + font size) must restore on every surface. // Appearance (zoom + font size) must restore on every surface.
@@ -131,6 +131,8 @@
if (typeof API !== 'undefined' && API.logout) API.logout(); if (typeof API !== 'undefined' && API.logout) API.logout();
location.reload(); location.reload();
} }
// Register fallback — app.js re-registers with a richer version.
sb.register('handleLogout', handleLogout);
</script> </script>
{{if eq .Surface "chat"}}{{template "scripts-chat" .}}{{end}} {{if eq .Surface "chat"}}{{template "scripts-chat" .}}{{end}}
@@ -140,7 +142,7 @@
{{if eq .Surface "settings"}}{{template "scripts-settings" .}}{{end}} {{if eq .Surface "settings"}}{{template "scripts-settings" .}}{{end}}
{{/* v0.27.0: Extension surface JS — loaded from /surfaces/{id}/js/main.js */}} {{/* v0.27.0: Extension surface JS — loaded from /surfaces/{id}/js/main.js */}}
{{if and .Manifest (eq .Manifest.Source "extension")}} {{if and .Manifest (eq .Manifest.Source "extension")}}
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/surfaces/{{.Surface}}/js/main.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/surfaces/{{.Surface}}/js/main.js?v={{.Version}}"></script>
{{end}} {{end}}
{{/* ── v0.28.0: Universal UserMenu hydration ────────────────── {{/* ── v0.28.0: Universal UserMenu hydration ──────────────────
@@ -151,29 +153,28 @@
richer behavior (team admin refresh). Chat migration to UserMenu richer behavior (team admin refresh). Chat migration to UserMenu
component is tracked as tech debt (Pre-1.0 sweep). component is tracked as tech debt (Pre-1.0 sweep).
*/}} */}}
<script nonce="{{.CSPNonce}}"> <script type="module" nonce="{{.CSPNonce}}">
(function () { if (typeof UserMenu === 'undefined') { /* noop */ }
if (typeof UserMenu === 'undefined') return; else if (UserMenu.primary) { /* noop */ }
if (UserMenu.primary) return; else if ((window.__SURFACE__ || '') === 'chat') { /* noop */ }
if ((window.__SURFACE__ || '') === 'chat') return; else {
var btn = document.getElementById('userMenuBtn'); var btn = document.getElementById('userMenuBtn');
if (!btn) return; if (btn) {
var menu = UserMenu.create({ id: '' });
UserMenu.primary = menu;
menu.setUser(window.__USER__ || {});
var menu = UserMenu.create({ id: '' }); var user = window.__USER__ || {};
UserMenu.primary = menu; menu.showAdmin(user.role === 'admin');
menu.setUser(window.__USER__ || {});
var user = window.__USER__ || {}; menu.bind({
menu.showAdmin(user.role === 'admin'); onSettings: function () { window.location.href = (window.__BASE__ || '') + '/settings'; },
onAdmin: function () { window.location.href = (window.__BASE__ || '') + '/admin'; },
menu.bind({ onDebug: function () { sb.call('openDebugModal'); },
onSettings: function () { window.location.href = (window.__BASE__ || '') + '/settings'; }, onSignout: function () { sb.call('handleLogout'); },
onAdmin: function () { window.location.href = (window.__BASE__ || '') + '/admin'; }, });
onDebug: function () { if (typeof openDebugModal === 'function') openDebugModal(); }, }
onSignout: function () { handleLogout(); }, }
});
})();
</script> </script>
{{/* ── Debug Log Modal (all surfaces) ───── */}} {{/* ── Debug Log Modal (all surfaces) ───── */}}
@@ -181,13 +182,13 @@
<div class="modal modal-wide"> <div class="modal modal-wide">
<div class="modal-header"> <div class="modal-header">
<h2>Debug Log</h2> <h2>Debug Log</h2>
<button class="modal-close" onclick="closeModal('debugModal')"></button> <button class="modal-close" onclick="sb.call('closeModal','debugModal')"></button>
</div> </div>
<div class="modal-tabs"> <div class="modal-tabs">
<button class="debug-tab active" data-tab="console" onclick="switchDebugTab('console')">Console <span id="debugConsoleCount" class="badge">0</span></button> <button class="debug-tab active" data-tab="console" onclick="sb.call('switchDebugTab','console')">Console <span id="debugConsoleCount" class="badge">0</span></button>
<button class="debug-tab" data-tab="network" onclick="switchDebugTab('network')">Network <span id="debugNetworkCount" class="badge">0</span></button> <button class="debug-tab" data-tab="network" onclick="sb.call('switchDebugTab','network')">Network <span id="debugNetworkCount" class="badge">0</span></button>
<button class="debug-tab" data-tab="state" onclick="switchDebugTab('state')">State</button> <button class="debug-tab" data-tab="state" onclick="sb.call('switchDebugTab','state')">State</button>
<button class="debug-tab" data-tab="repl" onclick="switchDebugTab('repl')">REPL</button> <button class="debug-tab" data-tab="repl" onclick="sb.call('switchDebugTab','repl')">REPL</button>
</div> </div>
<div class="modal-body" style="padding:0;overflow:hidden;display:flex;flex-direction:column;min-height:0;"> <div class="modal-body" style="padding:0;overflow:hidden;display:flex;flex-direction:column;min-height:0;">
<div id="debugConsoleTab" class="debug-tab-content" style="display:flex;flex-direction:column;flex:1;min-height:0;"> <div id="debugConsoleTab" class="debug-tab-content" style="display:flex;flex-direction:column;flex:1;min-height:0;">
@@ -212,19 +213,19 @@
</div> </div>
<div class="modal-footer" style="display:flex;justify-content:space-between;align-items:center;"> <div class="modal-footer" style="display:flex;justify-content:space-between;align-items:center;">
<div style="display:flex;gap:8px;"> <div style="display:flex;gap:8px;">
<button class="btn-danger btn-small" onclick="runDebugDiagnostics()">Diagnostics</button> <button class="btn-danger btn-small" onclick="sb.call('runDebugDiagnostics')">Diagnostics</button>
<button class="btn-danger btn-small" onclick="purgeCache()">Purge Cache</button> <button class="btn-danger btn-small" onclick="sb.call('purgeCache')">Purge Cache</button>
</div> </div>
<div style="display:flex;gap:8px;"> <div style="display:flex;gap:8px;">
<button class="btn-secondary btn-small" onclick="clearDebugLog()">Clear</button> <button class="btn-secondary btn-small" onclick="sb.call('clearDebugLog')">Clear</button>
<button class="btn-secondary btn-small" onclick="copyDebugLog()">Copy</button> <button class="btn-secondary btn-small" onclick="sb.call('copyDebugLog')">Copy</button>
<button class="btn-secondary btn-small" onclick="exportDebugLog()">Export</button> <button class="btn-secondary btn-small" onclick="sb.call('exportDebugLog')">Export</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/debug.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/debug.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/repl.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/repl.js?v={{.Version}}"></script>
<div id="toastContainer" class="toast-container"></div> <div id="toastContainer" class="toast-container"></div>
</body> </body>
</html> </html>

View File

@@ -78,7 +78,7 @@
<div class="modal" style="max-width:440px;"> <div class="modal" style="max-width:440px;">
<div class="modal-header"> <div class="modal-header">
<h2>Create User</h2> <h2>Create User</h2>
<button class="modal-close" onclick="closeModal('createUserModal')">&#10005;</button> <button class="modal-close" onclick="sb.call('closeModal','createUserModal')">&#10005;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"><label>Username</label><input type="text" id="adminNewUsername" placeholder="username" autocomplete="off"></div> <div class="form-group"><label>Username</label><input type="text" id="adminNewUsername" placeholder="username" autocomplete="off"></div>
@@ -87,7 +87,7 @@
<div class="form-group"><label>Role</label><select id="adminNewRole"><option value="user">User</option><option value="admin">Admin</option></select></div> <div class="form-group"><label>Role</label><select id="adminNewRole"><option value="user">User</option><option value="admin">Admin</option></select></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-md btn-ghost" onclick="closeModal('createUserModal')">Cancel</button> <button class="btn-md btn-ghost" onclick="sb.call('closeModal','createUserModal')">Cancel</button>
<button class="btn-md btn-primary" id="adminCreateUserBtn">Create User</button> <button class="btn-md btn-primary" id="adminCreateUserBtn">Create User</button>
</div> </div>
</div> </div>
@@ -98,7 +98,7 @@
<div class="modal" style="max-width:440px;"> <div class="modal" style="max-width:440px;">
<div class="modal-header"> <div class="modal-header">
<h2>Reset Password</h2> <h2>Reset Password</h2>
<button class="modal-close" onclick="closeModal('resetPwModal')">&#10005;</button> <button class="modal-close" onclick="sb.call('closeModal','resetPwModal')">&#10005;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p id="resetPwUser" style="font-weight:600;margin-bottom:8px;"></p> <p id="resetPwUser" style="font-weight:600;margin-bottom:8px;"></p>
@@ -108,7 +108,7 @@
<div class="form-group"><label>New Password</label><input type="password" id="resetPwInput" placeholder="min 8 chars" autocomplete="new-password"></div> <div class="form-group"><label>New Password</label><input type="password" id="resetPwInput" placeholder="min 8 chars" autocomplete="new-password"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-md btn-ghost" onclick="closeModal('resetPwModal')">Cancel</button> <button class="btn-md btn-ghost" onclick="sb.call('closeModal','resetPwModal')">Cancel</button>
<button class="btn-md btn-danger" id="resetPwConfirmBtn">Reset Password & Destroy Vault</button> <button class="btn-md btn-danger" id="resetPwConfirmBtn">Reset Password & Destroy Vault</button>
</div> </div>
</div> </div>
@@ -119,14 +119,14 @@
<div class="modal" style="max-width:440px;"> <div class="modal" style="max-width:440px;">
<div class="modal-header"> <div class="modal-header">
<h2 id="approveUserTitle">Approve User</h2> <h2 id="approveUserTitle">Approve User</h2>
<button class="modal-close" onclick="closeModal('approveUserModal')">&#10005;</button> <button class="modal-close" onclick="sb.call('closeModal','approveUserModal')">&#10005;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<input type="hidden" id="approveUserId"> <input type="hidden" id="approveUserId">
<div id="approveTeamsList"></div> <div id="approveTeamsList"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-md btn-ghost" onclick="closeModal('approveUserModal')">Cancel</button> <button class="btn-md btn-ghost" onclick="sb.call('closeModal','approveUserModal')">Cancel</button>
<button class="btn-md btn-primary" id="approveUserSubmitBtn">Activate User</button> <button class="btn-md btn-primary" id="approveUserSubmitBtn">Activate User</button>
</div> </div>
</div> </div>
@@ -137,14 +137,14 @@
<div class="modal" style="max-width:440px;"> <div class="modal" style="max-width:440px;">
<div class="modal-header"> <div class="modal-header">
<h2>Create Team</h2> <h2>Create Team</h2>
<button class="modal-close" onclick="closeModal('createTeamModal')">&#10005;</button> <button class="modal-close" onclick="sb.call('closeModal','createTeamModal')">&#10005;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"><label>Name</label><input type="text" id="adminTeamName" placeholder="Team name"></div> <div class="form-group"><label>Name</label><input type="text" id="adminTeamName" placeholder="Team name"></div>
<div class="form-group"><label>Description</label><input type="text" id="adminTeamDesc" placeholder="Optional description"></div> <div class="form-group"><label>Description</label><input type="text" id="adminTeamDesc" placeholder="Optional description"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-md btn-ghost" onclick="closeModal('createTeamModal')">Cancel</button> <button class="btn-md btn-ghost" onclick="sb.call('closeModal','createTeamModal')">Cancel</button>
<button class="btn-md btn-primary" id="adminCreateTeamBtn">Create Team</button> <button class="btn-md btn-primary" id="adminCreateTeamBtn">Create Team</button>
</div> </div>
</div> </div>
@@ -155,7 +155,7 @@
<div class="modal" style="max-width:440px;"> <div class="modal" style="max-width:440px;">
<div class="modal-header"> <div class="modal-header">
<h2>Create Group</h2> <h2>Create Group</h2>
<button class="modal-close" onclick="closeModal('createGroupModal')">&#10005;</button> <button class="modal-close" onclick="sb.call('closeModal','createGroupModal')">&#10005;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"><label>Name</label><input type="text" id="adminGroupName" placeholder="Group name"></div> <div class="form-group"><label>Name</label><input type="text" id="adminGroupName" placeholder="Group name"></div>
@@ -166,7 +166,7 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-md btn-ghost" onclick="closeModal('createGroupModal')">Cancel</button> <button class="btn-md btn-ghost" onclick="sb.call('closeModal','createGroupModal')">Cancel</button>
<button class="btn-md btn-primary" id="adminCreateGroupBtn">Create Group</button> <button class="btn-md btn-primary" id="adminCreateGroupBtn">Create Group</button>
</div> </div>
</div> </div>
@@ -177,11 +177,11 @@
<div class="modal" style="max-width:520px;"> <div class="modal" style="max-width:520px;">
<div class="modal-header"> <div class="modal-header">
<h2 id="providerFormTitle">Add Provider</h2> <h2 id="providerFormTitle">Add Provider</h2>
<button class="modal-close" onclick="closeModal('providerFormModal')">&#10005;</button> <button class="modal-close" onclick="sb.call('closeModal','providerFormModal')">&#10005;</button>
</div> </div>
<div class="modal-body" id="adminAddProviderForm"></div> <div class="modal-body" id="adminAddProviderForm"></div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-md btn-ghost" onclick="closeModal('providerFormModal')">Cancel</button> <button class="btn-md btn-ghost" onclick="sb.call('closeModal','providerFormModal')">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@@ -191,11 +191,11 @@
<div class="modal" style="max-width:520px;"> <div class="modal" style="max-width:520px;">
<div class="modal-header"> <div class="modal-header">
<h2 id="personaFormTitle">Create Persona</h2> <h2 id="personaFormTitle">Create Persona</h2>
<button class="modal-close" onclick="closeModal('personaFormModal')">&#10005;</button> <button class="modal-close" onclick="sb.call('closeModal','personaFormModal')">&#10005;</button>
</div> </div>
<div class="modal-body" id="adminAddPersonaForm"></div> <div class="modal-body" id="adminAddPersonaForm"></div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-md btn-ghost" onclick="closeModal('personaFormModal')">Cancel</button> <button class="btn-md btn-ghost" onclick="sb.call('closeModal','personaFormModal')">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@@ -206,20 +206,20 @@
{{/* Scripts */}} {{/* Scripts */}}
{{define "scripts-admin"}} {{define "scripts-admin"}}
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-settings.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-settings.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-admin.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-admin.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-handlers.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-handlers.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/settings-handlers.js?v={{.Version}}"></script> <script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/settings-handlers.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/knowledge-ui.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/knowledge-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/persona-kb.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/persona-kb.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/files.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/files.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-scaffold.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-scaffold.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/workflow-api.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/workflow-api.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/workflow-admin.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/workflow-admin.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/task-admin.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/task-admin.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-surfaces.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/admin-surfaces.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}"> <script nonce="{{.CSPNonce}}">
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// ── Admin navigation: don't pollute browser history ── // ── Admin navigation: don't pollute browser history ──

View File

@@ -25,19 +25,19 @@
<div class="splash-auth"> <div class="splash-auth">
<div id="splashError" class="splash-error" style="display:none;"></div> <div id="splashError" class="splash-error" style="display:none;"></div>
<div class="auth-tabs"> <div class="auth-tabs">
<button id="authTabLogin" class="auth-tab active" onclick="switchAuthTab('login')">Sign In</button> <button id="authTabLogin" class="auth-tab active" onclick="sb.call('switchAuthTab','login')">Sign In</button>
<button id="authTabRegister" class="auth-tab" onclick="switchAuthTab('register')">Register</button> <button id="authTabRegister" class="auth-tab" onclick="sb.call('switchAuthTab','register')">Register</button>
</div> </div>
<div id="authLoginForm"> <div id="authLoginForm">
<input type="text" id="authLogin" placeholder="Username or email" autocomplete="username"> <input type="text" id="authLogin" placeholder="Username or email" autocomplete="username">
<input type="password" id="authPassword" placeholder="Password" autocomplete="current-password"> <input type="password" id="authPassword" placeholder="Password" autocomplete="current-password">
<button id="authLoginBtn" onclick="handleLogin()">Sign In</button> <button id="authLoginBtn" onclick="sb.call('handleLogin')">Sign In</button>
</div> </div>
<div id="authRegisterForm" style="display:none;"> <div id="authRegisterForm" style="display:none;">
<input type="text" id="authUsername" placeholder="Username" autocomplete="username"> <input type="text" id="authUsername" placeholder="Username" autocomplete="username">
<input type="text" id="authEmail" placeholder="Email" autocomplete="email"> <input type="text" id="authEmail" placeholder="Email" autocomplete="email">
<input type="password" id="authRegPassword" placeholder="Password" autocomplete="new-password"> <input type="password" id="authRegPassword" placeholder="Password" autocomplete="new-password">
<button id="authRegisterBtn" onclick="handleRegister()">Create Account</button> <button id="authRegisterBtn" onclick="sb.call('handleRegister')">Create Account</button>
</div> </div>
<div id="authError" class="auth-error"></div> <div id="authError" class="auth-error"></div>
<div id="brandAuthFooter" class="auth-footer"></div> <div id="brandAuthFooter" class="auth-footer"></div>
@@ -80,7 +80,7 @@ window.addEventListener('unhandledrejection', function(e) {
<div id="sidebar" class="sidebar"> <div id="sidebar" class="sidebar">
<div class="sidebar-top"> <div class="sidebar-top">
{{/* Brand — click toggles sidebar */}} {{/* Brand — click toggles sidebar */}}
<div class="sb-brand" onclick="UI.toggleSidebar()" role="button" tabindex="0"> <div class="sb-brand" onclick="sb.call('UI.toggleSidebar')" role="button" tabindex="0">
<div id="brandSidebarLogo" class="sb-logo"> <div id="brandSidebarLogo" class="sb-logo">
<img src="{{.BasePath}}/favicon-32.png" alt="" class="brand-logo-img"> <img src="{{.BasePath}}/favicon-32.png" alt="" class="brand-logo-img">
</div> </div>
@@ -89,7 +89,7 @@ window.addEventListener('unhandledrejection', function(e) {
</div> </div>
{{/* New Chat button */}} {{/* New Chat button */}}
<button id="newChatBtn" class="sb-btn" onclick="newChat()" title="New chat"> <button id="newChatBtn" class="sb-btn" onclick="sb.call('newChat')" title="New chat">
<svg width="15" height="15" 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> <svg width="15" height="15" 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>
<span class="sb-label">New Chat</span> <span class="sb-label">New Chat</span>
</button> </button>
@@ -118,11 +118,11 @@ window.addEventListener('unhandledrejection', function(e) {
<div id="sidebarNav" class="sidebar-nav sidebar-tab-panel" data-tab-panel="chats"> <div id="sidebarNav" class="sidebar-nav sidebar-tab-panel" data-tab-panel="chats">
{{/* Projects section */}} {{/* Projects section */}}
<div class="sb-section" id="sbSectionProjects"> <div class="sb-section" id="sbSectionProjects">
<div class="sb-section-header" onclick="UI.toggleSidebarSection('projects')"> <div class="sb-section-header" onclick="sb.call('UI.toggleSidebarSection','projects')">
<span class="sb-section-arrow" id="sbArrowProjects"></span> <span class="sb-section-arrow" id="sbArrowProjects"></span>
<span class="sb-section-label">Projects</span> <span class="sb-section-label">Projects</span>
<button class="sb-section-add" title="New project" <button class="sb-section-add" title="New project"
onclick="event.stopPropagation();if(typeof createProject==='function')createProject();"> onclick="sb.callEvent(event,'createProject')">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button> </button>
</div> </div>
@@ -133,11 +133,11 @@ window.addEventListener('unhandledrejection', function(e) {
{{/* Channels section (DMs + named channels) */}} {{/* Channels section (DMs + named channels) */}}
<div class="sb-section" id="sbSectionChannels"> <div class="sb-section" id="sbSectionChannels">
<div class="sb-section-header" onclick="UI.toggleSidebarSection('channels')"> <div class="sb-section-header" onclick="sb.call('UI.toggleSidebarSection','channels')">
<span class="sb-section-arrow" id="sbArrowChannels"></span> <span class="sb-section-arrow" id="sbArrowChannels"></span>
<span class="sb-section-label">Channels</span> <span class="sb-section-label">Channels</span>
<button class="sb-section-add" title="New channel" <button class="sb-section-add" title="New channel"
onclick="event.stopPropagation();newChannelOrDM();"> onclick="sb.callEvent(event,'newChannelOrDM')">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button> </button>
</div> </div>
@@ -148,14 +148,14 @@ window.addEventListener('unhandledrejection', function(e) {
{{/* Chats section (personal AI chats + folders) */}} {{/* Chats section (personal AI chats + folders) */}}
<div class="sb-section" id="sbSectionChats"> <div class="sb-section" id="sbSectionChats">
<div class="sb-section-header" onclick="UI.toggleSidebarSection('chats')"> <div class="sb-section-header" onclick="sb.call('UI.toggleSidebarSection','chats')">
<span class="sb-section-arrow" id="sbArrowChats"></span> <span class="sb-section-arrow" id="sbArrowChats"></span>
<span class="sb-section-label">Chats</span> <span class="sb-section-label">Chats</span>
<div class="sb-section-add-group" onclick="event.stopPropagation();"> <div class="sb-section-add-group" onclick="event.stopPropagation();">
<button class="sb-section-add" title="New chat" onclick="newChat();"> <button class="sb-section-add" title="New chat" onclick="sb.call('newChat');">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button> </button>
<button class="sb-section-add" title="New folder" onclick="newFolder();"> <button class="sb-section-add" title="New folder" onclick="sb.call('newFolder');">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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> <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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>
</button> </button>
</div> </div>
@@ -223,12 +223,12 @@ window.addEventListener('unhandledrejection', function(e) {
<svg width="14" height="14" 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> <svg width="14" height="14" 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>
</button> </button>
{{/* Panel toggle */}} {{/* Panel toggle */}}
<button id="panelToggleBtn" class="action-btn" title="Toggle side panel" onclick="if(typeof PanelRegistry!=='undefined')PanelRegistry.toggle('preview')"> <button id="panelToggleBtn" class="action-btn" title="Toggle side panel" onclick="sb.call('PanelRegistry.toggle','preview')">
<svg width="16" height="16" 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> <svg width="16" height="16" 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> </button>
{{/* Notification bell (inline, not fixed) */}} {{/* Notification bell (inline, not fixed) */}}
<div id="notifWrap" class="notif-wrap"> <div id="notifWrap" class="notif-wrap">
<button class="notif-bell" onclick="Notifications.toggleDropdown()"> <button class="notif-bell" onclick="sb.call('Notifications.toggleDropdown')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
<span id="notifBadge" class="notif-badge" style="display:none;">0</span> <span id="notifBadge" class="notif-badge" style="display:none;">0</span>
</button> </button>
@@ -318,10 +318,10 @@ window.addEventListener('unhandledrejection', function(e) {
<span id="sidePanelLabel" class="side-panel-label"></span> <span id="sidePanelLabel" class="side-panel-label"></span>
<div class="side-panel-header-actions"> <div class="side-panel-header-actions">
<span id="sidePanelPanelActions"></span> <span id="sidePanelPanelActions"></span>
<button id="sidePanelFullscreenBtn" class="side-panel-btn" title="Toggle fullscreen" onclick="toggleSidePanelFullscreen()"> <button id="sidePanelFullscreenBtn" class="side-panel-btn" title="Toggle fullscreen" onclick="sb.call('toggleSidePanelFullscreen')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
</button> </button>
<button class="side-panel-btn" title="Close panel" onclick="PanelRegistry.closeAll()"> <button class="side-panel-btn" title="Close panel" onclick="sb.call('PanelRegistry.closeAll')">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button> </button>
</div> </div>
@@ -345,7 +345,7 @@ window.addEventListener('unhandledrejection', function(e) {
<div id="sidePanelOverlay" class="side-panel-overlay" style="display:none;"></div> <div id="sidePanelOverlay" class="side-panel-overlay" style="display:none;"></div>
{{/* ── Lightbox ────────────────────────────── */}} {{/* ── Lightbox ────────────────────────────── */}}
<div id="lightbox" class="lightbox" onclick="closeLightbox()"> <div id="lightbox" class="lightbox" onclick="sb.call('closeLightbox')">
<img id="lightboxImg" src="" alt=""> <img id="lightboxImg" src="" alt="">
</div> </div>
@@ -377,14 +377,14 @@ window.addEventListener('unhandledrejection', function(e) {
<div id="teamAdminContent" class="team-admin-content" style="display:none;"> <div id="teamAdminContent" class="team-admin-content" style="display:none;">
{{/* Tab bar */}} {{/* Tab bar */}}
<div id="teamAdminTabs" style="display:flex;gap:2px;padding:8px 16px;border-bottom:1px solid var(--border);flex-shrink:0;overflow-x:auto;"> <div id="teamAdminTabs" style="display:flex;gap:2px;padding:8px 16px;border-bottom:1px solid var(--border);flex-shrink:0;overflow-x:auto;">
<button class="admin-tab active" data-ttab="members" onclick="UI.switchTeamTab('members')">Members</button> <button class="admin-tab active" data-ttab="members" onclick="sb.call('UI.switchTeamTab','members')">Members</button>
<button class="admin-tab" data-ttab="providers" onclick="UI.switchTeamTab('providers')">Providers</button> <button class="admin-tab" data-ttab="providers" onclick="sb.call('UI.switchTeamTab','providers')">Providers</button>
<button class="admin-tab" data-ttab="personas" onclick="UI.switchTeamTab('personas')">Personas</button> <button class="admin-tab" data-ttab="personas" onclick="sb.call('UI.switchTeamTab','personas')">Personas</button>
<button class="admin-tab" data-ttab="knowledge" onclick="UI.switchTeamTab('knowledge')">Knowledge</button> <button class="admin-tab" data-ttab="knowledge" onclick="sb.call('UI.switchTeamTab','knowledge')">Knowledge</button>
<button class="admin-tab" data-ttab="groups" onclick="UI.switchTeamTab('groups')">Groups</button> <button class="admin-tab" data-ttab="groups" onclick="sb.call('UI.switchTeamTab','groups')">Groups</button>
<button class="admin-tab" data-ttab="settings" onclick="UI.switchTeamTab('settings')">Settings</button> <button class="admin-tab" data-ttab="settings" onclick="sb.call('UI.switchTeamTab','settings')">Settings</button>
<button class="admin-tab" data-ttab="usage" onclick="UI.switchTeamTab('usage')">Usage</button> <button class="admin-tab" data-ttab="usage" onclick="sb.call('UI.switchTeamTab','usage')">Usage</button>
<button class="admin-tab" data-ttab="activity" onclick="UI.switchTeamTab('activity')">Activity</button> <button class="admin-tab" data-ttab="activity" onclick="sb.call('UI.switchTeamTab','activity')">Activity</button>
</div> </div>
{{/* Tab content panels */}} {{/* Tab content panels */}}
@@ -482,8 +482,8 @@ window.addEventListener('unhandledrejection', function(e) {
<div class="form-group"><label>Config (JSON)</label><textarea id="rpConfig" rows="4">{}</textarea></div> <div class="form-group"><label>Config (JSON)</label><textarea id="rpConfig" rows="4">{}</textarea></div>
<div class="form-group"><label><input type="checkbox" id="rpActive" checked> Active</label></div> <div class="form-group"><label><input type="checkbox" id="rpActive" checked> Active</label></div>
<div style="display:flex;gap:8px;"> <div style="display:flex;gap:8px;">
<button id="rpSaveBtn" class="btn-primary" onclick="Pages.saveRoutingPolicy()">Save</button> <button id="rpSaveBtn" class="btn-primary" onclick="sb.call('Pages.saveRoutingPolicy')">Save</button>
<button id="rpCancelBtn" class="btn-secondary" onclick="Pages.hideRoutingForm()">Cancel</button> <button id="rpCancelBtn" class="btn-secondary" onclick="sb.call('Pages.hideRoutingForm')">Cancel</button>
</div> </div>
</div> </div>
@@ -510,28 +510,28 @@ window.addEventListener('unhandledrejection', function(e) {
{{/* CM6 bundle — chat-specific (code input, extension editor) */}} {{/* CM6 bundle — chat-specific (code input, extension editor) */}}
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/vendor/codemirror/codemirror.bundle.js?v={{$.Version}}" onerror="console.warn('[CM6] Bundle not available — falling back to textarea')"></script> <script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/vendor/codemirror/codemirror.bundle.js?v={{$.Version}}" onerror="console.warn('[CM6] Bundle not available — falling back to textarea')"></script>
{{/* App JS — order matches index.html (minus api/events/ui-primitives/ui-format already in base) */}} {{/* App JS — order matches index.html (minus api/events/ui-primitives/ui-format already in base) */}}
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/extensions.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/extensions.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/panels.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/panels.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/ui-settings.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/ui-settings.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/ui-admin.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/ui-admin.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/tokens.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/tokens.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/notes.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/notes.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/note-graph.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/note-graph.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/files.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/files.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/tools-toggle.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/tools-toggle.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/knowledge-ui.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/knowledge-ui.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/memory-ui.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/memory-ui.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/persona-kb.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/persona-kb.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/projects-ui.js?v={{$.Version}}"></script> <script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/projects-ui.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/channel-models.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/channel-models.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/notification-prefs.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/notification-prefs.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/notifications.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/notifications.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/chat.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/chat.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/settings-handlers.js?v={{$.Version}}"></script> <script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/settings-handlers.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/admin-handlers.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/admin-handlers.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/workflow-api.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/workflow-api.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/workflow-admin.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/workflow-admin.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/workflow-queue.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/workflow-queue.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/task-sidebar.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/task-sidebar.js?v={{$.Version}}"></script>
<script nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/app.js?v={{$.Version}}"></script> <script type="module" nonce="{{$.CSPNonce}}" src="{{$.BasePath}}/js/app.js?v={{$.Version}}"></script>
{{end}} {{end}}

View File

@@ -93,7 +93,7 @@
{{/* ── Scripts ─────────────────────────────── */}} {{/* ── Scripts ─────────────────────────────── */}}
{{define "scripts-editor"}} {{define "scripts-editor"}}
<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}}/vendor/codemirror/codemirror.bundle.js?v={{.Version}}" onerror="console.warn('[CM6] Bundle not available')"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/chat-pane.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/chat-pane.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/editor-surface.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/editor-surface.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app.js?v={{.Version}}"></script>
{{end}} {{end}}

View File

@@ -24,21 +24,21 @@
{{end}} {{end}}
{{define "scripts-notes"}} {{define "scripts-notes"}}
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/chat.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/chat.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/files.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/files.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/channel-models.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/channel-models.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/tokens.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/tokens.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/extensions.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/extensions.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notes.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notes.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/note-graph.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/note-graph.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/knowledge-ui.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/knowledge-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/persona-kb.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/persona-kb.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/panels.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/panels.js?v={{.Version}}"></script>
{{/* debug.js now loaded in base.html for all surfaces */}} {{/* debug.js now loaded in base.html for all surfaces */}}
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/projects-ui.js?v={{.Version}}"></script> <script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/projects-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/app.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}"> <script nonce="{{.CSPNonce}}">
// Notes surface boot: open notes panel in standalone mode // Notes surface boot: open notes panel in standalone mode
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {

View File

@@ -112,20 +112,20 @@
</div> </div>
</div> </div>
</div> </div>
<button class="btn-md btn-primary" onclick="if(typeof UI!=='undefined')UI.saveAppearance?.()">Save</button> <button class="btn-md btn-primary" onclick="sb.call('UI.saveAppearance')">Save</button>
{{else if eq .Section "profile"}} {{else if eq .Section "profile"}}
<div class="settings-section"> <div class="settings-section">
<h3>Profile</h3> <h3>Profile</h3>
<div class="form-group"><label>Display Name</label><input type="text" id="profileDisplayName" placeholder="Your display name"></div> <div class="form-group"><label>Display Name</label><input type="text" id="profileDisplayName" placeholder="Your display name"></div>
<div class="form-group"><label>Email</label><input type="email" id="profileEmail" disabled></div> <div class="form-group"><label>Email</label><input type="email" id="profileEmail" disabled></div>
<button class="btn-md btn-primary" onclick="if(typeof Pages!=='undefined')Pages.saveProfile?.()">Save Profile</button> <button class="btn-md btn-primary" onclick="sb.call('Pages.saveProfile')">Save Profile</button>
</div> </div>
<div class="settings-section" style="margin-top:20px;"> <div class="settings-section" style="margin-top:20px;">
<h3>Change Password</h3> <h3>Change Password</h3>
<div class="form-group"><label>Current Password</label><input type="password" id="settingsCurrentPw" autocomplete="current-password"></div> <div class="form-group"><label>Current Password</label><input type="password" id="settingsCurrentPw" autocomplete="current-password"></div>
<div class="form-group"><label>New Password</label><input type="password" id="settingsNewPw" autocomplete="new-password"></div> <div class="form-group"><label>New Password</label><input type="password" id="settingsNewPw" autocomplete="new-password"></div>
<div class="form-group"><label>Confirm New Password</label><input type="password" id="settingsConfirmPw" autocomplete="new-password"></div> <div class="form-group"><label>Confirm New Password</label><input type="password" id="settingsConfirmPw" autocomplete="new-password"></div>
<button class="btn-md btn-primary" onclick="if(typeof Pages!=='undefined')Pages.changePassword?.()">Update Password</button> <button class="btn-md btn-primary" onclick="sb.call('Pages.changePassword')">Update Password</button>
</div> </div>
{{else if eq .Section "providers"}} {{else if eq .Section "providers"}}
<div id="userProvidersDisabled" style="display:none;"> <div id="userProvidersDisabled" style="display:none;">
@@ -170,15 +170,15 @@
{{/* Scripts */}} {{/* Scripts */}}
{{define "scripts-settings"}} {{define "scripts-settings"}}
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-settings.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/ui-settings.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/settings-handlers.js?v={{.Version}}"></script> <script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/settings-handlers.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/task-settings.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/task-settings.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/knowledge-ui.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/knowledge-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/persona-kb.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/persona-kb.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/memory-ui.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notifications.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notification-prefs.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/notification-prefs.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/channel-models.js?v={{.Version}}"></script> <script type="module" nonce="{{.CSPNonce}}" src="{{.BasePath}}/js/channel-models.js?v={{.Version}}"></script>
<script nonce="{{.CSPNonce}}"> <script nonce="{{.CSPNonce}}">
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
const section = document.getElementById('settingsSection')?.dataset.section; const section = document.getElementById('settingsSection')?.dataset.section;

View File

@@ -8,6 +8,7 @@ const vm = require('vm');
*/ */
function loadExtensions(overrides = {}) { function loadExtensions(overrides = {}) {
const sandbox = createBrowserContext(overrides); const sandbox = createBrowserContext(overrides);
loadSource(sandbox, 'sb.js');
loadSource(sandbox, 'events.js'); loadSource(sandbox, 'events.js');
loadSource(sandbox, 'extensions.js'); loadSource(sandbox, 'extensions.js');
const ctx = vm.createContext(sandbox); const ctx = vm.createContext(sandbox);

View File

@@ -11,6 +11,7 @@ const vm = require('vm');
*/ */
function loadExtensions(overrides = {}) { function loadExtensions(overrides = {}) {
const sandbox = createBrowserContext(overrides); const sandbox = createBrowserContext(overrides);
loadSource(sandbox, 'sb.js');
loadSource(sandbox, 'events.js'); loadSource(sandbox, 'events.js');
loadSource(sandbox, 'extensions.js'); loadSource(sandbox, 'extensions.js');
// Extract const globals from the vm context // Extract const globals from the vm context

View File

@@ -116,6 +116,7 @@ function loadSource(sandbox, filename) {
*/ */
function loadAppModules(overrides = {}) { function loadAppModules(overrides = {}) {
const sandbox = createBrowserContext(overrides); const sandbox = createBrowserContext(overrides);
loadSource(sandbox, 'sb.js');
loadSource(sandbox, 'app-state.js'); loadSource(sandbox, 'app-state.js');
loadSource(sandbox, 'api.js'); loadSource(sandbox, 'api.js');
loadSource(sandbox, 'app.js'); loadSource(sandbox, 'app.js');

View File

@@ -4,8 +4,6 @@
// Admin actions: user management, roles, model visibility, // Admin actions: user management, roles, model visibility,
// personas, team management, global providers. // personas, team management, global providers.
(function() {
'use strict';
// ── Admin Actions ──────────────────────────── // ── Admin Actions ────────────────────────────
@@ -918,4 +916,3 @@ sb.register('toggleUserRole', toggleUserRole);
sb.register('updateTeamMember', updateTeamMember); sb.register('updateTeamMember', updateTeamMember);
sb.register('_closeExtEditForm', _closeExtEditForm); sb.register('_closeExtEditForm', _closeExtEditForm);
sb.register('saveAdminExtension', saveAdminExtension); sb.register('saveAdminExtension', saveAdminExtension);
})();

View File

@@ -5,8 +5,6 @@
// and calls the appropriate ADMIN_LOADERS[section]() from ui-admin.js. // and calls the appropriate ADMIN_LOADERS[section]() from ui-admin.js.
// Loaded only on the admin surface (/admin/:section). // Loaded only on the admin surface (/admin/:section).
(function() {
'use strict';
// Each admin section loader (ui-admin.js) expects specific container // Each admin section loader (ui-admin.js) expects specific container
// elements. SCAFFOLDING maps section name -> HTML to inject before // elements. SCAFFOLDING maps section name -> HTML to inject before
@@ -386,4 +384,3 @@
ADMIN_LOADERS[section](); ADMIN_LOADERS[section]();
} }
}); });
})();

View File

@@ -6,8 +6,6 @@
// //
// Exports: window._loadAdminSurfaces // Exports: window._loadAdminSurfaces
(function() {
'use strict';
async function _loadAdminSurfaces() { async function _loadAdminSurfaces() {
const container = document.getElementById('adminSurfacesContent'); const container = document.getElementById('adminSurfacesContent');
@@ -199,4 +197,3 @@ async function _loadSurfaceList() {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.register('_loadAdminSurfaces', _loadAdminSurfaces); sb.register('_loadAdminSurfaces', _loadAdminSurfaces);
})();

View File

@@ -11,8 +11,6 @@
// Exports: window.API // Exports: window.API
// ========================================== // ==========================================
(function() {
'use strict';
const BASE = window.__BASE__ || ''; const BASE = window.__BASE__ || '';
const _storageKey = BASE ? `sb_auth_${BASE.replace(/\//g, '')}` : 'sb_auth'; const _storageKey = BASE ? `sb_auth_${BASE.replace(/\//g, '')}` : 'sb_auth';
@@ -994,4 +992,3 @@ const API = {
}; };
sb.ns('API', API); sb.ns('API', API);
})();

View File

@@ -8,8 +8,6 @@
// Exports: window.App, window.fetchModels // Exports: window.App, window.fetchModels
// ========================================== // ==========================================
(function() {
'use strict';
const App = { const App = {
chats: [], chats: [],
@@ -139,4 +137,3 @@ async function fetchModels() {
sb.ns('App', App); sb.ns('App', App);
sb.register('fetchModels', fetchModels); sb.register('fetchModels', fetchModels);
})();

View File

@@ -10,8 +10,6 @@
// Exports: handleLogin, handleRegister, handleLogout, switchAuthTab, // Exports: handleLogin, handleRegister, handleLogout, switchAuthTab,
// startApp, initBanners // startApp, initBanners
(function() {
'use strict';
async function init() { async function init() {
console.log('🔀 Chat Switchboard initializing...'); console.log('🔀 Chat Switchboard initializing...');
@@ -588,4 +586,3 @@ sb.register('handleLogout', handleLogout);
sb.register('switchAuthTab', switchAuthTab); sb.register('switchAuthTab', switchAuthTab);
sb.register('startApp', startApp); sb.register('startApp', startApp);
sb.register('initBanners', initBanners); sb.register('initBanners', initBanners);
})();

View File

@@ -9,8 +9,6 @@
// //
// Exports: window.ChannelModels // Exports: window.ChannelModels
(function() {
'use strict';
const ChannelModels = { const ChannelModels = {
@@ -437,4 +435,3 @@ function _shortName(fullName) {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('ChannelModels', ChannelModels); sb.ns('ChannelModels', ChannelModels);
})();

View File

@@ -7,8 +7,6 @@
// //
// Exports: window.ChatPane // Exports: window.ChatPane
(function() {
'use strict';
const ChatPane = { const ChatPane = {
...createComponentRegistry('ChatPane'), ...createComponentRegistry('ChatPane'),
@@ -153,4 +151,3 @@ const ChatPane = {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('ChatPane', ChatPane); sb.ns('ChatPane', ChatPane);
})();

View File

@@ -11,8 +11,6 @@
// rejectWorkflowStage, _initChatListeners, // rejectWorkflowStage, _initChatListeners,
// summarizeAndContinue, toggleChannelAutoCompact // summarizeAndContinue, toggleChannelAutoCompact
(function() {
'use strict';
// ── Chat Input Abstraction ────────────────── // ── Chat Input Abstraction ──────────────────
// Wraps CM6 editor (when available) or fallback textarea. // Wraps CM6 editor (when available) or fallback textarea.
@@ -1279,4 +1277,3 @@ sb.register('rejectWorkflowStage', rejectWorkflowStage);
sb.register('_initChatListeners', _initChatListeners); sb.register('_initChatListeners', _initChatListeners);
sb.register('summarizeAndContinue', summarizeAndContinue); sb.register('summarizeAndContinue', summarizeAndContinue);
sb.register('toggleChannelAutoCompact', toggleChannelAutoCompact); sb.register('toggleChannelAutoCompact', toggleChannelAutoCompact);
})();

View File

@@ -20,8 +20,6 @@
// //
// Exports: window.CodeEditor // Exports: window.CodeEditor
(function() {
'use strict';
const CodeEditor = { const CodeEditor = {
...createComponentRegistry('CodeEditor'), ...createComponentRegistry('CodeEditor'),
@@ -332,4 +330,3 @@ function _ceDetectLanguage(path) {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('CodeEditor', CodeEditor); sb.ns('CodeEditor', CodeEditor);
})();

View File

@@ -10,8 +10,6 @@
// //
// Exports: window.DebugLog,window.openDebugModal,window.switchDebugTab,window.clearDebugLog,window.copyDebugLog,window.exportDebugLog,window.runDebugDiagnostics,window.purgeCache // Exports: window.DebugLog,window.openDebugModal,window.switchDebugTab,window.clearDebugLog,window.copyDebugLog,window.exportDebugLog,window.runDebugDiagnostics,window.purgeCache
(function() {
'use strict';
const DebugLog = { const DebugLog = {
entries: [], entries: [],
@@ -676,4 +674,3 @@ sb.register('copyDebugLog', copyDebugLog);
sb.register('exportDebugLog', exportDebugLog); sb.register('exportDebugLog', exportDebugLog);
sb.register('runDebugDiagnostics', runDebugDiagnostics); sb.register('runDebugDiagnostics', runDebugDiagnostics);
sb.register('purgeCache', purgeCache); sb.register('purgeCache', purgeCache);
})();

View File

@@ -21,8 +21,6 @@
// //
// Exports: window.initDragResize // Exports: window.initDragResize
(function() {
'use strict';
/** /**
* Attach drag-resize behavior to a handle element. * Attach drag-resize behavior to a handle element.
@@ -100,4 +98,3 @@ function _clientPos(e, isHoriz) {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.register('initDragResize', initDragResize); sb.register('initDragResize', initDragResize);
})();

View File

@@ -7,8 +7,6 @@
// (hidden) — this JS moves them into the right pane slots. // (hidden) — this JS moves them into the right pane slots.
// ========================================== // ==========================================
(function() {
'use strict';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
if (window.__SURFACE__ !== 'editor') return; if (window.__SURFACE__ !== 'editor') return;
@@ -641,4 +639,3 @@
} }
} }
})();

View File

@@ -13,8 +13,6 @@
// Exports: window.Events // Exports: window.Events
// ========================================== // ==========================================
(function() {
'use strict';
const Events = { const Events = {
@@ -332,4 +330,3 @@ const Events = {
}; };
sb.ns('Events', Events); sb.ns('Events', Events);
})();

View File

@@ -17,8 +17,6 @@
// //
// Exports: window.Extensions // Exports: window.Extensions
(function() {
'use strict';
const Extensions = { const Extensions = {
@@ -489,4 +487,3 @@ const Extensions = {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('Extensions', Extensions); sb.ns('Extensions', Extensions);
})();

View File

@@ -19,8 +19,6 @@
// //
// Exports: window.FileTree // Exports: window.FileTree
(function() {
'use strict';
const FileTree = { const FileTree = {
...createComponentRegistry('FileTree'), ...createComponentRegistry('FileTree'),
@@ -269,4 +267,3 @@ function _ftFileIcon(name) {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('FileTree', FileTree); sb.ns('FileTree', FileTree);
})();

View File

@@ -9,8 +9,6 @@
// Consumed by: chat.js (send flow), ui-core.js (message rendering), // Consumed by: chat.js (send flow), ui-core.js (message rendering),
// ui-admin.js (storage tab) // ui-admin.js (storage tab)
(function() {
'use strict';
// ── Constants ─────────────────────────────── // ── Constants ───────────────────────────────
@@ -857,4 +855,3 @@ sb.register('renderMessageFiles', renderMessageFiles);
sb.register('unstageFile', unstageFile); sb.register('unstageFile', unstageFile);
sb.register('openLightbox', openLightbox); sb.register('openLightbox', openLightbox);
sb.register('downloadFile', downloadFile); sb.register('downloadFile', downloadFile);
})();

View File

@@ -10,7 +10,6 @@
// //
// Depends on: API (api.js), App (app.js) // Depends on: API (api.js), App (app.js)
'use strict';
const KnowledgeUI = (() => { const KnowledgeUI = (() => {
console.debug('[KnowledgeUI] module loaded'); console.debug('[KnowledgeUI] module loaded');
@@ -553,3 +552,5 @@ const KnowledgeUI = (() => {
openChannelPopup, openChannelPopup,
}; };
})(); })();
sb.ns('KnowledgeUI', KnowledgeUI);

View File

@@ -7,8 +7,6 @@
// //
// Exports: window.MemoryUI // Exports: window.MemoryUI
(function() {
'use strict';
const MemoryUI = { const MemoryUI = {
@@ -370,4 +368,3 @@ function _debounce(fn, ms) {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('MemoryUI', MemoryUI); sb.ns('MemoryUI', MemoryUI);
})();

View File

@@ -14,8 +14,6 @@
// //
// Exports: window.ModelSelector // Exports: window.ModelSelector
(function() {
'use strict';
const ModelSelector = { const ModelSelector = {
...createComponentRegistry('ModelSelector'), ...createComponentRegistry('ModelSelector'),
@@ -175,4 +173,3 @@ const ModelSelector = {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('ModelSelector', ModelSelector); sb.ns('ModelSelector', ModelSelector);
})();

View File

@@ -21,8 +21,6 @@
// //
// Exports: window.NoteEditor // Exports: window.NoteEditor
(function() {
'use strict';
const NoteEditor = { const NoteEditor = {
...createComponentRegistry('NoteEditor'), ...createComponentRegistry('NoteEditor'),
@@ -441,4 +439,3 @@ const NoteEditor = {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('NoteEditor', NoteEditor); sb.ns('NoteEditor', NoteEditor);
})();

View File

@@ -8,8 +8,6 @@
// //
// Exports: window.openNoteGraph,window.closeNoteGraph,window._render,window._graphResetZoom,window._graphToggleOrphans,window.invalidateNoteGraph // Exports: window.openNoteGraph,window.closeNoteGraph,window._render,window._graphResetZoom,window._graphToggleOrphans,window.invalidateNoteGraph
(function() {
'use strict';
var _graphData = null; // cached { nodes, edges, unresolved } var _graphData = null; // cached { nodes, edges, unresolved }
var _graphState = null; // { panX, panY, zoom, animId, canvas, ctx } var _graphState = null; // { panX, panY, zoom, animId, canvas, ctx }
@@ -501,4 +499,3 @@ sb.register('_render', _render);
sb.register('_graphResetZoom', _graphResetZoom); sb.register('_graphResetZoom', _graphResetZoom);
sb.register('_graphToggleOrphans', _graphToggleOrphans); sb.register('_graphToggleOrphans', _graphToggleOrphans);
sb.register('invalidateNoteGraph', invalidateNoteGraph); sb.register('invalidateNoteGraph', invalidateNoteGraph);
})();

View File

@@ -3,8 +3,6 @@
// ========================================== // ==========================================
// Notes panel: editor, multi-select, folders, CRUD. // Notes panel: editor, multi-select, folders, CRUD.
(function() {
'use strict';
var _editingNoteId = null; var _editingNoteId = null;
var _notesSelectMode = false; var _notesSelectMode = false;
@@ -973,4 +971,3 @@ sb.register('_navigateToLinkedNote', _navigateToLinkedNote);
sb.register('toggleBacklinks', toggleBacklinks); sb.register('toggleBacklinks', toggleBacklinks);
sb.register('_toggleNoteSelectCb', _toggleNoteSelectCb); sb.register('_toggleNoteSelectCb', _toggleNoteSelectCb);
sb.register('_noteItemClick', _noteItemClick); sb.register('_noteItemClick', _noteItemClick);
})();

View File

@@ -7,8 +7,6 @@
// //
// Exports: window.NotifPrefs // Exports: window.NotifPrefs
(function() {
'use strict';
const NotifPrefs = { const NotifPrefs = {
_loaded: false, _loaded: false,
_prefs: [], _prefs: [],
@@ -169,4 +167,3 @@ NotifPrefs._getAdminSmtp = function() {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('NotifPrefs', NotifPrefs); sb.ns('NotifPrefs', NotifPrefs);
})();

View File

@@ -9,8 +9,6 @@
// //
// Exports: window.Notifications // Exports: window.Notifications
(function() {
'use strict';
const Notifications = { const Notifications = {
@@ -431,4 +429,3 @@ function _timeAgo(isoStr) {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('Notifications', Notifications); sb.ns('Notifications', Notifications);
})();

View File

@@ -3,8 +3,6 @@
// ========================================== // ==========================================
// v0.22.7: Splash init, login, register, animated grid. // v0.22.7: Splash init, login, register, animated grid.
(function() {
'use strict';
var base = window.__BASE__ || ''; var base = window.__BASE__ || '';
Pages.initSplash = function() { Pages.initSplash = function() {
@@ -186,4 +184,3 @@
function _val(id) { var el = document.getElementById(id); return el ? el.value.trim() : ''; } function _val(id) { var el = document.getElementById(id); return el ? el.value.trim() : ''; }
function _showErr(el, msg) { if (!el) return; el.textContent = msg; el.style.display = ''; } function _showErr(el, msg) { if (!el) return; el.textContent = msg; el.style.display = ''; }
})();

View File

@@ -13,8 +13,6 @@
// Exports: window.Pages, window._val (used by pages-splash.js) // Exports: window.Pages, window._val (used by pages-splash.js)
// ========================================== // ==========================================
(function() {
'use strict';
const Pages = { const Pages = {
@@ -349,4 +347,3 @@ async function _api(method, path, body) {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('Pages', Pages); sb.ns('Pages', Pages);
sb.register('_val', _val); // used by pages-splash.js sb.register('_val', _val); // used by pages-splash.js
})();

View File

@@ -21,8 +21,6 @@
// //
// Exports: window.PaneContainer // Exports: window.PaneContainer
(function() {
'use strict';
const PaneContainer = { const PaneContainer = {
_presets: {}, _presets: {},
@@ -589,4 +587,3 @@ PaneContainer.registerPreset('split', {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('PaneContainer', PaneContainer); sb.ns('PaneContainer', PaneContainer);
})();

View File

@@ -17,8 +17,6 @@
// //
// Exports: window.PanelRegistry,window.toggleSidePanelFullscreen,window._initWorkspaceResize,window._initPanelSwipe,window._initPanelResponsive,window._initPanelOverlay // Exports: window.PanelRegistry,window.toggleSidePanelFullscreen,window._initWorkspaceResize,window._initPanelSwipe,window._initPanelResponsive,window._initPanelOverlay
(function() {
'use strict';
// ── Panel Registry ────────────────────────── // ── Panel Registry ──────────────────────────
@@ -454,4 +452,3 @@ sb.register('_initWorkspaceResize', _initWorkspaceResize);
sb.register('_initPanelSwipe', _initPanelSwipe); sb.register('_initPanelSwipe', _initPanelSwipe);
sb.register('_initPanelResponsive', _initPanelResponsive); sb.register('_initPanelResponsive', _initPanelResponsive);
sb.register('_initPanelOverlay', _initPanelOverlay); sb.register('_initPanelOverlay', _initPanelOverlay);
})();

View File

@@ -6,10 +6,6 @@
// //
// Exports: window.renderPersonaKBPicker,window.loadPersonaKBs,window.savePersonaKBs // Exports: window.renderPersonaKBPicker,window.loadPersonaKBs,window.savePersonaKBs
(function() {
'use strict';
'use strict';
// ── Persona-KB Picker ─────────────────────────── // ── Persona-KB Picker ───────────────────────────
@@ -185,4 +181,3 @@ async function savePersonaKBs(personaId, kbPicker, apiPrefix) {
sb.register('renderPersonaKBPicker', renderPersonaKBPicker); sb.register('renderPersonaKBPicker', renderPersonaKBPicker);
sb.register('loadPersonaKBs', loadPersonaKBs); sb.register('loadPersonaKBs', loadPersonaKBs);
sb.register('savePersonaKBs', savePersonaKBs); sb.register('savePersonaKBs', savePersonaKBs);
})();

View File

@@ -6,8 +6,6 @@
// and drag-and-drop. // and drag-and-drop.
// ========================================== // ==========================================
(function() {
'use strict';
async function loadProjects() { async function loadProjects() {
try { try {
@@ -1871,9 +1869,9 @@ async function deleteFolder(folderId) {
const INTERVAL = 30000; // 30s const INTERVAL = 30000; // 30s
let timer = null; let timer = null;
async function beat() { async function beat() {
if (!API.isAuthed) return; if (!window.API || !window.API.isAuthed) return;
try { try {
await API.presenceHeartbeat(); await window.API.presenceHeartbeat();
} catch (_) { /* non-critical */ } } catch (_) { /* non-critical */ }
} }
function start() { function start() {
@@ -2090,4 +2088,3 @@ sb.register('renameFolder', renameFolder);
sb.register('_showAddParticipantSearch', _showAddParticipantSearch); sb.register('_showAddParticipantSearch', _showAddParticipantSearch);
sb.register('_addParticipant', _addParticipant); sb.register('_addParticipant', _addParticipant);
sb.register('_removeParticipant', _removeParticipant); sb.register('_removeParticipant', _removeParticipant);
})();

View File

@@ -18,8 +18,6 @@
// //
// Exports: window.REPL // Exports: window.REPL
(function() {
'use strict';
const REPL = { const REPL = {
@@ -557,4 +555,3 @@ const REPL = {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('REPL', REPL); sb.ns('REPL', REPL);
})();

View File

@@ -34,8 +34,9 @@ const sb = {
/** /**
* Register a standalone action function. * Register a standalone action function.
* Also sets window[name] for backward compatibility with cross-file * Also sets window[name] for backward compatibility with cross-file
* direct calls. This side-effect is removed when files convert to * direct calls. Template onclick handlers are fully migrated to sb.call()
* ES module imports (Phase 4). * (Phase 3b). This side-effect is removed in Phase 4 when files convert
* to ES module imports.
*/ */
register(name, fn) { register(name, fn) {
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
@@ -49,7 +50,8 @@ const sb = {
/** /**
* Register a namespace object (API, UI, App, etc.). * Register a namespace object (API, UI, App, etc.).
* Methods are accessible via dot notation: sb.resolve('UI.toast'). * Methods are accessible via dot notation: sb.resolve('UI.toast').
* Also sets window[name] for backward compatibility. * Also sets window[name] for backward compatibility with cross-file
* direct calls (e.g. API.listProjects()). Removed in Phase 4.
*/ */
ns(name, obj) { ns(name, obj) {
if (obj == null || typeof obj !== 'object') { if (obj == null || typeof obj !== 'object') {

View File

@@ -4,8 +4,6 @@
// Settings save, provider CRUD, avatar, command palette, // Settings save, provider CRUD, avatar, command palette,
// user model roles. // user model roles.
(function() {
'use strict';
// ── Settings ───────────────────────────────── // ── Settings ─────────────────────────────────
@@ -908,4 +906,3 @@ sb.register('toggleCmdPalette', toggleCmdPalette);
sb.register('closeCmdPalette', closeCmdPalette); sb.register('closeCmdPalette', closeCmdPalette);
sb.register('_initSettingsListeners', _initSettingsListeners); sb.register('_initSettingsListeners', _initSettingsListeners);
sb.register('_initAdminSettingsToggles', _initAdminSettingsToggles); sb.register('_initAdminSettingsToggles', _initAdminSettingsToggles);
})();

View File

@@ -4,8 +4,6 @@
// Admin section for managing scheduled tasks, run history, and global // Admin section for managing scheduled tasks, run history, and global
// task configuration. Registered as ADMIN_LOADERS.tasks in ui-admin.js. // task configuration. Registered as ADMIN_LOADERS.tasks in ui-admin.js.
(function() {
'use strict';
var SCAFFOLD_HTML = var SCAFFOLD_HTML =
'<div id="taskAdminTabs" style="display:flex;gap:8px;margin-bottom:16px">' + '<div id="taskAdminTabs" style="display:flex;gap:8px;margin-bottom:16px">' +
@@ -252,4 +250,3 @@
await loadTasks(); await loadTasks();
}); });
})();

View File

@@ -5,8 +5,6 @@
// persona/model picker, budget config, and starter templates. // persona/model picker, budget config, and starter templates.
// Loaded on the settings surface. // Loaded on the settings surface.
(function() {
'use strict';
const PRESETS = [ const PRESETS = [
{ label: 'Every morning (6am)', cron: '0 6 * * *' }, { label: 'Every morning (6am)', cron: '0 6 * * *' },
@@ -278,4 +276,3 @@
sb.register('_loadSettingsTasks', function() { sb.register('_loadSettingsTasks', function() {
loadTaskList(); loadTaskList();
}); });
})();

View File

@@ -4,8 +4,6 @@
// Sidebar section showing user's scheduled tasks with status indicators. // Sidebar section showing user's scheduled tasks with status indicators.
// Click opens the task's output channel. Same pattern as WorkflowQueue. // Click opens the task's output channel. Same pattern as WorkflowQueue.
(function() {
'use strict';
const TaskSidebar = { const TaskSidebar = {
@@ -141,4 +139,3 @@
} }
}, 2200); // After WorkflowQueue (2000ms) }, 2200); // After WorkflowQueue (2000ms)
}); });
})();

View File

@@ -5,8 +5,6 @@
// //
// Exports: window.Tokens,window.updateInputTokens,window.updateContextWarning // Exports: window.Tokens,window.updateInputTokens,window.updateContextWarning
(function() {
'use strict';
// ── Token Estimation + Context Tracking ───── // ── Token Estimation + Context Tracking ─────
@@ -167,4 +165,3 @@ function dismissContextWarning() {
sb.ns('Tokens', Tokens); sb.ns('Tokens', Tokens);
sb.register('updateInputTokens', updateInputTokens); sb.register('updateInputTokens', updateInputTokens);
sb.register('updateContextWarning', updateContextWarning); sb.register('updateContextWarning', updateContextWarning);
})();

View File

@@ -3,7 +3,6 @@
// Categories expand to show individual tool toggles. // Categories expand to show individual tool toggles.
// State persisted in localStorage, sent as disabled_tools[] in completion requests. // State persisted in localStorage, sent as disabled_tools[] in completion requests.
'use strict';
const ToolsToggle = (() => { const ToolsToggle = (() => {
const STORAGE_KEY = 'cs-disabled-tools'; const STORAGE_KEY = 'cs-disabled-tools';
@@ -242,3 +241,5 @@ const ToolsToggle = (() => {
return { init, getDisabledTools, refresh }; return { init, getDisabledTools, refresh };
})(); })();
sb.ns('ToolsToggle', ToolsToggle);

View File

@@ -4,8 +4,6 @@
// Fullscreen admin panel with category + section navigation. // Fullscreen admin panel with category + section navigation.
// All loadAdmin*() functions unchanged — same endpoints, same DOM IDs. // All loadAdmin*() functions unchanged — same endpoints, same DOM IDs.
(function() {
'use strict';
// ── Category → Section mapping ──────────── // ── Category → Section mapping ────────────
const ADMIN_SECTIONS = { const ADMIN_SECTIONS = {
@@ -1573,4 +1571,3 @@ Object.assign(UI, {
sb.ns('ADMIN_SECTIONS', ADMIN_SECTIONS); sb.ns('ADMIN_SECTIONS', ADMIN_SECTIONS);
sb.ns('ADMIN_LABELS', ADMIN_LABELS); sb.ns('ADMIN_LABELS', ADMIN_LABELS);
sb.ns('ADMIN_LOADERS', ADMIN_LOADERS); sb.ns('ADMIN_LOADERS', ADMIN_LOADERS);
})();

View File

@@ -10,8 +10,6 @@
// window.toggleSummarizedHistory (onclick handler) // window.toggleSummarizedHistory (onclick handler)
// ========================================== // ==========================================
(function() {
'use strict';
// ── Delegated Action Dispatcher ──────────────────── // ── Delegated Action Dispatcher ────────────────────
// Buttons use data-action="fnName" data-args='[...]' instead of onclick. // Buttons use data-action="fnName" data-args='[...]' instead of onclick.
@@ -1651,4 +1649,3 @@ sb.register('_uiDispatch', _uiDispatch);
sb.register('_toggleArchivedProjects', _toggleArchivedProjects); sb.register('_toggleArchivedProjects', _toggleArchivedProjects);
sb.register('_submitEditFromDOM', _submitEditFromDOM); sb.register('_submitEditFromDOM', _submitEditFromDOM);
sb.register('toggleSummarizedHistory', toggleSummarizedHistory); sb.register('toggleSummarizedHistory', toggleSummarizedHistory);
})();

View File

@@ -11,8 +11,6 @@
// popOutExtBlock (onclick handlers) // popOutExtBlock (onclick handlers)
// ========================================== // ==========================================
(function() {
'use strict';
// ── Message Formatting ────────────────────── // ── Message Formatting ──────────────────────
@@ -610,4 +608,3 @@ sb.register('downloadCode', downloadCode);
sb.register('popOutExtBlock', popOutExtBlock); sb.register('popOutExtBlock', popOutExtBlock);
sb.register('_copyCodeBlock', _copyCodeBlock); sb.register('_copyCodeBlock', _copyCodeBlock);
sb.register('_openNoteFromTool', _openNoteFromTool); sb.register('_openNoteFromTool', _openNoteFromTool);
})();

View File

@@ -11,8 +11,6 @@
// openModal, closeModal, checkTabsOverflow, Theme, mountAvatarUpload // openModal, closeModal, checkTabsOverflow, Theme, mountAvatarUpload
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
(function() {
'use strict';
// ══════════════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════════════
// §0 CORE UTILITIES // §0 CORE UTILITIES
@@ -1133,4 +1131,3 @@ sb.register('closeModal', closeModal);
sb.register('checkTabsOverflow', checkTabsOverflow); sb.register('checkTabsOverflow', checkTabsOverflow);
sb.ns('Theme', Theme); sb.ns('Theme', Theme);
sb.register('mountAvatarUpload', mountAvatarUpload); sb.register('mountAvatarUpload', mountAvatarUpload);
})();

View File

@@ -6,8 +6,6 @@
// //
// Exports: none (extends window.UI via Object.assign) // Exports: none (extends window.UI via Object.assign)
(function() {
'use strict';
Object.assign(UI, { Object.assign(UI, {
// ── General Settings (Settings Surface) ── // ── General Settings (Settings Surface) ──
@@ -867,4 +865,3 @@ Object.assign(UI, {
}); });
})();

View File

@@ -13,8 +13,6 @@
// //
// Exports: window.UserMenu // Exports: window.UserMenu
(function() {
'use strict';
const UserMenu = { const UserMenu = {
...createComponentRegistry('UserMenu'), ...createComponentRegistry('UserMenu'),
@@ -139,4 +137,3 @@ const UserMenu = {
// ── Exports ───────────────────────────────── // ── Exports ─────────────────────────────────
sb.ns('UserMenu', UserMenu); sb.ns('UserMenu', UserMenu);
})();

View File

@@ -4,8 +4,6 @@
// Admin panel for managing workflow definitions, stages, and publishing. // Admin panel for managing workflow definitions, stages, and publishing.
// Registered as ADMIN_LOADERS.workflows in ui-admin.js. // Registered as ADMIN_LOADERS.workflows in ui-admin.js.
(function() {
'use strict';
// ── Scaffold HTML ──────────────────────── // ── Scaffold HTML ────────────────────────
// Injected directly into adminDynamic by the loader since SCAFFOLDING // Injected directly into adminDynamic by the loader since SCAFFOLDING
@@ -470,4 +468,3 @@
} }
}); });
})();

View File

@@ -5,10 +5,6 @@
// publishing, instance lifecycle, and assignment queue operations. // publishing, instance lifecycle, and assignment queue operations.
// Loaded after api.js on the chat and admin surfaces. // Loaded after api.js on the chat and admin surfaces.
(function() {
'use strict';
if (typeof API === 'undefined') return;
// ── Workflow Definitions ──────────────── // ── Workflow Definitions ────────────────
API.listWorkflows = function(teamId) { API.listWorkflows = function(teamId) {
@@ -99,4 +95,3 @@
API.completeAssignment = function(id) { API.completeAssignment = function(id) {
return this._post(`/api/v1/workflow-assignments/${id}/complete`, {}); return this._post(`/api/v1/workflow-assignments/${id}/complete`, {});
}; };
})();

View File

@@ -6,8 +6,6 @@
// 2. Claimed assignments from team queues // 2. Claimed assignments from team queues
// Loaded on the chat surface after workflow-api.js. // Loaded on the chat surface after workflow-api.js.
(function() {
'use strict';
const WorkflowQueue = { const WorkflowQueue = {
@@ -198,4 +196,3 @@
}, 2000); }, 2000);
}); });
})();