Feat v0.6.3 dead code sweep (#38)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #38.
This commit is contained in:
676
src/dev.html
676
src/dev.html
@@ -1,676 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Switchboard Core — Dev Gallery</title>
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/sw-primitives.css">
|
||||
<link rel="stylesheet" href="css/sw-shell.css">
|
||||
<style>
|
||||
body { overflow: auto; padding: 0; }
|
||||
.dev-root { height: 100vh; display: flex; flex-direction: column; }
|
||||
.dev-nav { display: flex; gap: 0; border-bottom: 1px solid var(--border); background: var(--bg-surface); flex-shrink: 0; padding: 0 1rem; }
|
||||
.dev-nav button {
|
||||
padding: 0.6rem 1.2rem; background: none; border: none; border-bottom: 2px solid transparent;
|
||||
color: var(--text-2); font-family: var(--font); font-size: 0.85rem; font-weight: 500; cursor: pointer;
|
||||
}
|
||||
.dev-nav button:hover { color: var(--text); }
|
||||
.dev-nav button.active { color: var(--accent); border-bottom-color: var(--accent); }
|
||||
.dev-page { flex: 1; overflow: auto; }
|
||||
.gallery { max-width: 800px; margin: 0 auto; padding: 2rem; }
|
||||
.gallery h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
|
||||
.gallery .subtitle { color: var(--text-2); margin-bottom: 2rem; font-size: 0.9rem; }
|
||||
.section { margin-bottom: 2.5rem; }
|
||||
.section h2 {
|
||||
font-size: 0.85rem; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 0.5px; color: var(--text-3); margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem; border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.row { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; margin-bottom: 0.75rem; }
|
||||
.col { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.spacer { height: 0.5rem; }
|
||||
.shell-demo { height: 100%; }
|
||||
.shell-demo .sw-shell { height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
(async () => {
|
||||
try {
|
||||
const { h, render } = await import('./js/sw/vendor/preact.module.js');
|
||||
const hooksModule = await import('./js/sw/vendor/hooks.module.js?v=2');
|
||||
const { default: htm } = await import('./js/sw/vendor/htm.module.js');
|
||||
const html = htm.bind(h);
|
||||
|
||||
const { useState, useEffect, useRef, useMemo, useCallback } = hooksModule;
|
||||
window.preact = { h, render };
|
||||
window.hooks = hooksModule;
|
||||
window.html = html;
|
||||
|
||||
const { Button } = await import('./js/sw/primitives/button.js');
|
||||
const { Spinner } = await import('./js/sw/primitives/spinner.js');
|
||||
const { Avatar } = await import('./js/sw/primitives/avatar.js');
|
||||
const { FormField } = await import('./js/sw/primitives/form-field.js');
|
||||
const { Tooltip } = await import('./js/sw/primitives/tooltip.js');
|
||||
const { Banner } = await import('./js/sw/primitives/banner.js');
|
||||
const { Dialog } = await import('./js/sw/primitives/dialog.js');
|
||||
const { toast, ToastContainer } = await import('./js/sw/primitives/toast.js');
|
||||
const { Menu } = await import('./js/sw/primitives/menu.js');
|
||||
const { Drawer } = await import('./js/sw/primitives/drawer.js');
|
||||
const { Tabs } = await import('./js/sw/primitives/tabs.js');
|
||||
const { Dropdown } = await import('./js/sw/primitives/dropdown.js');
|
||||
const { AppShell } = await import('./js/sw/shell/app-shell.js');
|
||||
const { UserMenu } = await import('./js/sw/shell/user-menu.js');
|
||||
const { SurfaceViewport } = await import('./js/sw/shell/surface-viewport.js');
|
||||
const { DialogStack } = await import('./js/sw/shell/dialog-stack.js');
|
||||
const { confirm } = await import('./js/sw/primitives/confirm.js');
|
||||
const { prompt } = await import('./js/sw/primitives/prompt.js');
|
||||
|
||||
// ── SDK Boot ────────────────────────────
|
||||
const { boot } = await import('./js/sw/sdk/index.js');
|
||||
await boot().catch(e => console.warn('SDK boot (no backend):', e));
|
||||
const sw = window.sw;
|
||||
|
||||
// ── SDK Demo Page ───────────────────────
|
||||
function SDKPage() {
|
||||
const [authState, setAuthState] = useState(null);
|
||||
const [apiResult, setApiResult] = useState(null);
|
||||
const [permInput, setPermInput] = useState('model.use');
|
||||
const [canResult, setCanResult] = useState(null);
|
||||
const [eventLog, setEventLog] = useState([]);
|
||||
const [emitLabel, setEmitLabel] = useState('test.event');
|
||||
const logRef = useRef([]);
|
||||
|
||||
// Subscribe to all events for live log
|
||||
useEffect(() => {
|
||||
const unsub = sw.on('*', (payload, meta) => {
|
||||
const entry = { ts: new Date().toLocaleTimeString(), label: meta.event, local: meta.local };
|
||||
logRef.current = [entry, ...logRef.current].slice(0, 20);
|
||||
setEventLog([...logRef.current]);
|
||||
});
|
||||
return unsub;
|
||||
}, []);
|
||||
|
||||
function refreshAuth() {
|
||||
setAuthState({
|
||||
isAuthenticated: sw.auth.isAuthenticated,
|
||||
user: sw.auth.user,
|
||||
permissions: [...sw.auth.permissions],
|
||||
teams: sw.auth.teams,
|
||||
policies: sw.auth.policies,
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => { refreshAuth(); }, []);
|
||||
|
||||
async function testApi() {
|
||||
try {
|
||||
const res = await sw.api.channels.list();
|
||||
setApiResult({ ok: true, data: res });
|
||||
} catch (e) {
|
||||
setApiResult({ ok: false, error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
function testCan() {
|
||||
setCanResult(sw.can(permInput));
|
||||
}
|
||||
|
||||
const monoStyle = 'font-family: var(--font-mono, monospace); font-size: 0.8rem; background: var(--bg-1); padding: 0.75rem; border-radius: 6px; overflow: auto; max-height: 200px; white-space: pre-wrap; word-break: break-all; color: var(--text-2);';
|
||||
|
||||
return html`
|
||||
<div class="gallery">
|
||||
<h1>SDK Demo</h1>
|
||||
<p class="subtitle">Layer 1 — SDK modules (v0.37.3)</p>
|
||||
|
||||
<!-- Auth State -->
|
||||
<div class="section">
|
||||
<h2>sw.auth</h2>
|
||||
<div class="row" style="margin-bottom:0.5rem">
|
||||
<${Button} variant="secondary" size="sm" onClick=${refreshAuth}>Refresh<//>
|
||||
</div>
|
||||
<div style=${monoStyle}>${JSON.stringify(authState, null, 2)}</div>
|
||||
</div>
|
||||
|
||||
<!-- API Test -->
|
||||
<div class="section">
|
||||
<h2>sw.api</h2>
|
||||
<div class="row" style="margin-bottom:0.5rem">
|
||||
<${Button} variant="secondary" size="sm" onClick=${testApi}>sw.api.channels.list()<//>
|
||||
</div>
|
||||
${apiResult && html`
|
||||
<div style=${monoStyle}>
|
||||
${apiResult.ok
|
||||
? JSON.stringify(apiResult.data, null, 2)
|
||||
: 'Error: ' + apiResult.error}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<!-- RBAC Test -->
|
||||
<div class="section">
|
||||
<h2>sw.can()</h2>
|
||||
<div class="row">
|
||||
<input class="sw-input" style="width:180px" value=${permInput}
|
||||
onInput=${e => setPermInput(e.target.value)} />
|
||||
<${Button} variant="secondary" size="sm" onClick=${testCan}>Check<//>
|
||||
${canResult !== null && html`
|
||||
<span style="font-weight:600; color: ${canResult ? 'var(--success)' : 'var(--danger)'}">
|
||||
${canResult ? 'ALLOWED' : 'DENIED'}
|
||||
</span>
|
||||
`}
|
||||
</div>
|
||||
<div class="row" style="margin-top:0.5rem">
|
||||
<span style="color:var(--text-3); font-size:0.8rem">
|
||||
sw.isAdmin: ${String(sw.isAdmin)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Theme -->
|
||||
<div class="section">
|
||||
<h2>sw.theme</h2>
|
||||
<div class="row">
|
||||
<span style="color:var(--text-2); font-size:0.85rem">
|
||||
mode: ${sw.theme.mode} · resolved: ${sw.theme.current}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<${Button} variant="secondary" size="sm" onClick=${() => sw.theme.set('light')}>Light<//>
|
||||
<${Button} variant="secondary" size="sm" onClick=${() => sw.theme.set('dark')}>Dark<//>
|
||||
<${Button} variant="secondary" size="sm" onClick=${() => sw.theme.set('system')}>System<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Events -->
|
||||
<div class="section">
|
||||
<h2>sw.on / sw.emit</h2>
|
||||
<div class="row" style="margin-bottom:0.5rem">
|
||||
<input class="sw-input" style="width:180px" value=${emitLabel}
|
||||
onInput=${e => setEmitLabel(e.target.value)} />
|
||||
<${Button} variant="secondary" size="sm"
|
||||
onClick=${() => sw.emit(emitLabel, { demo: true }, { localOnly: true })}>Emit<//>
|
||||
</div>
|
||||
<div style=${monoStyle + ' max-height:160px;'}>
|
||||
${eventLog.length === 0 ? '(listening for events...)' :
|
||||
eventLog.map(e => `${e.ts} ${e.local ? 'L' : 'R'} ${e.label}`).join('\n')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pipe -->
|
||||
<div class="section">
|
||||
<h2>sw.pipe</h2>
|
||||
<div style=${monoStyle}>${JSON.stringify(sw.pipe.list(), null, 2)}</div>
|
||||
</div>
|
||||
|
||||
<!-- Namespaces -->
|
||||
<div class="section">
|
||||
<h2>sw.api namespaces</h2>
|
||||
<div style=${monoStyle}>${Object.keys(sw.api).filter(k => typeof sw.api[k] === 'object').join(', ')}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ── Shell Demo Page ──────────────────────
|
||||
function ShellPage() {
|
||||
const [confirmResult, setConfirmResult] = useState(null);
|
||||
const [promptResult, setPromptResult] = useState(null);
|
||||
|
||||
return html`
|
||||
<div class="gallery">
|
||||
<h1>Shell Demo</h1>
|
||||
<p class="subtitle">Layer 2 — Application shell (v0.37.4)</p>
|
||||
|
||||
<!-- UserMenu -->
|
||||
<div class="section">
|
||||
<h2>UserMenu</h2>
|
||||
<div class="row">
|
||||
<${UserMenu} />
|
||||
<${UserMenu} placement="down-left" />
|
||||
</div>
|
||||
<p style="color:var(--text-3); font-size:0.8rem; margin-top:0.5rem">
|
||||
RBAC-gated flyout. Without backend, shows all items for demo.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Imperative Confirm -->
|
||||
<div class="section">
|
||||
<h2>sw.confirm()</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="secondary" onClick=${async () => {
|
||||
const ok = await sw.confirm('Delete this item?', { destructive: true });
|
||||
setConfirmResult(ok);
|
||||
}}>Confirm (destructive)<//>
|
||||
<${Button} variant="secondary" onClick=${async () => {
|
||||
const ok = await sw.confirm('Proceed with this action?');
|
||||
setConfirmResult(ok);
|
||||
}}>Confirm (normal)<//>
|
||||
${confirmResult !== null && html`
|
||||
<span style="font-weight:600; color: ${confirmResult ? 'var(--success)' : 'var(--danger)'}">
|
||||
Result: ${String(confirmResult)}
|
||||
</span>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Imperative Prompt -->
|
||||
<div class="section">
|
||||
<h2>sw.prompt()</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="secondary" onClick=${async () => {
|
||||
const val = await sw.prompt('Enter a name:', { defaultValue: 'untitled' });
|
||||
setPromptResult(val);
|
||||
}}>Prompt<//>
|
||||
${promptResult !== null && html`
|
||||
<span style="font-weight:600; color:var(--text-2)">
|
||||
Result: ${JSON.stringify(promptResult)}
|
||||
</span>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Surface Viewport -->
|
||||
<div class="section">
|
||||
<h2>SurfaceViewport</h2>
|
||||
<p style="color:var(--text-3); font-size:0.8rem; margin-bottom:0.5rem">
|
||||
UserMenu is placed by the surface (top-right), not the shell.
|
||||
</p>
|
||||
<div style="border:1px dashed var(--border); border-radius:var(--radius, 6px); height:200px; position:relative; overflow:hidden;">
|
||||
<${SurfaceViewport} surface=${html`
|
||||
<div style="padding:1rem; color:var(--text-2); height:100%;">
|
||||
<p>Mock surface content. The shell provides the viewport; the surface owns everything inside.</p>
|
||||
<div style="position:absolute; top:0.5rem; right:0.5rem;">
|
||||
<${UserMenu} placement="down-left" />
|
||||
</div>
|
||||
</div>
|
||||
`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App component tree -->
|
||||
<div class="section">
|
||||
<h2>App Component Tree</h2>
|
||||
<div style="font-family:var(--font-mono, monospace); font-size:0.8rem; color:var(--text-2); background:var(--bg-1); padding:0.75rem; border-radius:6px; white-space:pre;">${
|
||||
`<App>
|
||||
<AppShell banner message footer>
|
||||
<SurfaceViewport surface={...} />
|
||||
</AppShell>
|
||||
<ToastContainer />
|
||||
<DialogStack />
|
||||
</App>`
|
||||
}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ── Surfaces Demo Page ────────────────────
|
||||
function SurfacesPage() {
|
||||
return html`
|
||||
<div class="gallery">
|
||||
<h1>Surfaces Demo</h1>
|
||||
<p class="subtitle">v0.37.5 — Login + Settings surfaces</p>
|
||||
|
||||
<div class="section">
|
||||
<h2>Login Surface</h2>
|
||||
<p style="color:var(--text-3); font-size:0.85rem; margin-bottom:0.75rem">
|
||||
The login surface is a standalone page (not embedded in AppShell).
|
||||
It renders at /login with its own layout: Hero panel + Auth panel.
|
||||
</p>
|
||||
<div style="font-family:var(--font-mono, monospace); font-size:0.8rem; color:var(--text-2); background:var(--bg-1); padding:0.75rem; border-radius:6px; white-space:pre;">${
|
||||
`<LoginSurface>
|
||||
<Hero />
|
||||
<AuthPanel>
|
||||
authMode === 'builtin':
|
||||
<LoginForm /> — sw.auth.login()
|
||||
<RegisterForm /> — sw.api.auth.register()
|
||||
authMode === 'oidc' | 'mtls':
|
||||
<SSOPanel />
|
||||
</AuthPanel>
|
||||
</LoginSurface>`
|
||||
}</div>
|
||||
<p style="color:var(--text-3); font-size:0.8rem; margin-top:0.5rem">
|
||||
Files: src/js/sw/surfaces/login/
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Settings Surface</h2>
|
||||
<p style="color:var(--text-3); font-size:0.85rem; margin-bottom:0.75rem">
|
||||
Full Preact surface with left nav + section routing.
|
||||
Core sections (General, Appearance, Profile, Teams, Models, Providers,
|
||||
Personas, Roles, Usage) are native Preact components.
|
||||
Deferred sections use BridgeSection wrappers.
|
||||
</p>
|
||||
<div style="font-family:var(--font-mono, monospace); font-size:0.8rem; color:var(--text-2); background:var(--bg-1); padding:0.75rem; border-radius:6px; white-space:pre;">${
|
||||
`<SettingsSurface>
|
||||
<Topbar />
|
||||
<Nav>
|
||||
General, Appearance, Models, Personas, Profile,
|
||||
Teams, Workflows, Tasks, Git Keys, Data & Privacy
|
||||
[BYOK] Providers, Roles, Usage
|
||||
</Nav>
|
||||
<Content>
|
||||
section === 'general' → <GeneralSection />
|
||||
section === 'appearance' → <AppearanceSection />
|
||||
section === 'profile' → <ProfileSection />
|
||||
section === 'teams' → <TeamsSection />
|
||||
section === 'models' → <ModelsSection />
|
||||
section === 'providers' → <ProvidersSection />
|
||||
section === 'personas' → <PersonasSection />
|
||||
section === 'roles' → <RolesSection />
|
||||
section === 'usage' → <UsageSection />
|
||||
section === 'tasks' etc → <BridgeSection /> (old JS)
|
||||
</Content>
|
||||
</SettingsSurface>`
|
||||
}</div>
|
||||
<p style="color:var(--text-3); font-size:0.8rem; margin-top:0.5rem">
|
||||
Files: src/js/sw/surfaces/settings/
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function PrimitivesPage() {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('tab1');
|
||||
const [dropVal, setDropVal] = useState('opt2');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const menuAnchor = useRef(null);
|
||||
|
||||
return html`
|
||||
<div class="gallery">
|
||||
<h1>Primitives Gallery</h1>
|
||||
<p class="subtitle">Layer 0 — Preact + htm components (v0.37.2)</p>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="section">
|
||||
<h2>Button</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="primary">Primary<//>
|
||||
<${Button} variant="secondary">Secondary<//>
|
||||
<${Button} variant="danger">Danger<//>
|
||||
<${Button} variant="ghost">Ghost<//>
|
||||
</div>
|
||||
<div class="row">
|
||||
<${Button} size="sm">Small<//>
|
||||
<${Button} size="md">Medium<//>
|
||||
<${Button} size="lg">Large<//>
|
||||
</div>
|
||||
<div class="row">
|
||||
<${Button} disabled>Disabled<//>
|
||||
<${Button} loading=${true}>Loading<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spinner -->
|
||||
<div class="section">
|
||||
<h2>Spinner</h2>
|
||||
<div class="row">
|
||||
<${Spinner} size="sm" />
|
||||
<${Spinner} size="md" />
|
||||
<${Spinner} size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Avatar -->
|
||||
<div class="section">
|
||||
<h2>Avatar</h2>
|
||||
<div class="row">
|
||||
<${Avatar} name="Jane Doe" size="sm" />
|
||||
<${Avatar} name="Jane Doe" size="md" />
|
||||
<${Avatar} name="Jane Doe" size="lg" />
|
||||
<${Avatar} name="X" size="md" />
|
||||
<${Avatar} src="https://i.pravatar.cc/48" name="Photo" size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FormField -->
|
||||
<div class="section">
|
||||
<h2>FormField</h2>
|
||||
<div class="col" style="max-width:320px">
|
||||
<${FormField} label="Username" required>
|
||||
<input class="sw-input" placeholder="Enter username" />
|
||||
<//>
|
||||
<${FormField} label="Email" error="Invalid email address">
|
||||
<input class="sw-input" value="bad@" />
|
||||
<//>
|
||||
<${FormField} label="Bio" hint="Optional, max 200 chars">
|
||||
<textarea class="sw-input" rows="2" placeholder="Tell us about yourself" />
|
||||
<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tooltip -->
|
||||
<div class="section">
|
||||
<h2>Tooltip</h2>
|
||||
<div class="row">
|
||||
<${Tooltip} content="Top tooltip">
|
||||
<${Button} variant="secondary">Hover me (top)<//>
|
||||
<//>
|
||||
<${Tooltip} content="Bottom tooltip" position="bottom">
|
||||
<${Button} variant="secondary">Bottom<//>
|
||||
<//>
|
||||
<${Tooltip} content="Right tooltip" position="right">
|
||||
<${Button} variant="secondary">Right<//>
|
||||
<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Banner -->
|
||||
<div class="section">
|
||||
<h2>Banner</h2>
|
||||
<div class="col">
|
||||
<${Banner} text="Informational banner message" variant="info" />
|
||||
<${Banner} text="Warning: maintenance scheduled" variant="warn" dismissible />
|
||||
<${Banner} text="Error: service unavailable" variant="error" />
|
||||
<${Banner} text="Successfully deployed v0.37.2" variant="success" dismissible />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="section">
|
||||
<h2>Tabs</h2>
|
||||
<${Tabs}
|
||||
tabs=${[
|
||||
{ label: 'General', value: 'tab1' },
|
||||
{ label: 'Appearance', value: 'tab2' },
|
||||
{ label: 'Notifications', value: 'tab3' },
|
||||
{ label: 'Disabled', value: 'tab4', disabled: true },
|
||||
]}
|
||||
active=${activeTab}
|
||||
onChange=${setActiveTab}
|
||||
/>
|
||||
<div style="padding: 1rem 0; color: var(--text-2); font-size: 0.9rem">
|
||||
Active tab: ${activeTab}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dropdown -->
|
||||
<div class="section">
|
||||
<h2>Dropdown</h2>
|
||||
<div class="row">
|
||||
<div style="width:220px">
|
||||
<${Dropdown}
|
||||
options=${[
|
||||
{ label: 'Claude Sonnet', value: 'opt1' },
|
||||
{ label: 'Claude Opus', value: 'opt2' },
|
||||
{ label: 'Claude Haiku', value: 'opt3' },
|
||||
{ label: 'GPT-4o', value: 'opt4' },
|
||||
{ label: 'Disabled', value: 'opt5', disabled: true },
|
||||
]}
|
||||
value=${dropVal}
|
||||
onChange=${setDropVal}
|
||||
searchable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu -->
|
||||
<div class="section">
|
||||
<h2>Menu</h2>
|
||||
<div class="row">
|
||||
<${Button} ref=${menuAnchor} variant="secondary"
|
||||
onClick=${() => setMenuOpen(!menuOpen)}>
|
||||
Open Menu
|
||||
<//>
|
||||
<${Menu}
|
||||
open=${menuOpen}
|
||||
anchor=${menuAnchor.current}
|
||||
items=${[
|
||||
{ label: 'Settings', action: 'settings', icon: '\u2699' },
|
||||
{ label: 'Profile', action: 'profile', icon: '\ud83d\udc64' },
|
||||
{ divider: true },
|
||||
{ label: 'Submenu', children: [
|
||||
{ label: 'Option A', action: 'a' },
|
||||
{ label: 'Option B', action: 'b' },
|
||||
]},
|
||||
{ divider: true },
|
||||
{ label: 'Disabled', action: 'x', disabled: true },
|
||||
{ label: 'Sign Out', action: 'logout' },
|
||||
]}
|
||||
onSelect=${(action) => { toast.info('Selected: ' + action); }}
|
||||
onClose=${() => setMenuOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dialog -->
|
||||
<div class="section">
|
||||
<h2>Dialog</h2>
|
||||
<div class="row">
|
||||
<${Button} onClick=${() => setDialogOpen(true)}>Open Dialog<//>
|
||||
</div>
|
||||
<${Dialog}
|
||||
open=${dialogOpen}
|
||||
title="Example Dialog"
|
||||
onClose=${() => setDialogOpen(false)}
|
||||
actions=${[
|
||||
{ label: 'Cancel', variant: 'secondary', onClick: () => setDialogOpen(false) },
|
||||
{ label: 'Confirm', variant: 'primary', onClick: () => { setDialogOpen(false); toast.success('Confirmed!'); } },
|
||||
]}
|
||||
>
|
||||
<p>This is a modal dialog with focus trapping, Escape to close, and backdrop click to dismiss.</p>
|
||||
<//>
|
||||
</div>
|
||||
|
||||
<!-- Drawer -->
|
||||
<div class="section">
|
||||
<h2>Drawer</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="secondary" onClick=${() => setDrawerOpen(true)}>Open Drawer<//>
|
||||
</div>
|
||||
<${Drawer}
|
||||
open=${drawerOpen}
|
||||
title="Settings"
|
||||
onClose=${() => setDrawerOpen(false)}
|
||||
>
|
||||
<p style="color: var(--text-2)">Slide-in panel for settings, admin sections, etc.</p>
|
||||
<div class="spacer" />
|
||||
<${FormField} label="Display Name">
|
||||
<input class="sw-input" value="Jane Doe" />
|
||||
<//>
|
||||
<//>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="section">
|
||||
<h2>Toast</h2>
|
||||
<div class="row">
|
||||
<${Button} variant="secondary" onClick=${() => toast.success('Operation successful')}>Success<//>
|
||||
<${Button} variant="secondary" onClick=${() => toast.error('Something went wrong')}>Error<//>
|
||||
<${Button} variant="secondary" onClick=${() => toast.warn('Check your settings')}>Warning<//>
|
||||
<${Button} variant="secondary" onClick=${() => toast.info('New message received')}>Info<//>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const VARIANTS = ['info', 'warn', 'error', 'success'];
|
||||
|
||||
function ShellControls({ bannerOn, setBannerOn, bannerVariant, setBannerVariant, bannerText, setBannerText,
|
||||
msgOn, setMsgOn, msgVariant, setMsgVariant, msgText, setMsgText,
|
||||
footerOn, setFooterOn }) {
|
||||
return html`
|
||||
<div style="display:flex; align-items:center; gap:0.75rem; padding:0.4rem 0; flex-wrap:wrap;">
|
||||
<${Button} variant=${bannerOn ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${() => setBannerOn(!bannerOn)}>Banner<//>
|
||||
${bannerOn && html`
|
||||
<${Dropdown} options=${VARIANTS.map(v => ({ label: v, value: v }))}
|
||||
value=${bannerVariant} onChange=${setBannerVariant} />
|
||||
<input class="sw-input" style="width:140px" placeholder="text..."
|
||||
value=${bannerText} onInput=${e => setBannerText(e.target.value)} />
|
||||
`}
|
||||
<${Button} variant=${msgOn ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${() => setMsgOn(!msgOn)}>Message<//>
|
||||
${msgOn && html`
|
||||
<${Dropdown} options=${VARIANTS.map(v => ({ label: v, value: v }))}
|
||||
value=${msgVariant} onChange=${setMsgVariant} />
|
||||
<input class="sw-input" style="width:140px" placeholder="text..."
|
||||
value=${msgText} onInput=${e => setMsgText(e.target.value)} />
|
||||
`}
|
||||
<${Button} variant=${footerOn ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${() => setFooterOn(!footerOn)}>Footer<//>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function DevRoot() {
|
||||
const [page, setPage] = useState('primitives');
|
||||
const [bannerOn, setBannerOn] = useState(false);
|
||||
const [msgOn, setMsgOn] = useState(false);
|
||||
const [footerOn, setFooterOn] = useState(false);
|
||||
const [bannerVariant, setBannerVariant] = useState('info');
|
||||
const [msgVariant, setMsgVariant] = useState('info');
|
||||
const [bannerText, setBannerText] = useState('');
|
||||
const [msgText, setMsgText] = useState('');
|
||||
|
||||
const banner = bannerOn ? { text: bannerText || 'Banner', variant: bannerVariant } : null;
|
||||
const msg = msgOn ? { text: msgText || 'Message', variant: msgVariant } : null;
|
||||
|
||||
return html`
|
||||
<${AppShell}
|
||||
banner=${banner}
|
||||
message=${msg}
|
||||
footer=${footerOn ? html`Switchboard Core · Layer 1 Shell` : null}
|
||||
>
|
||||
<div class="dev-root">
|
||||
<nav class="dev-nav">
|
||||
<button class=${page === 'primitives' ? 'active' : ''} onClick=${() => setPage('primitives')}>Primitives</button>
|
||||
<button class=${page === 'shell' ? 'active' : ''} onClick=${() => setPage('shell')}>Shell</button>
|
||||
<button class=${page === 'sdk' ? 'active' : ''} onClick=${() => setPage('sdk')}>SDK</button>
|
||||
<button class=${page === 'surfaces' ? 'active' : ''} onClick=${() => setPage('surfaces')}>Surfaces</button>
|
||||
<div style="flex:1" />
|
||||
<${ShellControls} ...${{
|
||||
bannerOn, setBannerOn, bannerVariant, setBannerVariant, bannerText, setBannerText,
|
||||
msgOn, setMsgOn, msgVariant, setMsgVariant, msgText, setMsgText,
|
||||
footerOn, setFooterOn
|
||||
}} />
|
||||
</nav>
|
||||
<div class="dev-page">
|
||||
${page === 'primitives' && html`<${PrimitivesPage} />`}
|
||||
${page === 'shell' && html`<${ShellPage} />`}
|
||||
${page === 'sdk' && html`<${SDKPage} />`}
|
||||
${page === 'surfaces' && html`<${SurfacesPage} />`}
|
||||
</div>
|
||||
</div>
|
||||
<//>
|
||||
<${ToastContainer} />
|
||||
<${DialogStack} />
|
||||
`;
|
||||
}
|
||||
|
||||
render(html`<${DevRoot} />`, document.getElementById('app'));
|
||||
} catch(e) { console.error('DEV:', e); }
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -243,7 +243,6 @@ describe('init() profile gate', () => {
|
||||
});
|
||||
|
||||
// ── Boot-time 401 redirect suppression ──────
|
||||
// v0.37.14: Stale cookie blank-login-page fix.
|
||||
// When the login page boots the SDK with stale localStorage tokens,
|
||||
// the REST client's 401 handler must NOT redirect to /login (we're
|
||||
// already there). boot() handles 401 gracefully on its own.
|
||||
|
||||
@@ -5,7 +5,6 @@ const path = require('path');
|
||||
const { createBrowserContext, loadSource, SRC } = require('./helpers');
|
||||
const vm = require('vm');
|
||||
|
||||
// v0.37.10: extensions.js deleted — extension system moves to Preact in v0.37.12.
|
||||
// Skip all tests if the source file doesn't exist.
|
||||
const EXTENSIONS_EXISTS = fs.existsSync(path.join(SRC, 'extensions.js'));
|
||||
const maybeDescribe = EXTENSIONS_EXISTS ? describe : describe.skip;
|
||||
|
||||
@@ -5,7 +5,6 @@ const path = require('path');
|
||||
const { createBrowserContext, loadSource, SRC } = require('./helpers');
|
||||
const vm = require('vm');
|
||||
|
||||
// v0.37.10: extensions.js deleted — extension system moves to Preact in v0.37.12.
|
||||
// Skip all tests if the source file doesn't exist.
|
||||
const EXTENSIONS_EXISTS = fs.existsSync(path.join(SRC, 'extensions.js'));
|
||||
const maybeDescribe = EXTENSIONS_EXISTS ? describe : describe.skip;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// simulated browser environment so tests run
|
||||
// against the ACTUAL frontend code.
|
||||
//
|
||||
// v0.37.10: Removed loadAppModules (api.js + app.js deleted).
|
||||
// ==========================================
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -108,7 +107,7 @@ function createBrowserContext(overrides = {}) {
|
||||
function loadSource(sandbox, filename) {
|
||||
const filepath = path.join(SRC, filename);
|
||||
if (!fs.existsSync(filepath)) {
|
||||
throw new Error(`Source file not found: ${filename} (deleted in v0.37.10?)`);
|
||||
throw new Error(`Source file not found: ${filename} (deleted?)`);
|
||||
}
|
||||
const code = fs.readFileSync(filepath, 'utf-8');
|
||||
const ctx = vm.createContext(sandbox);
|
||||
@@ -129,7 +128,6 @@ function readSourceSafe(filename) {
|
||||
/**
|
||||
* Extract the model-processing transform from fetchModels (app-state.js).
|
||||
* This is the core mapping logic that converts API response → App.models.
|
||||
* v0.22.8: unified persona naming — no more preset aliases.
|
||||
*/
|
||||
function processModelsResponse(data, hiddenModels = new Set()) {
|
||||
return (data.data || data.models || []).map(m => {
|
||||
|
||||
@@ -7,13 +7,9 @@
|
||||
// policy exists but the frontend doesn't
|
||||
// check it.
|
||||
//
|
||||
// v0.22.5: Updated for server-rendered Go templates.
|
||||
// v0.37.5: Settings surface moved to Preact — legacy SPA tests
|
||||
// replaced with component source audits.
|
||||
// v0.37.10: Legacy SPA source audit removed (ui-core.js, app.js,
|
||||
// pages.js, settings-handlers.js, ui-admin.js all deleted).
|
||||
// Policy gating now verified via Preact surfaces + admin templates.
|
||||
// v0.37.12: Admin Go templates deleted (Preact since v0.37.6). Template
|
||||
// element ID assertions removed — Preact component source audits
|
||||
// at the bottom of this file cover the same policy keys.
|
||||
//
|
||||
@@ -130,10 +126,9 @@ describe('Team member dropdown population', () => {
|
||||
});
|
||||
|
||||
// ── Kernel surface template ──────────────────
|
||||
// v0.1.0: Chat surface removed. Verify admin mount exists and
|
||||
// old SPA scaffold is gone.
|
||||
|
||||
describe('Kernel surface templates (v0.1.0)', () => {
|
||||
describe('Kernel surface templates', () => {
|
||||
const templateSrc = readAllTemplates();
|
||||
|
||||
it('admin-mount div exists', () => {
|
||||
@@ -153,7 +148,6 @@ describe('Kernel surface templates (v0.1.0)', () => {
|
||||
});
|
||||
|
||||
// ── Admin Preact surface ─────────────────────
|
||||
// v0.37.6: Admin surface handles settings save via Preact.
|
||||
// Verify the admin surface has policy key handling.
|
||||
|
||||
describe('Admin Preact surface handles settings', () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// ==========================================
|
||||
// Debug Bootstrap (v0.37.18)
|
||||
// Debug Bootstrap
|
||||
// ==========================================
|
||||
// Thin entry point: initializes the debug engine (synchronous,
|
||||
// captures early errors) then mounts the Preact debug modal
|
||||
// after Preact globals are available.
|
||||
//
|
||||
// Replaces the 673-line imperative debug.js from v0.37.14.
|
||||
// Replaces the 673-line imperative debug.js.
|
||||
// Engine: src/js/sw/components/debug/engine.js
|
||||
// Modal: src/js/sw/components/debug/index.js
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Bug badge that shows error count. Subscribes to engine
|
||||
// for reactive updates.
|
||||
//
|
||||
// v0.37.18: Preact rebuild from debug.js _updateBadge().
|
||||
|
||||
const html = window.html;
|
||||
const { useEffect } = window.hooks;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Filterable console log display with type coloring,
|
||||
// elapsed timestamps, and auto-scroll.
|
||||
//
|
||||
// v0.37.18: Preact rebuild from debug.js _renderConsoleTab().
|
||||
|
||||
const html = window.html;
|
||||
const { useState, useMemo, useRef, useEffect } = window.hooks;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// Singleton — init() must run before SDK boot to capture early errors.
|
||||
// UI-agnostic: Preact components subscribe via .subscribe().
|
||||
//
|
||||
// v0.37.18: Extracted from debug.js (v0.37.14).
|
||||
//
|
||||
// Exports: debugEngine (singleton)
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// Global overlay — not a routed surface.
|
||||
// Mounts via mountDebugModal(el, engine).
|
||||
//
|
||||
// v0.37.18: Preact rebuild of debug modal (CR P2-5).
|
||||
|
||||
import { debugEngine } from './engine.js';
|
||||
import { ConsoleTab } from './console-tab.js';
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Fetch log with expandable request/response details.
|
||||
// Newest entries first.
|
||||
//
|
||||
// v0.37.18: Preact rebuild from debug.js _renderNetworkTab().
|
||||
|
||||
const html = window.html;
|
||||
const { useState } = window.hooks;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
// command history, and collapsible JSON output.
|
||||
// Admin-gated OR ?debug=1 URL param.
|
||||
//
|
||||
// v0.37.18: Preact rebuild from repl.js (v0.37.14).
|
||||
|
||||
const html = window.html;
|
||||
const { useState, useRef, useEffect, useCallback } = window.hooks;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// ==========================================
|
||||
// Displays current application state snapshot as formatted JSON.
|
||||
//
|
||||
// v0.37.18: Preact rebuild from debug.js _renderStateTab().
|
||||
|
||||
const html = window.html;
|
||||
const { useMemo } = window.hooks;
|
||||
|
||||
@@ -131,13 +131,13 @@ export function createDomains(restClient) {
|
||||
updateWorkflow: (id, wfId, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}`, data),
|
||||
deleteWorkflow: (id, wfId) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}`),
|
||||
publishWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/publish`, {}),
|
||||
// Team workflow stages (v0.37.15 — FE wiring for existing BE routes)
|
||||
// Team workflow stages
|
||||
workflowStages: (id, wfId) => rc.get(`/api/v1/teams/${id}/workflows/${wfId}/stages`),
|
||||
createWorkflowStage: (id, wfId, data) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/stages`, data),
|
||||
updateWorkflowStage: (id, wfId, sid, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`, data),
|
||||
deleteWorkflowStage: (id, wfId, sid) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`),
|
||||
reorderWorkflowStages: (id, wfId, ids) => rc.patch(`/api/v1/teams/${id}/workflows/${wfId}/stages/reorder`, { ordered_ids: ids }),
|
||||
// Adopt global workflows (v0.3.6)
|
||||
// Adopt global workflows
|
||||
availableWorkflows: (id) => rc.get(`/api/v1/teams/${id}/workflows/available`),
|
||||
adoptWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/adopt`, {}),
|
||||
},
|
||||
@@ -239,7 +239,6 @@ export function createDomains(restClient) {
|
||||
del: (id) => rc.del(`/api/v1/admin/extensions/${id}`),
|
||||
},
|
||||
|
||||
// v0.38.1: Global connections
|
||||
connections: crud(rc, '/api/v1/admin/connections'),
|
||||
|
||||
packages: {
|
||||
@@ -251,20 +250,18 @@ export function createDomains(restClient) {
|
||||
del: (id) => rc.del(`/api/v1/admin/packages/${id}`),
|
||||
settings: (id) => rc.get(`/api/v1/admin/packages/${id}/settings`),
|
||||
updateSettings: (id, data) => rc.put(`/api/v1/admin/packages/${id}/settings`, data),
|
||||
dependencies: (id) => rc.get(`/api/v1/admin/packages/${id}/dependencies`), // v0.38.2
|
||||
consumers: (id) => rc.get(`/api/v1/admin/packages/${id}/consumers`), // v0.38.2
|
||||
update: (id, file) => rc.upload(`/api/v1/admin/packages/${id}/update`, file), // v0.5.4
|
||||
exportPkg: (id) => `/api/v1/admin/packages/${id}/export`, // v0.5.4 (URL for window.open)
|
||||
dependencies: (id) => rc.get(`/api/v1/admin/packages/${id}/dependencies`),
|
||||
consumers: (id) => rc.get(`/api/v1/admin/packages/${id}/consumers`),
|
||||
update: (id, file) => rc.upload(`/api/v1/admin/packages/${id}/update`, file),
|
||||
exportPkg: (id) => `/api/v1/admin/packages/${id}/export`,
|
||||
registry: () => rc.get('/api/v1/admin/packages/registry'),
|
||||
registryInstall: (url) => rc.post('/api/v1/admin/packages/registry/install', { url }),
|
||||
// v0.5.0: Extension permissions
|
||||
registryInstall: (url) => rc.post('/api/v1/admin/packages/registry/install', { download_url: url }),
|
||||
permissions: (id) => rc.get(`/api/v1/admin/extensions/${id}/permissions`),
|
||||
grantPerm: (id, perm) => rc.post(`/api/v1/admin/extensions/${id}/permissions/${perm}/grant`, {}),
|
||||
revokePerm: (id, perm) => rc.post(`/api/v1/admin/extensions/${id}/permissions/${perm}/revoke`, {}),
|
||||
grantAllPerms: (id) => rc.post(`/api/v1/admin/extensions/${id}/permissions/grant-all`, {}),
|
||||
},
|
||||
|
||||
// v0.38.2: Full dependency graph
|
||||
dependencies: {
|
||||
list: () => rc.get('/api/v1/admin/dependencies'),
|
||||
},
|
||||
@@ -278,7 +275,6 @@ export function createDomains(restClient) {
|
||||
del: (id) => rc.del(`/api/v1/admin/surfaces/${id}`),
|
||||
},
|
||||
|
||||
// v0.6.1: Backup/Restore
|
||||
backup: {
|
||||
create: (opts) => rc.post('/api/v1/admin/backup' + _qs(opts)),
|
||||
list: () => rc.get('/api/v1/admin/backups'),
|
||||
|
||||
@@ -27,7 +27,6 @@ export function createCan(authRef) {
|
||||
|
||||
/**
|
||||
* Is the current user a platform admin?
|
||||
* v0.2.0: Uses RBAC grant instead of legacy role field.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isAdmin() {
|
||||
|
||||
@@ -104,7 +104,7 @@ export async function boot() {
|
||||
sw.slots = slots;
|
||||
sw.actions = actions;
|
||||
|
||||
// Realtime — room-scoped pub/sub over WebSocket (v0.5.0)
|
||||
// Realtime — room-scoped pub/sub over WebSocket
|
||||
sw.realtime = realtime;
|
||||
|
||||
// Shell helpers — imperative confirm/prompt backed by primitives
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* SurfaceViewport — container where the active surface renders
|
||||
*
|
||||
* Deliberately thin for v0.37.4. Error boundaries and
|
||||
* Deliberately thin. Error boundaries and
|
||||
* surface transitions will be added in later versions.
|
||||
*/
|
||||
const { html } = window;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* Admin > Connections — global extension connection CRUD
|
||||
* v0.38.1: Scoped credential management for extensions
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useEffect, useCallback } = hooks;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Reads globals:
|
||||
* __SECTION__ — active section name (string)
|
||||
* __BASE__ — base path
|
||||
* __CONFIG_SECTIONS__ — v0.38.3: extension config sections (array|null)
|
||||
* __CONFIG_SECTIONS__
|
||||
*
|
||||
* Layout: topbar (back + category tabs) + body (sidebar nav + content area).
|
||||
* All 24+ sections are native Preact components loaded lazily.
|
||||
@@ -34,7 +34,7 @@ const ADMIN_LABELS = {
|
||||
audit: 'Audit',
|
||||
};
|
||||
|
||||
// ── v0.38.3: Extension config sections ──────
|
||||
// ── Extension config sections ──────
|
||||
// Packages declare config_section in their manifest targeting "admin".
|
||||
// We merge them into the appropriate category and section module map.
|
||||
const _configSections = window.__CONFIG_SECTIONS__ || [];
|
||||
@@ -72,7 +72,6 @@ const sectionModules = {
|
||||
audit: () => import(`./audit.js${_v}`),
|
||||
};
|
||||
|
||||
// v0.38.3: Register dynamic section loaders for extension config sections
|
||||
for (const cs of _configSections) {
|
||||
const pkgId = cs.package_id;
|
||||
const component = cs.component || 'js/config.js';
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function PackagesSection() {
|
||||
const [registryPkgs, setRegistryPkgs] = useState([]);
|
||||
const [registryLoading, setRegistryLoading] = useState(false);
|
||||
const [installing, setInstalling] = useState(false);
|
||||
const [permsId, setPermsId] = useState(null); // v0.5.0: permissions drawer
|
||||
const [permsId, setPermsId] = useState(null);
|
||||
const [perms, setPerms] = useState([]);
|
||||
|
||||
const BASE = window.__BASE__ || '';
|
||||
@@ -162,7 +162,7 @@ export default function PackagesSection() {
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
// ── Permissions drawer (v0.5.0) ────────────
|
||||
// ── Permissions drawer ────────────
|
||||
async function togglePerms(pkgId) {
|
||||
if (permsId === pkgId) { setPermsId(null); setPerms([]); return; }
|
||||
try {
|
||||
@@ -323,7 +323,7 @@ export default function PackagesSection() {
|
||||
</div>
|
||||
`}
|
||||
|
||||
${/* ── Inline permissions drawer (v0.5.0) ── */``}
|
||||
${/* ── Inline permissions drawer ── */``}
|
||||
${permsId === pkg.id && html`
|
||||
<div style="padding:8px 12px 12px 24px;background:var(--bg-2);border-bottom:1px solid var(--border);">
|
||||
${perms.length === 0
|
||||
|
||||
@@ -36,6 +36,7 @@ export default function SettingsSection() {
|
||||
message_variant: cfg_.message?.variant || 'info',
|
||||
footer_enabled: !!cfg_.footer?.enabled,
|
||||
footer_text: cfg_.footer?.text || '',
|
||||
package_registry_url: cfg_.package_registry?.url || '',
|
||||
});
|
||||
setVault(v);
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
@@ -78,6 +79,9 @@ export default function SettingsSection() {
|
||||
text: cfg.footer_text,
|
||||
}});
|
||||
|
||||
// Package Registry
|
||||
await sw.api.admin.settings.update('package_registry', { value: { url: cfg.package_registry_url } });
|
||||
|
||||
sw.toast('Settings saved', 'success');
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
finally { setSaving(false); }
|
||||
@@ -166,6 +170,13 @@ export default function SettingsSection() {
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="settings-section"><h3>Package Registry</h3>
|
||||
<div class="form-group"><label>Registry URL</label>
|
||||
<input value=${cfg.package_registry_url} onInput=${e => set('package_registry_url', e.target.value)} placeholder="https://registry.example.com/registry.json" />
|
||||
</div>
|
||||
<span class="text-muted" style="font-size:12px;">URL to a JSON registry index. Enables browsing and one-click install under Packages > Registry.</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-section"><h3>Email</h3>
|
||||
<button class="btn-small" onClick=${testEmail}>Send Test Email</button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* DocsSurface — builtin documentation viewer (v0.6.2)
|
||||
* DocsSurface — builtin documentation viewer
|
||||
*
|
||||
* Reads globals:
|
||||
* __SECTION__ — active doc slug (e.g. "GETTING-STARTED")
|
||||
@@ -8,7 +8,6 @@
|
||||
* Fetches markdown from GET /api/v1/docs/:name, renders with
|
||||
* a simple markdown-to-HTML converter. Sidebar lists all docs.
|
||||
*
|
||||
* v0.6.2: dark mode fix, topbar navigation, error handling.
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useEffect, useCallback, useMemo } = hooks;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* ConnectionsSection — personal extension connection CRUD
|
||||
* v0.38.1: Scoped credential management for extensions
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useEffect, useCallback } = hooks;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Reads globals:
|
||||
* __SECTION__ — active section name (string)
|
||||
* __BASE__ — base path
|
||||
* __CONFIG_SECTIONS__ — v0.38.3: extension config sections (array|null)
|
||||
* __CONFIG_SECTIONS__
|
||||
*
|
||||
* Layout: topbar + left nav + content area (same CSS classes as before).
|
||||
* All sections are native Preact components loaded lazily.
|
||||
@@ -44,7 +44,7 @@ const SECTION_TITLES = {
|
||||
connections: 'Connections', notifications: 'Notifications',
|
||||
};
|
||||
|
||||
// ── v0.38.3: Extension config sections ──────
|
||||
// ── Extension config sections ──────
|
||||
// Packages declare config_section in their manifest. The backend passes
|
||||
// matching entries via __CONFIG_SECTIONS__. We merge them into the nav
|
||||
// and section module map for lazy loading.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* Team Admin > Connections — team-scoped extension connection CRUD
|
||||
* v0.38.1: Scoped credential management for extensions
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useEffect, useCallback } = hooks;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Reads globals:
|
||||
* __SECTION__ — active section name (string)
|
||||
* __BASE__ — base path
|
||||
* __CONFIG_SECTIONS__ — v0.38.3: extension config sections (array|null)
|
||||
* __CONFIG_SECTIONS__
|
||||
*
|
||||
* Layout: topbar (back + team name) + sidebar nav + content area.
|
||||
* All 10+ sections are native Preact components loaded lazily.
|
||||
@@ -36,7 +36,7 @@ const sectionModules = {
|
||||
activity: () => import('./activity.js'),
|
||||
};
|
||||
|
||||
// ── v0.38.3: Extension config sections ──────
|
||||
// ── Extension config sections ──────
|
||||
const _configSections = window.__CONFIG_SECTIONS__ || [];
|
||||
const _base = window.__BASE__ || '';
|
||||
for (const cs of _configSections) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* Team Admin > Members
|
||||
* v0.3.4: custom team roles support
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useEffect, useCallback } = hooks;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Team Admin > Workflows — v0.37.15 rewrite
|
||||
* Team Admin > Workflows
|
||||
*
|
||||
* Tab layout: Workflows | Assignments | Monitor
|
||||
* - Workflows: CRUD + inline stage editor (E2)
|
||||
@@ -365,7 +365,6 @@ function StageForm({ stage, teams, onSave, onCancel }) {
|
||||
stage?.branch_rules ? (typeof stage.branch_rules === 'string' ? stage.branch_rules : JSON.stringify(stage.branch_rules, null, 2)) : ''
|
||||
);
|
||||
|
||||
// v0.3.4: stage_config fields
|
||||
const sc = stage?.stage_config ? (typeof stage.stage_config === 'string' ? JSON.parse(stage.stage_config || '{}') : stage.stage_config) : {};
|
||||
const [requiredRole, setRequiredRole] = useState(sc.required_role || '');
|
||||
const [valApprovals, setValApprovals] = useState(sc.validation?.required_approvals || '');
|
||||
@@ -664,7 +663,7 @@ function MonitorTab({ teamId }) {
|
||||
`;
|
||||
}
|
||||
|
||||
// ── Signoff Panel (v0.3.4) ──────────────────
|
||||
// ── Signoff Panel ──────────────────
|
||||
|
||||
function SignoffPanel({ instanceId, teamId }) {
|
||||
const [signoffs, setSignoffs] = useState([]);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
const CACHE_NAME = 'switchboard-%%APP_VERSION%%-%%BUILD_HASH%%';
|
||||
|
||||
// App shell files to pre-cache on install (cleaned up in v0.37.12)
|
||||
// App shell files to pre-cache on install (cleaned up)
|
||||
const SHELL_FILES = [
|
||||
'./',
|
||||
'./index.html',
|
||||
@@ -35,11 +35,8 @@ const SHELL_FILES = [
|
||||
'./css/sw-chat-surface.css',
|
||||
'./css/sw-notes-pane.css',
|
||||
'./css/sw-notes-surface.css',
|
||||
// JS — debug tooling (v0.37.18: repl.js absorbed into Preact debug components)
|
||||
// JS — debug tooling
|
||||
'./js/debug.js',
|
||||
// Vendor
|
||||
'./vendor/marked.min.js',
|
||||
'./vendor/purify.min.js',
|
||||
// Static assets
|
||||
'./favicon.svg',
|
||||
'./favicon-light.svg',
|
||||
|
||||
74
src/vendor/marked.min.js
vendored
74
src/vendor/marked.min.js
vendored
File diff suppressed because one or more lines are too long
3
src/vendor/purify.min.js
vendored
3
src/vendor/purify.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user