Feat rebrand armature (#43)
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m34s
CI/CD / test-sqlite (push) Successful in 2m46s
CI/CD / build-and-deploy (push) Successful in 1m55s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #43.
This commit is contained in:
2026-03-31 23:25:37 +00:00
committed by xcaliber
parent fb5284f667
commit 680ec3b897
321 changed files with 956 additions and 1033 deletions

View File

@@ -1,5 +1,5 @@
/* ==========================================
Switchboard Core — Admin Surfaces
Armature — Admin Surfaces
==========================================
v0.25.0: Surface lifecycle management UI.
========================================== */

View File

@@ -1,5 +1,5 @@
/* ==========================================
Switchboard Core — UserMenu Component CSS
Armature — UserMenu Component CSS
==========================================
v0.31.1: Flyout container + item styles moved to primitives.css
(.sw-menu-flyout). This file keeps only the trigger button,

View File

@@ -5,7 +5,7 @@
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500&display=swap');
/* ==========================================
Switchboard Core
Armature
Clean utility layout · Open WebUI inspired
========================================== */

View File

@@ -1,5 +1,5 @@
/* ==========================================
* Switchboard Core — Workflow Components CSS
* Armature — Workflow Components CSS
* ========================================== */
/* ── Stage list (admin builder) ────────── */

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 Build Script
// Armature — CM6 Build Script
// ==========================================
// Bundles CodeMirror 6 into a single IIFE that
// exposes window.CM. Run via scripts/build-editor.sh

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 Chat Input Factory
// Armature — CM6 Chat Input Factory
// ==========================================
// Minimal markdown editor for the chat message
// input. No line numbers, no gutter. Enter sends,

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 Code Editor Factory
// Armature — CM6 Code Editor Factory
// ==========================================
// Full-featured code editor: line numbers, bracket
// matching, search/replace, auto-indent, syntax
@@ -52,7 +52,7 @@ import { rust } from '@codemirror/lang-rust';
import { vim } from '@replit/codemirror-vim';
import { emacs } from '@replit/codemirror-emacs';
import { switchboardTheme } from './theme.mjs';
import { armatureTheme } from './theme.mjs';
// ── Language Registry ────────────────────────
@@ -144,7 +144,7 @@ export function codeEditor(target, opts = {}) {
]),
// Theme
switchboardTheme,
armatureTheme,
themeCompartment.of(darkMode ? [oneDark] : []),
// Configurable compartments

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 Bundle Entrypoint
// Armature — CM6 Bundle Entrypoint
// ==========================================
// esbuild compiles this into an IIFE that sets
// window.CM with factory functions and utilities.

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 @Mention Extension
// Armature — CM6 @Mention Extension
// ==========================================
// Provides:
// 1. Autocomplete triggered by @ for personas/users

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 Note Editor Factory
// Armature — CM6 Note Editor Factory
// ==========================================
// Rich markdown editor for the notes panel.
// Decorates headings, bold, italic, code blocks inline.

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 Theme
// Armature — CM6 Theme
// ==========================================
// Maps the app's CSS custom properties to CM6
// theme slots. Works in both light and dark mode.
@@ -12,10 +12,10 @@
import { EditorView } from '@codemirror/view';
/**
* Switchboard base theme — uses CSS variables so it
* Armature base theme — uses CSS variables so it
* adapts automatically when the theme toggle fires.
*/
export const switchboardTheme = EditorView.theme({
export const armatureTheme = EditorView.theme({
'&': {
backgroundColor: 'var(--bg-surface, var(--bg))',
color: 'var(--text)',

View File

@@ -1,5 +1,5 @@
// ==========================================
// Chat Switchboard — CM6 Wikilink Extension
// Armature — CM6 Wikilink Extension
// ==========================================
// Provides:
// 1. ViewPlugin that decorates [[Title]] as clickable chips

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 332 B

View File

@@ -1,51 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<defs>
<linearGradient id="panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f5f2ef"/>
<stop offset="100%" stop-color="#e8e4e0"/>
</linearGradient>
</defs>
<!-- Panel background -->
<rect x="4" y="4" width="56" height="56" rx="10" fill="url(#panel)" stroke="#c8c4c0" stroke-width="1.5"/>
<!-- Static jack holes (light rings) -->
<circle cx="22" cy="23" r="8" fill="#d0ccc8"/>
<circle cx="42" cy="23" r="8" fill="#d0ccc8"/>
<circle cx="42" cy="43" r="8" fill="#d0ccc8"/>
<circle cx="22" cy="43" r="8" fill="#d0ccc8"/>
<!--
Cascading color rotation (clockwise: TL → TR → BR → BL)
dur=12s, each cascade round=3s, fade=0.4s out + 0.4s in
Same animation as dark favicon, fade-through color = #d0ccc8
-->
<!-- TL: Blue → Purple → Green → Orange (offset 0s) -->
<circle cx="22" cy="23" r="5.5" fill="#2D7DD2">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.0333;0.0667;0.25;0.2833;0.3167;0.5;0.5333;0.5667;0.75;0.7833;0.8167;1"
values="#2D7DD2;#d0ccc8;#9B59B6;#9B59B6;#d0ccc8;#2EAA4E;#2EAA4E;#d0ccc8;#E8852E;#E8852E;#d0ccc8;#2D7DD2;#2D7DD2"/>
</circle>
<!-- TR: Orange → Blue → Purple → Green (offset 0.4s) -->
<circle cx="42" cy="23" r="5.5" fill="#E8852E">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.0333;0.0667;0.1;0.2833;0.3167;0.35;0.5333;0.5667;0.6;0.7833;0.8167;0.85;1"
values="#E8852E;#E8852E;#d0ccc8;#2D7DD2;#2D7DD2;#d0ccc8;#9B59B6;#9B59B6;#d0ccc8;#2EAA4E;#2EAA4E;#d0ccc8;#E8852E;#E8852E"/>
</circle>
<!-- BR: Purple → Green → Orange → Blue (offset 0.8s) -->
<circle cx="42" cy="43" r="5.5" fill="#9B59B6">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.0667;0.1;0.1333;0.3167;0.35;0.3833;0.5667;0.6;0.6333;0.8167;0.85;0.8833;1"
values="#9B59B6;#9B59B6;#d0ccc8;#2EAA4E;#2EAA4E;#d0ccc8;#E8852E;#E8852E;#d0ccc8;#2D7DD2;#2D7DD2;#d0ccc8;#9B59B6;#9B59B6"/>
</circle>
<!-- BL: Green → Orange → Blue → Purple (offset 1.2s) -->
<circle cx="22" cy="43" r="5.5" fill="#2EAA4E">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.1;0.1333;0.1667;0.35;0.3833;0.4167;0.6;0.6333;0.6667;0.85;0.8833;0.9167;1"
values="#2EAA4E;#2EAA4E;#d0ccc8;#E8852E;#E8852E;#d0ccc8;#2D7DD2;#2D7DD2;#d0ccc8;#9B59B6;#9B59B6;#d0ccc8;#2EAA4E;#2EAA4E"/>
</circle>
<svg viewBox="0 0 520 80" xmlns="http://www.w3.org/2000/svg">
<line x1="28" y1="8" x2="6" y2="68" stroke="#3B82F6" stroke-width="3.5" stroke-linecap="round"/>
<line x1="28" y1="8" x2="50" y2="68" stroke="#3B82F6" stroke-width="3.5" stroke-linecap="round"/>
<line x1="14" y1="46" x2="42" y2="46" stroke="#3B82F6" stroke-width="2.5" stroke-linecap="round"/>
<circle cx="28" cy="8" r="4" fill="#3B82F6"/>
<circle cx="14" cy="46" r="2.5" fill="#EF4444"/>
<circle cx="28" cy="46" r="2.5" fill="#3B82F6"/>
<circle cx="42" cy="46" r="2.5" fill="#EF4444"/>
<text x="58" y="62" fill="#222222" font-family="-apple-system, system-ui, 'Segoe UI', sans-serif" font-size="52" font-weight="300" letter-spacing="1">rmature</text>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 737 B

View File

@@ -1,53 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<defs>
<linearGradient id="panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3d3935"/>
<stop offset="100%" stop-color="#252220"/>
</linearGradient>
</defs>
<!-- Panel background -->
<rect x="4" y="4" width="56" height="56" rx="10" fill="url(#panel)" stroke="#4a4540" stroke-width="1.5"/>
<!-- Static jack holes (dark rings) -->
<circle cx="22" cy="23" r="8" fill="#111"/>
<circle cx="42" cy="23" r="8" fill="#111"/>
<circle cx="42" cy="43" r="8" fill="#111"/>
<circle cx="22" cy="43" r="8" fill="#111"/>
<!--
Cascading color rotation (clockwise: TL → TR → BR → BL)
dur=12s, each cascade round=3s, fade=0.4s out + 0.4s in
As dot N fades in, dot N+1 begins fading out (overlap)
Blue=#2D7DD2 Orange=#E8852E Green=#2EAA4E Purple=#9B59B6
-->
<!-- TL: Blue → Purple → Green → Orange (offset 0s) -->
<circle cx="22" cy="23" r="5.5" fill="#2D7DD2">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.0333;0.0667;0.25;0.2833;0.3167;0.5;0.5333;0.5667;0.75;0.7833;0.8167;1"
values="#2D7DD2;#111;#9B59B6;#9B59B6;#111;#2EAA4E;#2EAA4E;#111;#E8852E;#E8852E;#111;#2D7DD2;#2D7DD2"/>
</circle>
<!-- TR: Orange → Blue → Purple → Green (offset 0.4s) -->
<circle cx="42" cy="23" r="5.5" fill="#E8852E">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.0333;0.0667;0.1;0.2833;0.3167;0.35;0.5333;0.5667;0.6;0.7833;0.8167;0.85;1"
values="#E8852E;#E8852E;#111;#2D7DD2;#2D7DD2;#111;#9B59B6;#9B59B6;#111;#2EAA4E;#2EAA4E;#111;#E8852E;#E8852E"/>
</circle>
<!-- BR: Purple → Green → Orange → Blue (offset 0.8s) -->
<circle cx="42" cy="43" r="5.5" fill="#9B59B6">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.0667;0.1;0.1333;0.3167;0.35;0.3833;0.5667;0.6;0.6333;0.8167;0.85;0.8833;1"
values="#9B59B6;#9B59B6;#111;#2EAA4E;#2EAA4E;#111;#E8852E;#E8852E;#111;#2D7DD2;#2D7DD2;#111;#9B59B6;#9B59B6"/>
</circle>
<!-- BL: Green → Orange → Blue → Purple (offset 1.2s) -->
<circle cx="22" cy="43" r="5.5" fill="#2EAA4E">
<animate attributeName="fill" dur="12s" repeatCount="indefinite"
keyTimes="0;0.1;0.1333;0.1667;0.35;0.3833;0.4167;0.6;0.6333;0.6667;0.85;0.8833;0.9167;1"
values="#2EAA4E;#2EAA4E;#111;#E8852E;#E8852E;#111;#2D7DD2;#2D7DD2;#111;#9B59B6;#9B59B6;#111;#2EAA4E;#2EAA4E"/>
</circle>
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="6" fill="#14142a"/>
<line x1="16" y1="7" x2="26" y2="16" stroke="#c8c8d0" stroke-width="1" opacity="0.3"/>
<line x1="26" y1="16" x2="16" y2="25" stroke="#c8c8d0" stroke-width="1" opacity="0.3"/>
<line x1="16" y1="25" x2="6" y2="16" stroke="#c8c8d0" stroke-width="1" opacity="0.3"/>
<line x1="6" y1="16" x2="16" y2="7" stroke="#c8c8d0" stroke-width="1" opacity="0.3"/>
<line x1="6" y1="16" x2="16" y2="16" stroke="#c8c8d0" stroke-width="0.8" opacity="0.4"/>
<line x1="26" y1="16" x2="16" y2="16" stroke="#c8c8d0" stroke-width="0.8" opacity="0.4"/>
<line x1="16" y1="7" x2="16" y2="16" stroke="#c8c8d0" stroke-width="0.8" opacity="0.4"/>
<line x1="16" y1="16" x2="16" y2="25" stroke="#c8c8d0" stroke-width="0.8" opacity="0.4"/>
<circle cx="16" cy="7" r="2" fill="#3B82F6"/>
<circle cx="6" cy="16" r="2" fill="#3B82F6"/>
<circle cx="26" cy="16" r="2" fill="#EF4444"/>
<circle cx="16" cy="25" r="2" fill="#3B82F6"/>
<circle cx="16" cy="16" r="2.5" fill="#3B82F6"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Switchboard Core</title>
<title>Armature</title>
<script>window.__BASE__ = '%%BASE_PATH%%';</script>
<script>window.__VERSION__ = '%%APP_VERSION%%'; if (window.__VERSION__.includes('%%')) window.__VERSION__ = 'dev';</script>
<meta http-equiv="refresh" content="0;url=%%BASE_PATH%%/">

View File

@@ -238,7 +238,7 @@ export const debugEngine = {
try {
snap.storageKeys = Object.keys(localStorage).filter(k =>
k.startsWith('chatSwitchboard') || k.startsWith('switchboard') || k.startsWith('sb_')
k.startsWith('armatureChat') || k.startsWith('armature') || k.startsWith('arm_')
);
} catch (_) {
snap.storageKeys = '(error reading)';
@@ -355,7 +355,7 @@ export const debugEngine = {
// ── Export ───────────────────────────────
exportText() {
let text = `=== Switchboard Core Debug Export ===\n`;
let text = `=== Armature Debug Export ===\n`;
text += `Exported: ${new Date().toISOString()}\n`;
text += `URL: ${location.href}\n`;
text += `ENV: ${window.__ENV__ || 'unknown'} | VERSION: ${window.__VERSION__ || 'unknown'} | BASE: ${window.__BASE__ || '(root)'}\n`;
@@ -395,7 +395,7 @@ export const debugEngine = {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `switchboard-debug-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
a.download = `armature-debug-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
a.click();
URL.revokeObjectURL(url);
},

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Action Registry
// Armature — SDK: Action Registry
// ==========================================
// Named actions that extensions expose for invocation.
//

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: API Domain Namespaces
// Armature — SDK: API Domain Namespaces
// ==========================================
// Namespaced domain clients wrapping restClient.
// Each domain is a plain object with typed methods.

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Auth Module
// Armature — SDK: Auth Module
// ==========================================
// Owns token storage, login/logout/refresh lifecycle,
// and the permissions/teams/policies cache.
@@ -9,7 +9,7 @@
// ==========================================
const BASE = window.__BASE__ || '';
const _storageKey = BASE ? `sb_auth_${BASE.replace(/\//g, '')}` : 'sb_auth';
const _storageKey = BASE ? `arm_auth_${BASE.replace(/\//g, '')}` : 'arm_auth';
/**
* Create the auth module.
@@ -44,9 +44,9 @@ export function createAuth() {
}));
// Cookie sync for Go template page auth
if (_accessToken) {
document.cookie = `sb_token=${_accessToken}; path=/; max-age=900; SameSite=Strict`;
document.cookie = `arm_token=${_accessToken}; path=/; max-age=900; SameSite=Strict`;
} else {
document.cookie = 'sb_token=; path=/; max-age=0';
document.cookie = 'arm_token=; path=/; max-age=0';
}
}
@@ -71,7 +71,7 @@ export function createAuth() {
_policies = {};
localStorage.removeItem(_storageKey);
sessionStorage.removeItem('sw-chat-active');
document.cookie = 'sb_token=; path=/; max-age=0';
document.cookie = 'arm_token=; path=/; max-age=0';
if (_refreshTimer) { clearTimeout(_refreshTimer); _refreshTimer = null; }
}
@@ -197,9 +197,9 @@ export function createAuth() {
async boot() {
_loadTokens();
if (!_refreshToken) {
// No tokens in localStorage — clear any stale sb_token cookie
// No tokens in localStorage — clear any stale arm_token cookie
// so Go SSR middleware doesn't trust a leftover cookie.
document.cookie = 'sb_token=; path=/; max-age=0';
document.cookie = 'arm_token=; path=/; max-age=0';
return;
}

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: RBAC Gate
// Armature — SDK: RBAC Gate
// ==========================================
// Synchronous permission checking for render paths.
//

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Event Bus
// Armature — SDK: Event Bus
// ==========================================
// Labeled event bus with WebSocket bridge.
// Port of events.js as ES module with factory pattern.

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK Entry Point
// Armature — SDK Entry Point
// ==========================================
// Assembles all SDK modules into the `sw` object
// and runs the boot sequence.

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Markdown Renderer
// Armature — SDK: Markdown Renderer
// ==========================================
// Unified markdown rendering via `marked` (v16, vendored).
// Delegates fenced code blocks to sw.renderers and

View File

@@ -1,8 +1,8 @@
// ==========================================
// Switchboard Core — SDK: Pipe/Filter Pipeline
// Armature — SDK: Pipe/Filter Pipeline
// ==========================================
// Three-stage pipeline: pre-send, stream, render.
// Verbatim port of switchboard-sdk.js pipe code.
// Verbatim port of armature-sdk.js pipe code.
//
// Factory: createPipe()
// ==========================================

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Realtime
// Armature — SDK: Realtime
// ==========================================
// Room-scoped publish/subscribe over the WebSocket bridge.
//

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Renderer Registry
// Armature — SDK: Renderer Registry
// ==========================================
// Kernel-level renderer registration. Extensions register
// block renderers (fenced code blocks) and post renderers

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: REST Client
// Armature — SDK: REST Client
// ==========================================
// Internal HTTP engine for the new SDK.
// All domain clients and auth module use this.

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Slot System
// Armature — SDK: Slot System
// ==========================================
// Named shell regions where extensions inject UI.
//

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Storage Module
// Armature — SDK: Storage Module
// ==========================================
// Namespaced localStorage wrapper.
//

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core — SDK: Theme Control
// Armature — SDK: Theme Control
// ==========================================
// Theme management: localStorage + data-theme + system pref.
// Port of Theme from ui-primitives.js as ES module.
@@ -7,7 +7,7 @@
// Factory: createTheme(emitFn)
// ==========================================
const _key = 'switchboard_theme';
const _key = 'armature_theme';
/**
* Create the theme module.

View File

@@ -158,7 +158,7 @@ export default function SettingsSection() {
<label class="toggle-label"><input type="checkbox" checked=${cfg.footer_enabled} onChange=${e => set('footer_enabled', e.target.checked)} /><span class="toggle-track"></span><span>Show footer bar</span></label>
${cfg.footer_enabled && html`
<div style="margin-top:8px;">
<div class="form-group"><label>Text</label><input value=${cfg.footer_text} onInput=${e => set('footer_text', e.target.value)} placeholder="Powered by Switchboard Core" /></div>
<div class="form-group"><label>Text</label><input value=${cfg.footer_text} onInput=${e => set('footer_text', e.target.value)} placeholder="Powered by Armature" /></div>
</div>
`}
</div>

View File

@@ -26,9 +26,9 @@ export function Hero({ base = '', version = '', env = null }) {
<div class="login-hero-brand login-anim login-anim-1">
<img class="login-hero-brand-icon"
src="${base}/favicon.svg?v=${version}"
alt="Switchboard Core" />
alt="Armature" />
<span class="login-hero-brand-text">
Switchboard <span class="hl">Core</span>
Armature
</span>
</div>
<h1 class="login-anim login-anim-2">

View File

@@ -48,8 +48,8 @@ function LoginSurface() {
const switchToLogin = useCallback(() => setTab('login'), []);
const heading = tab === 'login'
? { title: 'Welcome back', sub: 'Sign in to your Switchboard instance' }
: { title: 'Create account', sub: 'Join your team on Switchboard' };
? { title: 'Welcome back', sub: 'Sign in to your Armature instance' }
: { title: 'Create account', sub: 'Join your team on Armature' };
return html`
<div class="login-shell">

View File

@@ -23,11 +23,11 @@ function WelcomeSurface() {
const hasSurfaces = surfaces.length > 0;
return html`
<${Topbar} title="Switchboard" />
<${Topbar} title="Armature" />
<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:24px;">
<div style="max-width:420px;width:100%;background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:32px;text-align:center;">
<div style="font-size:36px;margin-bottom:16px;">${hasSurfaces ? '🏠' : '📦'}</div>
<h2 style="font-size:20px;font-weight:600;margin:0 0 8px;">Welcome to Switchboard</h2>
<h2 style="font-size:20px;font-weight:600;margin:0 0 8px;">Welcome to Armature</h2>
${hasSurfaces ? html`
<p style="font-size:14px;color:var(--text-2);margin:0 0 24px;line-height:1.5;">
No default surface is configured. Choose a surface from

View File

@@ -1,6 +1,6 @@
{
"name": "Switchboard Core",
"short_name": "Switchboard",
"name": "Armature",
"short_name": "Armature",
"description": "Self-hosted AI chat — unified interface for multiple providers",
"start_url": "./",
"scope": "./",

View File

@@ -1,5 +1,5 @@
// ==========================================
// Switchboard Core Service Worker
// Armature Service Worker
// ==========================================
// Caches the app shell for offline / instant load.
// API calls always go to network (never cached).
@@ -9,7 +9,7 @@
// a new hash, which triggers install → precache → activate
// → purge old caches automatically.
const CACHE_NAME = 'switchboard-%%APP_VERSION%%-%%BUILD_HASH%%';
const CACHE_NAME = 'armature-%%APP_VERSION%%-%%BUILD_HASH%%';
// App shell files to pre-cache on install (cleaned up)
const SHELL_FILES = [