Chore roadmap v010x shift (#76)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-sqlite (push) Has been skipped
CI/CD / test-frontend (push) Has been skipped
CI/CD / test-go-pg (push) Has been skipped
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / build-and-deploy (push) Successful in 30s
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-sqlite (push) Has been skipped
CI/CD / test-frontend (push) Has been skipped
CI/CD / test-go-pg (push) Has been skipped
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / build-and-deploy (push) Successful in 30s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #76.
This commit is contained in:
115
ROADMAP.md
115
ROADMAP.md
@@ -1,6 +1,6 @@
|
||||
# Armature — Roadmap
|
||||
|
||||
## Current: v0.9.x — Workflow Redesign
|
||||
## Current: v0.9.x — Workflow Redesign + Multi-Surface Packages
|
||||
|
||||
Self-hosted extensible platform kernel. Auth, identity, packages, Starlark
|
||||
sandbox, storage, realtime, and ops are kernel primitives. Everything else
|
||||
@@ -143,65 +143,70 @@ Completes the workflow→package access story.
|
||||
|
||||
---
|
||||
|
||||
### v0.10.x — Reference Extensions
|
||||
### v0.10.x — Panels + Composable Layout
|
||||
|
||||
Panels are a new kernel rendering tier between surfaces (full-page) and
|
||||
block renderers (inline). They solve composable companion views — e.g.,
|
||||
a notes reference panel inside chat. Design doc: `docs/DESIGN-panels.md`.
|
||||
|
||||
| Version | Title |
|
||||
|---------|-------|
|
||||
| v0.10.0 | Panel Manifest + Lifecycle |
|
||||
| v0.10.1 | FloatingPanel Primitive |
|
||||
| v0.10.2 | Docked Panels + Mode Transitions |
|
||||
| v0.10.3 | Panel Communication Patterns |
|
||||
| v0.10.4 | Reference Panel: Notes (basic) |
|
||||
|
||||
---
|
||||
|
||||
### v0.11.x — Notes Reference Extension
|
||||
|
||||
Notes becomes the first reference extension — a production-quality
|
||||
knowledge base that exercises every kernel primitive. The UI/UX redesign
|
||||
is front-loaded as v0.11.0 so every subsequent feature version builds on
|
||||
a clean visual foundation. Design doc: `docs/DESIGN-notes-v011x.md`.
|
||||
|
||||
| Version | Title |
|
||||
|---------|-------|
|
||||
| v0.11.0 | UI/UX Foundation — visual redesign |
|
||||
| v0.11.1 | Deep Folders + Navigation |
|
||||
| v0.11.2 | Wikilinks + Backlinks |
|
||||
| v0.11.3 | Live Preview + Rich Editing |
|
||||
| v0.11.4 | Note Sharing + Permissions |
|
||||
| v0.11.5 | Graph + Outline Hardening |
|
||||
| v0.11.6 | Quick Switcher + Commands |
|
||||
| v0.11.7 | Daily Notes + Templates |
|
||||
| v0.11.8 | Transclusion + Embeds |
|
||||
| v0.11.9 | Composability: Slots + Actions |
|
||||
| v0.11.10 | Panel Enhancement + Quality Gate |
|
||||
|
||||
---
|
||||
|
||||
### v0.12.x — Reference Extensions (Remaining)
|
||||
|
||||
The kernel is complete. This series proves the platform by shipping
|
||||
first-party extensions that exercise every primitive. These are
|
||||
**extensions, not kernel code** — they ship as `.pkg` files and can be
|
||||
uninstalled.
|
||||
|
||||
**v0.10.0 — `vector-store` Library**
|
||||
|
||||
Document ingestion, chunk storage, embedding persistence, semantic
|
||||
similarity search. Consumes: `db.write`, `files.read`.
|
||||
|
||||
**v0.10.1 — `llm-bridge` Library**
|
||||
|
||||
Model abstraction, tool-use routing, provider BYOK via connections.
|
||||
Exposes `complete()`, `embed()`, `classify()`, `register_tool()`.
|
||||
Consumes: `connections.read`, `api.http`, `db.write`.
|
||||
|
||||
**v0.10.2 — `file-share` Extension**
|
||||
|
||||
File upload, storage via `files` module, download links, team/group
|
||||
ACLs via resource grants.
|
||||
|
||||
**v0.10.3 — `code-workspace` Extension**
|
||||
|
||||
Managed code repos via `workspace` module. Git operations, file browser
|
||||
surface, structural indexing. Consumes: `workspace.manage`, `files.write`.
|
||||
|
||||
**v0.10.4 — `image-gen` + `image-edit` Extensions**
|
||||
|
||||
Image generation/editing via external APIs. Composability demo:
|
||||
contributes to `chat:image-actions` slot.
|
||||
|
||||
**v0.10.5 — Chat System**
|
||||
|
||||
Generic 1-to-N messaging with slots. `chat-core` library + `chat`
|
||||
surface. Declares `chat:message-actions`, `chat:image-actions`,
|
||||
`chat:composer-tools` slots. Consumes: `db.write`, `realtime.publish`,
|
||||
`files.write`.
|
||||
| Version | Title |
|
||||
|---------|-------|
|
||||
| v0.12.0 | `vector-store` Library |
|
||||
| v0.12.1 | `llm-bridge` Library |
|
||||
| v0.12.2 | `file-share` Extension |
|
||||
| v0.12.3 | `code-workspace` Extension |
|
||||
| v0.12.4 | `image-gen` + `image-edit` Extensions |
|
||||
| v0.12.5 | Chat System |
|
||||
|
||||
---
|
||||
|
||||
### v0.11.x — Sidecar Tier + Polish
|
||||
### v0.13.x — Sidecar Tier + Polish
|
||||
|
||||
**v0.11.0 — Sidecar Tier**
|
||||
|
||||
Out-of-process extensions for workloads that can't run in Starlark (ML
|
||||
inference, media transcoding, language servers, local git). Sidecars are
|
||||
independent processes that connect inward to the kernel, following the
|
||||
cluster registry pattern. Authentication via mTLS or registration tokens.
|
||||
|
||||
**v0.11.1 — Native Dialog Audit**
|
||||
|
||||
Replace `prompt()`/`confirm()`/`alert()` with `sw.dialog` SDK primitives.
|
||||
|
||||
**v0.11.2 — Stability + Migration Tooling**
|
||||
|
||||
Versioned migrations, `armature migrate` CLI, backup/restore validation,
|
||||
pre-1.0 schema freeze.
|
||||
| Version | Title |
|
||||
|---------|-------|
|
||||
| v0.13.0 | Sidecar Tier |
|
||||
| v0.13.1 | Native Dialog Audit |
|
||||
| v0.13.2 | Stability + Migration Tooling |
|
||||
|
||||
---
|
||||
|
||||
@@ -219,6 +224,10 @@ Gate criteria:
|
||||
- Headless E2E green on PG + SQLite
|
||||
- `armature-ca.sh` + mTLS deployment guide
|
||||
- Single-binary + Docker + K8s deployment paths documented
|
||||
- Notes reference extension shipped with all v0.11.x features
|
||||
- Notes UI/UX reviewed against v0.11.0 design principles — no placeholder UI
|
||||
- At least 1 panel shipped and consumed cross-package (notes.reference → chat)
|
||||
- Panel lifecycle test coverage in SDK test runner
|
||||
|
||||
---
|
||||
|
||||
@@ -269,5 +278,9 @@ These are candidates, not commitments. Each requires a design doc.
|
||||
| `workspace` for real filesystem | Flat blob store can't serve git/compilers/ffmpeg. Managed disk paths. |
|
||||
| Capability negotiation at install | Fail loud with actionable message, not silently at runtime. |
|
||||
| Vector column with three-tier fallback | Works everywhere, works fast with pgvector. |
|
||||
| Sidecar deferred to v0.11.x | HTTP module covers external APIs. Sidecars connect inward (no k8s RBAC needed). |
|
||||
| Sidecar deferred to v0.13.x | HTTP module covers external APIs. Sidecars connect inward (no k8s RBAC needed). |
|
||||
| Workflow redesign before reference extensions | Clean up debt and promote primitives before building on top. |
|
||||
| Panels as kernel primitive (v0.10.x) | Z-index coordination, drag/resize, layout negotiation are kernel concerns. |
|
||||
| UI/UX redesign as v0.11.0 | Every feature version builds on the visual foundation. Front-loading avoids double work. |
|
||||
| Notes as dedicated v0.11.x series (11 versions) | Complex enough to need changeset discipline. Each version independently shippable. |
|
||||
| Notes before remaining reference extensions | Notes proves the full primitive surface. Other extensions build on proven patterns. |
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
# 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: `<img src="${BASE}/favicon.svg">`. 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: `<span>${s.user_id}</span>` — 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.
|
||||
744
docs/DESIGN-notes-v011x.md
Normal file
744
docs/DESIGN-notes-v011x.md
Normal file
@@ -0,0 +1,744 @@
|
||||
# DESIGN: Notes Reference Extension — v0.11.x
|
||||
|
||||
## Status: Proposed
|
||||
|
||||
## Purpose
|
||||
|
||||
Notes becomes the first **reference extension** — a first-party package
|
||||
that exercises every kernel primitive and proves the platform is capable
|
||||
of delivering a production-quality application. This is not a toy demo;
|
||||
it is the answer to "what can you build on Armature?"
|
||||
|
||||
The v0.11.x series takes the existing notes package (folders, tags,
|
||||
wikilinks, graph, CM6 editor, three view modes) and builds it into a
|
||||
full-featured knowledge base comparable to Obsidian, Notion, or Logseq —
|
||||
but running on Armature's extension architecture.
|
||||
|
||||
**The UI must be clean, inviting, and genuinely enjoyable to use.** The
|
||||
current notes surface is functional but visually mechanical — no
|
||||
personality, no micro-interactions, no sense of craft. If the reference
|
||||
extension feels like a developer prototype, the platform pitch fails
|
||||
regardless of how many kernel primitives it exercises. v0.11.0 is a
|
||||
full visual redesign before any feature work begins, and every subsequent
|
||||
version ships with UX quality built in, not bolted on.
|
||||
|
||||
### What Notes Exercises
|
||||
|
||||
| Kernel Primitive | How Notes Uses It |
|
||||
|-----------------|-------------------|
|
||||
| `db.write` | Notes, folders, tags, links, shares tables |
|
||||
| `sw.renderers` | Markdown rendering pipeline, custom block renderers |
|
||||
| `sw.panels` | `notes.reference` panel (from v0.10.4) |
|
||||
| `sw.slots` | Declares `notes:toolbar-actions`, `notes:note-footer` for extension composition |
|
||||
| `sw.events` | Realtime updates, panel ↔ surface communication |
|
||||
| `sw.markdown` | Unified markdown rendering with wikilink extensions |
|
||||
| `sw.shell.topbar` | Topbar slots for navigation context |
|
||||
| `sw.storage` | Editor state, sidebar collapse, preferences |
|
||||
| `sw.actions` | Exports `notes.create`, `notes.search` for cross-package calls |
|
||||
| Resource grants | Note and folder sharing via kernel permission model |
|
||||
| Starlark sandbox | All backend logic in `script.star` |
|
||||
| Settings cascade | Per-user editor mode, default view, daily note folder |
|
||||
| Surfaces | Multi-surface: full editor + public read-only surface |
|
||||
|
||||
---
|
||||
|
||||
## What Already Exists (v0.9.0)
|
||||
|
||||
### Frontend (1,806 lines — `js/main.js`)
|
||||
|
||||
- **NotesApp** — main shell with sidebar + editor layout
|
||||
- **FolderTree / FolderNode** — tree with expand/collapse, context menu,
|
||||
drag-and-drop note→folder, rename, create sub-folder (schema has
|
||||
`parent_id`, UI renders depth)
|
||||
- **NoteCard** — list items with title, snippet, date, tags, drag handle
|
||||
- **TagInput / TagFilter** — tag CRUD with autocomplete
|
||||
- **EditorPane** — three view modes (rendered / edit / split), CodeMirror
|
||||
6 integration with textarea fallback, auto-save, frontmatter parsing
|
||||
- **BacklinksPanel** — lists notes that link to the current note
|
||||
- **SidebarTabs** — Notes / Outline tabs
|
||||
- **SidebarOutline** — heading tree parsed from markdown body
|
||||
- **GraphPane** — canvas-based force-directed graph with folder coloring,
|
||||
orphan hiding, focus mode, pan/zoom
|
||||
- **Export** — download note as `.md` with frontmatter
|
||||
|
||||
### Backend (23K — `script.star`)
|
||||
|
||||
- Full CRUD: notes, folders, tags
|
||||
- Wikilink extraction (`_extract_wikilinks`) and link resolution
|
||||
(`_sync_links`) on every note save
|
||||
- Links / backlinks queries
|
||||
- Graph data endpoint (nodes + edges)
|
||||
- Search (title + body substring)
|
||||
- Stats endpoint
|
||||
- Folder CRUD with `parent_id`
|
||||
|
||||
### Schema (4 tables)
|
||||
|
||||
- `notes` — title, body, folder_id, creator_id, updated_at, pinned, archived
|
||||
- `tags` — note_id, tag
|
||||
- `links` — source_id, target_id, link_text
|
||||
- `folders` — name, parent_id, creator_id, sort_order
|
||||
|
||||
### Current CSS (795 lines — `css/main.css`)
|
||||
|
||||
Functional but visually flat. No animations, no keyframes, no
|
||||
micro-interactions. Identical `transition: var(--transition)` on every
|
||||
hover. No typographic hierarchy beyond font-size/weight. No visual
|
||||
rhythm. Sidebar and editor feel like admin panels, not a writing tool.
|
||||
|
||||
---
|
||||
|
||||
## Version Plan
|
||||
|
||||
### v0.11.0 — UI/UX Foundation
|
||||
|
||||
**Goal:** Complete visual redesign of the notes surface. Every pixel
|
||||
intentional. Every interaction feels crafted. This is the foundation
|
||||
that all subsequent feature versions build on.
|
||||
|
||||
This is NOT a "make it pretty" pass on the existing CSS. It is a
|
||||
ground-up rethink of the notes UI with the understanding that this is
|
||||
the first thing people see when evaluating what Armature can do.
|
||||
|
||||
**Design principles for notes:**
|
||||
|
||||
- **Writing-first.** The editor area dominates. Generous whitespace.
|
||||
Nothing competes for attention with the content the user is writing.
|
||||
- **Quiet chrome, loud content.** Sidebar, toolbar, and navigation fade
|
||||
into the background. The note body is the visual anchor — clean
|
||||
typography, comfortable line height, readable measure (60–80 chars).
|
||||
- **Progressive disclosure.** Folder tree, tags, backlinks, graph — all
|
||||
there but not all visible simultaneously. Contextual — show what's
|
||||
relevant to what the user is doing right now.
|
||||
- **Micro-interactions that feel alive.** Folder expand/collapse with
|
||||
rotation animation. Note cards with subtle lift on hover. Smooth
|
||||
sidebar resize. Mode transitions that animate, not snap. Save
|
||||
indicator that pulses, not just appears.
|
||||
- **Personality without kitsch.** The UI should feel like a well-designed
|
||||
indie app, not a Material Design template and not a Bootstrap theme.
|
||||
|
||||
**Specific deliverables:**
|
||||
|
||||
**Typography overhaul:**
|
||||
- Rendered note body: system serif stack for body text (Georgia, serif
|
||||
fallback) at 16px/1.7 line height. Headings in the system sans stack.
|
||||
Comfortable reading measure — `max-width: 720px` centered in the
|
||||
editor area with generous padding. Code blocks with distinct
|
||||
background and a monospace stack.
|
||||
- Editor (CM6): matching font size and line height so switching between
|
||||
rendered/edit mode doesn't jar. CM6 theme tokens aligned with note
|
||||
design tokens.
|
||||
- Sidebar text: smaller (13px), tighter, utility font. Clear hierarchy
|
||||
between folder names, note titles, snippet text, dates.
|
||||
|
||||
**Sidebar redesign:**
|
||||
- **Resizable** — drag handle between sidebar and editor. Width persisted.
|
||||
Smooth resize with no layout jank.
|
||||
- **Collapsible** — collapse to a thin icon strip (folder + search +
|
||||
graph icons) on narrow viewports or by user choice. Expand on hover
|
||||
or click. Collapse state persisted.
|
||||
- **Folder tree polish:** Indent guides (subtle vertical lines connecting
|
||||
parent→child). Folder icons that change on expand (open folder / closed
|
||||
folder, not just a triangle). Drop target highlighting with animation
|
||||
(not just background color change). Smooth height animation on
|
||||
expand/collapse.
|
||||
- **Note cards:** Subtle left border color-coded by folder (pulls from a
|
||||
soft palette, not harsh primaries). Title, snippet, relative date
|
||||
("3h ago" not "2026-04-03T12:34"). Tag pills with rounded, muted
|
||||
styling. Pin indicator as a subtle icon, not a text label.
|
||||
- **Search:** Inline search with clear button. Results highlight matching
|
||||
text. Smooth appear/disappear.
|
||||
|
||||
**Editor redesign:**
|
||||
- **Header:** Title input styled as a large heading (not an input field
|
||||
with a border). Folder breadcrumb below title in muted text. Toolbar
|
||||
actions as icon buttons with tooltips — no text labels cluttering the
|
||||
header. Save status indicator (saved ✓ / saving… / unsaved •) as a
|
||||
small, elegant badge.
|
||||
- **Mode switcher:** Segmented control (Read / Edit / Split) replacing
|
||||
the cycling button. Clear visual state.
|
||||
- **Rendered view:** Clean markdown rendering with proper spacing between
|
||||
elements. Block quotes with a left accent border. Tables with subtle
|
||||
borders and alternating row tinting. Inline code with pill-style
|
||||
background. Links with underline on hover only.
|
||||
- **Empty editor state:** "Select a note or create a new one" with a
|
||||
softly illustrated empty state — not a plain text message.
|
||||
|
||||
**Graph visual refresh:**
|
||||
- Dark-on-light node rendering with soft shadows (not flat circles with
|
||||
outlines). Node labels that appear on hover with smooth fade-in.
|
||||
Edge rendering with slight curves (not straight lines). Background
|
||||
subtle dot grid.
|
||||
|
||||
**Transitions and animations:**
|
||||
- Sidebar expand/collapse: 200ms ease-out slide.
|
||||
- Folder tree expand/collapse: 150ms height animation with children
|
||||
fading in.
|
||||
- Note card hover: subtle translateY(-1px) + box-shadow lift.
|
||||
- View mode switch: crossfade (100ms fade out → swap → 100ms fade in).
|
||||
- Save indicator: pulse animation on "saving", check mark with brief
|
||||
scale-up on "saved".
|
||||
- Graph node hover: scale(1.2) with spring easing.
|
||||
- Panel/dialog open: 150ms slide-up + fade-in (consistent with kernel
|
||||
Dialog animation).
|
||||
|
||||
**Color and theming:**
|
||||
- Notes should work beautifully in both light and dark themes.
|
||||
Use CSS custom properties (already the pattern via `var(--bg-surface)`,
|
||||
etc.) but add notes-specific tokens for accent colors, folder palette,
|
||||
and typography:
|
||||
```css
|
||||
--notes-body-font: Georgia, 'Times New Roman', serif;
|
||||
--notes-body-size: 16px;
|
||||
--notes-body-line-height: 1.7;
|
||||
--notes-body-measure: 720px;
|
||||
--notes-accent: var(--accent);
|
||||
--notes-folder-1: #6366f1; /* indigo */
|
||||
--notes-folder-2: #8b5cf6; /* violet */
|
||||
--notes-folder-3: #ec4899; /* pink */
|
||||
--notes-folder-4: #f59e0b; /* amber */
|
||||
--notes-folder-5: #10b981; /* emerald */
|
||||
--notes-folder-6: #06b6d4; /* cyan */
|
||||
```
|
||||
|
||||
**Responsive:**
|
||||
- Below 768px: sidebar collapses to overlay (slide-in from left).
|
||||
Editor goes full-width. Split view disabled. Touch-friendly tap
|
||||
targets (44px minimum on all interactive elements).
|
||||
- Between 768px and 1024px: sidebar narrower (220px). Editor gets
|
||||
remaining space.
|
||||
- Above 1024px: full layout with comfortable sidebar width.
|
||||
|
||||
**What this version does NOT change:**
|
||||
- No new features. Same note CRUD, same folders, same tags, same
|
||||
wikilinks, same graph, same three view modes.
|
||||
- No backend changes. No schema changes.
|
||||
- The existing JS components are restructured for the new layout but
|
||||
retain their current behavior.
|
||||
|
||||
**Deliverable:** Complete CSS rewrite (`css/main.css`), targeted JS
|
||||
changes for new layout structure (resizable sidebar, collapsible sidebar,
|
||||
mode switcher component, animation hooks), and updated component
|
||||
templates where the HTML structure needs to change for the new design.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.1 — Deep Folders + Navigation
|
||||
|
||||
**Goal:** Folders become a real hierarchy with breadcrumbs, not just a
|
||||
flat tree with parent_id.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Breadcrumb navigation** in editor header showing the folder path.
|
||||
Click any segment to navigate to that folder's note list. Styled
|
||||
consistently with the v0.11.0 muted-text breadcrumb design.
|
||||
- **Drag folder→folder** to reparent. Animated drop indicator shows
|
||||
nesting target with indent guide preview. Depth limit: 5 levels.
|
||||
- **Folder sort** — drag to reorder within a level. Smooth reorder
|
||||
animation. Persists via `sort_order` column (already in schema).
|
||||
- **Collapse/expand persistence** — expanded folder set stored in
|
||||
`sw.storage` keyed by user.
|
||||
- **Move dialog** — select destination folder from a tree picker when
|
||||
moving notes (alternative to drag for accessibility / many folders).
|
||||
Uses `sw.ui.Dialog` with the new folder tree component inside.
|
||||
|
||||
**UX standard:** All folder interactions use the animation language
|
||||
established in v0.11.0. Drop targets highlight with the folder color
|
||||
from the palette. Reparent shows a brief connection-line animation.
|
||||
|
||||
**Backend changes:**
|
||||
|
||||
- Validate `parent_id` chain on create/update (no cycles, depth ≤ 5).
|
||||
- Folder delete: require empty or offer cascade (move children to parent,
|
||||
or move contained notes to Unfiled).
|
||||
- `_list_folders()` returns full tree with `children_count` and
|
||||
`note_count` for each folder.
|
||||
|
||||
**Schema:** No changes.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.2 — Wikilinks + Backlinks
|
||||
|
||||
**Goal:** `[[wikilinks]]` become a first-class editing and navigation
|
||||
primitive with autocomplete, previews, and unresolved link handling.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **CM6 autocomplete** — typing `[[` triggers a fuzzy note title picker
|
||||
with a clean dropdown styled consistently with the notes design
|
||||
language (not browser-default autocomplete). Shows note title, folder
|
||||
path, and snippet preview. Keyboard navigable.
|
||||
- **Wikilink rendering** — rendered markdown converts `[[Title]]` to
|
||||
internal links with a subtle notes-specific style (dotted underline,
|
||||
small link icon). Distinct from external URLs.
|
||||
- **Hover preview** — hovering a wikilink shows a floating card with
|
||||
the target note's title, first ~200 chars rendered as markdown, and
|
||||
folder/tag metadata. Smooth fade-in, positioned to avoid viewport
|
||||
overflow. Same visual language as graph node hover cards.
|
||||
- **Unresolved links** — `[[Nonexistent Note]]` renders with a dashed
|
||||
styling and a muted color. Click to create the note with that title
|
||||
(pre-filled). Small "+" indicator on hover.
|
||||
- **Backlinks panel improvements:**
|
||||
- Show context snippet (surrounding text with the `[[link]]`
|
||||
highlighted).
|
||||
- Group by folder with folder color indicators.
|
||||
- Count badge in sidebar tab (animated increment on new backlinks).
|
||||
- Navigate to the linking note and scroll to the link location.
|
||||
- **Aliases** — new `aliases` column on `notes` table. Autocomplete
|
||||
searches both title and aliases.
|
||||
|
||||
**UX standard:** Autocomplete dropdown uses the notes card styling.
|
||||
Hover preview card shares visual language with graph tooltips. Unresolved
|
||||
link creation is a smooth inline experience, not a navigate-away-and-back.
|
||||
|
||||
**Backend changes:**
|
||||
|
||||
- `_sync_links()` resolves aliases as well as titles.
|
||||
- New endpoint: `GET /autocomplete?q=...` — fast title+alias prefix
|
||||
search (limit 10).
|
||||
- Backlinks response includes `context_snippet` field.
|
||||
|
||||
**Schema changes:**
|
||||
|
||||
- `notes` table: add `aliases` column (text, comma-separated).
|
||||
|
||||
---
|
||||
|
||||
### v0.11.3 — Live Preview + Rich Editing
|
||||
|
||||
**Goal:** The editor becomes genuinely pleasant to write in — live
|
||||
preview that scrolls in sync, or an optional inline-formatted mode.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Split pane scroll sync** — scrolling the CM6 editor scrolls the
|
||||
rendered preview to the corresponding position. Uses heading anchors
|
||||
for coarse sync and line-height interpolation for fine sync.
|
||||
- **Live preview debounce** — rendered pane updates as you type with
|
||||
150ms debounce. Smooth content transitions (no jarring reflow).
|
||||
- **Inline preview** (Obsidian-style) — optional mode where markdown
|
||||
syntax hides and formatting appears inline while editing. CM6
|
||||
decorations for bold, italic, headings, links, code blocks.
|
||||
Syntax reappears when cursor enters the formatted region. This is
|
||||
the "fourth mode" alongside rendered / edit / split — a true
|
||||
WYSIWYG-ish editing experience without leaving markdown.
|
||||
- **Image paste** — paste from clipboard, upload via `files` module,
|
||||
insert `` at cursor. Progress indicator during upload.
|
||||
Fallback: base64 inline if `files` module unavailable.
|
||||
- **Table editing** — tab-to-next-cell, auto-expand columns, add
|
||||
row/column with floating buttons on hover. Markdown tables become
|
||||
usable instead of a formatting chore.
|
||||
|
||||
**UX standard:** Split pane has an animated gutter with a subtle drag
|
||||
handle. Mode switcher expands to accommodate the fourth option (or
|
||||
becomes a dropdown if space is tight). Image paste shows a smooth
|
||||
inline loading skeleton.
|
||||
|
||||
**Backend changes:**
|
||||
|
||||
- Image upload endpoint: `POST /images` — receives multipart, stores
|
||||
via `files` module, returns URL.
|
||||
|
||||
**Schema:** No changes.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.4 — Note Sharing + Permissions
|
||||
|
||||
**Goal:** Notes become collaborative. Share a note or folder with
|
||||
a team, group, individual, or the public — with read-only or read-write
|
||||
permissions.
|
||||
|
||||
**Share Model:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Share Scope │ Granularity │ Permissions │
|
||||
├──────────────────────────┼─────────────┼─────────────┤
|
||||
│ Public (anonymous URL) │ note only │ read-only │
|
||||
│ Team │ note/folder │ ro / rw │
|
||||
│ Group │ note/folder │ ro / rw │
|
||||
│ Individual (user) │ note/folder │ ro / rw │
|
||||
└──────────────────────────┴─────────────┴─────────────┘
|
||||
```
|
||||
|
||||
**Folder sharing inherits:** sharing a folder grants access to all notes
|
||||
in that folder (and sub-folders). Individual note shares override folder
|
||||
permissions (higher wins).
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Share dialog** — clean modal (using `sw.ui.Dialog`) with scope
|
||||
selector, user/team/group picker (using `sw.ui.UserPicker` or new
|
||||
entity picker), permission toggle, and copy-link button. Share URL
|
||||
preview with visual indicator.
|
||||
- **Public notes surface** — new surface at `/s/notes/public/:share_id`.
|
||||
Renders a single note in read-only mode with clean, minimal chrome.
|
||||
Uses the v0.11.0 typography and rendered view styling. Armature
|
||||
branding footer. No authentication required.
|
||||
- **Shared-with-me view** — sidebar section (new tab) showing notes/folders
|
||||
shared by others. Grouped by owner with avatar. Visual distinction
|
||||
from your own notes.
|
||||
- **Share indicators** — shared notes show a subtle share icon on the
|
||||
note card. Folder tree shows share badge on shared folders. Hover
|
||||
reveals share scope.
|
||||
- **Permission enforcement** — Starlark backend checks share permissions
|
||||
on every read/write.
|
||||
- **Share revocation** — remove access from share dialog with
|
||||
confirmation.
|
||||
|
||||
**UX standard:** Share dialog is the most complex new UI in the series
|
||||
and must feel simple. One-click sharing for common cases (share with
|
||||
team, make public). Advanced options (specific users, groups) are
|
||||
available but not in the way. Copy-link button shows a brief "Copied!"
|
||||
toast. Public note surface is a showcase — the best the notes rendering
|
||||
can look.
|
||||
|
||||
**Backend changes:**
|
||||
|
||||
- New `note_shares` table.
|
||||
- New `folder_shares` table.
|
||||
- Access check function: `_can_access(user_id, note_id, permission)`.
|
||||
- `_list_notes()` includes shared notes with `shared_by` metadata.
|
||||
- Public note endpoint: unauthenticated GET by share_token.
|
||||
|
||||
**Schema changes:**
|
||||
|
||||
- New table: `note_shares` — note_id, share_type, target_id, permission,
|
||||
share_token, created_by, created_at.
|
||||
- New table: `folder_shares` — folder_id, share_type, target_id,
|
||||
permission, share_token, created_by, created_at.
|
||||
|
||||
**Manifest changes:**
|
||||
|
||||
- New surface: `/notes/public/:share_id` with `auth: "public"`.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.5 — Graph + Outline Hardening
|
||||
|
||||
**Goal:** The graph becomes a genuine navigation and discovery tool.
|
||||
The outline becomes a reliable TOC.
|
||||
|
||||
**Graph improvements:**
|
||||
|
||||
- **d3-force layout** — replace hand-rolled force sim with d3-force.
|
||||
Better convergence, collision avoidance, centering. ~15KB optional
|
||||
vendor (same pattern as CM6).
|
||||
- **Zoom controls** — explicit +/- buttons (styled as floating pills) plus
|
||||
scroll-to-zoom. Reset-to-fit button.
|
||||
- **Minimap** — small overview in corner with viewport rectangle.
|
||||
Draggable to navigate large graphs.
|
||||
- **Cluster by folder** — visual grouping with soft convex hulls in
|
||||
folder colors (from the v0.11.0 palette). Toggle on/off.
|
||||
- **Filter by tag** — highlight matching nodes, dim others. Smooth
|
||||
opacity transition.
|
||||
- **Search in graph** — highlight and smooth-pan to matching nodes.
|
||||
- **Hover card** — note title, snippet, folder, tag count. Same visual
|
||||
language as wikilink hover preview (v0.11.2).
|
||||
|
||||
**Outline improvements:**
|
||||
|
||||
- **Scroll sync** — active heading highlights as user scrolls. Uses
|
||||
`IntersectionObserver`. Smooth highlight transition.
|
||||
- **Click-to-scroll** — smooth scroll with brief heading highlight pulse.
|
||||
- **Indent levels** — h1→h6 with subtle indent guides matching the
|
||||
folder tree style.
|
||||
- **Collapse/expand** — heading sections collapsible in outline.
|
||||
Animated, consistent with folder tree animations.
|
||||
|
||||
**UX standard:** The graph should feel like a discovery tool, not a tech
|
||||
demo. Interactions should be fluid — pan, zoom, hover, click — with no
|
||||
jank. The outline should feel like a table of contents in a well-typeset
|
||||
book.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.6 — Quick Switcher + Commands
|
||||
|
||||
**Goal:** Keyboard-driven navigation. Power users never touch the mouse.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Quick switcher** (`Cmd+O` / `Ctrl+O`) — centered overlay with fuzzy
|
||||
search. Clean design: large input field, results below with note
|
||||
title, folder path, and snippet. Recent notes above search results.
|
||||
Arrow keys + Enter. Smooth open/close animation. Matches the visual
|
||||
language of command palettes in VS Code / Raycast.
|
||||
- **Command palette** (`Cmd+Shift+P`) — same overlay style, listing all
|
||||
available note actions as searchable commands. Extensible via
|
||||
`sw.actions`.
|
||||
- **Keyboard shortcuts:** Registered via a central keymap.
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Cmd+O` | Quick switcher |
|
||||
| `Cmd+Shift+P` | Command palette |
|
||||
| `Cmd+N` | New note |
|
||||
| `Cmd+S` | Save (in edit mode) |
|
||||
| `Cmd+E` | Toggle edit/rendered |
|
||||
| `Cmd+Shift+E` | Toggle split view |
|
||||
| `Cmd+D` | Open today's daily note |
|
||||
| `Cmd+G` | Toggle graph view |
|
||||
| `Cmd+B` | Toggle backlinks panel |
|
||||
| `Escape` | Close active panel/dialog |
|
||||
|
||||
**UX standard:** The quick switcher is a high-frequency interaction —
|
||||
it must open instantly (no perceptible delay), search results must
|
||||
appear as-you-type, and the whole flow (Cmd+O → type → Enter) should
|
||||
take under 2 seconds for a user who knows what they want.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.7 — Daily Notes + Templates
|
||||
|
||||
**Goal:** Recurring note patterns that lower friction.
|
||||
|
||||
**Daily notes:**
|
||||
|
||||
- Auto-created daily note — navigating to "today" creates a note titled
|
||||
`YYYY-MM-DD` in a configurable daily notes folder.
|
||||
- Daily note template — configurable template body.
|
||||
- **Calendar picker** — small, beautiful calendar widget in the sidebar.
|
||||
Days with notes dot-marked. Styled as a subtle, compact component
|
||||
(not a full-page calendar). Click a date to open that day's note.
|
||||
- Previous/next day nav arrows in editor header for daily notes.
|
||||
|
||||
**Templates:**
|
||||
|
||||
- Notes in a "Templates" folder are templates. No special type.
|
||||
- "New Note" dropdown offers template selection with preview.
|
||||
- Template variables: `{{date}}`, `{{time}}`, `{{title}}`, `{{folder}}` —
|
||||
simple string replacement.
|
||||
- **Slash commands** in CM6 — typing `/` at line start shows a styled
|
||||
command menu: `/template`, `/date`, `/time`, `/todo`, `/callout`,
|
||||
`/table`, `/code`, `/divider`. Extensible via `sw.slots`.
|
||||
|
||||
**UX standard:** The calendar picker should feel like a subtle,
|
||||
integrated part of the sidebar — not a jarring widget. Template
|
||||
selection should show a live preview of the template content.
|
||||
Slash commands should appear fast and be keyboard navigable.
|
||||
|
||||
**Settings additions:**
|
||||
|
||||
- `daily_note_folder` — folder name for daily notes.
|
||||
- `daily_note_template` — template body.
|
||||
- `template_folder` — folder name for templates.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.8 — Transclusion + Embeds
|
||||
|
||||
**Goal:** Notes reference and embed each other's content.
|
||||
|
||||
**Features:**
|
||||
|
||||
- **Transclusion** (`![[Note Title]]`) — embeds referenced note content
|
||||
inline in a clean bordered container with source title as header link.
|
||||
- **Section transclusion** (`![[Note Title#Heading]]`) — embeds content
|
||||
under specified heading only.
|
||||
- **Block transclusion** (`![[Note Title^block-id]]`) — single paragraph
|
||||
by block ID.
|
||||
- **Recursion guard** — depth limited to 3. Circular references show
|
||||
a clean warning card, not an error.
|
||||
- **File attachments** — drag-and-drop files onto editor to upload.
|
||||
Non-image files render as styled download cards (file icon, name,
|
||||
size).
|
||||
- **Embed preview in editor** — CM6 decoration shows read-only preview
|
||||
below `![[...]]` line. Collapsible with smooth animation.
|
||||
|
||||
**UX standard:** Transclusions should feel like natural parts of the
|
||||
document, not foreign inclusions. The border and header link should be
|
||||
subtle. Embedded content uses the same typography as the host note.
|
||||
Collapse animation is smooth.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.9 — Composability: Slots + Actions
|
||||
|
||||
**Goal:** Notes becomes a host surface that other extensions enhance.
|
||||
|
||||
**Slot declarations:**
|
||||
|
||||
```json
|
||||
{
|
||||
"slots": {
|
||||
"notes:toolbar-actions": {
|
||||
"description": "Action buttons in the note editor toolbar",
|
||||
"context": {
|
||||
"noteId": "string",
|
||||
"getContent": "function — returns markdown body",
|
||||
"setContent": "function — replaces markdown body",
|
||||
"getTitle": "function — returns title"
|
||||
}
|
||||
},
|
||||
"notes:note-footer": {
|
||||
"description": "Content rendered below the note body",
|
||||
"context": { "noteId": "string", "content": "string — HTML" }
|
||||
},
|
||||
"notes:sidebar-tabs": {
|
||||
"description": "Additional tabs in the notes sidebar",
|
||||
"context": { "activeNoteId": "string or null" }
|
||||
},
|
||||
"notes:slash-commands": {
|
||||
"description": "Additional slash commands in the editor",
|
||||
"context": { "insertText": "function(text)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Exported actions:**
|
||||
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
"actions": {
|
||||
"notes.create": "Create note (params: title, body, folder_id)",
|
||||
"notes.search": "Search notes (params: query, limit)",
|
||||
"notes.get": "Get note by ID (params: note_id)"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**UX standard:** Slot contributions must fit visually. Toolbar action
|
||||
buttons contributed by other extensions inherit the notes icon-button
|
||||
styling. Sidebar tabs contributed by extensions match the built-in tab
|
||||
design. The notes surface should not look different when extensions
|
||||
contribute to its slots.
|
||||
|
||||
---
|
||||
|
||||
### v0.11.10 — Panel Enhancement + Quality Gate
|
||||
|
||||
**Goal:** The `notes.reference` panel (shipped in v0.10.4) inherits
|
||||
the v0.11.x features. Final quality gate.
|
||||
|
||||
**Panel updates:**
|
||||
|
||||
- Reference panel gets the v0.11.0 visual design language.
|
||||
- Search, folder filter, tag filter in panel.
|
||||
- Preview pane shows rendered note content (v0.11.0 typography).
|
||||
- Click-to-insert: emits `panel.notes.reference.selected` for host.
|
||||
- Wikilink resolution works within panel preview.
|
||||
|
||||
**Quality gate criteria:**
|
||||
|
||||
- All v0.11.x features exercised in SDK test runner.
|
||||
- Notes-runner surface tests cover: CRUD, sharing, wikilinks, daily
|
||||
notes, templates, transclusion.
|
||||
- Graph renders correctly with 200+ nodes at 60fps.
|
||||
- Public note surface renders without authentication.
|
||||
- At least one slot contribution demonstrated.
|
||||
- Panel reference works in at least one other surface.
|
||||
- **UX review:** every screen, every interaction, every empty state
|
||||
reviewed against v0.11.0 design principles. No "placeholder" UI
|
||||
surviving to this point.
|
||||
- **Responsive:** full experience on mobile (sidebar overlay, touch
|
||||
targets, no horizontal scroll).
|
||||
- **Accessibility:** keyboard navigation through all views. ARIA labels.
|
||||
Focus management on panel/dialog open/close.
|
||||
- **Performance:** virtual scrolling on note list for 1000+ notes.
|
||||
Graph 60fps with 500+ nodes. Lazy-load graph and CM6.
|
||||
|
||||
---
|
||||
|
||||
## Uniquely Armature
|
||||
|
||||
| Feature | Obsidian/Notion | Armature Notes |
|
||||
|---------|-----------------|----------------|
|
||||
| Extension slots | Plugin API (Obsidian) | Kernel-level composability — any package contributes to any slot without coupling |
|
||||
| Sharing | Obsidian Publish (paid), Notion sharing | Built on kernel resource model — same permission patterns as every other extension |
|
||||
| Panels | N/A | Notes is a panel provider — embed a notes view inside chat, dashboard, or any surface |
|
||||
| Multi-surface | Single window | Full surface + public surface + reference panel, all from one package |
|
||||
| Server-side logic | Local only (Obsidian) | Starlark backend — search, link resolution, access control run server-side |
|
||||
| Realtime | N/A (Obsidian), built-in (Notion) | Via `sw.realtime` — same primitive available to all extensions |
|
||||
| Cross-extension actions | Plugin API | `sw.actions` — any package can call `notes.create` or `notes.search` |
|
||||
| Self-hosted | Obsidian Sync (paid) | Runs on your infrastructure, your data, your rules |
|
||||
|
||||
The thesis: **Notes isn't just a note-taking app — it's proof that
|
||||
Armature's extension architecture can deliver a production-quality
|
||||
knowledge base that competes with purpose-built SaaS tools, while
|
||||
remaining decomposable and extensible.** And it has to *look and feel*
|
||||
like it competes, not just architecturally compete.
|
||||
|
||||
---
|
||||
|
||||
## Schema Summary
|
||||
|
||||
### Existing tables (no changes)
|
||||
|
||||
- `notes` — title, body, folder_id, creator_id, updated_at, pinned, archived
|
||||
- `tags` — note_id, tag
|
||||
- `links` — source_id, target_id, link_text
|
||||
- `folders` — name, parent_id, creator_id, sort_order
|
||||
|
||||
### New columns
|
||||
|
||||
- `notes.aliases` (text) — comma-separated aliases (v0.11.2)
|
||||
|
||||
### New tables
|
||||
|
||||
- `note_shares` — note_id, share_type, target_id, permission, share_token, created_by, created_at (v0.11.4)
|
||||
- `folder_shares` — folder_id, share_type, target_id, permission, share_token, created_by, created_at (v0.11.4)
|
||||
|
||||
---
|
||||
|
||||
## Settings Summary
|
||||
|
||||
### Existing
|
||||
|
||||
- `default_view` — "recent" or "pinned"
|
||||
- `editor_mode` — "rendered", "edit", or "split"
|
||||
|
||||
### New
|
||||
|
||||
| Setting | Type | Default | Version |
|
||||
|---------|------|---------|---------|
|
||||
| `daily_note_folder` | string | "Daily Notes" | v0.11.7 |
|
||||
| `daily_note_template` | string | `"# {{date}}\n\n"` | v0.11.7 |
|
||||
| `template_folder` | string | "Templates" | v0.11.7 |
|
||||
| `inline_preview` | boolean | false | v0.11.3 |
|
||||
| `graph_hide_orphans` | boolean | false | v0.11.5 |
|
||||
| `graph_cluster_by` | string | "folder" | v0.11.5 |
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| UI redesign as v0.11.0, before features | Every subsequent version builds on the visual foundation. Shipping features on an ugly base means reworking the UI of every feature when the redesign eventually happens. Front-loading avoids double work and ensures every version screenshot looks like a real product. |
|
||||
| Notes as first reference extension | Exercises more kernel primitives than any other candidate. Complex enough to prove the platform; familiar enough to be immediately useful. |
|
||||
| Dedicated v0.11.x series (11 versions) | Notes is too large for a single version. Each v0.11.x is independently shippable and CI-green. |
|
||||
| UX quality threaded through every version | Each version specifies its UX standard, not just functionality. No "make it pretty later" — every feature ships with its final visual quality. |
|
||||
| Share model via new tables | Notes sharing needs note/folder-specific semantics (inheritance, public URLs, share tokens). Purpose-built tables beat generic resource grants for this use case. |
|
||||
| Aliases as comma-separated text | Small set (1–3 per note). Separate table adds join complexity for marginal normalization benefit. |
|
||||
| d3-force for graph | Existing hand-rolled sim has poor convergence at 100+ nodes. d3-force is battle-tested, ~15KB. |
|
||||
| Transclusion rendered on view, not live-synced | Live-sync requires WebSocket subs per embedded note. Disproportionate complexity pre-MVP. |
|
||||
| Templates are just notes in a folder | No template schema. KISS. |
|
||||
| Inline preview as fourth editing mode | Obsidian's killer UX feature. Users who want WYSIWYG-ish editing without leaving markdown get it without a ProseMirror dependency — CM6 decorations handle it. |
|
||||
|
||||
---
|
||||
|
||||
## Dependency Chain
|
||||
|
||||
```
|
||||
v0.11.0 UI/UX Foundation ← EVERYTHING depends on this
|
||||
v0.11.1 Deep Folders ← foundation for folder sharing (v0.11.4)
|
||||
v0.11.2 Wikilinks ← foundation for transclusion (v0.11.8)
|
||||
v0.11.3 Live Preview ← foundation for inline preview + embeds (v0.11.8)
|
||||
v0.11.4 Sharing ← depends on deep folders (v0.11.1)
|
||||
v0.11.5 Graph + Outline ← independent (hardening existing features)
|
||||
v0.11.6 Quick Switcher ← independent (keyboard navigation)
|
||||
v0.11.7 Daily Notes ← depends on deep folders (v0.11.1) for daily folder
|
||||
v0.11.8 Transclusion ← depends on wikilinks (v0.11.2) + live preview (v0.11.3)
|
||||
v0.11.9 Composability ← independent (slot/action declarations)
|
||||
v0.11.10 Polish + Gate ← depends on all above
|
||||
```
|
||||
|
||||
Versions v0.11.5, v0.11.6, and v0.11.9 are independent and can be
|
||||
reordered if priorities shift.
|
||||
743
docs/DESIGN-panels.md
Normal file
743
docs/DESIGN-panels.md
Normal file
@@ -0,0 +1,743 @@
|
||||
# DESIGN: Panels — v0.10.x
|
||||
|
||||
## Status: Proposed
|
||||
|
||||
## Problem
|
||||
|
||||
Packages export one rendering granularity today: a **surface** — a
|
||||
full-page application that owns `#extension-mount`. There is no way
|
||||
for a package to offer a lightweight, composable view of itself that
|
||||
another surface can pull in.
|
||||
|
||||
Concrete example: the `notes` package provides a full note editor
|
||||
surface at `/s/notes`. When a user is in the `chat` surface and wants
|
||||
to reference their notes, the only option is to navigate away. There is
|
||||
no mechanism to embed a notes panel alongside chat, whether as a
|
||||
floating window, a docked sidebar, or a bottom strip.
|
||||
|
||||
This forces users into one-thing-at-a-time workflows and prevents the
|
||||
"extensions extending extensions" composability story from reaching the
|
||||
UI layer. Slots and actions allow injection of buttons and menu items,
|
||||
but not entire companion views.
|
||||
|
||||
Without kernel coordination, package authors who need this will roll
|
||||
their own floating containers, producing:
|
||||
|
||||
- **Z-index wars** between packages and kernel overlays (Dialog at 1000,
|
||||
debug at 9999, toast in between).
|
||||
- **Inconsistent drag/resize** behavior (touch support, bounds clamping,
|
||||
accessibility).
|
||||
- **No position persistence** — panels reset on every navigation.
|
||||
- **No layout negotiation** — docked panels can't tell the host surface
|
||||
to shrink.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- **Window management system.** This is not a tiling WM. The kernel
|
||||
provides a small set of presentation modes (floating, docked). Complex
|
||||
layouts are post-1.0 horizon.
|
||||
- **Cross-surface panel sharing at runtime.** A panel runs inside one
|
||||
host surface's page context. It is not an iframe or a separate
|
||||
browsing context.
|
||||
- **Panel-to-panel communication.** Panels communicate with their host
|
||||
surface (and each other) via `sw.events`. No new IPC mechanism.
|
||||
- **Server-side panel rendering.** Panels are frontend-only. The kernel
|
||||
resolves dependencies and serves JS; rendering happens in the browser.
|
||||
|
||||
## What Already Exists
|
||||
|
||||
**Three rendering tiers exist today; panels fill the gap between the
|
||||
first two:**
|
||||
|
||||
| Tier | Scope | Example |
|
||||
|------|-------|---------|
|
||||
| **Surface** | Full-page app, owns `#extension-mount` | Notes editor, Chat, Admin |
|
||||
| *(gap)* | *Composable companion view* | *Notes reference panel in Chat* |
|
||||
| **Block renderer** | Inline content unit in markdown | Mermaid diagram, KaTeX formula |
|
||||
|
||||
**Kernel primitives that panels build on:**
|
||||
|
||||
- `sw.ui.Dialog` — centered modal with focus trap, backdrop, z-index 1000.
|
||||
- `sw.ui.Drawer` — slide-in side panel (left/right), backdrop, z-index 1000.
|
||||
- `sw.events` — pub/sub event bus, local and realtime. Already the
|
||||
communication channel between surfaces and slot contributors.
|
||||
- `sw.storage` — per-user localStorage wrapper with namespaced keys.
|
||||
- `sw.slots` — named UI injection points with manifest declarations.
|
||||
- Manifest `depends` field — package dependency resolution at install time.
|
||||
|
||||
**What panels add:** A second rendering granularity between surfaces and
|
||||
block renderers, with kernel-managed presentation modes and lifecycle.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### 1. Three-Tier Rendering Hierarchy
|
||||
|
||||
After this change, every package can export up to three things:
|
||||
|
||||
```
|
||||
Surface — full-page app, owns the mount, has shell topbar slots
|
||||
Panel — composable view, kernel-managed container, presentation-agnostic
|
||||
Renderer — inline content block (sw.renderers)
|
||||
```
|
||||
|
||||
The key architectural property: **the panel component does not know its
|
||||
presentation mode.** It renders into whatever container the kernel
|
||||
provides. Floating, docked-right, docked-bottom — that is a shell/layout
|
||||
concern. The panel receives a mount element and a size. Nothing else.
|
||||
|
||||
### 2. Manifest Schema
|
||||
|
||||
#### Provider: `panels` field
|
||||
|
||||
The package that provides a panel declares it in its manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "notes",
|
||||
"surfaces": ["/notes"],
|
||||
"panels": {
|
||||
"reference": {
|
||||
"entry": "js/panels/reference.js",
|
||||
"title": "Notes",
|
||||
"icon": "📝",
|
||||
"description": "Searchable note list with quick preview",
|
||||
"min_width": 280,
|
||||
"min_height": 200,
|
||||
"default_width": 400,
|
||||
"default_height": 350
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Panel IDs are namespaced: `<package_id>.<panel_key>`. The notes
|
||||
reference panel above is addressed as `notes.reference` everywhere
|
||||
in the SDK and in consumer manifests.
|
||||
|
||||
A package may declare multiple panels:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "notes",
|
||||
"panels": {
|
||||
"reference": { "entry": "js/panels/reference.js", ... },
|
||||
"graph": { "entry": "js/panels/graph.js", ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Consumer: `panels` dependency
|
||||
|
||||
The consuming surface declares which panels it wants available:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "chat",
|
||||
"surfaces": ["/chat"],
|
||||
"panels": ["notes.reference"]
|
||||
}
|
||||
```
|
||||
|
||||
This is a **soft dependency**: the panel is available if the providing
|
||||
package is installed and enabled. If not, `sw.panels.open()` returns
|
||||
`false` and the consuming surface can degrade gracefully (hide the
|
||||
button, show a tooltip explaining the missing package).
|
||||
|
||||
This is distinct from `depends` (hard dependency — install fails without
|
||||
it). A consumer does not need the provider installed to function; the
|
||||
panel is an enhancement.
|
||||
|
||||
### 3. Panel Entry Point Contract
|
||||
|
||||
The panel JS module exports a single `mount` function. The kernel calls
|
||||
it with a DOM element and a context object. It returns a cleanup function.
|
||||
|
||||
```js
|
||||
// notes/js/panels/reference.js
|
||||
const { html } = window;
|
||||
const { render } = preact;
|
||||
|
||||
export function mount(el, ctx) {
|
||||
// ctx.sw — SDK instance
|
||||
// ctx.params — optional params from sw.panels.open()
|
||||
// ctx.panelId — 'notes.reference'
|
||||
// ctx.close — function to request close
|
||||
// ctx.resize — function(width, height) to request resize
|
||||
|
||||
render(html`<${NotesReference} sw=${ctx.sw} />`, el);
|
||||
|
||||
// Return cleanup
|
||||
return () => render(null, el);
|
||||
}
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- The panel must not assume any particular container size. Use CSS
|
||||
`width: 100%; height: 100%` and let the kernel container control
|
||||
dimensions.
|
||||
- The panel must not create its own overlay, backdrop, or drag handle.
|
||||
The kernel wraps the panel in the appropriate chrome.
|
||||
- The panel must not set `z-index` on anything. The kernel manages
|
||||
stacking.
|
||||
- The panel may use `ctx.close()` to request its own dismissal (e.g.
|
||||
user clicks an "X" inside the panel content area).
|
||||
- The panel may use `ctx.resize(w, h)` to suggest a new size, but the
|
||||
kernel may clamp or ignore the request.
|
||||
- Communication with the host surface is via `sw.events` — the same
|
||||
bus surfaces and slot contributors already use.
|
||||
|
||||
### 4. SDK API — `sw.panels`
|
||||
|
||||
New SDK module: `src/js/sw/sdk/panels.js`
|
||||
|
||||
```js
|
||||
export function createPanels(events, storage) {
|
||||
const _registry = new Map(); // panelId → manifest entry
|
||||
const _active = new Map(); // panelId → { el, cleanup, mode, ... }
|
||||
|
||||
return {
|
||||
/**
|
||||
* Register a panel from manifest data. Called by the kernel
|
||||
* during surface load — not by package code directly.
|
||||
*/
|
||||
_register(panelId, manifest) { ... },
|
||||
|
||||
/**
|
||||
* Open a panel. Lazy-loads the JS entry if not yet loaded.
|
||||
*
|
||||
* @param {string} panelId — e.g. 'notes.reference'
|
||||
* @param {object} opts
|
||||
* @param {string} opts.mode — 'floating' | 'docked-right' | 'docked-left' | 'docked-bottom'
|
||||
* @param {object} opts.params — passed to mount(el, ctx)
|
||||
* @param {number} opts.width — override default width
|
||||
* @param {number} opts.height — override default height
|
||||
* @returns {Promise<boolean>} — false if panel not available
|
||||
*/
|
||||
async open(panelId, opts = {}) { ... },
|
||||
|
||||
/**
|
||||
* Close a panel. Calls cleanup, removes container.
|
||||
*/
|
||||
close(panelId) { ... },
|
||||
|
||||
/**
|
||||
* Toggle open/close.
|
||||
*/
|
||||
toggle(panelId, opts = {}) { ... },
|
||||
|
||||
/**
|
||||
* Check if a panel is currently open.
|
||||
*/
|
||||
isOpen(panelId) { ... },
|
||||
|
||||
/**
|
||||
* Check if a panel is available (provider installed + enabled).
|
||||
*/
|
||||
isAvailable(panelId) { ... },
|
||||
|
||||
/**
|
||||
* List available panel IDs for the current surface.
|
||||
*/
|
||||
list() { ... },
|
||||
|
||||
/**
|
||||
* List currently open panel IDs.
|
||||
*/
|
||||
active() { ... },
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Exposed on `sw` as:**
|
||||
|
||||
```js
|
||||
sw.panels.open('notes.reference', { mode: 'floating' });
|
||||
sw.panels.open('notes.reference', { mode: 'docked-right' });
|
||||
sw.panels.close('notes.reference');
|
||||
sw.panels.toggle('notes.reference');
|
||||
sw.panels.isOpen('notes.reference'); // boolean
|
||||
sw.panels.isAvailable('notes.reference'); // boolean
|
||||
sw.panels.list(); // ['notes.reference', 'notes.graph']
|
||||
sw.panels.active(); // ['notes.reference']
|
||||
```
|
||||
|
||||
### 5. Presentation Modes
|
||||
|
||||
#### Floating (v0.10.1)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ ← │ Chat │ 🔔 │ 👤 │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Host surface (interactive) │
|
||||
│ ┌───────────────────┐ │
|
||||
│ │ ≡ Notes ─ ✕ │ │
|
||||
│ │ │ │
|
||||
│ │ [panel content] │ │
|
||||
│ │ │ │
|
||||
│ │ ◢ │ │
|
||||
│ └───────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Draggable via title bar (pointer + touch events).
|
||||
- Resize via bottom-right handle.
|
||||
- Viewport bounds clamping — panel cannot be dragged fully offscreen
|
||||
(at least 48px of title bar must remain visible).
|
||||
- No backdrop — host surface remains fully interactive.
|
||||
- Z-index above surfaces, below Dialog/Drawer overlays.
|
||||
- On focus or drag-start, panel gets next-z (multiple floating panels
|
||||
stack correctly).
|
||||
- Escape key: configurable — close panel or do nothing (default: close).
|
||||
- Position and size persisted in `sw.storage` keyed by panel ID.
|
||||
|
||||
**Title bar chrome (kernel-provided):**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────┐
|
||||
│ ≡ │ {icon} {title} │ ─ ✕ │
|
||||
└────────────────────────────────────┘
|
||||
│ │ │
|
||||
drag handle minimize close
|
||||
```
|
||||
|
||||
- **≡** Drag handle / grip indicator.
|
||||
- **─** Minimize: collapses to a small pill at the bottom of the viewport.
|
||||
Click to restore. Minimized state persisted.
|
||||
- **✕** Close: calls cleanup, removes panel.
|
||||
- Title and icon from manifest.
|
||||
|
||||
#### Docked (v0.10.2)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ ← │ Chat │ 🔔 │ 👤 │
|
||||
├──────────────────────────────┬──┬────────────────────────┤
|
||||
│ │▐▐│ 📝 Notes ✕ │
|
||||
│ Host surface │▐▐│ │
|
||||
│ (shrinks to fit) │▐▐│ [panel content] │
|
||||
│ │▐▐│ │
|
||||
│ │▐▐│ │
|
||||
└──────────────────────────────┴──┴────────────────────────┘
|
||||
resize
|
||||
gutter
|
||||
```
|
||||
|
||||
- Docked panels consume space from the host surface. The
|
||||
`#extension-mount` area shrinks via CSS flex or grid.
|
||||
- Resize gutter between host and panel (drag to adjust split).
|
||||
- Three dock positions: right (default), left, bottom.
|
||||
- No z-index concerns — docked panels are in normal flow.
|
||||
- Split ratio persisted in `sw.storage` keyed by panel ID + mode.
|
||||
- Close button removes panel, host surface reclaims full width/height.
|
||||
|
||||
#### Mode Transitions (v0.10.2)
|
||||
|
||||
A floating panel can be dragged to a dock zone (edge highlight on
|
||||
hover). A docked panel's title bar can be dragged away to float.
|
||||
The panel component is never unmounted during a mode transition — only
|
||||
the wrapping container changes.
|
||||
|
||||
The user controls presentation mode. Surfaces can suggest a default
|
||||
mode in `sw.panels.open()`, but the user's last-used mode (persisted)
|
||||
takes precedence.
|
||||
|
||||
### 6. Z-Index Strategy
|
||||
|
||||
Panels slot into the existing stacking context:
|
||||
|
||||
| Layer | Z-Index | Contents |
|
||||
|-------|---------|----------|
|
||||
| Base | 0 | Surface content, docked panels (in flow) |
|
||||
| Floating panels | 100–199 | `sw.panels` floating mode (auto-incrementing) |
|
||||
| Shell overlays | 200–299 | Dropdowns, tooltips, menus |
|
||||
| Drawer | 1000 | `sw.ui.Drawer` |
|
||||
| Dialog | 1000 | `sw.ui.Dialog` + confirm/prompt |
|
||||
| Toast | 1100 | `sw.ui.toast` |
|
||||
| Debug | 9999 | Debug panel |
|
||||
|
||||
Floating panels start at z-index 100. Each panel that receives focus
|
||||
or drag-start gets `max(current panel z-indexes) + 1`, capped at 199.
|
||||
If the cap is hit, all floating panels are renumbered starting from 100
|
||||
(maintaining relative order). This prevents unbounded z-index growth.
|
||||
|
||||
When a Dialog or Drawer opens, floating panels remain visible beneath
|
||||
the overlay — they do not auto-hide. This preserves the "see while
|
||||
doing" property.
|
||||
|
||||
### 7. Dependency Resolution + Loading
|
||||
|
||||
**At install time:** The kernel validates that panel dependencies
|
||||
reference valid `<package>.<panel>` identifiers. Invalid references
|
||||
are warnings (soft deps), not errors.
|
||||
|
||||
**At surface load time:**
|
||||
|
||||
1. Kernel reads the surface's `panels` array from its manifest.
|
||||
2. For each declared panel, check if the providing package is installed
|
||||
and enabled. Build the available panel list.
|
||||
3. Register available panels into `sw.panels._register()` with their
|
||||
manifest metadata (entry path, title, icon, size constraints).
|
||||
4. Panel JS is **not** loaded yet — lazy loading on first
|
||||
`sw.panels.open()`.
|
||||
|
||||
**On `sw.panels.open(panelId)`:**
|
||||
|
||||
1. If panel JS not yet loaded: `import()` the entry module from the
|
||||
package's static asset path
|
||||
(`/api/v1/ext/<package>/static/js/panels/<name>.js`).
|
||||
2. Call `module.mount(el, ctx)` where `el` is the content area of the
|
||||
kernel-provided container (FloatingPanel or DockedPanel component).
|
||||
3. Store the cleanup function returned by `mount()`.
|
||||
4. Emit `panels.opened` event with `{ panelId, mode }`.
|
||||
|
||||
**On `sw.panels.close(panelId)`:**
|
||||
|
||||
1. Call stored cleanup function.
|
||||
2. Remove kernel container from DOM.
|
||||
3. Emit `panels.closed` event with `{ panelId }`.
|
||||
|
||||
### 8. Panel Communication
|
||||
|
||||
No new mechanism. Panels and their host surface share the same
|
||||
`sw.events` bus and `sw` SDK instance. Convention-based event
|
||||
namespacing:
|
||||
|
||||
```js
|
||||
// Host surface requests the notes panel to show a specific note
|
||||
sw.emit('panel.notes.reference.show', { noteId: '...' });
|
||||
|
||||
// Panel listens
|
||||
sw.on('panel.notes.reference.show', (data) => {
|
||||
navigateToNote(data.noteId);
|
||||
});
|
||||
|
||||
// Panel notifies the host that a note was selected
|
||||
sw.emit('panel.notes.reference.selected', { noteId: '...', title: '...' });
|
||||
|
||||
// Host listens
|
||||
sw.on('panel.notes.reference.selected', (data) => {
|
||||
insertNoteLink(data);
|
||||
});
|
||||
```
|
||||
|
||||
The `params` object in `sw.panels.open()` provides initial state:
|
||||
|
||||
```js
|
||||
sw.panels.open('notes.reference', {
|
||||
mode: 'floating',
|
||||
params: { folderId: 'inbox', highlight: noteId }
|
||||
});
|
||||
```
|
||||
|
||||
Params are passed to `mount(el, ctx)` as `ctx.params`. This avoids
|
||||
the need for an event round-trip on first open.
|
||||
|
||||
### 9. CSS
|
||||
|
||||
New file: `src/css/panels.css`
|
||||
|
||||
**Floating panel:**
|
||||
|
||||
```css
|
||||
.sw-panel-floating {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--bg-surface);
|
||||
box-shadow: var(--shadow-lg);
|
||||
overflow: hidden;
|
||||
/* z-index set dynamically by JS */
|
||||
}
|
||||
|
||||
.sw-panel-floating__titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
background: var(--bg-raised);
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sw-panel-floating__titlebar:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.sw-panel-floating__title {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sw-panel-floating__actions {
|
||||
display: flex;
|
||||
gap: var(--sp-1);
|
||||
}
|
||||
|
||||
.sw-panel-floating__action {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-3);
|
||||
cursor: pointer;
|
||||
padding: 2px 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.sw-panel-floating__action:hover {
|
||||
color: var(--text);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.sw-panel-floating__body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.sw-panel-floating__resize {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
/* Minimized pill */
|
||||
.sw-panel-pill {
|
||||
position: fixed;
|
||||
bottom: var(--sp-3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: var(--text-2);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sw-panel-pill:hover {
|
||||
color: var(--text);
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
```
|
||||
|
||||
**Docked panel:**
|
||||
|
||||
```css
|
||||
.sw-panel-dock-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sw-panel-dock-container--bottom {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sw-panel-dock-gutter {
|
||||
flex-shrink: 0;
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--border);
|
||||
cursor: col-resize;
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.sw-panel-dock-container--bottom .sw-panel-dock-gutter {
|
||||
cursor: row-resize;
|
||||
width: auto;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.sw-panel-docked {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: var(--bg-surface);
|
||||
}
|
||||
|
||||
.sw-panel-docked__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-2);
|
||||
padding: var(--sp-2) var(--sp-3);
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-raised);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sw-panel-docked__body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
```
|
||||
|
||||
**Touch targets (mobile):**
|
||||
|
||||
```css
|
||||
@media (max-width: 768px) {
|
||||
.sw-panel-floating__action {
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sw-panel-floating__titlebar {
|
||||
padding: var(--sp-3) var(--sp-4);
|
||||
}
|
||||
|
||||
.sw-panel-dock-gutter {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.sw-panel-dock-container--bottom .sw-panel-dock-gutter {
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 10. Backend Changes
|
||||
|
||||
**Minimal.** The kernel backend already serves extension static assets
|
||||
and reads manifests. Changes:
|
||||
|
||||
1. **Manifest parsing** (`server/extensions/manifest.go`): Parse the
|
||||
`panels` field from provider manifests (map of panel key → entry
|
||||
metadata). Parse the `panels` field from consumer manifests (array
|
||||
of panel ID strings). Store in the existing `PackageRecord` struct.
|
||||
|
||||
2. **Surface load endpoint** (`/api/v1/surfaces/:id`): Include resolved
|
||||
panel metadata in the response — for each panel declared by the
|
||||
surface, include the provider's panel manifest entry (title, icon,
|
||||
entry path, size constraints) if the provider is installed and
|
||||
enabled. This lets the frontend `sw.panels._register()` at surface
|
||||
boot without additional API calls.
|
||||
|
||||
3. **Static asset serving**: Already works — panels are served from
|
||||
the same package static path as surface JS
|
||||
(`/api/v1/ext/<package>/static/...`).
|
||||
|
||||
**No new tables.** No new migrations. Panel metadata lives in the
|
||||
existing manifest JSON stored in the packages table.
|
||||
|
||||
### 11. Admin Visibility
|
||||
|
||||
The admin packages page already shows package manifests. With panels:
|
||||
|
||||
- The package detail view shows declared panels with their metadata.
|
||||
- The dependency view shows which surfaces consume which panels.
|
||||
- Panel availability warnings surface when a provider package is
|
||||
disabled but consumers reference its panels.
|
||||
|
||||
This falls out naturally from the existing manifest introspection —
|
||||
no new admin surfaces needed.
|
||||
|
||||
---
|
||||
|
||||
## Event Inventory
|
||||
|
||||
| Event | Emitted by | Payload |
|
||||
|-------|-----------|---------|
|
||||
| `panels.opened` | Kernel (sw.panels) | `{ panelId, mode }` |
|
||||
| `panels.closed` | Kernel (sw.panels) | `{ panelId }` |
|
||||
| `panels.mode_changed` | Kernel (sw.panels) | `{ panelId, from, to }` |
|
||||
| `panels.focused` | Kernel (sw.panels) | `{ panelId }` |
|
||||
| `panel.<id>.*` | Convention (package code) | Package-defined |
|
||||
|
||||
All panel events are `localOnly: true` — they do not broadcast over
|
||||
the WebSocket. Panels are a frontend-only concern.
|
||||
|
||||
---
|
||||
|
||||
## Persistence
|
||||
|
||||
Panel state persisted in `sw.storage` (localStorage wrapper):
|
||||
|
||||
| Key | Value | Scope |
|
||||
|-----|-------|-------|
|
||||
| `panel_pos_<panelId>` | `{ x, y, w, h }` | Floating position + size |
|
||||
| `panel_mode_<panelId>` | `'floating' \| 'docked-right' \| ...` | Last-used presentation mode |
|
||||
| `panel_split_<panelId>` | `{ ratio: 0.3 }` | Docked split ratio |
|
||||
| `panel_minimized_<panelId>` | `true` | Minimized state |
|
||||
|
||||
Persisted per-user (sw.storage is already user-scoped). Cleared when
|
||||
the providing package is uninstalled.
|
||||
|
||||
---
|
||||
|
||||
## Version Plan
|
||||
|
||||
| Version | Title | Scope |
|
||||
|---------|-------|-------|
|
||||
| v0.10.0 | Panel Manifest + Lifecycle | Manifest `panels` field (provider + consumer). Backend parsing. `sw.panels` SDK module (register, open, close, toggle, isOpen, isAvailable, list). Lazy JS loading pipeline. No presentation UI yet — panels render into a plain unstyled container for contract validation. |
|
||||
| v0.10.1 | FloatingPanel Primitive | `FloatingPanel` Preact component with drag, resize, minimize, z-index stacking, viewport bounds, position persistence. `panels.css` floating section. Touch support. |
|
||||
| v0.10.2 | Docked Panels + Mode Transitions | `DockedPanel` component. Layout negotiation (flex resize of `#extension-mount`). Resize gutter. Drag-to-dock / drag-to-float transitions. `panels.css` docked section. Split ratio persistence. |
|
||||
| v0.10.3 | Panel Communication Patterns | Event namespace conventions documented. `ctx.params` for initial state. SDK helper: `sw.panels.send(panelId, event, data)` as sugar over `sw.emit('panel.' + panelId + '.' + event, data)`. Panel communication section in extension developer guide. |
|
||||
| v0.10.4 | Reference Panel: Notes | Notes package ships `panels.reference` — searchable note list with quick preview. Chat declares it as a consumer. End-to-end proof: open notes panel in chat, select a note, insert link. Test runner coverage. |
|
||||
|
||||
Each version independently CI-green. v0.10.0 is the foundation that
|
||||
all subsequent versions build on.
|
||||
|
||||
---
|
||||
|
||||
## Changeset Plan — v0.10.0
|
||||
|
||||
| CS | Scope | Description |
|
||||
|----|-------|-------------|
|
||||
| CS-1 | Backend (Go) | Manifest parsing: `panels` field on provider + consumer manifests. `PackageRecord` struct update. Surface load endpoint includes resolved panel metadata. |
|
||||
| CS-2 | Frontend (JS) | `sw.panels` SDK module: `createPanels()`, register, open, close, toggle, isOpen, isAvailable, list, active. Lazy `import()` loader. Unstyled container mount. |
|
||||
| CS-3 | Frontend (JS) | SDK boot integration: wire `sw.panels` into SDK boot sequence. Register panels from `__MANIFEST__` surface data. |
|
||||
| CS-4 | Frontend (JS) | Panel lifecycle events: `panels.opened`, `panels.closed`, `panels.focused`. |
|
||||
| CS-5 | Tests | SDK test runner: panel registration, open/close lifecycle, lazy loading, availability checks, event emission. |
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions
|
||||
|
||||
| Decision | Rationale |
|
||||
|----------|-----------|
|
||||
| Kernel primitive, not package-level | Z-index coordination, drag/resize consistency, layout negotiation, and position persistence are all kernel concerns. Multiple packages need this. |
|
||||
| Soft dependency (not `depends`) | Chat should work without notes installed. Panel availability is a runtime check, not an install-time requirement. |
|
||||
| Presentation-agnostic panel contract | `mount(el, ctx)` / cleanup is the entire API surface. Panels never know if they are floating or docked. This lets us add new modes without touching panel code. |
|
||||
| Lazy loading | Panel JS is not loaded until first `sw.panels.open()`. Surfaces that declare panels but the user never opens them pay zero cost. |
|
||||
| Event bus for communication | `sw.events` already exists and is the established pattern for cross-package communication. No new mechanism needed. |
|
||||
| No new backend tables | Panel metadata is manifest data stored in the existing packages table JSON. No schema changes. |
|
||||
| Z-index band 100–199 | Above surface content, below shell overlays and Dialog/Drawer. Renumber on cap to prevent unbounded growth. |
|
||||
| User controls mode, surface suggests | The surface can pass `mode: 'docked-right'` as a default, but the user's persisted mode preference wins. Users know their own workflow. |
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations (post-v0.10.x)
|
||||
|
||||
- **User-composable layouts.** Power users drag panels from different
|
||||
packages into a persistent workspace layout. Requires a layout
|
||||
persistence model beyond per-panel position.
|
||||
- **Panel marketplace metadata.** Panels become a discoverability
|
||||
feature: "this package provides 2 surfaces and 3 panels."
|
||||
- **Multi-monitor.** Pop a floating panel into a separate browser
|
||||
window via `window.open()` + `SharedWorker` for event bus. Very
|
||||
post-1.0.
|
||||
- **Keyboard shortcuts.** `Ctrl+Shift+N` opens the notes panel.
|
||||
Requires a keybinding registry (not yet a kernel primitive).
|
||||
@@ -5,7 +5,7 @@
|
||||
## 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
|
||||
A full audit (v0.7.0 surface audit) 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.
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
# Armature Usability Survey — Automated Checklist
|
||||
|
||||
> **Purpose**: Machine-auditable quality gate for the Armature UI.
|
||||
> Run all scripts first, then walk each section. A FAIL in any section blocks release.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Run these scripts from the project root and save their output:
|
||||
|
||||
```bash
|
||||
bash scripts/generate-ui-inventory.sh > ui-inventory.json
|
||||
bash scripts/check-contrast.sh > contrast-report.txt
|
||||
bash scripts/generate-coverage-matrix.sh > coverage-matrix.md
|
||||
bash scripts/audit-touch-targets.sh > touch-targets-report.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Viewport Correctness
|
||||
|
||||
**Pass criteria:**
|
||||
- No CSS file uses `100vh` (should be `100%` or `100dvh`)
|
||||
- `.sw-shell` uses `height: 100%`, not `100vh`
|
||||
- All extension surfaces use `height: 100%`
|
||||
- No `transform: scale()` for zoom (should use CSS `zoom`)
|
||||
|
||||
**Files to inspect:**
|
||||
- `src/css/sw-shell.css`
|
||||
- `src/css/layout.css`
|
||||
- `packages/*/css/main.css`
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
grep -rn '100vh' src/css/ packages/*/css/ --include='*.css'
|
||||
grep -rn 'transform.*scale' src/css/ packages/*/css/ --include='*.css'
|
||||
```
|
||||
|
||||
**Result:** PASS if zero matches. FAIL if any `100vh` or `transform: scale()` for layout sizing.
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Banner Integration
|
||||
|
||||
**Pass criteria:**
|
||||
- Banners are in-flow (no `position: fixed` on banner elements)
|
||||
- `--banner-top-height` and `--banner-bottom-height` are defined in `:root`
|
||||
- Shell layout accounts for banner height via CSS variables, not hardcoded px
|
||||
- No surface hardcodes `28px` or other banner height values
|
||||
|
||||
**Files to inspect:**
|
||||
- `src/css/sw-shell.css`
|
||||
- `src/css/variables.css` (`:root` block)
|
||||
- `src/js/sw/shell/app-shell.js`
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
grep -n 'position.*fixed' src/css/sw-shell.css | grep -i banner
|
||||
grep -n '28px' src/css/ -r --include='*.css'
|
||||
grep -n 'banner-top-height\|banner-bottom-height' src/css/variables.css
|
||||
```
|
||||
|
||||
**Result:** PASS if banners are in-flow and height is variable-driven. FAIL if fixed positioning or hardcoded heights.
|
||||
|
||||
---
|
||||
|
||||
## Section 3: Responsive Behavior
|
||||
|
||||
**Pass criteria:**
|
||||
- Kernel CSS uses `768px` (mobile) and `1024px` (tablet) breakpoints
|
||||
- No hardcoded widths that break below 768px (except intentional min-widths on dialogs)
|
||||
- Sidebar collapses on mobile
|
||||
- Extension surfaces adapt to narrow viewports
|
||||
|
||||
**Files to inspect:**
|
||||
- `src/css/layout.css`
|
||||
- `src/css/surfaces.css`
|
||||
- `packages/*/css/main.css`
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
# Verify breakpoints used
|
||||
grep -rn '@media.*max-width' src/css/ --include='*.css' | grep -v '768\|1024'
|
||||
# Check for hardcoded widths
|
||||
grep -rn 'width:.*[0-9]\+px' src/css/layout.css | grep -v 'max-width\|min-width\|--'
|
||||
```
|
||||
|
||||
**Result:** PASS if only 768px and 1024px breakpoints. WARN if other breakpoints exist but are justified. FAIL if layout breaks below 768px.
|
||||
|
||||
---
|
||||
|
||||
## Section 4: Styling Consistency
|
||||
|
||||
**Pass criteria:**
|
||||
- All spacing uses `--sp-*` tokens (no raw px for padding/margin/gap > 3px)
|
||||
- All `border-radius` uses `--radius-sm`, `--radius`, or `--radius-lg`
|
||||
- All `font-family` uses `var(--font)` or `var(--mono)`
|
||||
- No external font CDN imports (`@import url(` or Google Fonts references)
|
||||
- No stale fallback colors (`#b38a4e` or other non-token hex in property values)
|
||||
|
||||
**Files to inspect:**
|
||||
- All `src/css/*.css`
|
||||
- `packages/*/css/main.css`
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
# Raw px spacing (padding/margin/gap > 3px, not inside var())
|
||||
grep -rnE '(padding|margin|gap):\s*[0-9]+(px|rem)' src/css/ packages/*/css/ --include='*.css' | grep -v 'var(--' | grep -v '0px\|1px\|2px\|3px'
|
||||
# Raw border-radius
|
||||
grep -rn 'border-radius:' src/css/ packages/*/css/ --include='*.css' | grep -v 'var(--radius'
|
||||
# External fonts
|
||||
grep -rn '@import url\|fonts.googleapis' src/css/ --include='*.css'
|
||||
# Stale fallback gold color
|
||||
grep -rn '#b38a4e' src/css/ packages/*/css/ --include='*.css'
|
||||
```
|
||||
|
||||
**Result:** PASS if zero non-token values (excluding reset/keyframe contexts). WARN for 1-3 edge cases with justification. FAIL for systematic violations.
|
||||
|
||||
---
|
||||
|
||||
## Section 5: Accessibility — Contrast
|
||||
|
||||
**Pass criteria:**
|
||||
- `contrast-report.txt` shows all PASS for normal text (4.5:1 ratio)
|
||||
- No FAIL results in either dark or light theme
|
||||
|
||||
**Files to inspect:**
|
||||
- `contrast-report.txt` (generated above)
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
grep 'FAIL' contrast-report.txt
|
||||
```
|
||||
|
||||
**Result:** PASS if zero FAIL lines. FAIL if any contrast violation.
|
||||
|
||||
---
|
||||
|
||||
## Section 6: Accessibility — Touch Targets
|
||||
|
||||
**Pass criteria:**
|
||||
- `touch-targets-report.txt` shows zero violations
|
||||
- All close buttons have `min-width: 44px; min-height: 44px` in `@media (max-width: 768px)`
|
||||
- Menu items have `min-height: 44px` on mobile (already done in `sw-primitives.css`)
|
||||
|
||||
**Files to inspect:**
|
||||
- `touch-targets-report.txt` (generated above)
|
||||
- `src/css/sw-primitives.css` — close button rules
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
grep 'FAIL\|MISSING' touch-targets-report.txt
|
||||
```
|
||||
|
||||
**Result:** PASS if zero violations. FAIL if any close button lacks mobile touch target.
|
||||
|
||||
---
|
||||
|
||||
## Section 7: Accessibility — Focus Indicators
|
||||
|
||||
**Pass criteria:**
|
||||
- All interactive primitives have `:focus-visible` styles
|
||||
- No `outline: none` without a replacement focus indicator
|
||||
- Focus ring is visible on both dark and light themes
|
||||
|
||||
**Files to inspect:**
|
||||
- `src/css/sw-primitives.css`
|
||||
- `src/css/primitives.css`
|
||||
- `src/css/variables.css`
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
# Check for focus-visible on key primitives
|
||||
for cls in sw-btn sw-input sw-dropdown__trigger sw-menu__item sw-tabs__tab; do
|
||||
echo -n "$cls: "
|
||||
grep -c "\.${cls}.*:focus-visible\|\.${cls}:focus-visible" src/css/sw-primitives.css src/css/primitives.css 2>/dev/null || echo "0"
|
||||
done
|
||||
# Check for outline:none without replacement
|
||||
grep -n 'outline.*none\|outline.*0' src/css/*.css | grep -v 'focus-visible\|focus-within'
|
||||
```
|
||||
|
||||
**Result:** PASS if all 5 key primitives have `:focus-visible`. WARN if outline:none exists with adequate replacement. FAIL if missing focus indicators.
|
||||
|
||||
---
|
||||
|
||||
## Section 8: Component Uniformity
|
||||
|
||||
**Pass criteria:**
|
||||
- `coverage-matrix.md` shows no deprecated component usage (no ⚠ in the deprecated row)
|
||||
- All surfaces use `sw-*` primitives, not old `.btn-*`, `.toast`, `.popup-menu`
|
||||
|
||||
**Files to inspect:**
|
||||
- `coverage-matrix.md` (generated above)
|
||||
|
||||
**How to check:**
|
||||
```bash
|
||||
grep '⚠' coverage-matrix.md
|
||||
```
|
||||
|
||||
**Result:** PASS if zero ⚠ markers. FAIL if any deprecated component still in use.
|
||||
|
||||
---
|
||||
|
||||
## Scoring
|
||||
|
||||
| Section | Weight | Result |
|
||||
|---------|--------|--------|
|
||||
| 1. Viewport Correctness | Required | |
|
||||
| 2. Banner Integration | Required | |
|
||||
| 3. Responsive Behavior | Required | |
|
||||
| 4. Styling Consistency | Required | |
|
||||
| 5. Contrast | Required | |
|
||||
| 6. Touch Targets | Required | |
|
||||
| 7. Focus Indicators | Required | |
|
||||
| 8. Component Uniformity | Required | |
|
||||
|
||||
**Overall:** PASS requires all sections PASS or WARN. Any FAIL blocks the release.
|
||||
|
||||
---
|
||||
|
||||
## After the Survey
|
||||
|
||||
1. Fix all FAIL items
|
||||
2. Re-run affected scripts to confirm fixes
|
||||
3. Re-run the full survey
|
||||
4. Tag `v0.6.16` only after a clean survey pass
|
||||
Reference in New Issue
Block a user