Feat v0.6.11 css dedup (#46)
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 2m44s
CI/CD / test-sqlite (push) Successful in 2m54s
CI/CD / build-and-deploy (push) Successful in 1m17s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #46.
This commit is contained in:
2026-04-01 11:18:28 +00:00
committed by xcaliber
parent ca3f845c34
commit 786bc92768
44 changed files with 311 additions and 317 deletions

View File

@@ -2,6 +2,56 @@
All notable changes to Armature are documented here.
## v0.6.11 — CSS Deduplication
One class per concept. The old `primitives.css` button, toast, popup-menu,
dropdown, and tabs systems are retired. `sw-primitives.css` is the single
source of truth for all Preact component styles.
### Changed
- **Buttons**: All 29 files migrated from `.btn-primary` / `.btn-small` /
`.btn-danger` / `.btn-ghost` / `.btn-md` / `.btn-sm` to the BEM-style
`.sw-btn .sw-btn--{variant} .sw-btn--{size}` system.
- **Toasts**: Old `.toast-container` / `.toast` CSS deleted. SDK's
`sw.toast()` API already used `.sw-toast-*` classes — no JS changes.
- **Popup menus**: Old `.popup-menu` / `.popup-menu-item` CSS deleted
(unused — `.sw-menu` is the active system).
- **Dropdown collision resolved**: Old `.sw-dropdown` (styled `<select>`)
deleted from `primitives.css`. The `sw-primitives.css` custom dropdown
component (`.sw-dropdown` with BEM sub-elements) is authoritative.
- **Tabs collision resolved**: Old `.sw-tabs` / `.sw-tab-btn` deleted from
`primitives.css`. The `sw-primitives.css` scrollable tabs component
(`.sw-tabs__tab`) is authoritative.
- **`.settings-section` collision resolved**: Removed duplicate definition
from `modals.css`. The `surfaces.css` card-style definition is
authoritative; `.settings-content .settings-section` override resets
card styling for flat settings layouts.
### Added
- `.sw-btn--success` variant in `sw-primitives.css` (green action button).
- `--bg-active` CSS variable in both dark/light themes (`variables.css`).
- `scripts/audit-css-collisions.sh` — finds duplicate class selectors
across kernel CSS files and outputs a JSON collision report.
### Fixed
- `packages/sdk-test-runner/css/main.css`: Wrong variable names
(`--text3``--text-3`, `--text2``--text-2`, `--bg1`/`--bg2`
`--bg-raised`).
- `packages/icd-test-runner/css/main.css`: Replaced inline button fallback
styles with kernel `.sw-btn` system.
### Removed
- Old button classes: `.btn-primary`, `.btn-small`, `.btn-danger`,
`.btn-full`, `.btn-ghost`, `.btn-subtle`, `.btn-sm`, `.btn-md`.
- Old toast classes: `.toast-container`, `.toast`, `.toast.error/warning/success`.
- Old popup menu classes: `.popup-menu`, `.popup-menu-item`, `.popup-menu-*`.
- Old dropdown and tabs definitions from `primitives.css` that collided
with `sw-primitives.css`.
## v0.6.10 — Viewport Foundation
Single layout model. Every surface renders inside one containment chain:

View File

@@ -51,9 +51,9 @@ Shipped. Single layout model: `body → shell → surface`. CSS `zoom` replaces
`transform: scale()`. `100dvh` fallbacks everywhere. Extension surfaces use
`100%` instead of `100vh`. Dead shell layout code deprecated. See CHANGELOG.md.
### v0.6.11 — CSS Deduplication
### v0.6.11 — CSS Deduplication
Kill the old primitive system. One class per concept.
Shipped. Old primitive system retired. One class per concept. See CHANGELOG.md.
| Step | Description |
|------|-------------|
@@ -118,7 +118,7 @@ v0.6.9 Session Lifetime Config ✅ SHIPPED
v0.6.10 Viewport Foundation ✅ SHIPPED
v0.6.11 CSS Deduplication ← Can't audit styling until there's one source of truth
v0.6.11 CSS Deduplication ✅ SHIPPED
v0.6.12 Extension CSS Isolation ← Scoping requires the kernel CSS to be stable first

View File

@@ -1,6 +1,6 @@
# Armature — Roadmap
## Current: v0.6.10Viewport Foundation
## Current: v0.6.11CSS Deduplication
Self-hosted extensible platform. Auth, identity, packages, Starlark sandbox,
storage, realtime, and ops are kernel primitives. Everything else is an extension.

View File

@@ -1 +1 @@
0.6.10
0.6.11

View File

@@ -343,8 +343,8 @@
(ch.description ? '<div style="font-size:12px;color:var(--text-2);margin-bottom:8px;"><strong>Description:</strong> ' + (typeof esc === 'function' ? esc(ch.description) : ch.description) + '</div>' : '') +
(ch.created_at ? '<div style="font-size:12px;color:var(--text-3);margin-bottom:16px;">Created: ' + new Date(ch.created_at).toLocaleString() + '</div>' : '') +
'<div style="display:flex;justify-content:flex-end;gap:8px;">' +
'<button class="btn-small" id="dashDetailClose">Close</button>' +
'<button class="btn-small btn-primary" id="dashDetailOpen">Open Chat</button>' +
'<button class="sw-btn sw-btn--secondary sw-btn--sm" id="dashDetailClose">Close</button>' +
'<button class="sw-btn sw-btn--primary sw-btn--sm" id="dashDetailOpen">Open Chat</button>' +
'</div>';
modal.appendChild(box);

View File

@@ -509,7 +509,7 @@
border-bottom: 1px solid var(--border, #2a2a2e);
}
.surface-editor .notes-toolbar .btn-small {
.surface-editor .notes-toolbar .sw-btn--sm {
font-size: 11px;
padding: 3px 6px;
}

View File

@@ -447,11 +447,4 @@
color: var(--text-3);
}
.btn-danger {
background: var(--danger, #e53e3e);
color: #fff;
border: none;
}
.btn-danger:hover {
opacity: 0.9;
}
/* git-board danger button override — uses kernel .sw-btn--danger */

View File

@@ -168,7 +168,7 @@
onSelect=${function (o, r) { setOwner(o); setRepo(r); }} />
</div>
<div class="gb-header__right">
<button class="btn-small" onClick=${loadBoard} disabled=${loading}>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${loadBoard} disabled=${loading}>
${loading ? '↻' : 'Refresh'}
</button>
</div>
@@ -196,7 +196,7 @@
<p>Ask your admin to add a <strong>Gitea</strong> connection in
<strong>Admin → Connections</strong>, or add a personal one in
<strong>Settings → Connections</strong>.</p>
<button class="btn-small btn-primary"
<button class="sw-btn sw-btn--primary sw-btn--sm"
onClick=${function () { window.location.href = base + '/settings'; }}>
Open Settings
</button>
@@ -449,11 +449,11 @@
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) postComment();
}} />
<div class="gb-modal__actions">
<button class="btn-small btn-primary" disabled=${posting || !comment.trim()}
<button class="sw-btn sw-btn--primary sw-btn--sm" disabled=${posting || !comment.trim()}
onClick=${postComment}>
${posting ? 'Posting…' : 'Comment'}
</button>
<button class="btn-small ${issue.state === 'open' ? 'btn-danger' : 'btn-secondary'}"
<button class="sw-btn sw-btn--secondary sw-btn--sm ${issue.state === 'open' ? 'sw-btn sw-btn--danger sw-btn--md' : 'sw-btn sw-btn--secondary sw-btn--md'}"
onClick=${toggleState}>
${issue.state === 'open' ? 'Close Issue' : 'Reopen Issue'}
</button>

View File

@@ -34,8 +34,8 @@
'Registered via surface manifest', '') +
'</div>' +
'<div class="hello-actions">' +
'<button class="btn-primary" id="helloToast">Show Toast</button>' +
'<button class="btn-secondary" id="helloApi">Test API</button>' +
'<button class="sw-btn sw-btn--primary sw-btn--md" id="helloToast">Show Toast</button>' +
'<button class="sw-btn sw-btn--secondary sw-btn--md" id="helloApi">Test API</button>' +
'</div>' +
'<pre class="hello-manifest">' + esc(JSON.stringify(manifest, null, 2)) + '</pre>' +
'</div>';

View File

@@ -26,45 +26,9 @@
background: var(--bg-hover) !important;
}
/* Buttons — override small padding for our use */
#extension-mount .btn-primary,
#extension-mount .btn-secondary,
#extension-mount .btn-ghost {
font-size: 13px;
padding: 7px 16px;
border-radius: var(--radius, 8px);
cursor: pointer;
font-family: var(--font, 'DM Sans', sans-serif);
/* Buttons — uses kernel sw-btn system, minor overrides for weight */
#extension-mount .sw-btn {
font-weight: 600;
transition: background var(--transition, 180ms ease), opacity var(--transition, 180ms ease);
}
/* Fallback button styles if platform classes not loaded */
#extension-mount button.btn-primary {
background: var(--accent, #6c9fff);
color: #fff;
border: none;
}
#extension-mount button.btn-primary:hover {
opacity: 0.88;
}
#extension-mount button.btn-secondary {
background: var(--accent-dim, rgba(108,159,255,0.12));
color: var(--accent, #6c9fff);
border: none;
}
#extension-mount button.btn-secondary:hover {
background: var(--accent-dim, rgba(108,159,255,0.2));
}
#extension-mount button.btn-ghost {
background: transparent;
color: var(--text-2, #9898a8);
border: 1px solid var(--border, #2e2e35);
}
#extension-mount button.btn-ghost:hover {
background: var(--bg-hover, #2a2a30);
}
/* Status dot animation for running state */

View File

@@ -93,7 +93,7 @@
// Configure button
var cfgBtn = $('button', {
className: T.providerSetup.configured ? 'btn-ghost' : 'btn-primary',
className: T.providerSetup.configured ? 'sw-btn sw-btn--ghost sw-btn--sm' : 'sw-btn sw-btn--primary sw-btn--md',
style: { alignSelf: 'flex-end', whiteSpace: 'nowrap' },
onClick: function () {
if (!T.providerSetup.apiKey) {
@@ -110,7 +110,7 @@
if (T.providerSetup.configured) {
var clearBtn = $('button', {
className: 'btn-ghost',
className: 'sw-btn sw-btn--ghost sw-btn--sm',
style: { alignSelf: 'flex-end', color: 'var(--danger)', whiteSpace: 'nowrap' },
onClick: function () {
T.providerSetup.apiKey = '';
@@ -189,19 +189,19 @@
T.renderControls = function () {
T.el.controls.innerHTML = '';
var btnSmoke = $('button', {
className: 'btn-primary',
className: 'sw-btn sw-btn--primary sw-btn--md',
onClick: function () { T.runSuite('smoke'); }
}, 'Smoke');
var btnCrud = $('button', {
className: 'btn-secondary',
className: 'sw-btn sw-btn--secondary sw-btn--md',
onClick: function () { T.runSuite('crud'); }
}, 'CRUD');
var btnAll = $('button', {
className: 'btn-primary',
className: 'sw-btn sw-btn--primary sw-btn--md',
onClick: function () { T.runSuite('all'); }
}, 'Run All');
var btnClear = $('button', {
className: 'btn-ghost',
className: 'sw-btn sw-btn--ghost sw-btn--sm',
onClick: function () { T.results = []; T.renderProgress(); T.renderSummary(); T.renderDetail(); }
}, 'Clear');
T.el.controls.appendChild(btnSmoke);
@@ -210,14 +210,14 @@
// AuthZ button — only useful if admin (needs fixtures)
if (T.user.role === 'admin') {
var btnAuthz = $('button', {
className: T.fixtures.ready ? 'btn-secondary' : 'btn-ghost',
className: T.fixtures.ready ? 'sw-btn sw-btn--secondary sw-btn--md' : 'sw-btn sw-btn--ghost sw-btn--sm',
style: { opacity: T.fixtures.ready ? '1' : '0.5' },
onClick: function () { T.runSuite('authz'); }
}, 'AuthZ');
T.el.controls.appendChild(btnAuthz);
var btnSecurity = $('button', {
className: T.fixtures.ready ? 'btn-secondary' : 'btn-ghost',
className: T.fixtures.ready ? 'sw-btn sw-btn--secondary sw-btn--md' : 'sw-btn sw-btn--ghost sw-btn--sm',
style: {
opacity: T.fixtures.ready ? '1' : '0.5',
borderColor: T.fixtures.ready ? 'var(--danger)' : undefined,
@@ -230,7 +230,7 @@
// Provider tier button — needs provider setup
var btnProv = $('button', {
className: T.providerSetup.configured ? 'btn-secondary' : 'btn-ghost',
className: T.providerSetup.configured ? 'sw-btn sw-btn--secondary sw-btn--md' : 'sw-btn sw-btn--ghost sw-btn--sm',
style: { opacity: T.providerSetup.configured ? '1' : '0.5' },
onClick: function () { T.runSuite('provider'); }
}, 'Providers');
@@ -238,7 +238,7 @@
// SDK tier button — tests armature-sdk.js contract
var btnSdk = $('button', {
className: (typeof window.sw !== 'undefined') ? 'btn-secondary' : 'btn-ghost',
className: (typeof window.sw !== 'undefined') ? 'sw-btn sw-btn--secondary sw-btn--md' : 'sw-btn sw-btn--ghost sw-btn--sm',
style: { opacity: (typeof window.sw !== 'undefined') ? '1' : '0.5' },
onClick: function () { T.runSuite('sdk'); }
}, 'SDK');
@@ -246,7 +246,7 @@
// Packaging tier button — extension permission lifecycle (v0.29.0)
var btnPkg = $('button', {
className: 'btn-secondary',
className: 'sw-btn sw-btn--secondary sw-btn--md',
onClick: function () { T.runSuite('packaging'); }
}, 'Packaging');
T.el.controls.appendChild(btnPkg);
@@ -259,11 +259,11 @@
// Export buttons
var btnCopy = $('button', {
className: 'btn-ghost',
className: 'sw-btn sw-btn--ghost sw-btn--sm',
onClick: function () { T.exportReport('clipboard'); }
}, 'Copy Report');
var btnDownload = $('button', {
className: 'btn-ghost',
className: 'sw-btn sw-btn--ghost sw-btn--sm',
onClick: function () { T.exportReport('download'); }
}, 'Download');
T.el.controls.appendChild(btnCopy);
@@ -274,12 +274,12 @@
T.el.controls.appendChild($('div', { style: { width: '1px', height: '28px', background: 'var(--border)', flexShrink: '0' } }));
if (!T.fixtures.ready) {
T.el.controls.appendChild($('button', {
className: 'btn-secondary',
className: 'sw-btn sw-btn--secondary sw-btn--md',
onClick: async function () { await T.provisionFixtures(); T.renderControls(); }
}, 'Provision Test Users'));
} else {
T.el.controls.appendChild($('button', {
className: 'btn-ghost',
className: 'sw-btn sw-btn--ghost sw-btn--sm',
style: { color: 'var(--danger)' },
onClick: async function () { await T.teardownFixtures(); T.renderControls(); }
}, 'Tear Down Fixtures'));

View File

@@ -14,12 +14,12 @@
}
.sdkr-version {
font-size: 12px;
color: var(--text3, #888);
color: var(--text-3, #888);
font-family: 'JetBrains Mono', monospace;
}
.sdkr-desc {
font-size: 13px;
color: var(--text2, #aaa);
color: var(--text-2, #aaa);
margin: 0 0 16px 0;
line-height: 1.5;
}
@@ -30,7 +30,7 @@
padding: 10px 14px;
border: 1px solid var(--border, #333);
border-radius: 6px;
background: var(--bg1, #1a1a1a);
background: var(--bg-raised, #1a1a1a);
}
.sdkr-fixture-row {
display: flex;
@@ -41,14 +41,14 @@
.sdkr-fixture-label {
font-size: 12px;
font-weight: 600;
color: var(--text2, #aaa);
color: var(--text-2, #aaa);
text-transform: uppercase;
white-space: nowrap;
}
.sdkr-select {
padding: 4px 8px;
font-size: 13px;
background: var(--bg2, #222);
background: var(--bg-raised, #222);
color: var(--text, #e0e0e0);
border: 1px solid var(--border, #444);
border-radius: 4px;
@@ -57,7 +57,7 @@
.sdkr-input {
padding: 4px 8px;
font-size: 13px;
background: var(--bg2, #222);
background: var(--bg-raised, #222);
color: var(--text, #e0e0e0);
border: 1px solid var(--border, #444);
border-radius: 4px;
@@ -67,17 +67,17 @@
font-family: 'JetBrains Mono', monospace;
}
.sdkr-input::placeholder {
color: var(--text3, #666);
color: var(--text-3, #666);
font-family: inherit;
}
.sdkr-fixture-status {
font-size: 12px;
color: var(--text3, #888);
color: var(--text-3, #888);
font-family: 'JetBrains Mono', monospace;
}
.sdkr-fixture-note {
font-size: 11px;
color: var(--text3, #666);
color: var(--text-3, #666);
margin-top: 6px;
line-height: 1.4;
}
@@ -98,7 +98,7 @@
.sdkr-filter-label {
font-size: 12px;
font-weight: 600;
color: var(--text2, #aaa);
color: var(--text-2, #aaa);
text-transform: uppercase;
letter-spacing: 0.05em;
}
@@ -119,13 +119,13 @@
padding: 6px 14px;
border-radius: 6px;
border: 1px solid var(--border, #444);
background: var(--bg2, #2a2a2a);
background: var(--bg-raised, #2a2a2a);
color: var(--text, #e0e0e0);
font-size: 13px;
cursor: pointer;
transition: background 0.15s;
}
.sdkr-btn:hover { background: var(--bg3, #383838); }
.sdkr-btn:hover { background: var(--bg-hover, #383838); }
.sdkr-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.sdkr-btn-primary {
background: var(--accent, #3b82f6);
@@ -146,7 +146,7 @@
gap: 8px;
margin-bottom: 12px;
padding: 10px 12px;
background: var(--bg2, #1e1e1e);
background: var(--bg-raised, #1e1e1e);
border-radius: 8px;
border: 1px solid var(--border, #333);
}
@@ -173,8 +173,8 @@
.sdkr-table th {
text-align: left;
padding: 8px 10px;
background: var(--bg2, #1e1e1e);
color: var(--text2, #aaa);
background: var(--bg-raised, #1e1e1e);
color: var(--text-2, #aaa);
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
@@ -205,12 +205,12 @@
.sdkr-ms {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text3, #888);
color: var(--text-3, #888);
text-align: right;
}
.sdkr-detail {
font-size: 11px;
color: var(--text3, #888);
color: var(--text-3, #888);
max-width: 400px;
word-break: break-word;
}

View File

@@ -213,17 +213,7 @@
cursor: pointer;
font-weight: 500;
}
.demo-card .btn-primary {
background: var(--accent);
color: var(--text-on-color);
}
.demo-card .btn-primary:hover {
background: var(--accent-hover);
}
.demo-card .btn-primary:disabled {
opacity: 0.5;
cursor: default;
}
/* demo-card buttons — uses kernel .sw-btn--primary */
/* Public link row */
.public-link-row {

View File

@@ -216,7 +216,7 @@
}
const tryBtn = document.createElement('button');
tryBtn.className = 'btn btn-primary';
tryBtn.className = 'sw-btn sw-btn--primary sw-btn--md';
tryBtn.textContent = 'Try It';
tryBtn.disabled = !isReady;
if (!isReady) {

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# audit-css-collisions.sh — Find duplicate class selectors across kernel CSS files
# Outputs a JSON collision report to stdout.
# Usage: bash scripts/audit-css-collisions.sh [css-dir]
set -euo pipefail
CSS_DIR="${1:-src/css}"
# Extract class selectors from CSS files and track their file + line
declare -A SEEN # selector -> "file:line file:line ..."
for f in "$CSS_DIR"/*.css; do
[ -f "$f" ] || continue
lineno=0
while IFS= read -r line; do
lineno=$((lineno + 1))
# Extract class selectors (e.g. .foo-bar, .foo__bar--baz)
# Skip lines that are comments, inside @keyframes, or @media queries
[[ "$line" =~ ^[[:space:]]*/\* ]] && continue
[[ "$line" =~ ^[[:space:]]*@keyframes ]] && continue
# Match class selectors at the start of a rule
while [[ "$line" =~ \.([a-zA-Z_][a-zA-Z0-9_-]*) ]]; do
cls=".${BASH_REMATCH[1]}"
entry="$(basename "$f"):$lineno"
if [ -n "${SEEN[$cls]:-}" ]; then
SEEN[$cls]="${SEEN[$cls]} $entry"
else
SEEN[$cls]="$entry"
fi
# Remove the matched portion to find more classes on the same line
line="${line#*${BASH_REMATCH[0]}}"
done
done < "$f"
done
# Build JSON: only emit selectors defined in more than one file
echo "["
first=true
for cls in "${!SEEN[@]}"; do
locations="${SEEN[$cls]}"
# Get unique files
files=()
for loc in $locations; do
file="${loc%%:*}"
found=false
for f in "${files[@]:-}"; do
[ "$f" = "$file" ] && found=true && break
done
$found || files+=("$file")
done
# Only report if defined in 2+ different files
[ ${#files[@]} -lt 2 ] && continue
if $first; then first=false; else echo ","; fi
# Build locations array
locs_json=""
for loc in $locations; do
[ -n "$locs_json" ] && locs_json="$locs_json, "
locs_json="$locs_json\"$loc\""
done
files_json=""
for f in "${files[@]}"; do
[ -n "$files_json" ] && files_json="$files_json, "
files_json="$files_json\"$f\""
done
printf ' {"selector": "%s", "files": [%s], "locations": [%s]}' "$cls" "$files_json" "$locs_json"
done
echo ""
echo "]"

View File

@@ -16,7 +16,7 @@
{{if .Multiple}}multiple{{end}}
{{if .Accept}}accept="{{.Accept}}"{{end}}
style="display:none">
<button type="button" class="btn-small btn-primary"
<button type="button" class="sw-btn sw-btn--primary sw-btn--sm"
onclick="document.getElementById('{{.FieldName}}Input').click()">
Choose files…
</button>

View File

@@ -38,9 +38,6 @@
display: flex; justify-content: flex-end; gap: 0.5rem;
}
.settings-section { margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border); }
.settings-section:last-child { border-bottom: none; margin-bottom: 0; }
.settings-section h3 { font-size: 12px; font-weight: 600; color: var(--text-3); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.06em; }
.avatar-upload-row { display: flex; align-items: center; gap: 14px; margin-bottom: 1rem; }
.avatar-preview {
width: 56px; height: 56px; border-radius: 50%; background: var(--bg-raised);
@@ -196,8 +193,8 @@
.admin-user-actions { display: flex; gap: 6px; flex-shrink: 0; }
.admin-user-actions button { background: none; border: 1px solid var(--border); color: var(--text-2); border-radius: 4px; padding: 4px 10px; font-size: 12px; cursor: pointer; min-width: 68px; text-align: center; }
.admin-user-actions button:hover { border-color: var(--border-light); color: var(--text); }
.admin-user-actions .btn-danger { color: var(--danger); }
.admin-user-actions .btn-danger:hover { border-color: var(--danger); }
.admin-user-actions .sw-btn--danger { color: var(--danger); }
.admin-user-actions .sw-btn--danger:hover { border-color: var(--danger); }
.inline-select { background: var(--bg-surface); border: 1px solid var(--border); color: var(--text-2); border-radius: 4px; padding: 3px 8px; font-size: 12px; cursor: pointer; }
.admin-model-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); font-size: 14px; min-height: 44px; }

View File

@@ -1,27 +1,11 @@
/* ── primitives.css ──────────────────────────
Buttons, forms, toasts, popups, toggles, badges
Forms, toggles, badges, mode selector, REPL, SDK primitives.
Buttons/toasts/popup-menus migrated to sw-primitives.css (v0.6.11).
──────────────────────────────────────────── */
/* ── Buttons ─────────────────────────────── */
button { font-family: var(--font); cursor: pointer; }
.btn-primary {
background: var(--accent); color: var(--text-on-color); border: none;
padding: 8px 16px; border-radius: var(--radius);
font-weight: 500; font-size: 13px;
transition: background var(--transition);
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-full { width: 100%; }
.btn-small {
background: var(--bg-raised); border: 1px solid var(--border);
color: var(--text-2); padding: 6px 12px; border-radius: var(--radius);
font-size: 12px; transition: all var(--transition);
}
.btn-small:hover { background: var(--bg-hover); color: var(--text); }
.btn-small.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--text-on-color); }
.btn-danger { background: var(--danger); color: var(--text-on-color); border: none; border-radius: var(--radius); }
/* ── Forms ────────────────────────────────── */
@@ -61,20 +45,7 @@ select option { background: var(--bg-surface); color: var(--text); }
.checkbox-label { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; margin: 0.5rem 0; color: var(--text-2); }
/* ── Toast ────────────────────────────────── */
.toast-container { position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%); z-index: 10000; display: flex; flex-direction: column; gap: 0.5rem; }
.toast {
background: var(--bg-surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 8px 16px; font-size: 13px;
display: flex; align-items: center; gap: 8px;
animation: toast-in 0.25s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.toast.error { border-color: var(--danger); }
.toast.warning { border-color: var(--warning); }
.toast.success { border-color: var(--success); }
.toast button { background: none; border: none; color: var(--text-3); cursor: pointer; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }
/* Toast system — see sw-primitives.css (.sw-toast) */
/* ── Mode Selector (v0.21.3) ───────────── */
@@ -170,29 +141,7 @@ select option { background: var(--bg-surface); color: var(--text); }
.repl-json-toggle:hover { color: var(--accent); }
/* ── Popup Menu (shared base) ───────────── */
.popup-menu {
display: none; position: absolute; z-index: 200;
background: var(--bg-raised); border: 1px solid var(--border-light);
border-radius: var(--radius); padding: 4px;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
min-width: 160px;
}
.popup-menu.open { display: block; }
.popup-menu-item {
display: flex; align-items: center; gap: 10px;
padding: 8px 12px; border-radius: var(--radius);
background: none; border: none; color: var(--text-2);
cursor: pointer; font-size: 13px; width: 100%;
transition: background var(--transition), color var(--transition);
}
.popup-menu-item:hover { background: var(--bg-hover); color: var(--text); }
.popup-menu-item.disabled { opacity: 0.4; pointer-events: none; }
.popup-menu-danger:hover { color: var(--danger); }
.popup-menu-icon { flex-shrink: 0; display: flex; align-items: center; }
.popup-menu-hint { margin-left: auto; font-size: 11px; color: var(--text-3); }
.popup-menu-divider { height: 1px; background: var(--border); margin: 4px 8px; }
/* Popup menu — see sw-primitives.css (.sw-menu) */
/* ── Tools Toggle ────────────────────────── */
@@ -354,11 +303,7 @@ select option { background: var(--bg-surface); color: var(--text); }
.toggle-btn.active { background: var(--bg-raised); color: var(--text); }
.toggle-btn:hover { color: var(--text-2); }
.btn-ghost { background: transparent; color: var(--text-2); border: 1px solid var(--border); }
.btn-ghost:hover { background: var(--bg-hover); }
.btn-subtle { background: var(--accent-dim); color: var(--accent); border: none; }
.btn-sm { padding: 5px 11px; font-size: 12px; border-radius: 6px; cursor: pointer; font-weight: 500; font-family: inherit; }
.btn-md { padding: 7px 15px; font-size: 13px; border-radius: var(--radius); cursor: pointer; font-weight: 500; font-family: inherit; }
/* Button variants — see sw-primitives.css (.sw-btn--*) */
/* ── Badge ───────────────────────────────── */
.badge {
@@ -439,15 +384,7 @@ select option { background: var(--bg-surface); color: var(--text); }
border: none; height: 1px; background: var(--border, #2a2a2e); margin: 4px 0;
}
/* Dropdown — styled native select */
.sw-dropdown {
background: var(--bg-raised, #222); color: var(--text, #eee);
border: 1px solid var(--border, #2e2e35); border-radius: var(--radius, 6px);
padding: 6px 10px; font-size: 13px; font-family: inherit;
cursor: pointer; min-width: 100px;
}
.sw-dropdown:hover { border-color: var(--border-light, #444); }
.sw-dropdown:focus { outline: none; border-color: var(--accent, #b38a4e); }
/* Dropdown — see sw-primitives.css (.sw-dropdown) */
/* Toolbar — horizontal button row */
.sw-toolbar {
@@ -455,22 +392,7 @@ select option { background: var(--bg-surface); color: var(--text); }
padding: 4px 0;
}
/* Tabs — tabbed content container */
.sw-tabs { display: flex; flex-direction: column; height: 100%; }
.sw-tabs-bar {
display: flex; gap: 0; border-bottom: 1px solid var(--border, #2e2e35);
flex-shrink: 0;
}
.sw-tab-btn {
padding: 8px 16px; background: none; border: none; border-bottom: 2px solid transparent;
color: var(--text-2, #999); cursor: pointer; font-size: 12px; font-weight: 600;
font-family: inherit; text-transform: uppercase; letter-spacing: 0.5px;
transition: color 0.15s, border-color 0.15s;
}
.sw-tab-btn:hover { color: var(--text, #eee); }
.sw-tab-btn--active { color: var(--accent, #b38a4e); border-bottom-color: var(--accent, #b38a4e); }
.sw-tabs-content { flex: 1; min-height: 0; overflow: hidden; }
.sw-tab-panel { height: 100%; overflow-y: auto; }
/* Tabs — see sw-primitives.css (.sw-tabs) */
/* ── Responsive: Mobile ───────────────────── */

View File

@@ -23,6 +23,8 @@
.sw-btn--secondary:hover:not(:disabled) { background: var(--bg-hover); border-color: var(--border-light); }
.sw-btn--danger { background: var(--danger); color: var(--text-on-color); }
.sw-btn--danger:hover:not(:disabled) { opacity: 0.9; }
.sw-btn--success { background: var(--success); color: var(--text-on-color); }
.sw-btn--success:hover:not(:disabled) { opacity: 0.9; }
.sw-btn--ghost { background: transparent; color: var(--text-2); }
.sw-btn--ghost:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); }

View File

@@ -15,6 +15,7 @@
--bg-surface: #18181b;
--bg-raised: #222227;
--bg-hover: #2a2a30;
--bg-active: #2f2f38;
--bg-elevated: #42424e;
--border: #2e2e35;
--border-elevated: #555560;
@@ -81,6 +82,7 @@
--bg-surface: #ffffff;
--bg-raised: #eff0f3;
--bg-hover: #e5e6eb;
--bg-active: #dddee5;
--bg-elevated: #ffffff;
--border: #d5d6dc;
--border-elevated: #c0c0c8;

View File

@@ -27,8 +27,8 @@
}
.wf-stage-name { flex: 1; font-size: 13px; font-weight: 500; }
.wf-stage-row .badge { font-size: 10px; }
.wf-stage-row .btn-small { opacity: 0; transition: opacity 0.15s; }
.wf-stage-row:hover .btn-small { opacity: 1; }
.wf-stage-row .sw-btn--sm { opacity: 0; transition: opacity 0.15s; }
.wf-stage-row:hover .sw-btn--sm { opacity: 1; }
/* ── Queue sidebar ───────────────────── */
@@ -55,16 +55,16 @@
.sb-queue-item.active { background: var(--accent-dim); color: var(--accent); }
.sb-queue-icon { font-size: 14px; }
.sb-queue-label { flex: 1; }
.sb-queue-item .btn-small {
.sb-queue-item .sw-btn--sm {
opacity: 0; padding: 2px 6px; font-size: 11px;
transition: opacity 0.15s;
}
.sb-queue-item:hover .btn-small { opacity: 1; }
.sb-queue-item:hover .sw-btn--sm { opacity: 1; }
/* Collapsed sidebar — show icon only */
.sidebar.collapsed .sb-queue-item { justify-content: center; padding: 5px 0; margin: 0 6px; gap: 0; }
.sidebar.collapsed .sb-queue-label { display: none; }
.sidebar.collapsed .sb-queue-item .btn-small { display: none; }
.sidebar.collapsed .sb-queue-item .sw-btn--sm { display: none; }
.sidebar.collapsed .sb-queue-badge { display: none !important; }
/* Unpin button */
@@ -150,7 +150,7 @@
.wf-bar-status.stale { background: var(--warning-dim); color: var(--warning-light); }
.wf-bar-spacer { flex: 1; }
.wf-bar-actions { display: flex; gap: 6px; }
.wf-bar-actions .btn-small { font-size: 11px; padding: 3px 10px; }
.wf-bar-actions .sw-btn--sm { font-size: 11px; padding: 3px 10px; }
/* DnD stage reorder feedback */
.wf-stage-row.dragging { opacity: 0.4; }

View File

@@ -102,13 +102,13 @@ function DebugModal({ engine }) {
</div>
<div class="modal-footer debug-modal-footer">
<div class="debug-footer-left">
<button class="btn-danger btn-small" onClick=${() => { setActiveTab('console'); engine.runDiagnostics(); }}>Diagnostics</button>
<button class="btn-danger btn-small" onClick=${onPurge}>Purge Cache</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => { setActiveTab('console'); engine.runDiagnostics(); }}>Diagnostics</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${onPurge}>Purge Cache</button>
</div>
<div class="debug-footer-right">
<button class="btn-secondary btn-small" onClick=${onClear}>Clear</button>
<button class="btn-secondary btn-small" onClick=${() => engine.copyLog()}>Copy</button>
<button class="btn-secondary btn-small" onClick=${() => engine.downloadLog()}>Export</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${onClear}>Clear</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => engine.copyLog()}>Copy</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => engine.downloadLog()}>Export</button>
</div>
</div>
</div>

View File

@@ -77,9 +77,9 @@ export default function AuditSection() {
</div>
<div style="margin-top:12px;display:flex;align-items:center;gap:8px;">
<button class="btn-small" disabled=${page <= 1} onClick=${() => setPage(p => p - 1)}>\u2190 Prev</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" disabled=${page <= 1} onClick=${() => setPage(p => p - 1)}>\u2190 Prev</button>
<span class="text-muted" style="font-size:12px;">Page ${page} of ${totalPages} (${total} total)</span>
<button class="btn-small" disabled=${page >= totalPages} onClick=${() => setPage(p => p + 1)}>Next \u2192</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" disabled=${page >= totalPages} onClick=${() => setPage(p => p + 1)}>Next \u2192</button>
</div>
`
}

View File

@@ -118,13 +118,13 @@ export default function BackupSection() {
return html`
<div>
<div style="display:flex;gap:8px;margin-bottom:20px;flex-wrap:wrap;">
<button class="btn btn-primary" onclick=${() => createBackup(false)} disabled=${creating || restoring}>
<button class="sw-btn sw-btn--primary sw-btn--md" onclick=${() => createBackup(false)} disabled=${creating || restoring}>
${creating ? 'Creating\u2026' : 'Download Backup'}
</button>
<button class="btn" onclick=${() => createBackup(true)} disabled=${creating || restoring}>
${creating ? 'Creating\u2026' : 'Save to Server'}
</button>
<button class="btn btn-danger" onclick=${restoreBackup} disabled=${creating || restoring}>
<button class="sw-btn sw-btn--danger sw-btn--md" onclick=${restoreBackup} disabled=${creating || restoring}>
${restoring ? 'Restoring\u2026' : 'Restore from File'}
</button>
</div>
@@ -149,8 +149,8 @@ export default function BackupSection() {
<td>${formatBytes(b.size)}</td>
<td>${formatDate(b.created_at)}</td>
<td>
<button class="btn btn-sm" onclick=${() => downloadBackup(b.name)}>Download</button>
<button class="btn btn-sm btn-danger" onclick=${() => deleteBackup(b.name)} style="margin-left:4px;">Delete</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onclick=${() => downloadBackup(b.name)}>Download</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onclick=${() => deleteBackup(b.name)} style="margin-left:4px;">Delete</button>
</td>
</tr>
`)}

View File

@@ -38,7 +38,7 @@ export default function BroadcastSection() {
<option value="critical">Critical</option>
</select>
</div>
<button class="btn-md btn-primary" onClick=${send} disabled=${sending}>${sending ? 'Sending\u2026' : 'Send Broadcast'}</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${send} disabled=${sending}>${sending ? 'Sending\u2026' : 'Send Broadcast'}</button>
</div>
`;
}

View File

@@ -114,7 +114,7 @@ export default function ConnectionsSection() {
return html`
<div style="margin-bottom:12px;display:flex;align-items:center;gap:8px;">
<button class="btn-md btn-primary" onClick=${openAdd}
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${openAdd}
disabled=${!connTypes.length}>+ Add Connection</button>
${!connTypes.length && html`
<span style="color:var(--text-3);font-size:12px;">
@@ -165,11 +165,11 @@ export default function ConnectionsSection() {
</div>
`)}
<div style="display:flex;gap:8px;margin-top:12px;">
<button class="btn-md btn-primary" disabled=${saving}
<button class="sw-btn sw-btn--primary sw-btn--md" disabled=${saving}
onClick=${save}>
${saving ? 'Saving\u2026' : editId ? 'Update' : 'Add'}
</button>
<button class="btn-md btn-secondary" onClick=${cancel}>Cancel</button>
<button class="sw-btn sw-btn--secondary sw-btn--md" onClick=${cancel}>Cancel</button>
</div>
</div>
`}
@@ -196,7 +196,7 @@ export default function ConnectionsSection() {
<button class="icon-btn" title="Edit" onClick=${() => openEdit(c)}>
\u{270F}\u{FE0F}
</button>
<button class="icon-btn icon-btn-danger" title="Delete"
<button class="icon-btn icon-sw-btn sw-btn--danger sw-btn--md" title="Delete"
onClick=${() => del(c)}>
\u{1F5D1}
</button>

View File

@@ -112,11 +112,11 @@ export default function GroupsSection() {
if (detail) return html`
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<button class="btn-small" onClick=${() => setDetail(null)}>\u2190 Back</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setDetail(null)}>\u2190 Back</button>
<h4 style="margin:0;">${detail.name}</h4>
<span class="text-muted" style="font-size:12px;">${detail.scope}</span>
<div style="flex:1"></div>
<button class="btn-small btn-danger" onClick=${deleteGroup}>Delete</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${deleteGroup}>Delete</button>
</div>
<div class="settings-section" style="margin-bottom:16px;">
@@ -134,7 +134,7 @@ export default function GroupsSection() {
</div>
<div style="margin-bottom:16px;">
<button class="btn-small btn-primary" onClick=${savePermissions}>Save Permissions</button>
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${savePermissions}>Save Permissions</button>
</div>
<hr style="border:none;border-top:1px solid var(--border);margin:16px 0;" />
@@ -144,7 +144,7 @@ export default function GroupsSection() {
${members.map(m => html`
<div class="admin-surface-row" key=${m.id || m.user_id}>
<span style="flex:1;">${m.username || m.user_id}</span>
<button class="btn-small btn-danger" onClick=${() => removeMember(m.user_id || m.id)}>Remove</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => removeMember(m.user_id || m.id)}>Remove</button>
</div>
`)}
</div>
@@ -160,12 +160,12 @@ export default function GroupsSection() {
</div>
</div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Add</button>
<button type="button" class="btn-small" onClick=${() => setShowAddMember(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Add</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowAddMember(false)}>Cancel</button>
</div>
</form>
`
: html`<button class="btn-small" style="margin-top:8px;" onClick=${() => setShowAddMember(true)}>+ Add Member</button>`
: html`<button class="sw-btn sw-btn--secondary sw-btn--sm" style="margin-top:8px;" onClick=${() => setShowAddMember(true)}>+ Add Member</button>`
}
</div>
`;
@@ -174,7 +174,7 @@ export default function GroupsSection() {
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="flex:1"></div>
<button class="btn-md btn-primary" onClick=${() => setShowCreate(true)}>+ Add</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${() => setShowCreate(true)}>+ Add</button>
</div>
${showCreate && html`
@@ -187,8 +187,8 @@ export default function GroupsSection() {
</div>
</div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Create</button>
<button type="button" class="btn-small" onClick=${() => setShowCreate(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Create</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowCreate(false)}>Cancel</button>
</div>
</form>
`}

View File

@@ -252,10 +252,10 @@ export default function PackagesSection() {
options=${TYPE_OPTIONS.map(t => ({ label: t.charAt(0).toUpperCase() + t.slice(1), value: t }))}
/>
<span style="flex:1;" />
<button class="btn-small" onClick=${uploadPackage} disabled=${installing}>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${uploadPackage} disabled=${installing}>
${installing ? 'Installing\u2026' : 'Install Package'}
</button>
<button class="btn-small" onClick=${toggleRegistry}>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${toggleRegistry}>
${registryOpen ? 'Close Registry' : 'Browse Registry'}
</button>
</div>
@@ -276,28 +276,28 @@ export default function PackagesSection() {
${pkg.is_system && html`<span class="badge" style="margin-left:4px;">system</span>`}
</div>
<div class="admin-actions-cell">
<button class="btn-small"
<button class="sw-btn sw-btn--secondary sw-btn--sm"
onClick=${() => toggleEnabled(pkg)}
disabled=${CORE_IDS.has(pkg.id) || pkg.status === 'dormant' || pkg.status === 'pending_review'}
title=${pkg.status === 'dormant' ? 'Requires chat' : pkg.status === 'pending_review' ? 'Grant permissions to activate' : ''}>
${pkg.enabled ? 'Disable' : 'Enable'}
</button>
${hasManifestSettings(pkg) && html`
<button class="btn-small" onClick=${() => toggleSettings(pkg.id)}>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => toggleSettings(pkg.id)}>
${expandedId === pkg.id ? 'Close' : 'Settings'}
</button>
`}
${hasDeclaredPerms(pkg) && html`
<button class="btn-small" onClick=${() => togglePerms(pkg.id)}>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => togglePerms(pkg.id)}>
${permsId === pkg.id ? 'Close' : 'Permissions'}
</button>
`}
${pkg.source !== 'core' && html`
<button class="btn-small" onClick=${() => updatePkg(pkg)}>Update</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => updatePkg(pkg)}>Update</button>
`}
<button class="btn-small" onClick=${() => exportPkg(pkg.id)}>Export</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => exportPkg(pkg.id)}>Export</button>
${pkg.source !== 'core' && !pkg.is_system && html`
<button class="btn-small btn-danger" onClick=${() => deletePkg(pkg)}>Delete</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => deletePkg(pkg)}>Delete</button>
`}
</div>
</div>
@@ -317,7 +317,7 @@ export default function PackagesSection() {
style="flex:1;padding:4px 8px;background:var(--bg-1,var(--bg));color:var(--text);border:1px solid var(--border);border-radius:4px;font-size:12px;" />
</div>
`)}
<button class="btn-small" style="margin-top:4px;" onClick=${() => saveSettings(pkg.id)}>Save</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" style="margin-top:4px;" onClick=${() => saveSettings(pkg.id)}>Save</button>
`
}
</div>
@@ -333,7 +333,7 @@ export default function PackagesSection() {
${perms.map(p => html`
<div key=${p.permission} style="display:flex;gap:8px;align-items:center;margin-bottom:4px;">
<code style="min-width:140px;font-size:12px;">${p.permission}</code>
<button class="btn-small ${p.granted ? 'btn-danger' : 'btn-primary'}"
<button class="sw-btn sw-btn--secondary sw-btn--sm ${p.granted ? 'sw-btn sw-btn--danger sw-btn--md' : 'sw-btn sw-btn--primary sw-btn--md'}"
style="min-width:64px;font-size:11px;"
onClick=${() => p.granted ? revokePerm(pkg.id, p.permission) : grantPerm(pkg.id, p.permission)}>
${p.granted ? 'Revoke' : 'Grant'}
@@ -345,7 +345,7 @@ export default function PackagesSection() {
`)}
</div>
${perms.some(p => !p.granted) && html`
<button class="btn-small btn-primary" onClick=${() => grantAllPerms(pkg.id)}>Grant All</button>
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${() => grantAllPerms(pkg.id)}>Grant All</button>
`}
`
}
@@ -376,7 +376,7 @@ export default function PackagesSection() {
</div>
<div class="admin-actions-cell">
${typeBadge(rp.type)}
<button class="btn-small btn-primary"
<button class="sw-btn sw-btn--primary sw-btn--sm"
onClick=${() => installFromRegistry(rp.download_url)}>Install</button>
</div>
</div>

View File

@@ -231,11 +231,11 @@ export default function SettingsSection() {
</div>
<div class="settings-section"><h3>Email</h3>
<button class="btn-small" onClick=${testEmail}>Send Test Email</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${testEmail}>Send Test Email</button>
</div>
<div style="display:flex;gap:8px;align-items:center;margin-top:16px;">
<button class="btn-md btn-primary" onClick=${save} disabled=${saving}>${saving ? 'Saving\u2026' : 'Save Settings'}</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${save} disabled=${saving}>${saving ? 'Saving\u2026' : 'Save Settings'}</button>
</div>
</div>
`;

View File

@@ -61,7 +61,7 @@ export default function StorageSection() {
<div class="settings-section" style="margin-bottom:16px;">
<h4>Orphaned Blobs</h4>
<p class="text-muted" style="font-size:12px;">${orphans.count || 0} orphans found</p>
${(orphans.count || 0) > 0 && html`<button class="btn-small btn-primary" onClick=${cleanup}>Clean Up</button>`}
${(orphans.count || 0) > 0 && html`<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${cleanup}>Clean Up</button>`}
</div>
`}

View File

@@ -94,10 +94,10 @@ export default function TeamsSection() {
if (detail) return html`
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<button class="btn-small" onClick=${() => setDetail(null)}>\u2190 Back</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setDetail(null)}>\u2190 Back</button>
<h4 style="margin:0;">${detail.name}</h4>
<div style="flex:1"></div>
<button class="btn-small btn-danger" onClick=${() => deleteTeam(detail.id)}>Delete Team</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => deleteTeam(detail.id)}>Delete Team</button>
</div>
${detail.description && html`<p class="text-muted" style="font-size:12px;margin-bottom:12px;">${detail.description}</p>`}
@@ -112,7 +112,7 @@ export default function TeamsSection() {
<option value="member">member</option>
<option value="admin">admin</option>
</select>
<button class="btn-small btn-danger" onClick=${() => removeMember(m.id)}>Remove</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => removeMember(m.id)}>Remove</button>
</div>
`)}
</div>
@@ -132,12 +132,12 @@ export default function TeamsSection() {
</div>
</div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Add</button>
<button type="button" class="btn-small" onClick=${() => setShowAddMember(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Add</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowAddMember(false)}>Cancel</button>
</div>
</form>
`
: html`<button class="btn-small" style="margin-top:8px;" onClick=${() => setShowAddMember(true)}>+ Add Member</button>`
: html`<button class="sw-btn sw-btn--secondary sw-btn--sm" style="margin-top:8px;" onClick=${() => setShowAddMember(true)}>+ Add Member</button>`
}
</div>
`;
@@ -147,7 +147,7 @@ export default function TeamsSection() {
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="flex:1"></div>
<button class="btn-md btn-primary" onClick=${() => setShowCreate(true)}>+ Add</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${() => setShowCreate(true)}>+ Add</button>
</div>
${showCreate && html`
@@ -157,8 +157,8 @@ export default function TeamsSection() {
<div class="form-group"><label>Description</label><input name="desc" placeholder="Optional" /></div>
</div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Create</button>
<button type="button" class="btn-small" onClick=${() => setShowCreate(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Create</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowCreate(false)}>Cancel</button>
</div>
</form>
`}

View File

@@ -103,7 +103,7 @@ export default function UsersSection() {
<input placeholder="Search users\u2026" value=${search} onInput=${e => setSearch(e.target.value)} autocomplete="off" />
</div>
<div style="flex:1"></div>
<button class="btn-md btn-primary" onClick=${() => setShowCreate(true)}>+ Add</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${() => setShowCreate(true)}>+ Add</button>
</div>
${showCreate && html`
@@ -117,8 +117,8 @@ export default function UsersSection() {
</div>
</div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Create</button>
<button type="button" class="btn-small" onClick=${() => setShowCreate(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Create</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowCreate(false)}>Cancel</button>
</div>
</form>
`}
@@ -134,14 +134,14 @@ export default function UsersSection() {
</div>
<div class="admin-actions-cell">
${u.status === 'pending' && html`
<button class="btn-small btn-primary" onClick=${() => approveUser(u.id)}>Approve</button>
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${() => approveUser(u.id)}>Approve</button>
`}
${u.is_active
? html`<button class="btn-small" onClick=${() => toggleActive(u.id, false)}>Disable</button>`
: html`<button class="btn-small" onClick=${() => toggleActive(u.id, true)}>Enable</button>`
? html`<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => toggleActive(u.id, false)}>Disable</button>`
: html`<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => toggleActive(u.id, true)}>Enable</button>`
}
<button class="btn-small" onClick=${() => resetPassword(u.id, u.username)}>Reset PW</button>
<button class="btn-small btn-danger" onClick=${() => deleteUser(u.id, u.username)}>Delete</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => resetPassword(u.id, u.username)}>Reset PW</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => deleteUser(u.id, u.username)}>Delete</button>
</div>
</div>
`)}

View File

@@ -80,10 +80,10 @@ export default function WorkflowsSection() {
if (editing) return html`
<form onSubmit=${updateWorkflow}>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<button type="button" class="btn-small" onClick=${() => setEditing(null)}>\u2190 Back</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setEditing(null)}>\u2190 Back</button>
<h4 style="margin:0;">Edit: ${editing.name}</h4>
<div style="flex:1"></div>
<button type="button" class="btn-small btn-danger" onClick=${() => deleteWorkflow(editing.id)}>Delete</button>
<button type="button" class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => deleteWorkflow(editing.id)}>Delete</button>
</div>
<div class="form-row">
<div class="form-group"><label>Name</label><input name="name" value=${editing.name || ''} /></div>
@@ -113,7 +113,7 @@ export default function WorkflowsSection() {
</div>
<div class="form-row" style="margin-top:16px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Save</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Save</button>
</div>
</form>
`;
@@ -122,7 +122,7 @@ export default function WorkflowsSection() {
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="flex:1"></div>
<button class="btn-md btn-primary" onClick=${() => setShowCreate(true)}>+ Add</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${() => setShowCreate(true)}>+ Add</button>
</div>
${showCreate && html`
@@ -138,8 +138,8 @@ export default function WorkflowsSection() {
</div>
<div class="form-group"><label>Description</label><input name="description" placeholder="Optional" /></div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Create</button>
<button type="button" class="btn-small" onClick=${() => setShowCreate(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Create</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowCreate(false)}>Cancel</button>
</div>
</form>
`}

View File

@@ -73,6 +73,6 @@ export function AppearanceSection() {
</div>
</div>
</div>
<button class="btn-md btn-primary" onClick=${save}>Save</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${save}>Save</button>
`;
}

View File

@@ -114,7 +114,7 @@ export function ConnectionsSection() {
return html`
<div style="margin-bottom:12px;">
<button class="btn-md btn-primary" onClick=${openAdd}
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${openAdd}
disabled=${!connTypes.length}>+ Add Connection</button>
${!connTypes.length && html`
<span style="margin-left:8px;color:var(--text-3);font-size:12px;">
@@ -169,11 +169,11 @@ export function ConnectionsSection() {
</div>
`)}
<div style="display:flex;gap:8px;margin-top:12px;">
<button class="btn-md btn-primary" disabled=${saving}
<button class="sw-btn sw-btn--primary sw-btn--md" disabled=${saving}
onClick=${save}>
${saving ? 'Saving\u2026' : editId ? 'Update' : 'Add'}
</button>
<button class="btn-md btn-secondary" onClick=${cancel}>Cancel</button>
<button class="sw-btn sw-btn--secondary sw-btn--md" onClick=${cancel}>Cancel</button>
</div>
</div>
`}
@@ -207,7 +207,7 @@ export function ConnectionsSection() {
<button class="icon-btn" title="Edit" onClick=${() => openEdit(c)}>
\u{270F}\u{FE0F}
</button>
<button class="icon-btn icon-btn-danger" title="Delete"
<button class="icon-btn icon-sw-btn sw-btn--danger sw-btn--md" title="Delete"
onClick=${() => del(c)}>
\u{1F5D1}
</button>

View File

@@ -54,7 +54,7 @@ export function GeneralSection() {
If your chosen surface is uninstalled, the global default is used.
</span>
</div>
<button class="btn-md btn-primary" style="margin-top:12px;"
<button class="sw-btn sw-btn--primary sw-btn--md" style="margin-top:12px;"
disabled=${saving} onClick=${save}>
${saving ? 'Saving\u2026' : 'Save'}
</button>

View File

@@ -81,7 +81,7 @@ export default function NotificationsSection() {
onChange=${() => togglePref(type, enabled)} />
<span class="toggle-track"></span>
</label>
<button class="btn-small btn-ghost"
<button class="sw-btn sw-btn--ghost sw-btn--sm"
disabled=${isSaving}
onClick=${() => resetPref(type)}
title="Reset to default">

View File

@@ -124,12 +124,12 @@ export function ProfileSection() {
}
</div>
<div style="display:flex;gap:8px;">
<button class="btn-sm btn-secondary"
<button class="sw-btn sw-btn--secondary sw-btn--sm"
onClick=${() => fileRef.current?.click()}>
Upload
</button>
${avatarUrl && html`
<button class="btn-sm btn-secondary" onClick=${removeAvatar}>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${removeAvatar}>
Remove
</button>
`}
@@ -148,7 +148,7 @@ export function ProfileSection() {
<label>Email</label>
<input type="email" disabled value=${email} />
</div>
<button class="btn-md btn-primary" disabled=${savingProfile}
<button class="sw-btn sw-btn--primary sw-btn--md" disabled=${savingProfile}
onClick=${saveProfile}>
${savingProfile ? 'Saving\u2026' : 'Save Profile'}
</button>
@@ -171,7 +171,7 @@ export function ProfileSection() {
<input type="password" autocomplete="new-password"
value=${confirmPw} onInput=${e => setConfirmPw(e.target.value)} />
</div>
<button class="btn-md btn-primary" disabled=${savingPw}
<button class="sw-btn sw-btn--primary sw-btn--md" disabled=${savingPw}
onClick=${changePassword}>
${savingPw ? 'Updating\u2026' : 'Update Password'}
</button>

View File

@@ -77,9 +77,9 @@ export default function ActivitySection({ teamId }) {
</div>
<div style="margin-top:12px;display:flex;align-items:center;gap:8px;">
<button class="btn-small" disabled=${page <= 1} onClick=${() => setPage(p => p - 1)}>\u2190 Prev</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" disabled=${page <= 1} onClick=${() => setPage(p => p - 1)}>\u2190 Prev</button>
<span class="text-muted" style="font-size:12px;">Page ${page} of ${totalPages} (${total} total)</span>
<button class="btn-small" disabled=${page >= totalPages} onClick=${() => setPage(p => p + 1)}>Next \u2192</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" disabled=${page >= totalPages} onClick=${() => setPage(p => p + 1)}>Next \u2192</button>
</div>
`
}

View File

@@ -114,7 +114,7 @@ export default function ConnectionsSection({ teamId }) {
return html`
<div style="margin-bottom:12px;">
<button class="btn-md btn-primary" onClick=${openAdd}
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${openAdd}
disabled=${!connTypes.length}>+ Add Connection</button>
</div>
@@ -157,11 +157,11 @@ export default function ConnectionsSection({ teamId }) {
</div>
`)}
<div style="display:flex;gap:8px;margin-top:12px;">
<button class="btn-md btn-primary" disabled=${saving}
<button class="sw-btn sw-btn--primary sw-btn--md" disabled=${saving}
onClick=${save}>
${saving ? 'Saving\u2026' : editId ? 'Update' : 'Add'}
</button>
<button class="btn-md btn-secondary" onClick=${cancel}>Cancel</button>
<button class="sw-btn sw-btn--secondary sw-btn--md" onClick=${cancel}>Cancel</button>
</div>
</div>
`}
@@ -187,7 +187,7 @@ export default function ConnectionsSection({ teamId }) {
<button class="icon-btn" title="Edit" onClick=${() => openEdit(c)}>
\u{270F}\u{FE0F}
</button>
<button class="icon-btn icon-btn-danger" title="Delete"
<button class="icon-btn icon-sw-btn sw-btn--danger sw-btn--md" title="Delete"
onClick=${() => del(c)}>
\u{1F5D1}
</button>

View File

@@ -185,7 +185,7 @@ function TeamAdminSurface() {
<span class="settings-topbar-title">
${teamName}
${adminTeams.length > 1 && html`
<button class="btn-small btn-ghost" style="margin-left:8px;font-size:11px;"
<button class="sw-btn sw-btn--ghost sw-btn--sm" style="margin-left:8px;font-size:11px;"
onClick=${() => setSelectedTeam(null)}>Switch Team</button>
`}
</span>

View File

@@ -79,7 +79,7 @@ export default function MembersSection({ teamId }) {
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="flex:1"></div>
<button class="btn-md btn-primary" onClick=${openAdd}>+ Add Member</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${openAdd}>+ Add Member</button>
</div>
${showAdd && html`
@@ -98,8 +98,8 @@ export default function MembersSection({ teamId }) {
</div>
</div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Add</button>
<button type="button" class="btn-small" onClick=${() => setShowAdd(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Add</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowAdd(false)}>Cancel</button>
</div>
</form>
`}
@@ -113,7 +113,7 @@ export default function MembersSection({ teamId }) {
onChange=${e => updateRole(m.id || m.user_id, e.target.value)}>
${roles.map(r => html`<option key=${r} value=${r}>${r}</option>`)}
</select>
<button class="btn-small btn-danger" onClick=${() => removeMember(m.id || m.user_id)}>Remove</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => removeMember(m.id || m.user_id)}>Remove</button>
</div>
`)}
</div>
@@ -121,7 +121,7 @@ export default function MembersSection({ teamId }) {
<div style="margin-top:24px;border-top:1px solid var(--border-1);padding-top:12px;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<h4 style="margin:0;flex:1;">Team Roles</h4>
<button class="btn-small" onClick=${() => setShowRoles(!showRoles)}>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowRoles(!showRoles)}>
${showRoles ? 'Hide' : 'Manage'}
</button>
</div>
@@ -132,7 +132,7 @@ export default function MembersSection({ teamId }) {
<div style="display:flex;gap:8px;align-items:center;">
<input value=${newRole} onInput=${e => setNewRole(e.target.value)}
placeholder="New role name" style="width:150px;" />
<button class="btn-small btn-primary" onClick=${async () => {
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${async () => {
if (!newRole.trim()) return;
const updated = [...new Set([...roles, newRole.trim()])];
try {
@@ -147,7 +147,7 @@ export default function MembersSection({ teamId }) {
${roles.filter(r => r !== 'admin' && r !== 'member').map(r => html`
<div class="admin-surface-row" key=${r}>
<span style="flex:1;">${r}</span>
<button class="btn-small btn-danger" onClick=${async () => {
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${async () => {
const updated = roles.filter(x => x !== r);
try {
await sw.api.put('/api/v1/teams/' + teamId + '/roles', { roles: updated });

View File

@@ -65,7 +65,7 @@ export default function SettingsSection({ teamId }) {
`}
<div style="display:flex;gap:8px;align-items:center;margin-top:16px;">
<button class="btn-md btn-primary" onClick=${save} disabled=${saving}>${saving ? 'Saving\u2026' : 'Save Settings'}</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${save} disabled=${saving}>${saving ? 'Saving\u2026' : 'Save Settings'}</button>
</div>
</div>
`;

View File

@@ -46,7 +46,7 @@ export default function WorkflowsSection({ teamId }) {
<div class="admin-tabs" style="display:flex;gap:4px;margin-bottom:16px;">
${TABS.map(t => html`
<button key=${t}
class="btn-small ${tab === t ? 'btn-primary' : ''}"
class="sw-btn sw-btn--secondary sw-btn--sm ${tab === t ? 'sw-btn sw-btn--primary sw-btn--md' : ''}"
onClick=${() => setTab(t)}>${t}</button>
`)}
</div>
@@ -123,8 +123,8 @@ function WorkflowsTab({ teamId }) {
<div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<div style="flex:1"></div>
<button class="btn-md" onClick=${openAdopt}>Adopt Global</button>
<button class="btn-md btn-primary" onClick=${() => setShowCreate(true)}>+ Add</button>
<button class="sw-btn sw-btn--secondary sw-btn--md" onClick=${openAdopt}>Adopt Global</button>
<button class="sw-btn sw-btn--primary sw-btn--md" onClick=${() => setShowCreate(true)}>+ Add</button>
</div>
${showAdopt && html`
@@ -132,7 +132,7 @@ function WorkflowsTab({ teamId }) {
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<strong>Adopt a Global Workflow</strong>
<div style="flex:1"></div>
<button class="btn-small" onClick=${() => setShowAdopt(false)}>Cancel</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowAdopt(false)}>Cancel</button>
</div>
${available.length === 0 && html`<div class="empty-hint">No global workflows available</div>`}
<div class="admin-list">
@@ -143,7 +143,7 @@ function WorkflowsTab({ teamId }) {
<span class="text-muted" style="margin-left:8px;font-size:11px;">/${w.slug}</span>
</div>
<span class="badge">${w.entry_mode}</span>
<button class="btn-small btn-primary" onClick=${() => adoptWorkflow(w.id)}>Adopt</button>
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${() => adoptWorkflow(w.id)}>Adopt</button>
</div>
`)}
</div>
@@ -163,8 +163,8 @@ function WorkflowsTab({ teamId }) {
</div>
<div class="form-group"><label>Description</label><input name="description" placeholder="Optional" /></div>
<div class="form-row" style="margin-top:8px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Create</button>
<button type="button" class="btn-small" onClick=${() => setShowCreate(false)}>Cancel</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Create</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setShowCreate(false)}>Cancel</button>
</div>
</form>
`}
@@ -266,11 +266,11 @@ function WorkflowEditor({ teamId, workflow, onBack }) {
return html`
<form onSubmit=${updateWorkflow}>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
<button type="button" class="btn-small" onClick=${onBack}>\u2190 Back</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${onBack}>\u2190 Back</button>
<h4 style="margin:0;">Edit: ${workflow.name}</h4>
<div style="flex:1"></div>
<button type="button" class="btn-small" onClick=${publishWorkflow}>Publish</button>
<button type="button" class="btn-small btn-danger" onClick=${deleteWorkflow}>Delete</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${publishWorkflow}>Publish</button>
<button type="button" class="sw-btn sw-btn--danger sw-btn--sm" onClick=${deleteWorkflow}>Delete</button>
</div>
<div class="form-row">
<div class="form-group"><label>Name</label><input name="name" value=${workflow.name || ''} /></div>
@@ -299,7 +299,7 @@ function WorkflowEditor({ teamId, workflow, onBack }) {
<input type="text" readOnly value=${window.location.origin + (window.__BASE__ || '') + '/api/v1/public/workflows/' + workflow.id + '/start'}
style="flex:1;font-size:11px;padding:4px 8px;background:var(--input-bg);border:1px solid var(--border);border-radius:4px;color:var(--text);font-family:var(--mono);"
onClick=${e => e.target.select()} />
<button type="button" class="btn-small" onClick=${(e) => {
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${(e) => {
const url = window.location.origin + (window.__BASE__ || '') + '/api/v1/public/workflows/' + workflow.id + '/start';
navigator.clipboard.writeText(url).then(() => {
e.target.textContent = 'Copied!';
@@ -311,7 +311,7 @@ function WorkflowEditor({ teamId, workflow, onBack }) {
`}
<div class="form-row" style="margin-top:16px;gap:8px;">
<button type="submit" class="btn-small btn-primary">Save</button>
<button type="submit" class="sw-btn sw-btn--primary sw-btn--sm">Save</button>
</div>
</form>
@@ -320,7 +320,7 @@ function WorkflowEditor({ teamId, workflow, onBack }) {
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<h5 style="margin:0;">Stages (${stages.length})</h5>
<div style="flex:1"></div>
<button class="btn-small" onClick=${() => setEditingStage('new')}>+ Add Stage</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setEditingStage('new')}>+ Add Stage</button>
</div>
<div class="admin-list">
@@ -331,8 +331,8 @@ function WorkflowEditor({ teamId, workflow, onBack }) {
<span class="badge">${s.stage_mode || '\u2014'}</span>
<span class="badge">${s.audience || 'team'}</span>
${s.assignment_team_id && html`<span class="badge">team assign</span>`}
<button class="btn-small" onClick=${() => setEditingStage(s.id)}>Edit</button>
<button class="btn-small btn-danger" onClick=${() => deleteStage(s.id)}>\u00d7</button>
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => setEditingStage(s.id)}>Edit</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => deleteStage(s.id)}>\u00d7</button>
</div>
`)}
${stages.length === 0 && html`<div class="empty-hint">No stages defined</div>`}
@@ -502,10 +502,10 @@ function StageForm({ stage, teams, onSave, onCancel }) {
<span class="toggle-track"></span><span>Auto-advance when complete</span>
</label>
<div class="form-row" style="margin-top:12px;gap:8px;">
<button type="button" class="btn-small btn-primary" onClick=${submit}>
<button type="button" class="sw-btn sw-btn--primary sw-btn--sm" onClick=${submit}>
${stage ? 'Update' : 'Add Stage'}
</button>
<button type="button" class="btn-small" onClick=${onCancel}>Cancel</button>
<button type="button" class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${onCancel}>Cancel</button>
</div>
</div>
`;
@@ -578,10 +578,10 @@ function AssignmentsTab({ teamId }) {
${a.sla_breached && html`<span class="badge badge-danger">SLA</span>`}
</div>
<div style="display:flex;gap:4px;">
<button class="btn-small" onClick=${() => unclaim(a.id)}>Release</button>
<button class="btn-small btn-primary"
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => unclaim(a.id)}>Release</button>
<button class="sw-btn sw-btn--primary sw-btn--sm"
onClick=${() => { location.href = sw.base + '/w/' + a.channel_id; }}>Open</button>
<button class="btn-small btn-success" onClick=${() => complete(a.id)}>Complete</button>
<button class="sw-btn sw-btn--success sw-btn--sm" onClick=${() => complete(a.id)}>Complete</button>
</div>
</div>
`)}
@@ -597,7 +597,7 @@ function AssignmentsTab({ teamId }) {
<span class="badge">${a.stage_name || `Stage ${a.stage}`}</span>
<span class="text-muted" style="font-size:11px;">${_timeAgo(a.created_at)}</span>
</div>
<button class="btn-small btn-primary" onClick=${() => claim(a.id)}>Claim</button>
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${() => claim(a.id)}>Claim</button>
</div>
`)}
${unassigned.length === 0 && html`<div class="empty-hint">No available assignments</div>`}
@@ -648,11 +648,11 @@ function MonitorTab({ teamId }) {
<span class="text-muted" style="font-size:11px;">Age: ${_formatDuration(inst.age_seconds)}</span>
</div>
<div style="display:flex;gap:4px;">
<button class="btn-small"
<button class="sw-btn sw-btn--secondary sw-btn--sm"
onClick=${() => setExpandedSignoff(expandedSignoff === inst.id ? null : inst.id)}>Signoffs</button>
<button class="btn-small"
<button class="sw-btn sw-btn--secondary sw-btn--sm"
onClick=${() => { location.href = sw.base + '/w/' + inst.channel_id; }}>Open</button>
<button class="btn-small btn-danger" onClick=${() => cancelInstance(inst.channel_id)}>Cancel</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => cancelInstance(inst.channel_id)}>Cancel</button>
</div>
${expandedSignoff === inst.id && html`<${SignoffPanel} instanceId=${inst.id} teamId=${teamId} />`}
</div>
@@ -703,8 +703,8 @@ function SignoffPanel({ instanceId, teamId }) {
<div style="display:flex;gap:4px;align-items:center;">
<input value=${comment} onInput=${e => setComment(e.target.value)}
placeholder="Comment (optional)" style="flex:1;font-size:12px;" />
<button class="btn-small btn-success" onClick=${() => submit('approve')}>Approve</button>
<button class="btn-small btn-danger" onClick=${() => submit('reject')}>Reject</button>
<button class="sw-btn sw-btn--success sw-btn--sm" onClick=${() => submit('approve')}>Approve</button>
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => submit('reject')}>Reject</button>
</div>
${signoffs.length > 0 && html`
<div style="margin-top:6px;">