From 1fbd836c5cf35984c57ff5aeaa88393d8fd5b916 Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Wed, 1 Apr 2026 20:08:57 +0000 Subject: [PATCH] Feat v0.7.0 shell contract + surface audit + rebrand Two-slot shell topbar (home, left, center, bell, user menu) with SDK API (setLeft/setSlot/setTitle/hide/show). All 4 primary surfaces migrated: Settings and Team Admin to Pattern B (flat tabs), Admin to Pattern C (category tabs + sidebar), Docs to Pattern A (default). Backend WS events: package.changed (broadcast), auth.changed (targeted), notification.all_read. User menu re-fetches on package/auth changes. Bell syncs read state across tabs. Error handling pass with .sw-inline-error CSS primitive. Empty state guidance for Admin Workflows/Groups. Announcement global dismiss via localStorage. Rebrand assets deployed (both b/e icon variants, wordmarks, full icon library). Docs outline scroll-to-heading fix. Bug fixes: ICD security assertion tightened, workflow-demo error surfacing, signoff display names, hello-dashboard + team-admin/groups.js deleted. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 52 ++ ROADMAP.md | 286 +++++------ VERSION | 2 +- docs/AUDIT-surfaces.md | 209 ++++++++ docs/DESIGN-shell-contract.md | 448 ++++++++++++++++++ docs/DESIGN-surface-runners.md | 407 ++++++++++++++++ icons/apple-touch-icon-b-dark.png | Bin 0 -> 1631 bytes icons/apple-touch-icon-b-light.png | Bin 0 -> 1616 bytes icons/apple-touch-icon-e-dark.png | Bin 0 -> 1828 bytes icons/apple-touch-icon-e-light.png | Bin 0 -> 1826 bytes icons/favicon-16-b-dark.png | Bin 0 -> 218 bytes icons/favicon-16-b-light.png | Bin 0 -> 214 bytes icons/favicon-16-e-dark.png | Bin 0 -> 304 bytes icons/favicon-16-e-light.png | Bin 0 -> 312 bytes icons/favicon-32-b-dark.png | Bin 0 -> 363 bytes icons/favicon-32-b-light.png | Bin 0 -> 332 bytes icons/favicon-32-e-dark.png | Bin 0 -> 457 bytes icons/favicon-32-e-light.png | Bin 0 -> 459 bytes icons/favicon-animated.svg | 20 + icons/favicon.svg | 16 + icons/icon-128-b-dark.png | Bin 0 -> 1186 bytes icons/icon-128-b-light.png | Bin 0 -> 1186 bytes icons/icon-128-e-dark.png | Bin 0 -> 1350 bytes icons/icon-128-e-light.png | Bin 0 -> 1341 bytes icons/icon-192-b-dark.png | Bin 0 -> 1813 bytes icons/icon-192-b-light.png | Bin 0 -> 1786 bytes icons/icon-192-e-dark.png | Bin 0 -> 1962 bytes icons/icon-192-e-light.png | Bin 0 -> 1974 bytes icons/icon-256-b-dark.png | Bin 0 -> 2556 bytes icons/icon-256-b-light.png | Bin 0 -> 2553 bytes icons/icon-256-e-dark.png | Bin 0 -> 2628 bytes icons/icon-256-e-light.png | Bin 0 -> 2617 bytes icons/icon-48-b-dark.png | Bin 0 -> 496 bytes icons/icon-48-b-light.png | Bin 0 -> 492 bytes icons/icon-48-e-dark.png | Bin 0 -> 564 bytes icons/icon-48-e-light.png | Bin 0 -> 559 bytes icons/icon-512-b-dark.png | Bin 0 -> 5554 bytes icons/icon-512-b-light.png | Bin 0 -> 5548 bytes icons/icon-512-e-dark.png | Bin 0 -> 5837 bytes icons/icon-512-e-light.png | Bin 0 -> 5839 bytes icons/icon-64-b-dark.png | Bin 0 -> 626 bytes icons/icon-64-b-light.png | Bin 0 -> 620 bytes icons/icon-64-e-dark.png | Bin 0 -> 711 bytes icons/icon-64-e-light.png | Bin 0 -> 707 bytes icons/icon-b-dark-animated.svg | 48 ++ icons/icon-b-dark.svg | 21 + icons/icon-b-light-animated.svg | 43 ++ icons/icon-b-light.svg | 17 + icons/icon-e-dark-animated.svg | 67 +++ icons/icon-e-dark.svg | 23 + icons/icon-e-light-animated.svg | 63 +++ icons/icon-e-light.svg | 19 + icons/icon-morph-dark-animated.svg | 174 +++++++ icons/icon-morph-light-animated.svg | 127 +++++ icons/preview-sheet.png | Bin 0 -> 15331 bytes icons/wordmark-dark.svg | 12 + icons/wordmark-light.svg | 10 + packages/hello-dashboard/css/main.css | 17 - packages/hello-dashboard/js/main.js | 84 ---- packages/hello-dashboard/manifest.json | 13 - packages/icd-test-runner/js/tier-security.js | 12 +- packages/workflow-demo/js/main.js | 12 +- server/events/bus_test.go | 26 + server/events/types.go | 11 +- server/events/ws.go | 6 + server/handlers/groups.go | 22 + server/handlers/notifications.go | 5 +- server/handlers/packages.go | 25 + server/handlers/teams.go | 60 +++ server/main.go | 5 + server/pages/templates/base.html | 8 +- server/pages/templates/surfaces/admin.html | 1 + server/pages/templates/surfaces/docs.html | 1 + .../pages/templates/surfaces/extension.html | 1 + server/pages/templates/surfaces/settings.html | 1 + .../pages/templates/surfaces/team-admin.html | 1 + src/apple-touch-icon.png | Bin 0 -> 1631 bytes src/css/sw-primitives.css | 19 + src/css/sw-shell.css | 83 ++++ src/favicon-light-256.png | Bin 0 -> 2553 bytes src/favicon-light-32.png | Bin 0 -> 332 bytes src/favicon-light.svg | 25 +- src/icons/apple-touch-icon-b-dark.png | Bin 0 -> 1631 bytes src/icons/apple-touch-icon-b-light.png | Bin 0 -> 1616 bytes src/icons/apple-touch-icon-e-dark.png | Bin 0 -> 1828 bytes src/icons/apple-touch-icon-e-light.png | Bin 0 -> 1826 bytes src/icons/favicon-16-b-dark.png | Bin 0 -> 218 bytes src/icons/favicon-16-b-light.png | Bin 0 -> 214 bytes src/icons/favicon-16-e-dark.png | Bin 0 -> 304 bytes src/icons/favicon-16-e-light.png | Bin 0 -> 312 bytes src/icons/favicon-32-b-dark.png | Bin 0 -> 363 bytes src/icons/favicon-32-b-light.png | Bin 0 -> 332 bytes src/icons/favicon-32-e-dark.png | Bin 0 -> 457 bytes src/icons/favicon-32-e-light.png | Bin 0 -> 459 bytes src/icons/favicon-animated.svg | 20 + src/icons/favicon.svg | 16 + src/icons/icon-128-b-dark.png | Bin 0 -> 1186 bytes src/icons/icon-128-b-light.png | Bin 0 -> 1186 bytes src/icons/icon-128-e-dark.png | Bin 0 -> 1350 bytes src/icons/icon-128-e-light.png | Bin 0 -> 1341 bytes src/icons/icon-192-b-dark.png | Bin 0 -> 1813 bytes src/icons/icon-192-b-light.png | Bin 0 -> 1786 bytes src/icons/icon-192-e-dark.png | Bin 0 -> 1962 bytes src/icons/icon-192-e-light.png | Bin 0 -> 1974 bytes src/icons/icon-256-b-dark.png | Bin 0 -> 2556 bytes src/icons/icon-256-b-light.png | Bin 0 -> 2553 bytes src/icons/icon-256-e-dark.png | Bin 0 -> 2628 bytes src/icons/icon-256-e-light.png | Bin 0 -> 2617 bytes src/icons/icon-48-b-dark.png | Bin 0 -> 496 bytes src/icons/icon-48-b-light.png | Bin 0 -> 492 bytes src/icons/icon-48-e-dark.png | Bin 0 -> 564 bytes src/icons/icon-48-e-light.png | Bin 0 -> 559 bytes src/icons/icon-512-b-dark.png | Bin 0 -> 5554 bytes src/icons/icon-512-b-light.png | Bin 0 -> 5548 bytes src/icons/icon-512-e-dark.png | Bin 0 -> 5837 bytes src/icons/icon-512-e-light.png | Bin 0 -> 5839 bytes src/icons/icon-64-b-dark.png | Bin 0 -> 626 bytes src/icons/icon-64-b-light.png | Bin 0 -> 620 bytes src/icons/icon-64-e-dark.png | Bin 0 -> 711 bytes src/icons/icon-64-e-light.png | Bin 0 -> 707 bytes src/icons/icon-b-dark-animated.svg | 48 ++ src/icons/icon-b-dark.svg | 21 + src/icons/icon-b-light-animated.svg | 43 ++ src/icons/icon-b-light.svg | 17 + src/icons/icon-e-dark-animated.svg | 67 +++ src/icons/icon-e-dark.svg | 23 + src/icons/icon-e-light-animated.svg | 63 +++ src/icons/icon-e-light.svg | 19 + src/icons/icon-morph-dark-animated.svg | 174 +++++++ src/icons/icon-morph-light-animated.svg | 127 +++++ src/icons/preview-sheet.png | Bin 0 -> 15331 bytes src/icons/wordmark-dark.svg | 12 + src/icons/wordmark-light.svg | 10 + src/js/sw/sdk/index.js | 28 +- src/js/sw/shell/notification-bell.js | 32 +- src/js/sw/shell/shell-topbar.js | 95 ++++ src/js/sw/shell/user-menu.js | 24 +- src/js/sw/surfaces/admin/groups.js | 14 +- src/js/sw/surfaces/admin/index.js | 49 +- src/js/sw/surfaces/admin/packages.js | 12 +- src/js/sw/surfaces/admin/workflows.js | 13 +- src/js/sw/surfaces/docs/index.js | 11 +- src/js/sw/surfaces/settings/index.js | 98 +--- src/js/sw/surfaces/team-admin/groups.js | 37 -- src/js/sw/surfaces/team-admin/index.js | 157 ++---- src/js/sw/surfaces/team-admin/workflows.js | 2 +- src/manifest.json | 17 +- src/wordmark-dark.svg | 12 + src/wordmark-light.svg | 10 + src/wordmark.svg | 10 + 150 files changed, 3250 insertions(+), 533 deletions(-) create mode 100644 docs/AUDIT-surfaces.md create mode 100644 docs/DESIGN-shell-contract.md create mode 100644 docs/DESIGN-surface-runners.md create mode 100644 icons/apple-touch-icon-b-dark.png create mode 100644 icons/apple-touch-icon-b-light.png create mode 100644 icons/apple-touch-icon-e-dark.png create mode 100644 icons/apple-touch-icon-e-light.png create mode 100644 icons/favicon-16-b-dark.png create mode 100644 icons/favicon-16-b-light.png create mode 100644 icons/favicon-16-e-dark.png create mode 100644 icons/favicon-16-e-light.png create mode 100644 icons/favicon-32-b-dark.png create mode 100644 icons/favicon-32-b-light.png create mode 100644 icons/favicon-32-e-dark.png create mode 100644 icons/favicon-32-e-light.png create mode 100644 icons/favicon-animated.svg create mode 100644 icons/favicon.svg create mode 100644 icons/icon-128-b-dark.png create mode 100644 icons/icon-128-b-light.png create mode 100644 icons/icon-128-e-dark.png create mode 100644 icons/icon-128-e-light.png create mode 100644 icons/icon-192-b-dark.png create mode 100644 icons/icon-192-b-light.png create mode 100644 icons/icon-192-e-dark.png create mode 100644 icons/icon-192-e-light.png create mode 100644 icons/icon-256-b-dark.png create mode 100644 icons/icon-256-b-light.png create mode 100644 icons/icon-256-e-dark.png create mode 100644 icons/icon-256-e-light.png create mode 100644 icons/icon-48-b-dark.png create mode 100644 icons/icon-48-b-light.png create mode 100644 icons/icon-48-e-dark.png create mode 100644 icons/icon-48-e-light.png create mode 100644 icons/icon-512-b-dark.png create mode 100644 icons/icon-512-b-light.png create mode 100644 icons/icon-512-e-dark.png create mode 100644 icons/icon-512-e-light.png create mode 100644 icons/icon-64-b-dark.png create mode 100644 icons/icon-64-b-light.png create mode 100644 icons/icon-64-e-dark.png create mode 100644 icons/icon-64-e-light.png create mode 100644 icons/icon-b-dark-animated.svg create mode 100644 icons/icon-b-dark.svg create mode 100644 icons/icon-b-light-animated.svg create mode 100644 icons/icon-b-light.svg create mode 100644 icons/icon-e-dark-animated.svg create mode 100644 icons/icon-e-dark.svg create mode 100644 icons/icon-e-light-animated.svg create mode 100644 icons/icon-e-light.svg create mode 100644 icons/icon-morph-dark-animated.svg create mode 100644 icons/icon-morph-light-animated.svg create mode 100644 icons/preview-sheet.png create mode 100644 icons/wordmark-dark.svg create mode 100644 icons/wordmark-light.svg delete mode 100644 packages/hello-dashboard/css/main.css delete mode 100644 packages/hello-dashboard/js/main.js delete mode 100644 packages/hello-dashboard/manifest.json create mode 100644 src/apple-touch-icon.png create mode 100644 src/favicon-light-256.png create mode 100644 src/favicon-light-32.png create mode 100644 src/icons/apple-touch-icon-b-dark.png create mode 100644 src/icons/apple-touch-icon-b-light.png create mode 100644 src/icons/apple-touch-icon-e-dark.png create mode 100644 src/icons/apple-touch-icon-e-light.png create mode 100644 src/icons/favicon-16-b-dark.png create mode 100644 src/icons/favicon-16-b-light.png create mode 100644 src/icons/favicon-16-e-dark.png create mode 100644 src/icons/favicon-16-e-light.png create mode 100644 src/icons/favicon-32-b-dark.png create mode 100644 src/icons/favicon-32-b-light.png create mode 100644 src/icons/favicon-32-e-dark.png create mode 100644 src/icons/favicon-32-e-light.png create mode 100644 src/icons/favicon-animated.svg create mode 100644 src/icons/favicon.svg create mode 100644 src/icons/icon-128-b-dark.png create mode 100644 src/icons/icon-128-b-light.png create mode 100644 src/icons/icon-128-e-dark.png create mode 100644 src/icons/icon-128-e-light.png create mode 100644 src/icons/icon-192-b-dark.png create mode 100644 src/icons/icon-192-b-light.png create mode 100644 src/icons/icon-192-e-dark.png create mode 100644 src/icons/icon-192-e-light.png create mode 100644 src/icons/icon-256-b-dark.png create mode 100644 src/icons/icon-256-b-light.png create mode 100644 src/icons/icon-256-e-dark.png create mode 100644 src/icons/icon-256-e-light.png create mode 100644 src/icons/icon-48-b-dark.png create mode 100644 src/icons/icon-48-b-light.png create mode 100644 src/icons/icon-48-e-dark.png create mode 100644 src/icons/icon-48-e-light.png create mode 100644 src/icons/icon-512-b-dark.png create mode 100644 src/icons/icon-512-b-light.png create mode 100644 src/icons/icon-512-e-dark.png create mode 100644 src/icons/icon-512-e-light.png create mode 100644 src/icons/icon-64-b-dark.png create mode 100644 src/icons/icon-64-b-light.png create mode 100644 src/icons/icon-64-e-dark.png create mode 100644 src/icons/icon-64-e-light.png create mode 100644 src/icons/icon-b-dark-animated.svg create mode 100644 src/icons/icon-b-dark.svg create mode 100644 src/icons/icon-b-light-animated.svg create mode 100644 src/icons/icon-b-light.svg create mode 100644 src/icons/icon-e-dark-animated.svg create mode 100644 src/icons/icon-e-dark.svg create mode 100644 src/icons/icon-e-light-animated.svg create mode 100644 src/icons/icon-e-light.svg create mode 100644 src/icons/icon-morph-dark-animated.svg create mode 100644 src/icons/icon-morph-light-animated.svg create mode 100644 src/icons/preview-sheet.png create mode 100644 src/icons/wordmark-dark.svg create mode 100644 src/icons/wordmark-light.svg create mode 100644 src/js/sw/shell/shell-topbar.js delete mode 100644 src/js/sw/surfaces/team-admin/groups.js create mode 100644 src/wordmark-dark.svg create mode 100644 src/wordmark-light.svg create mode 100644 src/wordmark.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 406ef37..3eb9797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,58 @@ All notable changes to Armature are documented here. +## v0.7.0 — Shell Contract + Surface Audit + Rebrand + +**Shell Infrastructure** +- Kernel-injected two-slot topbar for all surfaces (home, left slot, center slot, bell, user menu) +- `sw.shell.topbar` SDK API: `setLeft()`, `setSlot()`, `setTitle()`, `hide()`, `show()` +- `.sw-topbar__tabs` / `.sw-topbar__tab` CSS classes for consistent tab styling +- Shell topbar auto-mounts on extension surfaces via `#shell-topbar` div + +**Backend WS Events** +- `package.changed` event broadcast on install/uninstall/enable/disable/update +- `auth.changed` event targeted to affected user on team/group membership changes +- `notification.all_read` event split from `notification.read` for cleaner badge sync +- `Hub.Broadcast()` method for untargeted all-client events + +**User Menu + Bell Reactivity** +- User menu re-fetches surface list on `package.changed` and `auth.changed` events +- Notification bell syncs on `notification.read` and `notification.all_read` across tabs + +**Surface Migrations** +- Settings: Pattern B (flat tabs in topbar, no sidebar, full-width content) +- Admin: Pattern C (category tabs in topbar, surface-owned sidebar below) +- Team Admin: Pattern B (flat tabs, no sidebar, Groups tab removed) +- Docs: Pattern A (shell topbar auto-renders, removed explicit Topbar import) +- All 4 surfaces now have notification bell and user menu via shell topbar + +**Error Handling + Empty States** +- `.sw-inline-error` CSS primitive for inline error + retry pattern +- `.sw-empty-state` CSS primitive for guided empty states +- Admin Workflows, Packages, Groups: inline error on list fetch failure +- Admin Workflows, Groups: descriptive empty state guidance + +**Announcement Global Dismiss** +- Announcement dismiss state persisted to localStorage keyed by content hash +- Dismissed once on any surface, dismissed everywhere + +**Rebrand Assets** +- `favicon-light.svg` renamed to `wordmark.svg` (was a 520x80 wordmark, not an icon) +- New `favicon-light.svg`: actual square light-mode icon +- New `wordmark-dark.svg`, `wordmark-light.svg` for dark/light backgrounds +- New `favicon-light-32.png`, `favicon-light-256.png` raster icons +- Full icon library deployed to `src/icons/` (both b/e variants, animated SVGs) +- `manifest.json` description updated to "Self-hosted extension platform" +- Light-mode icon entries added to PWA manifest + +**Bug Fixes** +- Docs: "On this page" outline links now scroll to headings (IDs were missing from rendered HTML) +- ICD security tier: tightened path traversal assertion (400/422, not 409), added `finally` cleanup +- Workflow demo: replaced silent catch with inline error + retry +- Team Admin: signoff panel shows display names instead of raw UUIDs +- Deleted `packages/hello-dashboard/` (dead package) +- Deleted `team-admin/groups.js` (37-line dead-end, no CRUD) + ## v0.6.18 — CI Bundle Wiring Wire `BUNDLED_PACKAGES` env var into the Gitea CI pipeline so each diff --git a/ROADMAP.md b/ROADMAP.md index 20082a5..6859948 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # Armature — Roadmap -## Current: v0.6.14 — Visual Polish +## Current: v0.6.18 — CI Bundle Wiring Self-hosted extensible platform. Auth, identity, packages, Starlark sandbox, storage, realtime, and ops are kernel primitives. Everything else is an extension. @@ -19,152 +19,168 @@ upgrade test harness, cluster registry + HA. --- -## v0.6.0 — MVP +## v0.6.x — Completed (MVP + Hardening) -Extension, communication, and operations tracks converge. First -externally usable release. +All v0.6.x work is shipped and documented in `CHANGELOG.md`. Summary: -Design docs: `docs/DESIGN-cluster-registry.md` — PG-backed cluster registry and self-assembling mesh. - -### v0.6.0 — Cluster Registry + HA - -PG is the consensus layer. Zero new infrastructure. `UNLOGGED` table + `LISTEN/NOTIFY` replaces etcd/Consul/Redis for homelab-to-small-team scale. - -| Step | Status | Description | -|------|--------|-------------| -| `node_registry` table | ✅ | `UNLOGGED TABLE` — node_id, endpoint, seq, registered_at, heartbeat, stats JSONB. Postgres migration 013. | -| Node registration | ✅ | Self-registration on startup: `INSERT ... ON CONFLICT DO UPDATE`. `node_id` = `hostname-PID` or `CLUSTER_NODE_ID` env override. | -| Heartbeat tick | ✅ | Every 10s: update own heartbeat + collect runtime stats (goroutines, heap, GC, uptime, ws_clients). | -| Stale sweep | ✅ | Every heartbeat tick: `DELETE WHERE heartbeat < now() - 30s`. All nodes run it — idempotent, no ring topology. | -| Self-eviction | ✅ | If heartbeat UPDATE returns 0 rows: node was swept by peer → log error + `os.Exit(1)`. K8s restarts → re-register. | -| LISTEN/NOTIFY routing | ✅ | Durable events (messages, state changes) fan-out via `pg_notify`. All replicas receive, push to local WS subscribers, drop if irrelevant. Phase 1: ephemeral events (typing, presence) also via NOTIFY (8KB limit, ~60 bytes each). | -| Cluster API | ✅ | `GET /api/v1/admin/cluster` — returns `{data: [...]}` envelope with all registered nodes. | -| Admin cluster dashboard | ✅ | `cluster-dashboard` surface package renders one card per node: node_id, endpoint, uptime, ws_clients, heap, GC pause. JSONB stats — future keys render automatically, no schema migration. Auto-refresh every 10s. | -| Health endpoint | ✅ | `GET /health` includes `node_id` and `cluster: {size, peers, heartbeat_age_ms}`. | -| Config | ✅ | `CLUSTER_NODE_ID`, `CLUSTER_HEARTBEAT_INTERVAL` (default 10s), `CLUSTER_STALE_THRESHOLD` (default 30s), `CLUSTER_ENDPOINT` (Phase 2 mesh, auto-detect). | -| Single-node regression | ✅ | One-node behavior identical to pre-cluster: one registry row, NOTIFY delivers back to same instance. No special-casing. SQLite returns nil store — all cluster code guarded. | -| Multi-node integration test | ✅ | Docker Compose: 3 instances, shared PG. `ci/e2e-cluster-test.sh`: registration, stale sweep on stop, re-registration on restart. 3 unit tests + 2 handler tests. | - -### v0.6.1 — Backup/Restore + Documentation - -| Step | Status | Description | -|------|--------|-------------| -| Backup handler | ✅ | `POST /api/v1/admin/backup` streams `.swb` ZIP (JSONL core + ext_data tables + package assets). `POST /api/v1/admin/restore` wipes DB and restores from archive. Dialect-neutral (SQLite + Postgres). | -| Server-side backups | ✅ | `GET /api/v1/admin/backups` list, `GET /download`, `DELETE`. Store backups in `{STORAGE_PATH}/backups/`. | -| Admin backup section | ✅ | New "Backup" section under `/admin/backup`. Create (download or server-side), list, download, delete, restore with destructive confirmation. | -| Documentation API | ✅ | `GET /api/v1/docs` lists, `GET /api/v1/docs/:name` returns raw markdown. Authenticated (not admin-only). | -| Docs surface | ✅ | Builtin surface at `/docs/:section`. Sidebar navigation, client-side markdown renderer. 5 new docs: Getting Started, Extension Guide, API Reference, Deployment, Package Format. | -| Tests | ✅ | 6 handler tests (basic backup, ext_data backup, round-trip restore, schema mismatch, dump/restore table, list empty). E2E script `ci/e2e-backup-test.sh`. | - -### v0.6.2 — Docs Polish + Dynamic OpenAPI - -| Step | Status | Description | -|------|--------|-------------| -| Dark mode fix | ✅ | Added `--bg-code` to CSS variables (dark + light). Replaced all hardcoded light-mode fallbacks in docs CSS with theme-aware variables. Table styling, code blocks, nav items all readable in dark mode. | -| Loading & error handling | ✅ | Replaced borrowed `settings-placeholder` with docs-specific pulse animation. Added error state with retry button for failed doc list fetch. | -| Topbar navigation | ✅ | Imported `Topbar` + `UserMenu` into docs surface. Users can now navigate to other surfaces via the avatar menu. | -| Docs icon + menu entry | ✅ | Added `📖 Docs` entry to UserMenu standard items. Docs accessible from any surface's user menu. | -| `api_schema` manifest field | ✅ | Optional `api_schema` array in `manifest.json`. Parsed lazily by spec builder. Malformed entries logged and skipped — never blocks extension loading. | -| OpenAPI spec builder | ✅ | `BuildOpenAPISpec()` merges static kernel spec with extension routes. Tier 1: auto-generated stubs for all `api_routes`. Tier 2: `api_schema` replaces stubs with rich path items (params, body, response). | -| Dynamic spec endpoint | ✅ | `GET /api/docs/openapi.json` serves merged spec. Swagger UI updated to use JSON endpoint. Static YAML preserved for backward compat. | -| Tests | ✅ | 7 new handler tests: zero extensions, stubs, rich schema, multi-extension, malformed schema, disabled exclusion, required fields. | +| Version | Title | Key Deliverables | +|---------|-------|-----------------| +| v0.6.0 | Cluster Registry + HA | PG-backed node registry, heartbeat sweep, LISTEN/NOTIFY routing, self-eviction | +| v0.6.1 | Backup/Restore + Docs | `.swb` archive format, server-side backups, docs surface + 5 guides | +| v0.6.2 | Docs Polish + OpenAPI | Dark mode fix, topbar nav, `api_schema` manifest field, dynamic spec builder | +| v0.6.3 | Dead Code Sweep | Registry install fix, dead Go/JS/HTML deletion, narrowed default bundle | +| v0.6.4 | Admin Health/Metrics | Cluster dashboard merged into Admin tab, block renderer `requires` removed | +| v0.6.5 | Renderer Pipeline | `sw.renderers.register()` kernel primitive, unified markdown, docs rewrite | +| v0.6.6 | Final Hardening | Dependency auto-activation, `ValidateManifest()`, OIDC nonce, ICD/SDK update | +| v0.6.7 | Native mTLS | `TLS_MODE` config, `MTLSNativeProvider`, node-to-node mTLS, `armature-ca.sh` | +| v0.6.8 | Cookie Fix + UI Roadmap | Cookie SameSite fix, UI hardening roadmap published | +| v0.6.9 | Session Lifetime Config | Admin-configurable TTLs, idle timeout, "keep me logged in" | +| v0.6.10 | Viewport Foundation | Single layout model, CSS zoom, 100dvh, dead shell deprecated | +| v0.6.11 | CSS Deduplication | Old primitive system retired, one class per concept | +| v0.6.12 | Extension CSS Isolation | Prefix enforcement via linter, all 12 in-tree packages migrated | +| v0.6.13 | Responsive & Spacing | Spacing token scale (4px grid), tablet breakpoint | +| v0.6.14 | Visual Polish | Stale fallback colors purged, fonts self-hosted, radius tokens | +| v0.6.15 | User Display Audit | Batch user resolve API, `sw.users` SDK module | +| v0.6.16 | Usability Survey Gate | Four audit scripts, contrast/touch-target fixes | +| v0.6.17 | Bug Fixes & Welcome | Notes folder fix, team member add fix, welcome auto-disable, zero default bundle | +| v0.6.18 | CI Bundle Wiring | `BUNDLED_PACKAGES` env var wired into Gitea CI pipeline | --- -## v0.6.x — Hardening +## v0.7.x — Test Infrastructure + Quality Gate -Closes every audit finding before the public release. No new features — only correctness, dead code elimination, and architectural cleanup. Sequence is fixed: each version is a gate for the next. +The v0.6.x series built the kernel. v0.7.x makes it provably correct. -### v0.6.3 — Dead Code Sweep + Registry Fix +A full surface audit (docs/AUDIT-surfaces.md) found 7 cross-surface issues +and 18 surface-specific issues across Settings, Admin, Team Admin, and Docs. +Only Docs is properly built. The fix is three phases: (1) establish a shell +contract and bring all four primary surfaces to parity, (2) build a runner +framework for end-to-end browser tests, (3) automate those runners headlessly +in CI. -Pure cleanup. No behavior changes except fixing the broken registry install flow. +Design docs: +- `docs/DESIGN-shell-contract.md` — two-slot topbar, three navigation patterns, surface migrations +- `docs/DESIGN-surface-runners.md` — runner framework, requires declarations, headless E2E +- `docs/AUDIT-surfaces.md` — full audit findings + +### v0.7.0 — Shell Contract + Surface Audit + Rebrand Cleanup + +Design doc: `docs/DESIGN-shell-contract.md` + +**Shell Infrastructure** | Step | Status | Description | |------|--------|-------------| -| Fix registry install | ✅ | SDK sends `{ url }`, handler expects `{ download_url }`. Fix `api-domains.js` to send `{ download_url: url }`. Every Install click currently returns 400. | -| Registry settings UI | ✅ | Add "Package Registry" section to admin settings with URL input field. Only way to configure registry today is a raw `PUT /api/v1/admin/settings/package_registry` — no user will find it. | -| Registry tooling + docs | ✅ | `scripts/generate-registry.sh` scans a directory of `.pkg` files and emits registry JSON. `docs/PACKAGE-REGISTRY.md` documents the format. | -| Delete dead kernel Go | ✅ | `store/interfaces.go:178–183` — orphaned ChannelListFilter comments. `pages/pages.go:922–930` — `roleFilterType()` + template registration (chat vestige, maps nonexistent roles). `main.go:67` — orphaned provider-type comment. | -| Delete dead vendor JS | ✅ | `vendor/marked.min.js` and `vendor/purify.min.js` — 62KB, zero production imports. Only referenced in test helpers. | -| Delete `dev.html` | ✅ | 676 lines, not imported by anything. Dead. | -| Remove `dashboard` from default bundle | ✅ | Requires `legacy-sdk` (doesn't exist), auto-installs as dormant on fresh installs. Confusing. Remove from `defaultBundledPackages`. | -| Remove `hello-dashboard` | ✅ | Proof-of-concept from early development. Move to `examples/` or delete. | -| Strip stale version comments | ✅ | 60+ files had legacy version annotations (`// v0.29.x:`, `// v0.33.x:`). Single sed pass. | -| Narrow default bundle | ✅ | Default: `notes`, `chat`, `chat-core`, `mermaid-renderer`, `schedules`. Everything else available via registry or `BUNDLED_PACKAGES=*`. | +| Shell topbar (two-slot model) | done | Kernel injects topbar into `surface-extension` template. Two named slots: **left** (defaults to manifest title) and **center** (`flex: 1`, for tabs/pickers/search). Home link, notification bell, and user menu always present. | +| Topbar customization API | done | `sw.shell.topbar.setLeft(vnode)` overrides left slot. `sw.shell.topbar.setSlot(vnode)` sets center slot. `sw.shell.topbar.setTitle(str)` shorthand for text-only left. `sw.shell.topbar.hide()` / `.show()` for full-bleed surfaces. | +| Kernel tab CSS | done | `.sw-topbar__tabs` and `.sw-topbar__tab` classes for consistent tab styling in the center slot. Surfaces use these for free or style their own slot content. | +| Notification read broadcast | done | Backend emits `notification.read` and `notification.all_read` WS events. Bell listens for `.created`, `.read`, `.all_read`. Cross-surface sync without refetch. | +| User menu reactivity | done | Emit `package.changed` (install/uninstall/enable/disable) and `auth.changed` (role/membership) WS events. UserMenu listens and re-fetches surface list. Most impactful single fix. | +| Shell announcement global dismiss | done | Dismissed state persisted to localStorage keyed by content hash. Dismiss once, dismissed everywhere. | -### v0.6.4 — Admin Health/Metrics Tab + Cluster Merge - -Structural move: cluster dashboard becomes an Admin tab. Better home for health/metrics — shared context with other admin panels, no separate nav entry. +**Surface Migrations** | Step | Status | Description | |------|--------|-------------| -| "Health / Metrics" admin tab | ✅ | New tab in Admin surface. DB-agnostic metrics for all deployments. Cluster cards conditional on PG + multi-node detection. | -| Runtime metrics | ✅ | Per-node: goroutines, heap alloc/sys, stack in use, GC cycles, last GC pause, GC CPU %, uptime, WebSocket clients, extensions loaded, open FDs. | -| DB pool metrics | ✅ | All deployments: DB latency (`SELECT 1` round-trip), pool active/idle/max, wait count, wait duration. PG-only: table bloat (`n_dead_tup`), active backends (`pg_stat_activity`). | -| Cluster metrics | ✅ | PG multi-node only: cluster size, peer list with endpoint + uptime, heartbeat age per node, event bus publish/deliver rates. | -| Extension runtime metrics | ✅ | Starlark exec/min, errors/min, avg duration, HTTP outbound requests/min, trigger fires/min, schedule overruns. | -| Fatten heartbeat payload | ✅ | Heartbeat JSONB carries full metric set. `GET /api/v1/admin/metrics` for single-node SQLite fallback (same shape). | -| Retire `cluster-dashboard` | ✅ | Remove package once Admin Health tab ships. Update `defaultBundledPackages`. | -| Fix block renderer `requires` | ✅ | `mermaid-renderer`, `katex-renderer`, `csv-table`, `diff-viewer` all have `"requires": ["chat"]`. These are content renderers, not chat features. Remove constraint — they should activate without chat. | -| Health endpoint consolidation | ✅ | `/health` and `/api/v1/health` return near-identical JSON. Merge or clearly differentiate with docs. | +| Settings → Pattern B (flat tabs) | done | Delete custom topbar + sidebar nav. 6 sections become flat tabs in topbar center slot. Content full-width. Fix Teams section: add team admin link, role display, leave action. Remove sessionStorage return URL logic. | +| Admin → Pattern C (category tabs + sidebar) | done | Delete custom `admin-topbar`. `setLeft()` for favicon + "Administration". `setSlot()` for category tabs (People / Workflows / System / Monitoring). Admin sidebar (sub-navigation) unchanged — surface-owned, below the topbar. Bell + user menu come free from shell. Delete bespoke CatIcon renderer if using standard SVGs. | +| Team Admin → Pattern B (flat tabs) | done | Delete custom topbar + sidebar nav. 5 sections (Members / Connections / Workflows / Settings / Activity — Groups removed) become flat tabs. Content full-width. `setTitle()` for team-specific name. Remove sessionStorage return URL. Fix signoff user display (`user_id` → `sw.users.displayName()`). | +| Team Admin: remove Groups tab | done | 37-line dead-end. Read-only "No groups" with no create/docs/link. Remove until team-scoped group management is properly designed. | +| Docs → Pattern A (default) | done | Delete explicit Topbar import. Shell topbar auto-renders with manifest title. Docs sidebar (document list) is in content area, unaffected. | -### v0.6.5 — Renderer Pipeline + Docs Rewrite - -Most complex sub-version. Lifts block rendering to a kernel SDK primitive so all surfaces share it, then rewrites the docs for an external audience. +**Error Handling + UX Pass** | Step | Status | Description | |------|--------|-------------| -| SDK renderer primitive | ✅ | `sw.renderers.register(pattern, handler)` — kernel-level registration. Extensions call once; all surfaces consume. Decouples renderer discovery from chat surface. | -| Notes hooks SDK renderer pipeline | ✅ | Notes delegates to `sw.markdown.renderSync()` + `sw.renderers.runPostRenderers()`. Hand-rolled renderer deleted. | -| Docs hooks SDK renderer pipeline | ✅ | Docs delegates to `sw.markdown.renderSync()` + `sw.renderers.runPostRenderers()`. Hand-rolled renderer deleted (~160 lines). | -| Unify markdown renderer | ✅ | `sw.markdown` module lazy-loads `marked` v16 (vendored). Notes, Docs, Chat all consume it. Two hand-rolled parsers deleted. | -| Sanitize HTML output | ✅ | DOMPurify wired as default post-render step in `sw.markdown`. SVG-safe config allows mermaid output. Notes sanitizes; Docs opts out (system-authored). | -| Mermaid/KaTeX/CSV/Diff work everywhere | ✅ | Browser extension loader injects renderer scripts into all pages. Extensions register via `sw:ready` event. All four render in Notes, Docs, and Chat. | -| Docs rewrite for external audience | ✅ | All fork references removed from docs, CHANGELOG, ROADMAP. DESIGN-WORKFLOW-REDESIGN-0.2.6.md replaced with DESIGN-WORKFLOWS.md. | -| Add Mermaid architecture diagrams | ✅ | Six diagrams in ARCHITECTURE.md: system overview, request flow, extension lifecycle, realtime events, settings cascade, cluster topology. | -| Surface alias decision | ✅ | Migrated SDK + ICD runner to `/admin/packages/`; aliases removed from main.go. | -| `CONTRIBUTING.md` + tutorial | ✅ | CONTRIBUTING.md at repo root + docs/TUTORIAL-FIRST-EXTENSION.md walkthrough. | +| Inline error states | done | Replace `catch { toast }` with inline error + retry on all list endpoints. New `.sw-inline-error` CSS primitive. Systematic pass across Settings, Admin, Team Admin. | +| Empty state guidance | done | Every "No X" message gets one-line explanation + primary action (create button or doc link). Admin Groups, Workflows, Teams; Team Admin Workflows; Settings Notifications. | -### v0.6.7 — Native mTLS - -End-to-end mutual TLS without a reverse proxy. Targets systemd+podman deployments where every connection (client→server, node→node) is mTLS. Design: `docs/DESIGN-native-mtls.md`. +**Bug Fixes** | Step | Status | Description | |------|--------|-------------| -| `TLS_MODE` config | ✅ | Three values: `none` (default, plain HTTP) · `server` (TLS, no client cert) · `mtls` (mutual TLS, client cert required). Independent of `AUTH_MODE`. | -| TLS server mode | ✅ | Go binary calls `ListenAndServeTLS` directly. `TLS_CERT`, `TLS_KEY`, `TLS_CA` path config. TLS 1.3 minimum, no fallback. `server/config/tls.go` loader + validation. | -| `MTLSNativeProvider` | ✅ | Reads `r.TLS.PeerCertificates[0]` — no header trust. `Subject.CommonName` → username. `sha256(Raw)` → `external_id`. Auto-provisions `auth_source=mtls`. Renamed existing `mtls.go` → `mtls_proxy.go`. Shared helpers in `mtls_helpers.go`. | -| Node-to-node mTLS | ✅ | `BuildPeerTLSConfig()` constructs outbound TLS config with node cert + CA pool. Forward-looking — cluster registry is DB-backed (PG LISTEN/NOTIFY), no HTTP peer calls yet. | -| `armature-ca.sh` | ✅ | Shell wrapper around openssl. Three commands: `init` (CA keypair) · `issue-node --name --san ` (365d) · `issue-user --cn ` (90d). All output PEM. | -| Unit tests | ✅ | Ephemeral CA via `crypto/x509`. Fabricated `PeerCertificates`. Valid cert → user provisioned · no TLS → `ErrNoCert` · empty certs → `ErrNoCert` · no CN → `ErrInvalidCreds`. | -| Integration tests | ✅ | Real TLS listener on localhost. No cert / wrong CA / expired → TLS handshake rejected. Valid cert → 200. Peer certificate CN + email visibility verified. | +| evil-chat cleanup | done | ICD security tier: `finally` cleanup block + tighten `409` assertion. | +| Workflow demo error surfacing | done | Replace silent `catch` with inline error + retry. | +| Hello dashboard removal | done | Delete `packages/hello-dashboard/`. | -### v0.6.6 — Final Hardening - -Final pass before public release. Security, correctness, and developer experience. +**Rebrand** | Step | Status | Description | |------|--------|-------------| -| Extension dependency auto-activation | ✅ | Installing a package with unmet `depends`/`requires` auto-installs dependencies from the bundled set. If not bundled: clear error listing what's missing. | -| `ValidateManifest()` gate | ✅ | Single `ValidateManifest()` function in `package_validate.go`. Called at install time (both upload and bundled). 12 unit tests. | -| Package signing hook | ✅ | Optional `signature` field in manifest (reserved). `PACKAGE_VERIFY_SIGNATURES` env var (default false, log-only). No cryptographic code yet — schema slot reserved. | -| OIDC state nonce validation | ✅ | `oidcClaims.Nonce` field added. `ValidateIDTokenNonce()` compares ID token nonce against stored state. Callback rejects mismatched nonces. | -| Schema migration stub decision | ✅ | Stub replaced with log-only function documenting additive-only policy. Downgrade rejection preserved. | -| ICD/SDK runner update pass | ✅ | ICD smoke tier: added metrics, cluster, backups, docs, OpenAPI JSON endpoints. SDK admin domain: added metrics, cluster, backups tests. | -| Stale TODO resolution | ✅ | `main.go` session middleware: replaced with `OptionalAuth()` (auth if token present, anonymous pass-through). `auth.go:221` OIDC nonce: resolved. `starlark_helpers.go:96` migration stub: resolved. | +| Light-mode icon SVG | done | New `favicon-light.svg` — square icon, transparent bg, dark node fills. Rename current `favicon-light.svg` (wordmark) to `wordmark.svg`. | +| Dark-mode wordmark SVG | done | New `wordmark-dark.svg` — light text for dark backgrounds. | +| Light-mode raster assets | done | `favicon-light-32.png`, `favicon-light-256.png`. | +| PWA manifest description | done | "Self-hosted extension platform — build, compose, and run extensions." | +| REBRAND-SPEC.md | | Land into `docs/`. Find/replace patterns, validation checklist, asset inventory. | +| base.html favicon swap | done | Verify theme swap works with new square light icon. | -Then ship. +**Tests** + +| Step | Status | Description | +|------|--------|-------------| +| Shell topbar renders for extensions | done | Home, left slot, center slot, bell, user menu present. | +| Topbar API (setLeft, setSlot, hide) | done | Custom content renders. Hide removes topbar. | +| All 4 surfaces use shell topbar | done | No double topbars. Each pattern (A/B/C) renders correctly. | +| User menu reactive to package install | | Install → menu updates without reload. | +| Notification bell cross-surface sync | | Dismiss on Notes → clears on Chat. | +| Inline error on API failure | | Error + retry shown, not empty list. | + +### v0.7.1 — Surface Runner Framework + +Design doc: `docs/DESIGN-surface-runners.md` + +| Step | Status | Description | +|------|--------|-------------| +| Runner framework (`sw.testing`) | | SDK module: suite/test/assert, lifecycle hooks, structured JSON results. | +| `requires` declarations | | Runner manifests declare dependencies. Missing packages → clean skip. | +| Cleanup enforcement | | `s.track(type, id)` auto-deletes in `afterAll`. No leaked state. | +| Warning tier | | pass / fail / warning. No silent catch swallowing. | +| ICD runner migration | | Refactor to `sw.testing`. Test logic preserved. | +| SDK runner migration | | Refactor to `sw.testing`. Domain suites preserved. | +| Runner registry surface | | `/s/test-runners` — list runners, run-all, results dashboard. | + +### v0.7.2 — Package Runners + CI Gate + +| Step | Status | Description | +|------|--------|-------------| +| Notes runner | | CRUD, folders, tags, backlinks, search, rendering. | +| Chat runner | | `requires: ["chat", "chat-core"]`. Channels, messaging, renderers. | +| Schedules runner | | CRUD, cron, toggle, Starlark exec. | +| Workflow runner | | `requires: ["content-approval"]`. Install detection, stages, signoff. | +| Renderer runner | | `requires: ["mermaid-renderer"]`. Register contract, post-render hooks. | +| Runner result API | | `POST /api/v1/test-runners/run`, `GET /results`. CI can curl and assert. | +| CI integration | | `test-runners` stage in Gitea CI. PG + SQLite. Zero failures = pass. | + +### v0.7.3 — Headless E2E Automation + +| Step | Status | Description | +|------|--------|-------------| +| Playwright test harness | | `ci/e2e-surface-test.sh` — docker-compose, chromium, run-all, assert. | +| Screenshot-on-failure | | Full-page screenshot + console log. CI artifacts. | +| Navigation smoke test | | Playwright visits every surface. Asserts topbar, no JS errors, home link works. | +| Visual regression baseline | | Optional screenshot diff. Not a gate — report for review. | +| CI pipeline integration | | After unit tests + API runners. Failure blocks merge. | + +### v0.7.4 — Documentation + Deferred Surface Work + +| Step | Status | Description | +|------|--------|-------------| +| Permissions & Groups guide | | RBAC model, permission slugs with descriptions, group scoping, settings cascade. | +| Workflows user guide | | Entry modes, stages, team roles, signoff gates, SLA, public forms. | +| Docs content refresh | | Review all 5 existing docs for accuracy at v0.7.x. | +| Extension config section docs | | Document `config_section` manifest field for Settings/Admin extensibility. | +| Team Admin Workflows evaluation | | 723-line inline designer — extract to surface or split into files. Decision doc if needed. | --- -## Post-MVP +## Post-v0.7.x -- LLM participation (`llm-bridge` extension: subscribes to `chat.message.created`, calls `provider.complete()`, streams response via `realtime.publish`, posts via `chat.send()`. Bot participants with persona config. Multi-model conversations.) -- Rich media extensions: image generation, code sandbox, STT/TTS -- Desktop app (Tauri or Electron) -- Sidecar tier: container-based extensions -- Federation: cross-instance package sharing -- Plugin marketplace with signing and review +- **LLM participation** (`llm-bridge` extension) +- **Rich media extensions:** image generation, code sandbox, STT/TTS +- **Desktop app** (Tauri or Electron) +- **Sidecar tier:** container-based extensions +- **Federation:** cross-instance package sharing +- **Plugin marketplace** with signing and review --- @@ -172,22 +188,24 @@ Then ship. | Decision | Rationale | |----------|-----------| -| Tasks → extension | Scheduler was the most entangled kernel component (~3,400 lines). Rebuilding as extension validates the trigger system and removes the worst compilation debt. Three trigger primitives (time, webhook, event) replace the monolithic scheduler. | -| Sessions removed | Kernel-managed sessions replaced by workflow instances with dedicated storage (ext_data tables or kernel table). | -| `custom` stage mode | Stage mode `custom` delegates to a surface package, proving extension composability. Chat-in-workflow is handled by the chat extension, not the kernel. | -| Providers removed from kernel | Provider configs, model catalog, routing policies — all moved to extension track. Kernel provides credential storage (connections) and the Starlark `provider.complete` module as the interface. | -| Kernel permissions simplified | 6 platform permissions. Extensions define their own capability requirements in manifests. | -| Preact+htm retained | 3KB runtime, no build step, works for extension authors without bundler config. KISS. | -| Single Docker image | Drop the frontend/backend split. Go binary + assets + migrations in one image. Simpler deployment, fewer moving parts. | -| Admin → RBAC group | The `role` column is pre-RBAC. v0.2.0 replaces it with a seeded "Admins" group + `surface.admin.access` grant. All users auto-join "Everyone" group. Admin middleware becomes a grant check, not a role check. | -| Settings cascade | RBAC controls scope auth (who can set at what level). `user_overridable` flag controls whether lower scopes can override higher. Two orthogonal axes, composes cleanly with extension manifests. | -| No new migrations pre-MVP | Edit existing migration SQL files in place. No migration chains until schema is in production. | -| Notes over Editor | First surface is Obsidian-style notes (rich text, folders, backlinks) instead of a code editor. Notes is a stronger E2E proof — it exercises ext_data, storage, and the SDK more fully than a pure-browser CM6 editor. | -| No built-in auto-install | Extensions ship in the repo but are not auto-installed. Distribution model TBD — explicit install only. Keeps the kernel clean and avoids opinionated defaults. | -| Chat as extension, not kernel | Human-to-human chat built entirely as library + surface packages. Zero kernel awareness of conversations, messages, or participants. Kernel gains one generic `realtime` module. Proves near-infinite extensibility. LLM participation layers on top via a separate bridge extension — the chat system doesn't know or care whether a participant is human or AI. | -| PG as consensus layer | Horizontal scaling uses PG as the sole coordinator (UNLOGGED node_registry + LISTEN/NOTIFY). No etcd, Consul, Redis, or Raft. Rationale: system is already tightly coupled to PG; adding a second consensus layer doubles operational complexity for zero benefit at homelab-to-small-team scale. UNLOGGED table is visible to all connections, survives session disconnect, and cleans up automatically on PG crash — correct behavior since all nodes are dead anyway. Sweep-all for health (every node deletes stale rows) is simpler than ring topology and has no edge cases. | -| Two trigger tiers | Event + webhook triggers are extension-declared (manifest contract, full sandbox). Scheduled tasks are user-created ad-hoc (restricted sandbox — no raw HTTP, no DB table creation, connections-only outbound). Separation keeps extension contracts static and user automation safe. | -| Scheduled task identity | Tasks run as their creator (RBAC-scoped). Admin-created tasks can opt into system context. Creator deactivation pauses the schedule. Ensures audit trail and permission boundaries. | -| Builtin package rationale | A builtin must enhance kernel surfaces or be required to demonstrate the platform's own capabilities. **notes** — primary content surface, exercises ext_data/storage/SDK/settings/realtime fully. **chat + chat-core** — primary communication surface, proves the extensibility thesis (100% extension, zero kernel awareness). **mermaid-renderer** — docs surface uses Mermaid diagrams to explain the architecture; without it the platform's own documentation doesn't render (self-bootstrapping). **schedules** — UI for the kernel's scheduled task system; without it users can't manage cron jobs (kernel primitive UI). All others are domain features, examples, dormant, or LLM-only tools — available via registry or `BUNDLED_PACKAGES=*`. | -| Cluster dashboard retired | `cluster-dashboard` shipped as a standalone surface package (v0.6.0) as an expedient. Health/metrics belong inside the Admin surface as a tab — shared context, no separate nav entry, no install required. Merged in v0.6.4. | -| Block renderers decoupled from chat | `mermaid-renderer`, `katex-renderer`, `csv-table`, `diff-viewer` shipped with `"requires": ["chat"]` because renderer discovery lived inside the chat surface. These are content renderers, not chat features. v0.6.4 removes the constraint; v0.6.5 lifts renderer registration to the kernel SDK so all surfaces share it without reimplementing discovery. | +| Tasks → extension | Three trigger primitives replace the monolithic scheduler. | +| Sessions removed | Workflow instances with dedicated storage replace kernel sessions. | +| `custom` stage mode | Delegates to a surface package, proving extension composability. | +| Providers removed from kernel | Connections + Starlark `provider.complete` as the interface. | +| Kernel permissions simplified | 6 platform permissions. Extensions define their own. | +| Preact+htm retained | 3KB runtime, no build step, KISS. | +| Single Docker image | Go binary + assets + migrations. | +| Admin → RBAC group | Grant check replaces role check. | +| Settings cascade | Scope auth + `user_overridable`. Two orthogonal axes. | +| No new migrations pre-MVP | Proper versioned migrations post-MVP. | +| Chat as extension, not kernel | Zero kernel awareness. Proves extensibility thesis. | +| PG as consensus layer | UNLOGGED node_registry + LISTEN/NOTIFY. No etcd/Consul/Redis. | +| Two trigger tiers | Extension-declared (full sandbox) vs user ad-hoc (restricted). | +| Builtin package rationale | Must enhance kernel surfaces or demonstrate platform capabilities. | +| Two-slot topbar model | Left slot (title/branding) + center slot (`flex: 1`, tabs/pickers). Two named slots cover every navigation pattern: simple title (Pattern A), flat tabs full-width (Pattern B), category tabs + surface-owned sidebar (Pattern C). Surfaces without sub-items get full content width; surfaces with hierarchical navigation add their own sidebar below the topbar. Shell provides the stage; surface decides the theater. | +| All four primary surfaces migrate to shell topbar | Admin was "keep custom topbar" initially. The two-slot model makes it unnecessary — category tabs fit in the center slot, sidebar is surface-owned below. One topbar implementation replaces four. Bell + user menu + reactivity come free on every surface. | +| Settings / Team Admin → flat tabs (Pattern B) | Both had thin sidebars (~140px) that consumed width without justification. 5–6 sections fit cleanly in topbar tabs. Full-width content is a better use of space for these surfaces. | +| Team Admin Groups removed | 37-line read-only dead-end. Admin Groups has full CRUD. Restore when properly designed. | +| Docs is the reference surface | Only surface with shell Topbar, bell, user menu, inline errors. Others converge. | +| Surface runners over expanding ICD | Different test tier, different failure class. | +| Headless E2E via Playwright | Runners produce structured JSON; Playwright navigates and reads output. | diff --git a/VERSION b/VERSION index 724e8d9..faef31a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.18 +0.7.0 diff --git a/docs/AUDIT-surfaces.md b/docs/AUDIT-surfaces.md new file mode 100644 index 0000000..da4b0a9 --- /dev/null +++ b/docs/AUDIT-surfaces.md @@ -0,0 +1,209 @@ +# Surface Audit — Settings, Admin, Team Admin, Docs + +## Audit Methodology + +For each surface: read the index.js (tab/section structure), read every +section module, check the HTML template, trace the topbar/navigation +pattern, identify bugs, missing features, dead ends, and legacy baggage. + +--- + +## 1. Settings Surface + +**Files:** `src/js/sw/surfaces/settings/` (7 files, ~868 lines) +**Template:** `surfaces/settings.html` — mounts into `#settings-mount` +**Topbar:** Custom — back arrow + user icon + "Settings" title. No bell. No user menu. + +### Sections + +| Section | Lines | Status | Notes | +|---------|-------|--------|-------| +| General | 62 | ✅ OK | Default surface picker. Clean. | +| Appearance | 78 | ✅ OK | Theme toggle (light/dark/system) + UI scale slider. | +| Profile | 180 | ✅ OK | Display name, email, avatar upload, password change. | +| Teams | 47 | ⚠️ Thin | Read-only list of your teams. No actions. No link to team admin. | +| Connections | 222 | ✅ OK | Personal BYOK connection CRUD. Functional. | +| Notifications | 96 | ✅ OK | Toggle notification types on/off. | + +### Issues Found + +| # | Severity | Issue | +|---|----------|-------| +| S1 | **P1** | **No notification bell.** Custom topbar renders back arrow + icon + "Settings" — no bell, no user menu dropdown. User can't see notifications or navigate to other surfaces without using the back button. | +| S2 | **P2** | **Teams section is a dead-end.** Lists your teams with no actions — can't leave team, can't navigate to team admin, can't see team details. Just names. Should either link to team admin or show useful info. | +| S3 | **P2** | **Back button uses sessionStorage return URL.** `sb_settings_return` stash means: open settings in a new tab → back goes to `/` (correct). But open settings from a deep link → back goes to referrer, which might be unexpected. Shell topbar with consistent home link would fix this. | +| S4 | **P3** | **Extension config sections.** `__CONFIG_SECTIONS__` injection works but has no documentation. Extension authors don't know they can add settings sections. Needs a docs entry. | + +### Shell Topbar Migration + +Settings renders its own `settings-topbar`. With shell topbar injection: +- **Option A (simple):** `sw.shell.topbar.hide()` and keep custom topbar. Works immediately. +- **Option B (ideal):** Remove custom topbar. Shell topbar provides back + title + bell + user menu. Settings nav stays in the sidebar. +- **Recommendation:** Option B. Settings topbar adds nothing the shell topbar doesn't. The back arrow just navigates to `/`. + +--- + +## 2. Admin Surface + +**Files:** `src/js/sw/surfaces/admin/` (13 files, ~2,522 lines) +**Template:** `surfaces/admin.html` — mounts into `#admin-mount` +**Topbar:** Custom — favicon + "Administration" + category tabs (People/Workflows/System/Monitoring) + UserMenu component. No notification bell. + +### Sections + +| Section | Category | Lines | Status | Notes | +|---------|----------|-------|--------|-------| +| Users | People | 152 | ✅ OK | User list, create, edit status/role. Functional. | +| Teams | People | 178 | ✅ OK | Team list, create, member management. | +| Groups | People | 207 | ✅ OK | Full CRUD — create, delete, permission toggles, member add/remove. Functional but **undocumented** (see issues). | +| Workflows | Workflows | 163 | ⚠️ | CRUD + stage editor. `sw.api.workflows.list()` — needs same error-surfacing treatment as workflow-demo. | +| Settings | System | 242 | ✅ OK | Comprehensive: default surface, registration, banner, message bar, footer, session TTLs, vault, package registry, email test. Actually solid. | +| Storage | System | 76 | ✅ OK | Status cards, orphan cleanup. Clean. | +| Packages | System | 391 | ⚠️ | Core feature. Large. Package list, install, uninstall, registry browse. **User menu doesn't update after install/uninstall** (main bug Jeff reported). | +| Connections | System | 210 | ✅ OK | Global connection CRUD. | +| Broadcast | System | 44 | ✅ OK | Send broadcast. Minimal. | +| Backup | System | 162 | ✅ OK | Create/restore/download/delete. Works. | +| Health | Monitoring | 209 | ✅ OK | Runtime, DB pool, cluster, extension metrics. | +| Audit | Monitoring | 88 | ✅ OK | Audit log viewer with pagination. | + +### Issues Found + +| # | Severity | Issue | +|---|----------|-------| +| A1 | **P1** | **User menu not reactive to package changes.** `UserMenu` fetches surface list once on mount (`useEffect([authenticated])`). Installing/uninstalling a package doesn't trigger re-fetch. User must refresh the page to see new surfaces in the menu. Same for role changes (adding as team-admin). | +| A2 | **P1** | **No notification bell.** Admin topbar has category tabs + UserMenu but no NotificationBell component. | +| A3 | **P2** | **Groups: no documentation or inline help.** Admin Groups has full CRUD but zero explanation of what groups are, what permissions mean, or how the RBAC model works. "No groups" → user creates one → sees a list of permission slugs like `surface.admin.access` with no description. Every permission should have a human-readable description. | +| A4 | **P2** | **Workflows: silent error potential.** `sw.api.workflows.list()` — if this fails, `catch (e) { sw.toast(e.message, 'error'); }` fires a toast but leaves the list empty. Better than workflow-demo's silent swallow, but the toast disappears and the user is left with an empty list + no context. Should show inline error state. | +| A5 | **P2** | **Packages: no post-install feedback.** After installing a package, the package list refreshes (good) but the user menu doesn't update (bad — A1). User installs Notes, doesn't see it in the menu, thinks it's broken. | +| A6 | **P3** | **Admin topbar favicon is hardcoded.** Line 142: ``. Should respect light/dark theme favicon swap. | +| A7 | **P3** | **Category icon rendering is fragile.** Custom compact SVG format (`C12 12 3\|M19.4 15...`) in `CatIcon`. Works but is unmaintainable — any icon change requires understanding the custom format. Should use standard SVG paths or lucide/feather icons. | + +### Shell Topbar Migration + +Admin has the most complex custom topbar — category tabs are genuinely useful navigation. Options: +- **Option A (recommended):** `sw.shell.topbar.hide()`. Admin keeps its custom topbar but adds NotificationBell component to its existing right-side area next to UserMenu. +- **Option B:** Shell topbar with `sw.shell.topbar.setSlot()` for category tabs. Works but requires rethinking the layout since shell topbar has fixed structure (home | title | slot | bell | user). +- **Recommendation:** Option A for v0.7.0. Admin's custom topbar is bespoke enough to warrant keeping. Just wire in the bell. + +--- + +## 3. Team Admin Surface + +**Files:** `src/js/sw/surfaces/team-admin/` (7 files, ~1,119 lines) +**Template:** `surfaces/team-admin.html` — mounts into `#team-admin-mount` +**Topbar:** Custom — back arrow + "Team Admin: {team name}" title. No bell. No user menu. + +### Sections + +| Section | Lines | Status | Notes | +|---------|-------|--------|-------| +| Members | ~90 | ✅ OK | Member list, add, remove. Functional. | +| Groups | 37 | ❌ Dead-end | Read-only "No groups" display. No create, no docs, no link to admin. | +| Connections | ~120 | ✅ OK | Team-scoped connections. Same pattern as user/admin connections. | +| Workflows | 723 | ⚠️ Massive | Three tabs: Workflows (CRUD + inline stage editor), Assignments (claim/release/complete), Monitor (active instances + signoff). This is 65% of the surface's code. | +| Settings | 72 | ✅ OK | Team name + description. Clean. | +| Activity | ~80 | ✅ OK | Audit log. Works. | + +### Issues Found + +| # | Severity | Issue | +|---|----------|-------| +| T1 | **P1** | **Groups is a dead-end.** 37 lines. Read-only list of team groups. No "Create Group" button. No explanation of what groups are. No link to Admin > Groups where creation actually happens. A team admin user who isn't a platform admin literally cannot create team groups. The Admin groups page supports `scope: team` but that creates a global group with team scope — it's unclear if team-admin should even see groups at all. | +| T2 | **P1** | **Workflows "Adopt Global" — same silent-error class.** `sw.api.teams.availableWorkflows(teamId)` — if this fails, the catch fires a toast but the adopt panel shows "No global workflows available" — indistinguishable from "there genuinely aren't any" vs "the API errored." | +| T3 | **P1** | **Workflows is disproportionately complex.** 723 lines — inline stage editor with mode/type selectors, SLA fields, stage reordering, team role assignment per stage. This is a full workflow designer embedded in a tab. It works but it's a maintenance burden and the UX is dense. Question: should this complexity live here or be a separate workflow-designer surface? | +| T4 | **P1** | **No notification bell.** Same as Settings — custom topbar with no bell. | +| T5 | **P2** | **No user menu.** Unlike Admin (which renders UserMenu), Team Admin has no user menu in its topbar. User can't navigate to other surfaces except via the back button. | +| T6 | **P2** | **Signoff panel shows raw user_id.** Line 714: `${s.user_id}` — shows UUID instead of display name. Should use `sw.users.displayName(s.user_id)`. | +| T7 | **P3** | **Back button uses sessionStorage.** Same pattern as Settings (`sb_team_admin_return`). Shell topbar would fix. | + +### Shell Topbar Migration + +Team Admin has a simple topbar (back + title). Direct replacement: +- Shell topbar provides: home link + "Team Admin: {name}" title + bell + user menu. +- Team name from `sw.api.teams.get(teamId)` → `sw.shell.topbar.setTitle('Team Admin: ' + team.name)`. +- Delete the custom topbar entirely. + +--- + +## 4. Docs Surface + +**Files:** `src/js/sw/surfaces/docs/` (1 file, 313 lines) +**Template:** `surfaces/docs.html` — mounts into `#docs-mount` +**Topbar:** Imports and renders `shell/topbar.js` (the SDK Topbar component). **Only surface that uses the shell Topbar.** + +### Features + +| Feature | Status | Notes | +|---------|--------|-------| +| Document list sidebar | ✅ OK | Fetches from `/api/v1/docs`, renders nav links. | +| Markdown rendering | ✅ OK | Uses `sw.markdown.renderSync()` + post-renderers (mermaid, katex). | +| Document outline | ✅ OK | Parses H1-H4 from markdown, renders table of contents. | +| Search | ✅ OK | Filters documents in sidebar. | +| URL updates | ✅ OK | `history.replaceState` on doc change. | +| Topbar | ✅ OK | Uses shell `Topbar` component — has title, bell, user menu. | + +### Issues Found + +| # | Severity | Issue | +|---|----------|-------| +| D1 | **P2** | **Stale content.** The docs themselves may be outdated — GETTING-STARTED, EXTENSION-GUIDE, API-REFERENCE, DEPLOYMENT, PACKAGE-FORMAT were written in v0.6.1. 18 versions later, some content is likely stale. Needs a content review pass. | +| D2 | **P3** | **No docs for RBAC/Groups.** Admin Groups exists with full CRUD but there's no documentation explaining the permission model, what each permission slug means, how groups interact with teams, or how the settings cascade works. This directly causes the "groups WTF" reaction. | +| D3 | **P3** | **No docs for Workflows.** The workflow engine is complex (multi-stage, team roles, signoff gates, SLA, public entry) but has no user-facing documentation. `DESIGN-WORKFLOWS.md` exists but is a design doc, not a user guide. | +| D4 | **P3** | **Shell topbar migration.** Docs already imports `shell/topbar.js` — when shell topbar injection lands, Docs will get a double topbar. Needs migration: delete the import, let shell topbar handle it. Docs currently passes no custom slot content, so it's a pure delete. | + +--- + +## Cross-Surface Issues + +These affect multiple or all surfaces: + +| # | Severity | Issue | Surfaces | +|---|----------|-------|----------| +| X1 | **P0** | **User menu not reactive.** Package install/uninstall, role changes, team membership changes — none trigger a menu refresh. User must reload the page. | All (via UserMenu component) | +| X2 | **P1** | **No notification bell on 3/4 surfaces.** Only Docs has a bell (via Topbar import). Settings, Admin, and Team Admin all lack it. | Settings, Admin, Team Admin | +| X3 | **P1** | **No user menu on 2/4 surfaces.** Settings and Team Admin have no user menu at all. Admin and Docs have one. | Settings, Team Admin | +| X4 | **P2** | **Every surface has its own topbar.** Four different topbar implementations. None use the (not-yet-existing) shell topbar injection. Shell topbar (v0.7.0) eliminates this duplication. | All | +| X5 | **P2** | **Silent error swallowing.** Multiple sections use `catch (e) { toast }` which fires a toast and leaves an empty/stale UI. Toast disappears after seconds; user is left confused. Every list endpoint needs an inline error state with retry. | Admin Workflows, Team Admin Workflows, Packages | +| X6 | **P2** | **Empty states provide no guidance.** "No groups", "No workflows", "No notifications" — no explanation of what the feature is, why it's empty, or what action to take. Every empty state should have a one-line explanation and a primary action (create, link to docs, etc.). | Admin Groups, Team Admin Groups, Workflows | +| X7 | **P3** | **Raw IDs in UI.** Team Admin signoff panel shows `user_id` UUIDs. Any surface showing IDs should resolve via `sw.users.displayName()`. | Team Admin Workflows | + +--- + +## Recommendations + +### Immediate (fold into v0.7.0) + +1. **User menu reactivity** — emit `package.changed` and `auth.changed` events over WS + local custom events. UserMenu listens and re-fetches surface list. This is the single most impactful fix. + +2. **Shell topbar migration for Settings + Team Admin** — both have simple topbars that the shell topbar directly replaces. Docs deletes its Topbar import. Admin keeps its custom topbar but adds NotificationBell. + +3. **Remove Team Admin Groups tab** — it's 37 lines of dead-end. Team-scoped group management should either (a) be added properly with create/edit/delete or (b) removed until it's properly designed. Showing "No groups" with no path forward is worse than not showing the tab. + +4. **Error states** — replace `catch { toast }` with inline error + retry UI on every list endpoint. Systematic pass across all four surfaces. + +5. **Empty state copy** — every "No X" message gets a one-line explanation + primary action button or doc link. + +### Deferred (v0.7.1+ / runner coverage) + +6. **Admin Groups documentation** — write a "Permissions & Groups" doc for the docs surface. Explain the RBAC model, list all permission slugs with descriptions, explain group scoping. + +7. **Workflow user guide** — write a "Workflows" doc. Entry modes, stage types, team roles, signoff gates, SLA. + +8. **Team Admin Workflows simplification** — the 723-line inline stage editor is the most complex piece of UI in the entire application. Consider extracting to a dedicated workflow-designer surface or at minimum breaking into separate files. + +9. **Docs content refresh** — review all 5 docs for accuracy at v0.6.18+. + +10. **Settings Teams section** — either add useful actions (link to team admin, show team role, leave team) or remove the tab. + +--- + +## Asset Inventory + +| Surface | Lines (total) | Sections | Custom Topbar | Bell | UserMenu | Error Handling | +|---------|--------------|----------|---------------|------|----------|---------------| +| Settings | 868 | 6 | Yes (back+icon) | ❌ | ❌ | Toast only | +| Admin | 2,522 | 12 | Yes (tabs+menu) | ❌ | ✅ | Toast only | +| Team Admin | 1,119 | 6 | Yes (back+title) | ❌ | ❌ | Toast only | +| Docs | 313 | 1 | Shell Topbar ✅ | ✅ | ✅ | Inline error ✅ | + +Docs is the gold standard. The other three need to converge toward its pattern. diff --git a/docs/DESIGN-shell-contract.md b/docs/DESIGN-shell-contract.md new file mode 100644 index 0000000..a327f66 --- /dev/null +++ b/docs/DESIGN-shell-contract.md @@ -0,0 +1,448 @@ +# DESIGN: Shell Contract — v0.7.0 + +## Status: Proposed + +## Problem + +Extension surfaces render into `#extension-mount` with no shell chrome. +A full audit (docs/AUDIT-surfaces.md) found: 3/4 primary surfaces lack a +notification bell, 2/4 lack a user menu, 4 different topbar implementations, +user menu never updates on package/role changes, toast-and-forget error +handling, and empty states that explain nothing. + +## Solution + +### 1. Shell Topbar — Two-Slot Model + +The kernel injects a topbar for all extension surfaces. Two named slots +(left + center) let surfaces customize without replacing the entire bar. + +**Layout:** + +``` +┌──────────────────────────────────────────────────────────────┐ +│ ← │ [left] │ [center: flex-1] │ 🔔 │ 👤 │ +└──────────────────────────────────────────────────────────────┘ +``` + +- **← (home):** Always visible. Navigates to `__BASE__/`. Simple ``. +- **Left slot:** Defaults to manifest title. Surfaces override with `setLeft()`. +- **Center slot:** Empty by default. Surfaces inject tabs, search, pickers. + `flex: 1` — expands to fill available space. +- **Bell + User Menu:** Always visible. Kernel-managed. + +**Template change** (`surfaces/extension.html`): + +```html +{{define "surface-extension"}} +
+
+
+
+{{end}} +``` + +**SDK API:** + +```js +sw.shell.topbar.setLeft(vnode) // Override left slot (default: title) +sw.shell.topbar.setSlot(vnode) // Set center slot content +sw.shell.topbar.setTitle(str) // Shorthand: setLeft with plain text +sw.shell.topbar.hide() // Remove topbar entirely +sw.shell.topbar.show() // Restore after hiding +``` + +### 2. Three Navigation Patterns + +The shell topbar provides the top bar. What happens below it is the +surface's business. Three patterns emerge naturally: + +#### Pattern A — Default (simple extensions, Docs) + +``` +┌──────────────────────────────────────────┐ +│ ← │ Surface Title │ 🔔 │ 👤 │ +├──────────────────────────────────────────┤ +│ │ +│ Content (full width) │ +│ │ +└──────────────────────────────────────────┘ +``` + +Title only, no tabs, no sidebar. Content gets everything. +Zero code required — kernel defaults handle it. + +**Used by:** Docs, Notes, Chat, simple extensions. + +#### Pattern B — Flat Tabs (no sidebar, full width) + +``` +┌──────────────────────────────────────────────────────────┐ +│ ← │ Title │ Tab1 │ Tab2 │ Tab3 │ Tab4 │ │ 🔔 │ 👤 │ +├──────────────────────────────────────────────────────────┤ +│ │ +│ Content (full width, no sidebar) │ +│ │ +└──────────────────────────────────────────────────────────┘ +``` + +Tabs in the topbar center slot. No sidebar — content fills the full +viewport width. For surfaces with 3–7 sections that don't have sub-items. +More real estate for content than a sidebar layout. + +**Surface code:** + +```js +sw.shell.topbar.setSlot(html` +
+`); +``` + +**Used by:** Team Admin (Members / Connections / Workflows / Settings / Activity), +Settings, Schedules. + +#### Pattern C — Category Tabs + Sidebar (two-level) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ ← │ Title │ People │ Workflows │ System │ Mon │ 🔔 │ 👤 │ +├──────────┬──────────────────────────────────────────────────┤ +│ Users │ │ +│ Teams │ Content │ +│ Groups │ │ +│ │ │ +└──────────┴──────────────────────────────────────────────────┘ +``` + +Major categories in the topbar (via center slot). Surface renders its own +sidebar inside the content area for sub-navigation within the active +category. The shell topbar doesn't know about the sidebar — it's a +surface-level div below `#extension-mount`. + +**Surface code:** + +```js +// Topbar: major categories +sw.shell.topbar.setLeft(html` + + Administration +`); +sw.shell.topbar.setSlot(html` +
+ ${categories.map(c => html` + + <${CatIcon} paths=${c.icon} /> ${c.label} + + `)} +
+`); + +// Content area: sidebar is surface-owned +return html` +
+
+ ${sidebarSections.map(s => html`...`)} +
+
+ <${SectionComponent} /> +
+
+`; +``` + +**Used by:** Admin. + +### 3. Kernel-Provided Tab CSS + +The kernel provides `.sw-topbar__tabs` and `.sw-topbar__tab` CSS so +surfaces get consistent tab styling. Not required — surfaces can style +their own slot content however they want. + +```css +.sw-topbar__tabs { + display: flex; + align-items: center; + gap: var(--sp-1); + height: 100%; +} + +.sw-topbar__tab { + display: flex; + align-items: center; + gap: var(--sp-2); + padding: var(--sp-2) var(--sp-3); + font-size: 13px; + font-weight: 500; + color: var(--text-2); + text-decoration: none; + border-radius: var(--radius-sm); + transition: color var(--transition), background var(--transition); + white-space: nowrap; +} + +.sw-topbar__tab:hover { + color: var(--text); + background: var(--bg-hover); +} + +.sw-topbar__tab.active { + color: var(--text); + background: var(--bg-2); +} +``` + +### 4. Primary Surface Migrations + +#### Settings → Pattern B (flat tabs) + +Currently: custom topbar (back + icon + "Settings"), sidebar nav. +After: shell topbar with flat tabs, no sidebar. Content full width. + +Settings has 6 sections (General / Appearance / Profile / Teams / +Connections / Notifications) — perfect for flat tabs. The sidebar was +thin (~140px) and ate width from the content area for no good reason. + +**Migration:** +- Delete `settings-topbar` div and CSS. +- Delete sidebar nav. Move section links into `sw.shell.topbar.setSlot()`. +- Remove `sb_settings_return` sessionStorage — shell home link handles it. +- Content area becomes full-width. +- Fix Teams section: add "Open Team Admin →" link, show role, add leave action. + +Extension config sections (`__CONFIG_SECTIONS__`) render as additional +tabs after the divider. Same as today, just in the topbar instead of sidebar. + +#### Admin → Pattern C (category tabs + sidebar) + +Currently: custom topbar (favicon + "Administration" + category tabs + UserMenu). +After: shell topbar with category tabs in center slot, surface-owned sidebar. + +**Migration:** +- Delete custom `admin-topbar` div and CSS. +- `sw.shell.topbar.setLeft()` with favicon + "Administration". +- `sw.shell.topbar.setSlot()` with category tabs (People / Workflows / System / Monitoring). +- Bell and user menu come from the shell — delete the explicit `<${UserMenu}>`. +- Admin sidebar and content area unchanged — they're below the topbar. +- Delete custom `CatIcon` renderer if category tab icons use standard SVG. +- Fix hardcoded `favicon.svg` — left slot can use theme-aware image. + +**Result:** Admin looks identical to today but its topbar is kernel-managed. +Bell added for free. User menu reactive for free. + +#### Team Admin → Pattern B (flat tabs) + +Currently: custom topbar (back + "Team Admin: {name}"), sidebar nav. +After: shell topbar with flat tabs, no sidebar. + +With Groups removed, Team Admin has 5 sections: Members / Connections / +Workflows / Settings / Activity. Perfect for flat tabs. + +**Migration:** +- Delete `team-admin-topbar` div and CSS. +- `sw.shell.topbar.setTitle('Team Admin: ' + team.name)` after team fetch. +- Section tabs into `sw.shell.topbar.setSlot()`. +- Delete sidebar nav. Content full-width. +- Remove `sb_team_admin_return` sessionStorage. +- Remove Groups tab entirely (37-line dead-end). +- Fix signoff display: `user_id` → `sw.users.displayName()`. + +#### Docs → Pattern A (default) + +Currently: imports `shell/topbar.js` and renders it explicitly. +After: shell topbar auto-renders. No surface code needed. + +**Migration:** +- Delete `import { Topbar }` and `<${Topbar}>` render. +- Shell topbar provides title + bell + user menu automatically. +- Docs sidebar (document list) is in the content area, unaffected. + +### 5. User Menu Reactivity + +**The single most impactful fix.** + +**Backend — new WS events:** + +```go +// After package install/uninstall/enable/disable: +h.hub.BroadcastToUser(userID, "package.changed", map[string]string{ + "action": "installed", "id": packageID, +}) + +// After team role/membership change: +h.hub.BroadcastToUser(userID, "auth.changed", map[string]string{ + "reason": "team_role", +}) +``` + +**Frontend** (`user-menu.js`): + +```js +useEffect(() => { + if (!sw?.api?.surfaces?.list) return; + + function fetchSurfaces() { + sw.api.surfaces.list().then(data => { + const raw = Array.isArray(data) ? data : data?.data || []; + setAllSurfaces(raw); + }).catch(() => {}); + } + + fetchSurfaces(); + + const off1 = sw.on?.('package.changed', fetchSurfaces); + const off2 = sw.on?.('auth.changed', fetchSurfaces); + + return () => { + if (typeof off1 === 'function') off1(); + if (typeof off2 === 'function') off2(); + }; +}, [authenticated]); +``` + +**Event inventory:** + +| Event | Trigger | Payload | +|-------|---------|---------| +| `package.changed` | Install, uninstall, enable, disable | `{ action, id }` | +| `auth.changed` | Team role change, group membership change | `{ reason }` | +| `notification.read` | Mark notification read | `{ id }` | +| `notification.all_read` | Mark all read | `{}` | + +### 6. Notification Read Broadcast + +**Backend** — after `MarkRead()` / `MarkAllRead()`: + +```go +h.hub.BroadcastToUser(userID, "notification.read", map[string]string{"id": id}) +h.hub.BroadcastToUser(userID, "notification.all_read", nil) +``` + +**Frontend** — `NotificationBell` listens for `.created`, `.read`, `.all_read`: + +```js +const onRead = (e) => { + setNotifications(prev => prev.map(n => + n.id === e.id ? { ...n, read_at: new Date().toISOString() } : n + )); +}; +const onAllRead = () => { + setNotifications(prev => prev.map(n => ({ + ...n, read_at: n.read_at || new Date().toISOString() + }))); +}; +``` + +### 7. Shell Announcement Global Dismiss + +On dismiss, write: `localStorage.setItem('armature_dismissed_' + hash(text), '1')`. +On mount, check. New announcements (changed text) show again. + +Implementation: inline ` {{end}}
diff --git a/server/pages/templates/surfaces/admin.html b/server/pages/templates/surfaces/admin.html index 02d3454..9ab2f34 100644 --- a/server/pages/templates/surfaces/admin.html +++ b/server/pages/templates/surfaces/admin.html @@ -5,6 +5,7 @@ */}} {{define "surface-admin"}} +
{{/* Preact renders into this div. Show a loading state until it mounts. */}}
Loading admin…
diff --git a/server/pages/templates/surfaces/docs.html b/server/pages/templates/surfaces/docs.html index 4675115..293201e 100644 --- a/server/pages/templates/surfaces/docs.html +++ b/server/pages/templates/surfaces/docs.html @@ -4,6 +4,7 @@ */}} {{define "surface-docs"}} +
Loading docs…
diff --git a/server/pages/templates/surfaces/extension.html b/server/pages/templates/surfaces/extension.html index e5a6333..11f269e 100644 --- a/server/pages/templates/surfaces/extension.html +++ b/server/pages/templates/surfaces/extension.html @@ -13,6 +13,7 @@ {{define "surface-extension"}}
+
{{end}} diff --git a/server/pages/templates/surfaces/settings.html b/server/pages/templates/surfaces/settings.html index b500731..6cdcdfb 100644 --- a/server/pages/templates/surfaces/settings.html +++ b/server/pages/templates/surfaces/settings.html @@ -4,6 +4,7 @@ */}} {{define "surface-settings"}} +
{{/* Preact renders into this div. Show a loading state until it mounts. */}}
Loading settings…
diff --git a/server/pages/templates/surfaces/team-admin.html b/server/pages/templates/surfaces/team-admin.html index 5543da9..8a89b8d 100644 --- a/server/pages/templates/surfaces/team-admin.html +++ b/server/pages/templates/surfaces/team-admin.html @@ -5,6 +5,7 @@ */}} {{define "surface-team-admin"}} +
Loading team admin…
diff --git a/src/apple-touch-icon.png b/src/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..056ff41f486ddda6b1c36806d45382c13ec803a3 GIT binary patch literal 1631 zcmY+FeKZql9LJ}-wnc=cB)b}SR?SYfS*}%*fSP-W6tXB_zcP^D@?D zi>w!1oJ&y*li`w=L?Umu7?*C`t#j&}{&=3}oaguUoagy{KTp~zM;m2DEkytTpiH(U zxyV+bd_X|5na~Bo0stU=GKqLP>h8j*DIIFJ`^tpgUb_SJJJbfxkbsT_>e1Ur2w5j= zZ6RrEVU63Y%F2Kw0hZ*LDg;BOYPh{-8y91yS7x`Ah+38NMV6U(_LwcPr>J?u_2a+1 zzR<{qLyVCAAE1UVRFHsvz=8$JsIFBx@%$hhIbViU$KJ|PU-g)WrtY&N2cZvBecN98 z-ciz;W$sk6xOJcw%Fgq#vQ5t+LUC33!Pt#5)#?x8n-sdqZwua5G@3dpzEJy|!U4UE>$>{wHz5tm3Rs5KkQhl20 zPvG`obj&LudU+Z9w$6L(zduJpf4fnb?g!+*Hf)oXe*M;RtASw}IXtDaj+ zmgY6sNNpy3W+z51^s(_4!EEtz#-xv?-E|V;jj5SRXQmObfHUx$#2#H~(cyrYlKPMHQhff#Zcals(Vwl`KQxYMQ z19rbwXDR7})VI?`u#z8b#9&%#o!@6EKZT&CRLB)?`6+N+wm$+ ze8{ta5vjmGk6l9_yVql$kh;0)zUx(l*yPh{Zc}1ZVxDO_35LyWcfUR?B~MrgOHrGj zf_g-tuCbxFQ}%%w?{5Ekl-haI33zp6tx08D{!VsdZD%iA;8w9x`_LOu&M-cCcKJi= zjPf8MOh*`_s{3)Xa#gMPS-kk+o#7VUlzwi~o;6}H7~b-N?XT~oIAq$O%{R8HYO3?a zQs-30{zBpJrF-{H^8q`Oua2!Icb}ypALS3(UT0?_V zQBITjt%};z4;-tIr2@*~damGVg6tB)IG9XI&TOa6)qm1*BjS9y^jy*f-GC#aBPMi% z^$BqzUOH00F@t$p)hcB!hIwBnEMd}()P;~pM;bgJo0YW3V+}k)k)C}sd?Wk*pqEIu zdr%zD9|`03=UHhfoqKv&f^Ii>d0woWP#Q`I$hJ=2^I8hhZW*)>iqEtQ?uuC@Z`bcQY)CH?4wq4^fth@l2n z4k90UZ}QSiw25GM9L@K-r8sdw<}K@8CZbe-+DP8qGBb>bJh%^!c?X{%AxiY8;~e74 ztC@8W^q6KlF3HVscWFQWZlfL*LAklqytyAX<>G1b_zFkEW2~c2SjnFL$hx{rP# z!1j>0AzxJajSIqUkLZt*zX;q16}Vpp5>;V|9a0ie3I8y;8$O eKie0})y++aaI9DMjX2rI0U%pBk^~m#ll}*K7XrHg literal 0 HcmV?d00001 diff --git a/src/css/sw-primitives.css b/src/css/sw-primitives.css index 5c07ea7..994f66d 100644 --- a/src/css/sw-primitives.css +++ b/src/css/sw-primitives.css @@ -292,6 +292,25 @@ .sw-dropdown__option--disabled:hover { background: transparent; } .sw-dropdown__empty { padding: var(--sp-3); text-align: center; color: var(--text-3); font-size: 0.85rem; } +/* ── Inline Error ─────────────────────────── */ + +.sw-inline-error { + display: flex; align-items: center; gap: var(--sp-3); + padding: var(--sp-3) var(--sp-4); + background: var(--bg-2); border: 1px solid var(--danger); + border-radius: var(--radius); font-size: 13px; color: var(--danger); +} + +/* ── Empty State ──────────────────────────── */ + +.sw-empty-state { + padding: var(--sp-6) var(--sp-4); + text-align: center; color: var(--text-3); font-size: 13px; +} +.sw-empty-state__text { + margin-bottom: var(--sp-3); +} + /* ── Focus Indicators ─────────────────────── */ .sw-btn:focus-visible { diff --git a/src/css/sw-shell.css b/src/css/sw-shell.css index d1e7085..9fea6de 100644 --- a/src/css/sw-shell.css +++ b/src/css/sw-shell.css @@ -156,6 +156,89 @@ gap: var(--sp-2); } +/* ── Shell Topbar — kernel-injected two-slot model ── */ + +.sw-topbar--shell { + gap: var(--sp-2); +} + +.sw-topbar__home { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + color: var(--text-3); + text-decoration: none; + border-radius: var(--radius); + transition: color var(--transition), background var(--transition); + flex-shrink: 0; +} + +.sw-topbar__home:hover { + color: var(--text); + background: var(--bg-hover); +} + +.sw-topbar__left { + display: flex; + align-items: center; + gap: var(--sp-2); + flex-shrink: 0; +} + +.sw-topbar__center { + flex: 1; + display: flex; + align-items: center; + min-width: 0; + overflow-x: auto; +} + +/* ── Topbar Tabs — consistent tab styling in center slot ── */ + +.sw-topbar__tabs { + display: flex; + align-items: center; + gap: var(--sp-1); + height: 100%; +} + +.sw-topbar__tab { + display: flex; + align-items: center; + gap: var(--sp-2); + padding: var(--sp-2) var(--sp-3); + font-size: 13px; + font-weight: 500; + color: var(--text-2); + text-decoration: none; + border-radius: var(--radius-sm); + transition: color var(--transition), background var(--transition); + white-space: nowrap; + cursor: pointer; +} + +.sw-topbar__tab:hover { + color: var(--text); + background: var(--bg-hover); +} + +.sw-topbar__tab.active { + color: var(--text); + background: var(--bg-2); +} + +@media (max-width: 768px) { + .sw-topbar__tabs { + gap: 0; + } + .sw-topbar__tab { + padding: var(--sp-2); + font-size: 12px; + } +} + /* ── User menu trigger ───────────────────── */ .sw-user-menu__trigger { diff --git a/src/favicon-light-256.png b/src/favicon-light-256.png new file mode 100644 index 0000000000000000000000000000000000000000..e420b6d489c83c9a982a977b98d3c1e842c9db80 GIT binary patch literal 2553 zcma)8c~nzZ8o%%56<-ufmW~Z;q6n0!2ozjUOrop`A_W8s3`Vv(3St2Rge4C>r(nxw zVTvu%&@xb;XJNh~zcRwY%CO#+%$P>eJJAx&PAd01!q&zw2)*ZubMedm6^`@6@( z!-AGCwOI-PSiU{jKLP-V5FvnC5J!CG*;D|?((V3RqjFEF$C@+kVr*oOl48zB_Xj45 zX|3yM9)|}$TcZuI&bS$E%|Lx_$Iw!1!rgv^2RAzAUH|ZmAzPHyJ(IVeZ@TC^lMz;# z@-*OS?_jeKAJF?q6AoqUmFzd#?QlIkTbPoy}fFL(%H;-RF)!>MF5=$-J4&adk7gtd#0djdhIDoYR!g>*+SB$IO^i_vtsUKi@zBYm2(ANs9 zs>@{T7SSms#E6Tnay{ahefH)gDu!LNIj5030%&YpBF@LNb7E%MOwQOCp`H_nuDF03 zc9mD6k&2lhB$0}7!^*SNt;T+J0P9T%ecb;J(}68?P02&vY}@>>Pb^RJE%FQoSt?G- z(+rF2+K;A0!1B@pjO+7?a97?eU<;nwX%*AL~ z0NAwdvChh$X9p|3J|P#f;3Num*PlW7=5~Qiw7(O1iVe_|f3JpF%9S7uq*p+vPB;B-`ax6EwIT&mp)_{sRL zg|6c4L`Gs@xP+-;>9QuBxITq{;at8mH!?$%M&qWGKxAyHF{h7>tqpO=cXlWKd zSUEL+(03EG=iK1iPrlIfmFKBKb>9#H16c?E} z|AY0WTcZy;ue5(Iu|NCnJb7ZQr0$kkt1+s65!$BVSMkGU$9lx1nGx<(4w_A~<2(8e z_&$X_Ht1dDo{VHX?#>5KMth^Q?aWmPJ5o=SHq`G_cb%~FYsSJ5_}@fXinJu zd)6&l@oyri-^Sc3wn7)?(sJD@o{Tdk^9yLrr91l@h0YW`((;3@t#kZck>CBCvB_AD zs7cw{C_=us;7y;g$l!bMY>Do)iV8#CTnkGHtO z{&+(nPRQ@OleSra_S?IfMAE4j#A;qzNK$sDo7~W9tx14upA9tUvlF~uLw+q3Ey&e% z8mMz)B~L{o_eWmrt!!Z4RsTS|5R0|DV;{%7{9<{~qP2O=L z__~k2&v;UM+VQTpDT0QFD}0K)6ua@%1}tI;uRNK`5a6L9N66ukm9->#BQ9RUxhCpt z(#RhnEfLM`H@!Hbj6>4uwrLGX;?VuTk&Z1D6z+R~udJ6bt3lMg*=5-~(O4@O+A)Vfp~v@w*~xJDaIpK3ia9a-*u}Qq>)v2ylxao7B?mK~=P#33K|>gN zF$p(NvGP*sdoqbTG*pGg%XJGbz?NS86GLbVw~(kOKg89jG|Yd(Ta^(4_^aVv)BdU__^zgSBylW<@~u_e{R#3plHRs~6Q z1}9*DoB^q3>Oi)01Q9JH-V~Gw@I<-a23BC2N@&a+|5;Bdn86L4So?(bsf^TpH9oOyS!7 zSJEneKHsG`?flbwBdyRk2d@lRB%XiDm;~|c&cC>Lw@CECD>7y+K-&17GG-lE-ICoa zV~Utdzf{~CxAgZV%8QXd*y&FydPS@M=B59o)NDk4ngrnC>9BNvk#0CfF8O9Hon1K z6Iu6_FH{P8J=#)g31b>K1@qD-*B59J7VL2*EGGRF8+nCKG8}A2bDE3h3J$wNN*aOcqVy+0_|^0!#|K-G&4r~ty^Z#xe@94h(3vQ5P literal 0 HcmV?d00001 diff --git a/src/favicon-light-32.png b/src/favicon-light-32.png new file mode 100644 index 0000000000000000000000000000000000000000..64188c6a7bd08597c82deef73a6d01e98074f572 GIT binary patch literal 332 zcmV-S0ki&zP)yE5Qa-tEht{55>={kB%3WDFXX)pP|!2P8Z z3~5~-FP1@dT@`lDbDO(fJh!Wtx_K6!>>$RRoToaR&s2FNJLn!N% zn37IH9|ZM`NV<|K)kx6-_?mR9lD>wb1;Ar=0NeRE0kJx7#U}vAKZ?0k*HyvoWDj%3 e&@0FZfxX_&qQ`*0GmT~d0000 - - - - - - - - rmature + + + + + + + + + + + + + + + + diff --git a/src/icons/apple-touch-icon-b-dark.png b/src/icons/apple-touch-icon-b-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..056ff41f486ddda6b1c36806d45382c13ec803a3 GIT binary patch literal 1631 zcmY+FeKZql9LJ}-wnc=cB)b}SR?SYfS*}%*fSP-W6tXB_zcP^D@?D zi>w!1oJ&y*li`w=L?Umu7?*C`t#j&}{&=3}oaguUoagy{KTp~zM;m2DEkytTpiH(U zxyV+bd_X|5na~Bo0stU=GKqLP>h8j*DIIFJ`^tpgUb_SJJJbfxkbsT_>e1Ur2w5j= zZ6RrEVU63Y%F2Kw0hZ*LDg;BOYPh{-8y91yS7x`Ah+38NMV6U(_LwcPr>J?u_2a+1 zzR<{qLyVCAAE1UVRFHsvz=8$JsIFBx@%$hhIbViU$KJ|PU-g)WrtY&N2cZvBecN98 z-ciz;W$sk6xOJcw%Fgq#vQ5t+LUC33!Pt#5)#?x8n-sdqZwua5G@3dpzEJy|!U4UE>$>{wHz5tm3Rs5KkQhl20 zPvG`obj&LudU+Z9w$6L(zduJpf4fnb?g!+*Hf)oXe*M;RtASw}IXtDaj+ zmgY6sNNpy3W+z51^s(_4!EEtz#-xv?-E|V;jj5SRXQmObfHUx$#2#H~(cyrYlKPMHQhff#Zcals(Vwl`KQxYMQ z19rbwXDR7})VI?`u#z8b#9&%#o!@6EKZT&CRLB)?`6+N+wm$+ ze8{ta5vjmGk6l9_yVql$kh;0)zUx(l*yPh{Zc}1ZVxDO_35LyWcfUR?B~MrgOHrGj zf_g-tuCbxFQ}%%w?{5Ekl-haI33zp6tx08D{!VsdZD%iA;8w9x`_LOu&M-cCcKJi= zjPf8MOh*`_s{3)Xa#gMPS-kk+o#7VUlzwi~o;6}H7~b-N?XT~oIAq$O%{R8HYO3?a zQs-30{zBpJrF-{H^8q`Oua2!Icb}ypALS3(UT0?_V zQBITjt%};z4;-tIr2@*~damGVg6tB)IG9XI&TOa6)qm1*BjS9y^jy*f-GC#aBPMi% z^$BqzUOH00F@t$p)hcB!hIwBnEMd}()P;~pM;bgJo0YW3V+}k)k)C}sd?Wk*pqEIu zdr%zD9|`03=UHhfoqKv&f^Ii>d0woWP#Q`I$hJ=2^I8hhZW*)>iqEtQ?uuC@Z`bcQY)CH?4wq4^fth@l2n z4k90UZ}QSiw25GM9L@K-r8sdw<}K@8CZbe-+DP8qGBb>bJh%^!c?X{%AxiY8;~e74 ztC@8W^q6KlF3HVscWFQWZlfL*LAklqytyAX<>G1b_zFkEW2~c2SjnFL$hx{rP# z!1j>0AzxJajSIqUkLZt*zX;q16}Vpp5>;V|9a0ie3I8y;8$O eKie0})y++aaI9DMjX2rI0U%pBk^~m#ll}*K7XrHg literal 0 HcmV?d00001 diff --git a/src/icons/apple-touch-icon-b-light.png b/src/icons/apple-touch-icon-b-light.png new file mode 100644 index 0000000000000000000000000000000000000000..ac5ec2853a0a5f4593e28822b27b73b48d4fb673 GIT binary patch literal 1616 zcmZvddpOg39LGIcBc3sr$YnA@&P**T)MyL|%LuDT%gHtDgf1i+X+kBJG{bCeU0hZp z6i%B>HO5`JWuC2e|$gR=lQ%pm+v2+&+D7whI53-Y061SNkN?U zV%>q8y?SK9fJJwMw@FEX)t#`I17~P6qUMu>4$y>;C2eMSgy;0vP1B6a7v;N)okGxA z;^L=ia_YCIar+t1JYQ||bayk#&|MLUbgCaLO7`U?Z0(&`z<@5KtIm(K+kDG@Wg}*b zUWf0SIUL;%N>im?xHC<;R)o=?z6*nH+xKMMu3$UcHf48C(`8XYE-Ncb$RHaAm>_HG z59;S%BR-}XJQ+=LlnsS6( z%|rNoU6zAyl~)^hUEg*5X0O$1b+&ZHWJlIB$6xAK?3v29evRt-?#&*>d6(W-0A2;E z_`SJ#5Y{w%%6L!*u*(wP-dYPso*d5qT#QB65$h;FXTegPjuk&Rsi$Y?NJG5Mq6)%e z`SEU0KHQKaS(#mY@Iq3;vYGdPno*8DSGG)C%1EZGWYLPT`IqSWNS2ANI0^(KKHYAL zFN43kDYPK!i_d`2TxxTiuGj*Ee$1)rrNmg1b%^lsrmFn{u53&~N$wg(fddb1(xwev zuprM?Ye((@gfCbKN=A4!0tu3omgXjsXfBhAAY^pgD|>-sw~TLW*WI$7?XajBlnho< z)K7100kZL#nAlWHT`@TElV&6@M&P=320cz6E6-cHHm!*ovN;m3^W|aLC2r_s)li4c z8xL1d{9aFAFSVgzUD!t) z7L96;89LwXBN>&O zz3U;3HhGs7=M<1w>#~Hc?g^ckI%=4SnVX(I$u7OqXDkVxS@UNdi5Z0)iUt6PqYF_zz zb6&#Qoo}D!7k2vcR8ceu%|@L&W&kTB-10HN=TZ9Q`JwN8oX=aZ16|QImDaHk)D#I_ zv}U&Rk5P0$h67pc?K3B2i%G8=Pj6}^f$JG>P>Iy|9Mj!uGJFmq>g#}bA4)%1%%au=RqR8ouFlR8a+i8h~yMiC=zSkf>cgJAcaFaIIP|DdIQAUtAc=tdBo z_^?s5^$TUk56DQ5%C6F?BbMh9ED*DO4b6Ql<*CN$15tU$ZMoU0^qalq_I`I7 zRjB1eksCA7S%H!4=^u74IT^vkn3WK_4`gPpYOORi=CLtc3Bj(*E&Y(4Sp|#za-6YW zE=AMXDHw^s<>+pP2L|1CIm}50r+<@p-cU?RSEY8oWK0pI9Sg5d5WlS<;FwM24s1K% Pr;u{8$6+gNkCOie+5ZgY literal 0 HcmV?d00001 diff --git a/src/icons/apple-touch-icon-e-dark.png b/src/icons/apple-touch-icon-e-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..25077bb62fae9b4b827d6d2dae51ac34b3af49b8 GIT binary patch literal 1828 zcmZ9NX*e6$8prikNpvE$R7(w$2`U{XQgm*Rj)+!bi6s?FrzL`5MBCV_q74#7%iIYK zWu&#$Qez2jZN0U{Qktf!orJDvk=R8o<)&ute7GOZInR0C^S&S6|L^^u>uz`lkcy6q zf`S4Fhqd>RpS&*%pd{Z>LrTXL6qIIh_UJQl1=4Xugcz%N^=)0912*R*qW0=Fg+*;) zO3&!bc*|n$*|N+d&C}8UC|lHSvsKvJJ1|X{R&7m1`2^d06y!N94c*?Us&6tdn%_M}A!Hzo zRMh+G8mErU9=*#j3N|rqdVqr#X5V?lHtc%}ba^ZYaLg!1Ynbzpqk$_p2=@W z7S50mj-9}4GG*1=gk;yH$zZ}1K?HzK*#QgZb*dCpJ13dhHro1QPWJBd4Qj<6xOk`p zK5>9FNbW}@!w~#2)n{tF+LzetzFKH`wierpnkS_Fng3_$RPMgg4)@L@CbYQOvsiJ& zYwHhVtA*l{SIwa(lAJDhQR8N<34WOfcvp0Jq&%z3 zD{m&bNi%sWNa#@s5_$BUBkr6hcpi!}Rc_0DNV!+Z{rQSxjj7}qqejY=RRrmCR9kYB z4|i=eu=}~d_=3sL#m7Tx+|HhUyuGnKxB3VXzbs|7xQgznSbf-hQ~hJkaHxr-BO8<7 zNz{lMF1INlA9Dh*I@uhQse2W`YVvk+#7P@!Av7?QZd>qqm9j#CTH2qJumXYcdi00A z&J-8oj?Uf=FurJnW4Q(OMupg_e`(^b1VnKDeDbqWYV)mD*Ppx?bq+!lM&BSYsknaZS_()x&S{Uto9EOR~?Dp4f-Hu>*djBW4p zm=qLxfc`G5)tTCwxR4Z)KNLTa2yA6_)1T5k$#q9l$y<-Y#8ShfS<#>O{-DkCN_c6R zD#=q7Rhde3FtSR3s6j7g2$JBrPnt@)MPC4}CwVGKqr2v|V_|@Gx98{tmxgr@;qjDg zmfA`Na`XkoYt4^EZ*Fb>ba*o^<%%v^*`QNzKeBCzT?>r2V(4Y-$k?1NG5C~A-y&cm zjZ;e|&%J_Ywy;;*PAHt3bfwkD*!F1V_ zIO|}OJ$@Q4E<87`zZD*vP}iB8Nm#Xt^6Ps$?+9+SNQAH+KGSTNtdm zX2pYO{%OaN<%}C=oJNi@luD}gqGotOj!%w8i^4d$nQJeajS8E0 z0?O|*rzHjT8NZgbHH{sLj5~5evj8f)ffgo2^S21jh~$> zGSxrxWMk=W)-<2mq2Hwn2!k$e7BK6Uf!z$KO`@LeC4^{uVPmOmqBKt+` zg<_0^Zk&_7e9^<0JAd$(5x%XS5szCiAM@S zrJy~uPs}{I-%J|i@QsC_%yDoMzH&1AM%CD+jBmdr=akt`RC)K-5 zBAHsxWWtKk=B0Or>Ws4RA$OTt1mDc`9M?252!XK-0}b(dg#ouSPQnbrObkmKtN-c4 z@K;4*2!SH?69!nD&5ZB$0Y_j4x9S>dSU}yxDI4%6csyN~_MI^{T>kqh;7;M~YwY|P F{{;=_cXa>& literal 0 HcmV?d00001 diff --git a/src/icons/apple-touch-icon-e-light.png b/src/icons/apple-touch-icon-e-light.png new file mode 100644 index 0000000000000000000000000000000000000000..27037529cbd1fc8cda67268d867ebece773c1cdb GIT binary patch literal 1826 zcmY+Fc{tnI8i3s)NLmv?DWf7+WBk+>+cdFFEJ17?VJOu$)<~)pjje_uN;|f;Ce+A? zc0#EsDysH7QEgF?+uAC%bfiQrHK{P9m}u|J^V~nqInVjy`_6OD_kQoWa>CnH38Dp& zk&#hycXRfYuI&AxC@-DuhUCpN7}= z`E5-N&DQ6j_`*|PpSJ^SZ*xF~?F0tit8mtkyl4p2`_Cd}LX+T`t+9Iz@kg)A%J#rZ zb)QzWX{JjyhIr$^L zsmzgQmct|tXf8zsa%h(UIVFOkR<*LFCxTQa8l3#%?38$FzGy9Kvo&{Se1fhOH+fz^ z9g(XTtm9Fr>3Ib`Pc18C=Jet4Blq{?*vygW!4kUD$D|N!uUeKgrLlpyKxO*uwcg+v z$MVR{$LIy?0B8?ux;17q!#y@2CV9z*a3&vB^Sb>-TQoH`4+F(U9X?Zxu&suiG{_it$+O?7B2~hapb)A3248H z$oBLG2s)}$*t=5frvY1xVxIK_XiAM4;qm5gFlrVs9DiyM zp*u&m;ry%{;c%fPq`La0-L^yP%C5Pnn>OVX+C%E8Ynsr=3H$IHxqM2=RA*^h;k-Gi zVWW`(d1-Nk9S}TWb$Qtz#*VUEE_d3fgG?UiHQAX}746j>oLss*we!wbS=3aQy)jAM zYx*TZ5dqZ`MkUlYG`zE3;k{dpg?cT|!9>w=M~W5Gj09`e1dhI7Q8D#v@&qCD^H>Kp zE5s8$p=v0nu%Ioi$Ly`|*!xN4b>|w6w{*WJ;XC#Eah-VAwmH+M>^>O&F|i8=A!>qm z^a5FA{>~FLM{nL%j^R$mq_ugJPQpHN`w8j4>0e`U-2J3yagEB|_A}|YjBt#oZ zrTJi%dWjYiHHaV)n=Ys{8=1fv-xv99TjU%sU`CA;(0!Oj*+_-`R`|b^hI{a14fdT5 z+b=hLDg!1|(5%V_9o>BGo#ow)Mh-ona8ak-RIrxB$_**+F7BO)h6E2?4>OuC;Ui`O zp8>7GSy!21ddRn1eYiR#`4Cpt_gg6D;-ooi;RYtg%5!lvTmI-jH;E842{Q)!1Q>Jz z$<#kZ*3mQMBjg73pUPWo4GUfrjcF$n7H()bp}Mq>*o-B54>2;kQq z#JDbhA9R(@Y*~c^Ey6;8 zK!1-yMEaoeipRBXmdgstCSZ%UYWOB6Y=t5qC}Nma=ja+Ql-Dz>8cPfFiszKq!IbW} z@D$kat3LMx{7pI#Pz#I4WOTiHx?9B7UKi$n0DtEx~ zNP%sqff_FfcT|ij&P{@VSe^?NlGlg-r>al(lUnRuE$`1Ab=JT4OV7dYcppEem?}@u zLq?`_itU4xk z;`1{pW#yf-5*s>&&bml0m{;Cb!<;bdqh!|bY@${^jnE+Y;0|h zVzb(}=R~$$73hfwIB$LU!s{~bb&^LfIM?y45})_5jPcC+8zzSjrN%n7qTN;X|@keANQotQa^=fk(MtxrGL*UV@Hg2Vqm zB^fb>#>N!4;0CLUq=KU25s!6goSceo42$QQ)W6%eu?Of-22WQ% Jmvv4FO#o9ePm}-v literal 0 HcmV?d00001 diff --git a/src/icons/favicon-16-e-dark.png b/src/icons/favicon-16-e-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8db958e5ce5c4ce76cd0c59d5ee37b51b8369405 GIT binary patch literal 304 zcmV-00nh%4P)?5DJCn3@*(Hy1RC%qoboPIYdtITs%VsK`6%VPPu~)HYF+5N`45B zg!ldL&tm{EpRZmBkTDHop|I6#sv$1xW3YjRf0=j&wvk9>L@LvgR{(HaKNOM50H94H z&#d2pJH>)Sx$xrpth@xyv^$a7bfzbJ06^~#cNg{&bkT1@6p4k03AE%nk;;T9E~7gL zk-x;tl;2+J5X;>(1OvB#aV{Xj=L^72C0000l+b7EK-%EH>49v4Oy2CYga0G8T{% zG8+Pb>1vsoU3ZXn5pbv!K7D+yvk8)JM`rVSG4&Dv&~)7~g|i7RqyKupn+rUgz}d*m zhP>bHZ|THqe~R%9t;egu|yNCYgGzsU8^%#O^rJP9>b>q0000< KMNUMnLSTYXGJR73 literal 0 HcmV?d00001 diff --git a/src/icons/favicon-32-b-dark.png b/src/icons/favicon-32-b-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b78d0fe79e2208597f925043f35e44f886b8a2e3 GIT binary patch literal 363 zcmV-x0hIoUP)M2N1tCgz^ShWA-4HoPy9OaaHi+$pobp9^HW^K=3R9wHGWQ1dp& z*Qf$YHMtN{*Ssx+kXosRBta4|aQu!c;Vm!(50UT^u)(SE^;SFt$E*K#*jjVP!Z`xj z?(Bs3pUqUtwm+?o{d}C0Rpq;Vd3n$Tj6}^_=Li6NPD56e+6Mnh z0C)I{HQ;p~FeQdNSE?z5kVl;3Qz>3$D1_9uTBnpLX(t?#pzaYtM>3_Fe1BT$p+y=+ zU~CckM9}BhK&fzm`BXd$05JTc7(2S|nlN)S1sniPK}-qEdI3Imv#1b)PW}J@002ov JPDHLkV1lAwmCpbG literal 0 HcmV?d00001 diff --git a/src/icons/favicon-32-b-light.png b/src/icons/favicon-32-b-light.png new file mode 100644 index 0000000000000000000000000000000000000000..64188c6a7bd08597c82deef73a6d01e98074f572 GIT binary patch literal 332 zcmV-S0ki&zP)yE5Qa-tEht{55>={kB%3WDFXX)pP|!2P8Z z3~5~-FP1@dT@`lDbDO(fJh!Wtx_K6!>>$RRoToaR&s2FNJLn!N% zn37IH9|ZM`NV<|K)kx6-_?mR9lD>wb1;Ar=0NeRE0kJx7#U}vAKZ?0k*HyvoWDj%3 e&@0FZfxX_&qQ`*0GmT~d0000Zq}6o!9LkB}*Qic~35Bsc?0d;<2YEOhLcFqkU>I3U$>6k`utY}OyD8&dI@-OCV81{&x}R9x7TQq zpAu^Uy|KzPXf0sMaW%ixXp?AdLWat)IuNOiEu2Vg%I)r8f&;*lsfY}#P>3kuf&d^y z^xg-~^N5s>a}8?&JzBwwh!B5GYbC&EUT${TsRRA!q zfblk5XXUoyrYMpo-g}N~)I( z2oKDF>_p0K{Cj%m*fail&~i9@e4>Ly(a_^{ziaq8%kLU~F5&e7*W+I=`Q-v>9xQtk zVE8=}&H_YYq{?Is0IE#ZL}KL9$9fVnNXqK{(y>eXA0l7^yVTtzkmkXXFSG5LvB)+I zUyiHI!dgI|SVg8<8<;YfO}~4zNwiKvhN?2z6N$MkoJh>aO|v(_0pQ8hM3u?2l+}V8 z0)Uj&>n(7u8-4-RX`6nG>OEtqvvnh8y zh#2Y$2mb#H0AK%6>^f02^n70F0}g0I(4~aV`T~&^+k$s|JVO8g002ovPDHLkV1jun B%+>$^ literal 0 HcmV?d00001 diff --git a/src/icons/favicon-animated.svg b/src/icons/favicon-animated.svg new file mode 100644 index 0000000..75b2e4b --- /dev/null +++ b/src/icons/favicon-animated.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/src/icons/favicon.svg b/src/icons/favicon.svg new file mode 100644 index 0000000..9b3b932 --- /dev/null +++ b/src/icons/favicon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-128-b-dark.png b/src/icons/icon-128-b-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..15b979098eaeb7cd7e59fc56dfaafc3597a3765d GIT binary patch literal 1186 zcmV;T1YP@yP) zF;W{b6o!9$as;Y0R2gPcrJ(fRo&h6e>u_-E(gaCn6G$JgttI~EBj`T~aG2mW@wUSIv1dY%{DSAdid87hHm0dkCB?-IBb zAZPjZEP+b_=6LV+DuFuz=A5{Bl)#k$C1&m3Byb}@$t9a730w$JW4-Q00`meeY}U+) zX#r}7@1Y8q6`*$bJ=P^KDL^aX_gI#|oB*wb-(yt*Qv$Raevd^7%-jyn9sb45{rT>( z$J@)N9M9dls6$K$kn;r{1Ro0f9LOVr6$zRbAlC~S34WI>B3O{1ssOPT?MmVI%p$?x zZ=d%b^X1QT%D+gW+9WX6qCLatnHpr4`M_{u7z+Q`@e)+20Am_2bA2@Xb@Zo%d7fiV zFec<0m7sV9U~JJIJHl&Jf}#SHZqd##`T^i6v}7F1)KE)HU}_^sew*YdA3Q)R5`*r!2S_P7pfm4@urI&)m zD*$7Q_SjG<_5px+xH((26RSX$O`xvZtYYET+77NZ2dL1ZJyn7%6UgC~_Mwn#-V4y( zz%aTS*b@3Yz`S!fyM9v zg8~5UNnka6z^DK~I}%t9A22Kc@Ge0u5v+#~7#9F|k)TF3@DM)WK!Bc-5_kz8a3law zMgmXa1I`2h=9R!(_<%zJfO#bF7(U=s03fdfUc(0*3jpMiz;pP3a{+)f3A~377`q#o z!fu$QI+I(+eFf+lR)Pwh$<0Dj0eVVyCR>1}0sx$y$uVdI(2qxlp%FlYkpSdK0CFS% zIWz+3$D_m02mo+)CdZ%>02tSq%mkePK)f@V3A(Ld@e#@jAl{JxD!{`q zYgGVpJ_w*H;Gf-$_bPxS@JlG$;9Gzs@W-J{56%H3fgg@#d2|ve3G5TgFuDqm1a=vd z-K22ULtuj`gZ8A+EfLz}lxm$^-X%j_Qs1Kg17u5o--%BFJOBUy07*qoM6N<$g0N`> AC;$Ke literal 0 HcmV?d00001 diff --git a/src/icons/icon-128-b-light.png b/src/icons/icon-128-b-light.png new file mode 100644 index 0000000000000000000000000000000000000000..0af1d2857b7c6561bf4bf114e8f00b5c02033408 GIT binary patch literal 1186 zcmV;T1YP@yP) zy;9pi6orpHnF%da?syWHu2iI8CJm{1DC(ri45cg5@g#Iqsi8^=5D0s%)z4~o?>*-$ zg=I88-(K&oeC-ux^7Qog-`wXA)7fk;R&&mubh1LPlPNNcIzhqPV2lWZ9;o1LGDL)R z>;VejE<>{G6+q$JCzjz!0TjL;`mziPpz!^1D$^qY6uw_V*{)*`5WYCfIJycDzIhiw zqo0Rv)~W#HtO1RFM%v6(0mzX6h|pD*xk)@fh#EsWd#U@-*?go6Cy(;a4kTJ5$s(8w*sUr-<~CK zDZmo%-CiYdC%}>uH;)py5+KK{-J1k%1jxB$^CW=_0ZOdby+~kQ0EW$)B{3~P>F_;N z0kZ;>4!^~^1SSQjCHxl45||U9w(wi5N?=NWTElO#D1n*V!KuT)x;?u*Ja%{U{Fvgo zYZtYP2?0{Rpq=1DVP67iM6e=3^#Y`NAtS*bl0*ax5)>66)}mb~{FX%|`1<4f(PLhI z{+{zMW>IVs7;Dj9VDyX)GRb_PJ24D}-*-F*MJm9M#>-S6&3+sGbHcn#F(()ja*0Zi zy#g?{Xb&CXB`QHy0dlu!Cm8(z@EBS$4rOepxg{{Q5hT)6{g*^F&F*^Yxo6+f%^{~= z$Di}_V9!A4(ri~$7-C}a-PIesyXZS;nMta8tHu&#VGTHi4REw~d8cYCE{t9H2mp_E-s$Odz{k+NVM;c`rbB z1H{e9fyM9v zg8~5cNnka6z^DK~JrYgb1I`2hmX*L;_<%zJfMq1`7(U=s03fXdUc(0*3jm~%z;pP3a{+*P5_k_EFm^X^ z4u@gp>P)U3_Z6U}TL}tuCRYnh1!&3HnQQ@?3IK3+CdZ%=KtCQGhDHDpMgou{0mzX6 z146HwclU|7c>n+a07*qoM6N<$f>g}< A-2eap literal 0 HcmV?d00001 diff --git a/src/icons/icon-128-e-dark.png b/src/icons/icon-128-e-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f7f81149405c00fd8c81244d7d0103764ab209fa GIT binary patch literal 1350 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 literal 0 HcmV?d00001 diff --git a/src/icons/icon-192-b-dark.png b/src/icons/icon-192-b-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7668a2779bddf6275ec33505dcdedcdb2ef3c0d3 GIT binary patch literal 1813 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 J&|nup`zJm@f)M}! literal 0 HcmV?d00001 diff --git a/src/icons/icon-192-e-light.png b/src/icons/icon-192-e-light.png new file mode 100644 index 0000000000000000000000000000000000000000..17df65f465601f098a85fdc8c574582a725ac4dd GIT binary patch literal 1974 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 literal 0 HcmV?d00001 diff --git a/src/icons/icon-256-b-light.png b/src/icons/icon-256-b-light.png new file mode 100644 index 0000000000000000000000000000000000000000..e420b6d489c83c9a982a977b98d3c1e842c9db80 GIT binary patch literal 2553 zcma)8c~nzZ8o%%56<-ufmW~Z;q6n0!2ozjUOrop`A_W8s3`Vv(3St2Rge4C>r(nxw zVTvu%&@xb;XJNh~zcRwY%CO#+%$P>eJJAx&PAd01!q&zw2)*ZubMedm6^`@6@( z!-AGCwOI-PSiU{jKLP-V5FvnC5J!CG*;D|?((V3RqjFEF$C@+kVr*oOl48zB_Xj45 zX|3yM9)|}$TcZuI&bS$E%|Lx_$Iw!1!rgv^2RAzAUH|ZmAzPHyJ(IVeZ@TC^lMz;# z@-*OS?_jeKAJF?q6AoqUmFzd#?QlIkTbPoy}fFL(%H;-RF)!>MF5=$-J4&adk7gtd#0djdhIDoYR!g>*+SB$IO^i_vtsUKi@zBYm2(ANs9 zs>@{T7SSms#E6Tnay{ahefH)gDu!LNIj5030%&YpBF@LNb7E%MOwQOCp`H_nuDF03 zc9mD6k&2lhB$0}7!^*SNt;T+J0P9T%ecb;J(}68?P02&vY}@>>Pb^RJE%FQoSt?G- z(+rF2+K;A0!1B@pjO+7?a97?eU<;nwX%*AL~ z0NAwdvChh$X9p|3J|P#f;3Num*PlW7=5~Qiw7(O1iVe_|f3JpF%9S7uq*p+vPB;B-`ax6EwIT&mp)_{sRL zg|6c4L`Gs@xP+-;>9QuBxITq{;at8mH!?$%M&qWGKxAyHF{h7>tqpO=cXlWKd zSUEL+(03EG=iK1iPrlIfmFKBKb>9#H16c?E} z|AY0WTcZy;ue5(Iu|NCnJb7ZQr0$kkt1+s65!$BVSMkGU$9lx1nGx<(4w_A~<2(8e z_&$X_Ht1dDo{VHX?#>5KMth^Q?aWmPJ5o=SHq`G_cb%~FYsSJ5_}@fXinJu zd)6&l@oyri-^Sc3wn7)?(sJD@o{Tdk^9yLrr91l@h0YW`((;3@t#kZck>CBCvB_AD zs7cw{C_=us;7y;g$l!bMY>Do)iV8#CTnkGHtO z{&+(nPRQ@OleSra_S?IfMAE4j#A;qzNK$sDo7~W9tx14upA9tUvlF~uLw+q3Ey&e% z8mMz)B~L{o_eWmrt!!Z4RsTS|5R0|DV;{%7{9<{~qP2O=L z__~k2&v;UM+VQTpDT0QFD}0K)6ua@%1}tI;uRNK`5a6L9N66ukm9->#BQ9RUxhCpt z(#RhnEfLM`H@!Hbj6>4uwrLGX;?VuTk&Z1D6z+R~udJ6bt3lMg*=5-~(O4@O+A)Vfp~v@w*~xJDaIpK3ia9a-*u}Qq>)v2ylxao7B?mK~=P#33K|>gN zF$p(NvGP*sdoqbTG*pGg%XJGbz?NS86GLbVw~(kOKg89jG|Yd(Ta^(4_^aVv)BdU__^zgSBylW<@~u_e{R#3plHRs~6Q z1}9*DoB^q3>Oi)01Q9JH-V~Gw@I<-a23BC2N@&a+|5;Bdn86L4So?(bsf^TpH9oOyS!7 zSJEneKHsG`?flbwBdyRk2d@lRB%XiDm;~|c&cC>Lw@CECD>7y+K-&17GG-lE-ICoa zV~Utdzf{~CxAgZV%8QXd*y&FydPS@M=B59o)NDk4ngrnC>9BNvk#0CfF8O9Hon1K z6Iu6_FH{P8J=#)g31b>K1@qD-*B59J7VL2*EGGRF8+nCKG8}A2bDE3h3J$wNN*aOcqVy+0_|^0!#|K-G&4r~ty^Z#xe@94h(3vQ5P literal 0 HcmV?d00001 diff --git a/src/icons/icon-256-e-dark.png b/src/icons/icon-256-e-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..bd195deea0baf02d5923c99d2e68931e7d32b2d4 GIT binary patch literal 2628 zcmZuzdpy)>7k_>~Ga4iJp|X~#bs4fzLS=Vm#HomG?T@d!D^L$*WBt^3qYHrC)RUF6Xt4I-3lZ zHw-&c#>ZYX&*u6z|FJ#dVd1VzzezUeu^CWc@NX?QV>@;HAKy1WK@eSU72lq&)Une_ z*BqBmH)T8e#pIlo%#^YE6W7*cx3sve3ng8y5;nioPiAwyL$%v~q&~LeL{`tBS?v6U%Qs8!asHnyg&+u)n3lSXHcmIc9r%RoOziCEC)~ z=D5IR$!$Zj;E-YDQo2+)#TY|TPlZGJdIZyEaZY%O`b9XTXNpLFN(vAaFap275>H@U z?$fn-L2kj36;7(ih?E|Ew0o^r63b1a-U0GFIZk9L{;cC;j4j&MyE{;~{1#m9SihR_ zX%DbDRbQGgC`$+}175hJg`~?TiuAEGv=_$$$63O$lTdO?N{v3c25<}>)5}Ip88?pe z-e(-w^~r_vuf7b^rPQoNyXOn?N-*zA09Hd9w>WL5hIVHp$hNsCAm&Q^j^4}xgk z!r_^r=&40JZE3=_@9{3s)7f9vCGi`IU$A$MOjArRsH5F~udHrivVjoD8{eJlqTOXG&vtxU>>EnYqkEl-twwY zX&qpC6e>G%}Xu{GjN?dUlkyz0o`3x(!q6qsQ4+^KLaflfpc;yprUu1Ku`sSSbQoI^;e<&a|1iYa(W_yLgfnJ>zyokee@ z8X>X=_+dvG#yN#9Zd_iKso;l-(NPCZhCQ@>+()A|UM%U~DWA&9o0d1mM=dWGckDig zBfmM5N}HMy`!ml;g)dWw_-IywD}5^_;LFdy89_V|7QkO=s*?7L#Q70P9$3CE#$Q(vSCC(efi} zLn$?p6|@mc3(L;|fn2>3)J+RU#YQhU&Q&EUSu>;=IA8KsdP_BH{Rjr&@*)h%_J80C z#UWqc0{G^afVK-E(2qR!*WG;T3>4!LNcQGUMfkH88&~A=*I`=g5H#F9`dSVItv0c>H|N-y+T5!BT3A} z`eni&n&62^F5#J^b5p2|XhJZZb~} zir1+32ff|hn(blGJHZl}nao;?wqytu=GV7$zpn}6D>c^3evRR0Rm+=90uFge;|L5Z z1T!jrvw3IDdkKtdIF9t*(pJY^u>`Whqu6-$gEJHeR6Dxk&rlqBbOXLJJ|CRpD0F!Il9`m;iNlo zQ{Am8voU3W{o3|dt2xAj za1tNsXq|}~*%*2x3xy&nS|}*qcO)So>xxMU?)rU^cZ0QNl9`Fl71 zgu$;HTb5&L7cPR_*jVvcJL=!hPpx^X%bzy61~xD2Zv9X5{|xM$2;%)|HgOBCQDF=$ zSgj5NZJPlVJc=OeT@V4fGUAyV4&iIF&@?QgftNRO&IA~NaNX-X zd*9W~hMR}_5pL(K9{Ak!h(OWE&OwVRgoFMrCd$^ZZW literal 0 HcmV?d00001 diff --git a/src/icons/icon-256-e-light.png b/src/icons/icon-256-e-light.png new file mode 100644 index 0000000000000000000000000000000000000000..c3acafcb6499b9ef83e5069969e0e18b7f3ea203 GIT binary patch literal 2617 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 B*y{iQ literal 0 HcmV?d00001 diff --git a/src/icons/icon-512-b-light.png b/src/icons/icon-512-b-light.png new file mode 100644 index 0000000000000000000000000000000000000000..eac0a45c82fba5aef8c714a681ddf7ffa0df61d3 GIT binary patch literal 5548 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 JON#{i{|CLa+amw~ literal 0 HcmV?d00001 diff --git a/src/icons/icon-512-e-dark.png b/src/icons/icon-512-e-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..44186c2de5456aa1373d31e4adc38b55dbd83b2b GIT binary patch literal 5837 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 literal 0 HcmV?d00001 diff --git a/src/icons/icon-512-e-light.png b/src/icons/icon-512-e-light.png new file mode 100644 index 0000000000000000000000000000000000000000..d33f4a5f4258791dae446e1ded17d3596eb8a322 GIT binary patch literal 5839 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 literal 0 HcmV?d00001 diff --git a/src/icons/icon-64-b-dark.png b/src/icons/icon-64-b-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..7336cee3a8fab8b1929744afd89b8f76b6f5f7c8 GIT binary patch literal 626 zcmV-&0*(ENP)Ig8%mM%JX-@wQ&ZyApQ`v7PytW}K&=XZIsj^2Q4W6l_;G%H+;2%@Ae#YG^Sbv~ zA^~7BO97$yHJ}2Z7}T`jQ7nWc0EFmU)$DP<1tIztI~H^RLi8=?-2m`s`t{+olQ1~% z25bnZ0H)?m!iInbV2ODtOb93d)|i*Vf{+}5k9jE!2uT5in8#8w0I|%co&`w&#J=(= zAw(~Vt}(wh9Zqpe1p!bEEDg|>^|?UEF~07v&uurK-%G;P${T>r#L~Td2mkljzR>0U=&0|4fMV&(Hf^sQ=^W9z>cwe7Ak^=UhDF|JAnf~gI;ng`H(W3B^olj@O~I&#+3D^nXv zk@P%U14>g<;?n#9$3@e+oQ@=an|e>lO&f=UvCR z`c3yb>KXtr4-_k(=e+M!vn1aFCf(aUAo@;l&8|%?h}e`WuSl{Lj=gnFnN%f9h9rbg z5UDfDr0K8_!x%CfFw$w1ijWTktXcwEO>hka%t8ukRd@*m>?#t;LsHVDZ#o@QWKW5O zq|-4~e;!94@t>&zpcu6HpT`08=`@Avv%=H?P^$u<4uDz}0CfP=VzxgO=B~EgHFX96 z*#BCZtE6Y9Hk6>LS7z$SRjS9?8jv#toj0r5T=E3lMg0X1_)?;Ri&$6y0000>9l<__-CX8@@ZQl*r=6YtOtm8p9; zAu;DW)$wo2Kk$8D&gOCo18874y!mQ6|6e#9-UoH(tS3Sdjr$;7W4vbh%mU?W)LD5;2HnfGOU#&qeR2 zKs11RK&8r=QsoTK`KriPN|iJ2cv$NIrOH{JFSh5d0RT969ThAFxQJ?F76sl7xI_p? zHh43DGI*axkQ?ALSRd1Z)BuioOKq@|0l0+aUbz8&&E8K($5^i4K0BAwoei*+tWtHn zDPGgngjfkm2Ov#n18Pdxd!@>Iy0DtjaKso?vL=31)bMEv95X=B9Rzf_0@D!+uY`(tzc$N|m z5Z3@V1NPGqq>m4i{d7bfPiNBQ23D`*ZMxhzeZI2+ShS=GShR}?aSBKWAWdfjuHQaS z_LVAkN|igZs9K;@xvPro&oR8;2B&8MY=Ul#*qS8NXruKwE}~7S8zK`NASFHwCVv|A zkqtgg_4YVSkqthDYkYTvj@e-Dw}E3l0pRNhwVoCncin{jO;J;zS-Dk3#&*|D#9o!P zm93Zlq`43)LFoXbk?aUH?sE}emzNGenrJ}Np9(|gaCjfY3;<~UYiX!x%?Uox2TZL= t-HA}a8c)`MkWH{@YZMzwk<@fkzX7n?Y&zIbsoeko002ovPDHLkV1jW|Kr{dV literal 0 HcmV?d00001 diff --git a/src/icons/icon-64-e-light.png b/src/icons/icon-64-e-light.png new file mode 100644 index 0000000000000000000000000000000000000000..86787cd5444de2d10ad58be7febb0063549a1577 GIT binary patch literal 707 zcmV;!0zCbRP)H=C@|MDw`XP7}PSr7Vp}ZV)RoW z86Z6%VzERlmUPZnWpjgAET!WStpgB?WnJ%Hk6i-*aO^rRL=5l|)yFIjf*Wv(5Q%IE zW&mgKA&p=+AY`y1rUk1367!bZU@rq`2`jyF1N@pj-K>9N`M7@=Tuu)*z*Vwp)$z7? z!%!1aC8!;MHiHdlDPgY>i)+5Hx-sL?bQf6ztRVJvy<5=0008QGcjxn+J;ro?|0%yd ztPK$p7`G82jSbQf;xZz1MgatFi5)Xywasg@U6l=*Z3*E9k)EK-2wObBw;9dT#iM<_ z1foq*BaDhSHzHkv{5mjEzPCVA3!k!s8|j00LoLKDjRH`E*`bS1Ee*; z&w%M>4bSK2i|J;~9nWA^+Z^)Cm)F%c_xk)`1E^?C6R2n(6VepW4nUj127KH<9PA?& zd&FYT7F7=ri+xo#e~!`pK6pI~P!mjJ#MLC>#u#nJ@eysq-4I#O067U^F#FSBh-?UH zYOu#)j%)}i+z`4W^pg$tejg;(69Bo6Q159Wao26c-xRe4+Lc>XHez?(M(tHuSJ`^% zPnr*@64VYr8_kYT=e`u_b$RUov`GeZ{i!f^=JVMkWdK0;UrS@fXwK+?F<@)W+D^m@ p)p)Q5#B72~Tc_AqilnZa`UR?2jBCMB-Pr&D002ovPDHLkV1i6EKllIu literal 0 HcmV?d00001 diff --git a/src/icons/icon-b-dark-animated.svg b/src/icons/icon-b-dark-animated.svg new file mode 100644 index 0000000..072d040 --- /dev/null +++ b/src/icons/icon-b-dark-animated.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-b-dark.svg b/src/icons/icon-b-dark.svg new file mode 100644 index 0000000..fecb325 --- /dev/null +++ b/src/icons/icon-b-dark.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-b-light-animated.svg b/src/icons/icon-b-light-animated.svg new file mode 100644 index 0000000..a737dcc --- /dev/null +++ b/src/icons/icon-b-light-animated.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-b-light.svg b/src/icons/icon-b-light.svg new file mode 100644 index 0000000..484622f --- /dev/null +++ b/src/icons/icon-b-light.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-e-dark-animated.svg b/src/icons/icon-e-dark-animated.svg new file mode 100644 index 0000000..257e265 --- /dev/null +++ b/src/icons/icon-e-dark-animated.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-e-dark.svg b/src/icons/icon-e-dark.svg new file mode 100644 index 0000000..ac7902f --- /dev/null +++ b/src/icons/icon-e-dark.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-e-light-animated.svg b/src/icons/icon-e-light-animated.svg new file mode 100644 index 0000000..9303228 --- /dev/null +++ b/src/icons/icon-e-light-animated.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-e-light.svg b/src/icons/icon-e-light.svg new file mode 100644 index 0000000..897906c --- /dev/null +++ b/src/icons/icon-e-light.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-morph-dark-animated.svg b/src/icons/icon-morph-dark-animated.svg new file mode 100644 index 0000000..3cae65c --- /dev/null +++ b/src/icons/icon-morph-dark-animated.svg @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/icon-morph-light-animated.svg b/src/icons/icon-morph-light-animated.svg new file mode 100644 index 0000000..ec26d79 --- /dev/null +++ b/src/icons/icon-morph-light-animated.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/preview-sheet.png b/src/icons/preview-sheet.png new file mode 100644 index 0000000000000000000000000000000000000000..e0d57bc6d9c831331698d407d798ec068110f741 GIT binary patch literal 15331 zcmd_RcT`hr+wL2w(wiDU5W$6rNDz@OpcGLsR1pP2k);Ts3LyxDA|SdVpmeE%w16~~ z79fBKsB{QL0tBQ7Lg+07&J1h4-?!hrzp>9gW9&1|IOqJ~7&tS>oX?!|x$oz`?%(yi zGBMKUVi#ryfk0fhZ|RzWKn!)jKiA_dz!9-?_sr*#| zyYJ#3!FEickO(d%$MBb~=LmdIYiGBrl;Kt<>GuN|U#O^5JCUImzDAvyXv?Pb;et8YMm3!pp`lAf2cd)?=) z^PoSBCzwCobLlA_pvvtqxB`QDa8YBvk_`t+ebCJDr~dF*$1rHrC=&UY$9CtyO)Oyj z_c<9?Hfd9EGn-GXsL1GtevY2^&FUXYOg@vR;%~q>rIxmwK7yi;*D3ruX&at;1;YWl zpV6ouhRf2cmPTU*_1m5Xw{lMOh_bRud6cZ8Kj3(I*kt&I+__;|1(4_h^i=j>>*%ai z`{_rT9}zFE%&#!~K=YRjUfvuAZYSWPnN8;D0Ur5%()XszYq(>;+|r2WJ#ILxe<7if zqw$_3b5rcrNdt)$BUd6vl!R^aH!_~N=At|(vAUZ#ojwo;y#in$pb!Kw#_Gd^7~7GP zR^xR$w-ARkvHCAP=ih=5H{1=DeG=;^yl<&1MoR}&E&qk$s-Qhj+97P)&h16U){y?B zW?dTwt;4sNt(t`KojVsz<T7WZP}fh!rPKiJ{E5wou;JuOU}vPlRei4t+i0Y&P&O7=6IQ;GHZ)xZZe$W zD@OKz8Krp &{-hdyLSUNcC!|CIBMs=LC5NI%0_vrEAI78 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 HKmR`fzcmVC literal 0 HcmV?d00001 diff --git a/src/icons/wordmark-dark.svg b/src/icons/wordmark-dark.svg new file mode 100644 index 0000000..863a5a9 --- /dev/null +++ b/src/icons/wordmark-dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + rmature + diff --git a/src/icons/wordmark-light.svg b/src/icons/wordmark-light.svg new file mode 100644 index 0000000..5682907 --- /dev/null +++ b/src/icons/wordmark-light.svg @@ -0,0 +1,10 @@ + + + + + + + + + rmature + diff --git a/src/js/sw/sdk/index.js b/src/js/sw/sdk/index.js index bed8c81..525d9c6 100644 --- a/src/js/sw/sdk/index.js +++ b/src/js/sw/sdk/index.js @@ -154,7 +154,7 @@ export async function boot() { sw.ui = Object.freeze({}); } - // Topbar — standard navigation bar for surfaces + // Topbar — standard navigation bar for surfaces (legacy API) try { const topbarMod = await import('../shell/topbar.js'); _shell.Topbar = topbarMod.Topbar; @@ -162,7 +162,16 @@ export async function boot() { console.warn('[sw] Topbar import failed:', e.message); } - // Freeze shell now that Topbar is loaded + // Shell Topbar — kernel-injected two-slot topbar + try { + const shellTopbarMod = await import('../shell/shell-topbar.js'); + _shell.topbar = shellTopbarMod.shellTopbarAPI; + _shell._ShellTopbar = shellTopbarMod.ShellTopbar; + } catch (e) { + console.warn('[sw] ShellTopbar import failed:', e.message); + } + + // Freeze shell now that all shell modules are loaded sw.shell = Object.freeze(_shell); // UserMenu render helper — surfaces call sw.userMenu(container, opts) @@ -174,7 +183,7 @@ export async function boot() { }; // Marker for idempotency - sw._sdk = '0.6.5'; + sw._sdk = '0.7.0'; // 8. Expose globally window.sw = sw; @@ -210,7 +219,18 @@ export async function boot() { console.warn('[sw] Imperative host mount failed:', e.message); } - // 11. Signal ready + // 11. Auto-mount shell topbar if #shell-topbar exists + try { + const shellMount = document.getElementById('shell-topbar'); + if (shellMount && sw.shell._ShellTopbar && auth.isAuthenticated) { + const { render } = preact; + render(html`<${sw.shell._ShellTopbar} />`, shellMount); + } + } catch (e) { + console.warn('[sw] Shell topbar mount failed:', e.message); + } + + // 12. Signal ready events.emit('sdk.ready', {}, { localOnly: true }); document.dispatchEvent(new CustomEvent('sw:ready', { detail: { sw } })); console.log(`[sw] SDK v${sw._sdk} ready`); diff --git a/src/js/sw/shell/notification-bell.js b/src/js/sw/shell/notification-bell.js index b93091c..31fddff 100644 --- a/src/js/sw/shell/notification-bell.js +++ b/src/js/sw/shell/notification-bell.js @@ -39,9 +39,35 @@ export function NotificationBell({ onNavigate } = {}) { // ── WebSocket live updates ────────── useEffect(() => { if (!window.sw?.on) return; - const handler = () => reload(); - window.sw.on('notification.created', handler); - return () => window.sw.off?.('notification.created', handler); + + // New notification — refetch the list + const onCreated = () => reload(); + + // Single notification marked read (from another tab/surface) + const onRead = (e) => { + const id = e?.id; + if (!id) return; + setNotifications(prev => prev.map(n => + n.id === id ? { ...n, read_at: n.read_at || new Date().toISOString() } : n + )); + }; + + // All notifications marked read (from another tab/surface) + const onAllRead = () => { + setNotifications(prev => prev.map(n => ({ + ...n, read_at: n.read_at || new Date().toISOString() + }))); + }; + + window.sw.on('notification.created', onCreated); + window.sw.on('notification.read', onRead); + window.sw.on('notification.all_read', onAllRead); + + return () => { + window.sw.off?.('notification.created', onCreated); + window.sw.off?.('notification.read', onRead); + window.sw.off?.('notification.all_read', onAllRead); + }; }, [reload]); // ── Close on outside click ────────── diff --git a/src/js/sw/shell/shell-topbar.js b/src/js/sw/shell/shell-topbar.js new file mode 100644 index 0000000..262f357 --- /dev/null +++ b/src/js/sw/shell/shell-topbar.js @@ -0,0 +1,95 @@ +/** + * ShellTopbar — Kernel-injected topbar for all surfaces + * + * Two named slots: left (defaults to manifest title) and center (flex:1). + * Home link, notification bell, and user menu always present. + * + * Surfaces customize via sw.shell.topbar.setLeft/setSlot/setTitle/hide/show. + * This component is auto-mounted by the SDK boot if #shell-topbar exists. + */ +const { html } = window; +const { useState, useEffect } = hooks; + +import { NotificationBell } from './notification-bell.js'; +import { UserMenu } from './user-menu.js'; + +// ── Shared state — SDK API writes here, component reads ── + +let _leftContent = null; +let _centerContent = null; +let _titleOverride = null; +let _visible = true; +let _listeners = new Set(); + +function notify() { + _listeners.forEach(fn => fn()); +} + +/** SDK-facing API — exported for sw.shell.topbar.* */ +export const shellTopbarAPI = { + /** Override left slot with a vnode */ + setLeft(vnode) { + _leftContent = vnode; + _titleOverride = null; + notify(); + }, + /** Set center slot content (tabs, pickers, search) */ + setSlot(vnode) { + _centerContent = vnode; + notify(); + }, + /** Shorthand: set left slot to plain text title */ + setTitle(str) { + _titleOverride = str; + _leftContent = null; + notify(); + }, + /** Hide the entire topbar (full-bleed surfaces) */ + hide() { + _visible = false; + notify(); + }, + /** Show the topbar after hiding */ + show() { + _visible = true; + notify(); + }, +}; + +export function ShellTopbar() { + const [, setTick] = useState(0); + + useEffect(() => { + const listener = () => setTick(t => t + 1); + _listeners.add(listener); + return () => _listeners.delete(listener); + }, []); + + if (!_visible) return null; + + const BASE = window.__BASE__ || ''; + const manifestTitle = window.__MANIFEST__?.title || ''; + + // Left slot: custom vnode > title override > manifest title + const leftSlot = _leftContent + ? _leftContent + : html`${_titleOverride || manifestTitle}`; + + return html` +
+ + + + + + +
${leftSlot}
+
${_centerContent}
+
+ <${NotificationBell} /> + <${UserMenu} placement="down-right" /> +
+
+ `; +} diff --git a/src/js/sw/shell/user-menu.js b/src/js/sw/shell/user-menu.js index 9ef31e7..377ef54 100644 --- a/src/js/sw/shell/user-menu.js +++ b/src/js/sw/shell/user-menu.js @@ -34,15 +34,29 @@ export function UserMenu({ placement = 'down-right', onAction, extraItems }) { const user = sw?.auth?.user; const authenticated = sw?.auth?.isAuthenticated; - // Fetch installed surfaces once on mount. + // Fetch installed surfaces on mount and when packages/auth change. const CORE_IDS = new Set(['admin', 'settings', 'team-admin', 'workflow', 'workflow-landing', 'docs', 'welcome']); useEffect(() => { if (!sw?.api?.surfaces?.list) return; - sw.api.surfaces.list().then(data => { - const raw = Array.isArray(data) ? data : data?.data || []; - setAllSurfaces(raw); - }).catch(() => {}); + + function fetchSurfaces() { + sw.api.surfaces.list().then(data => { + const raw = Array.isArray(data) ? data : data?.data || []; + setAllSurfaces(raw); + }).catch(() => {}); + } + + fetchSurfaces(); + + // Re-fetch when packages or auth change (WS events from CS-1) + const off1 = sw.on?.('package.changed', fetchSurfaces); + const off2 = sw.on?.('auth.changed', fetchSurfaces); + + return () => { + if (typeof off1 === 'function') off1(); + if (typeof off2 === 'function') off2(); + }; }, [authenticated]); const extSurfaces = allSurfaces.filter(s => !CORE_IDS.has(s.id)); diff --git a/src/js/sw/surfaces/admin/groups.js b/src/js/sw/surfaces/admin/groups.js index 6f4bad7..9851984 100644 --- a/src/js/sw/surfaces/admin/groups.js +++ b/src/js/sw/surfaces/admin/groups.js @@ -15,11 +15,14 @@ export default function GroupsSection() { const [showCreate, setShowCreate] = useState(false); const [showAddMember, setShowAddMember] = useState(false); + const [error, setError] = useState(null); + const loadGroups = useCallback(async () => { + setError(null); try { const data = await sw.api.admin.groups.list(); setGroups(data || []); - } catch (e) { sw.toast(e.message, 'error'); } + } catch (e) { setError(e.message); } finally { setLoading(false); } }, []); @@ -109,6 +112,13 @@ export default function GroupsSection() { if (loading) return html`
Loading\u2026
`; + if (error) return html` +
+ ${error} + +
+ `; + if (detail) return html`
@@ -194,7 +204,7 @@ export default function GroupsSection() { `}
- ${groups.length === 0 && html`
No groups
`} + ${groups.length === 0 && html`
Groups control access to surfaces and features via permissions.
`} ${groups.map(g => html`
openDetail(g)}> ${g.name} diff --git a/src/js/sw/surfaces/admin/index.js b/src/js/sw/surfaces/admin/index.js index daedfbc..eaa0889 100644 --- a/src/js/sw/surfaces/admin/index.js +++ b/src/js/sw/surfaces/admin/index.js @@ -15,7 +15,6 @@ const { render } = preact; import { ToastContainer } from '../../primitives/toast.js'; import { DialogStack } from '../../shell/dialog-stack.js'; -import { UserMenu } from '../../shell/user-menu.js'; // ── Category → Section mapping ────────────── const ADMIN_SECTIONS = { @@ -131,36 +130,36 @@ function AdminSurface() { if (href) location.replace(href); }, []); + // Shell topbar: Pattern C — favicon + title in left slot, category tabs in center + useEffect(() => { + if (!window.sw?.shell?.topbar) return; + + window.sw.shell.topbar.setLeft(html` + + Administration + `); + + window.sw.shell.topbar.setSlot(html` +
+ ${Object.entries(CATEGORY_META).map(([cat, meta]) => html` + + <${CatIcon} paths=${meta.icon} /> + ${meta.label} + + `)} +
+ `); + }, [activeCat, navClick]); + const title = ADMIN_LABELS[section] || 'Admin'; const sidebarSections = ADMIN_SECTIONS[activeCat] || []; return html`
- ${/* Top Bar */``} -
- - - Administration - - -
- ${Object.entries(CATEGORY_META).map(([cat, meta]) => html` - - <${CatIcon} paths=${meta.icon} /> - ${meta.label} - - `)} -
- -
- <${UserMenu} placement="down-right" /> -
-
-
- ${/* Left Nav */``} + ${/* Left Nav — surface-owned, below topbar */``}
${sidebarSections.map(key => html` { + setError(null); try { const opts = typeFilter !== 'all' ? { type: typeFilter } : undefined; const data = await sw.api.admin.packages.list(opts); setPackages(data || []); - } catch (e) { sw.toast(e.message, 'error'); } + } catch (e) { setError(e.message); } finally { setLoading(false); } }, [typeFilter]); @@ -228,6 +231,13 @@ export default function PackagesSection() { // ── Render ─────────────────────────────────── if (loading) return html`
Loading\u2026
`; + if (error) return html` +
+ ${error} + +
+ `; + const enabled = packages.filter(p => p.enabled).length; const dormant = packages.filter(p => p.status === 'dormant').length; const hasManifestSettings = (pkg) => diff --git a/src/js/sw/surfaces/admin/workflows.js b/src/js/sw/surfaces/admin/workflows.js index f924750..cae00c3 100644 --- a/src/js/sw/surfaces/admin/workflows.js +++ b/src/js/sw/surfaces/admin/workflows.js @@ -13,12 +13,14 @@ export default function WorkflowsSection() { const [editing, setEditing] = useState(null); // null | 'new' | workflow const [stages, setStages] = useState([]); const [showCreate, setShowCreate] = useState(false); + const [error, setError] = useState(null); const load = useCallback(async () => { + setError(null); try { const data = await sw.api.workflows.list(); setWorkflows(data || []); - } catch (e) { sw.toast(e.message, 'error'); } + } catch (e) { setError(e.message); } finally { setLoading(false); } }, []); @@ -77,6 +79,13 @@ export default function WorkflowsSection() { if (loading) return html`
Loading\u2026
`; + if (error) return html` +
+ ${error} + +
+ `; + if (editing) return html`
@@ -145,7 +154,7 @@ export default function WorkflowsSection() { `}
- ${workflows.length === 0 && html`
No workflows
`} + ${workflows.length === 0 && html`
Workflows define multi-stage approval processes with team roles and SLA tracking.
`} ${workflows.map(w => html`
openEdit(w)}>
diff --git a/src/js/sw/surfaces/docs/index.js b/src/js/sw/surfaces/docs/index.js index d827504..847fc29 100644 --- a/src/js/sw/surfaces/docs/index.js +++ b/src/js/sw/surfaces/docs/index.js @@ -16,8 +16,6 @@ const { render } = preact; // Preload marked so renderSync works in useMemo if (sw?.markdown) sw.markdown.preload(); -import { Topbar } from '../../shell/topbar.js'; - function DocsSurface() { const [docs, setDocs] = useState([]); const [active, setActive] = useState(window.__SECTION__ || 'GETTING-STARTED'); @@ -98,6 +96,14 @@ function DocsSurface() { const el = articleRef.current; if (!el || !renderedHtml) return; if (sw?.renderers) sw.renderers.runPostRenderers(el); + + // Inject IDs onto headings so outline links can scroll to them + el.querySelectorAll('h1, h2, h3, h4').forEach(h => { + if (!h.id) { + h.id = h.textContent.trim().toLowerCase() + .replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); + } + }); }, [renderedHtml]); function retryList() { @@ -108,7 +114,6 @@ function DocsSurface() { } return html` - <${Topbar} title="Documentation" />