# Changelog
All notable changes to Armature are documented here.
## v0.7.7 — API Tokens + Extension Permissions
Personal access tokens (PATs) for programmatic API access, plus extension-declared
user permissions for backend RBAC enforcement.
**API Tokens (PATs)**
- Migration 015: `api_tokens` table (PG + SQLite) with SHA-256 hash, prefix, JSON permissions, expiry
- Token store interface + PG/SQLite implementations (Create, GetByHash, ListForUser, Revoke, CleanExpired, UpdateLastUsed)
- `POST /api/v1/auth/tokens` — create token (returns plaintext once), permissions validated as subset of user's
- `GET /api/v1/auth/tokens` — list my tokens; `DELETE /api/v1/auth/tokens/:id` — revoke
- `POST /api/v1/admin/tokens` — create token for any user (audit logged as `admin.token.create`)
- Auth middleware: `Bearer arm_pat_...` tokens validated alongside JWTs, user active check, fire-and-forget `last_used_at` update
- Permission scoping: PAT permissions used directly at request time (git model — retained until revoked)
- `auth.HashToken()` shared SHA-256 utility (replaces local `hashToken()` in auth.go)
- Settings UI: API Tokens tab with create form, permission checkboxes, copy-once display, revoke button
- Admin UI: "PAT" button on user rows creates tokens for any user
- `BootstrapPAT`: `ARMATURE_BOOTSTRAP_PAT=true` env var creates admin PAT at startup, writes to `/tmp/armature-admin-pat.txt`
- E2E smoke test: reads bootstrap PAT before falling back to login flow
**Extension-Declared User Permissions**
- Dynamic permission registry: `RegisterExtensionPermissions()` / `UnregisterExtensionPermissions()` with RWMutex
- `AllPermissionsWithExtensions()` returns kernel + extension permissions; `AllPermissionsGrouped()` for admin UI
- `user_permissions` manifest field: extensions declare user-facing permissions
- `gate_permission` manifest field: ext_api.go checks user permission before calling `on_request`
- `req["permissions"]` in Starlark request dict: user's effective permissions included for inline checks
- `permissions.check(user_id, perm)` Starlark module: read-only permission check, always available (no sandbox gate)
- Group UI: permissions grouped by source (Platform / package name) with section headings
- Boot-time scan: `RegisterAllExtensionUserPermissions()` populates registry from active packages
- Uninstall cleanup: `UnregisterExtensionPermissions()` called on package delete
**Tests:** 10 new tests (7 handler + 3 auth registry)
---
## v0.7.6 — Code Hygiene + Test Coverage
**Critical Fixes**
- Removed `channels` from `allowedViews` in `db_module.go` — `ext_view_channels` does not exist; `db.view("channels")` would crash
- Fixed 5 dead API routes in `workflow.html` — rewired to public workflow API (`/api/v1/public/workflows/`)
- Fixed `RenderWorkflow` handler to pass route `:id` param as entry token (was reading unset `channel_id`)
**Dead Code Removal**
- Deleted `SeedTestChannel()` from `database/testhelper.go` (inserted into nonexistent channels table)
- Removed `RunContext.ChannelID` from `sandbox/runner.go` (vestigial, unused)
- Removed `webhook.Payload.ChannelID` field (channels no longer exist — breaking webhook JSON change)
- Fixed stale comments in `storage.go`, `prometheus.go`, `workflow_module.go` referencing dead `/channels` paths
**Migration Hygiene**
- Added SQLite placeholder `013_cluster_registry.sql` (PG-only migration, aligns numbering)
- Renumbered SQLite `013_test_runner_type.sql` → `014` to match PG (compat rename in `migrate.go`)
- Documented missing migration 008 in both 009 files (merged into 007 during pre-1.0 consolidation)
**Test Coverage**
- 82 new workflow routing tests (`routing_test.go`): `ResolveNextStage`, `ResolveStageByName`, `ParseStageConfig`, `evaluateCondition` with all 10 operators
- 10 new middleware tests (`permissions_test.go`): `RequirePermission`, `RequireAdmin`, permission caching, `RateLimiter` (allow/deny/fail-open)
**Bug Fixes**
- Removed dead Admin "Storage" tab from System category (backend endpoint preserved for future Monitoring use)
- Fixed backup download: `sw.auth.token()` → `sw.auth._getToken()` — both "Download Backup" and server backup download now work
## v0.7.5 — Headless E2E + CI Gate
**CI Gating Redesign**
- `VERSION` and `scripts/*` no longer trigger frontend/backend tests — deploy-only (pipeline v0.18.0)
- `docs/*` changes now trigger build-and-deploy (docs are served in-app via Docs surface)
- Path gating comment block updated to reflect corrected model
**E2E Smoke Test**
- `ci/e2e-smoke-test.sh` — authenticates as admin, discovers surfaces, runs Playwright navigation test
- `ci/e2e-smoke-driver.js` — visits every surface, asserts shell topbar present, no JS console errors, home link works
- Screenshot-on-failure: full-page PNG + console log saved as CI artifacts
- Baseline screenshots captured for every surface (informational, not a gate)
**CI Pipeline Integration**
- `test-runners` stage re-enabled with broad trigger condition (BE/FE/packages/infra)
- New `e2e-smoke` stage: boots server via docker-compose, runs Playwright smoke test, failure blocks merge
- `docker-compose.ci.yml` gains `e2e-smoke` service (same Playwright v1.52.0 image)
- `build-and-deploy` now depends on both `test-runners` and `e2e-smoke`
**Documentation**
- `docs/DESIGN-storage-primitives.md` — v0.8.x storage primitives design (files module, workspace module, capability negotiation, vector columns)
- `docs/DESIGN-extension-composability.md` — v0.8.4 composability design (slots/contributes manifest fields, lib.require relaxation, SDK helpers)
- `ROADMAP.md` expanded through v1.0: v0.8.x storage primitives, v0.9.x reference extensions, v0.10.x sidecar tier, v1.0 gate criteria, design principles, full design decisions log
## v0.7.4 — Documentation + Deferred Surface Work
**Docs Category Grouping**
- Backend `Category` field on `docEntry` struct in `server/handlers/docs.go`
- Frontend sidebar groups docs by category with `.docs-category-heading` CSS
- Four categories: Getting Started, Platform, Extension Development, Operations
- 14 docs in ordered list (was 7 ordered + 3 auto-discovered)
**New Documentation (4 guides)**
- `PERMISSIONS-AND-GROUPS.md` — RBAC model, 7 permission slugs, system/custom groups, settings cascade, extension permissions
- `WORKFLOWS.md` — Entry modes, stage modes/types/audiences, signoff gates, SLA enforcement, branch rules, Starlark hooks
- `STARLARK-REFERENCE.md` — Sandbox constraints, 10 modules with function signatures and permission gates, example hook script
- `FRONTEND-JS-GUIDE.md` — Preact+htm runtime, 16 SDK modules with API reference, shell topbar patterns, CSS contract
**Extension Guide Updates**
- `config_section` manifest field documented: schema, backend discovery (`configSectionsForSurface()`), frontend `__CONFIG_SECTIONS__` contract, example component
- Starlark Sandbox API section replaced with pointer to new Starlark Reference
**Docs Content Refresh**
- GETTING-STARTED: `sb_data` → `armature_data` volume name
- ARCHITECTURE: `sb.register()`/`sb.ns()` → `sw` SDK references, shell topbar mention
- DEPLOYMENT: `sb_storage` → `armature_storage`, added `TLS_MODE` env var
- TUTORIAL-FIRST-EXTENSION: `--bg-2` → `--bg-secondary` CSS variable
- EXTENSION-CSS: self-hosted font notes on `--font` and `--mono`
- `docs.go`: added `AUDIT-` and `USABILITY-` prefix filters for auto-discovery
**Team Admin Workflows Split**
- `workflows.js` (722 lines) split into 3 ES modules:
- `workflows.js` (~160 lines) — `WorkflowsSection` + `WorkflowsTab` + imports
- `workflow-editor.js` (~240 lines) — `WorkflowEditor` + `StageForm`
- `workflow-monitor.js` (~210 lines) — `AssignmentsTab` + `MonitorTab` + `SignoffPanel`
- External import contract unchanged (default export stays in `workflows.js`)
**Bug Fixes**
- Docs outline `scrollToHeading` now scrolls `.docs-content` container instead of `scrollIntoView`, preventing topbar from being pushed off-screen
- `--bg-2` (undefined CSS variable) replaced with `--bg-secondary` in `sw-shell.css` and `sw-primitives.css`
## v0.7.3 — Extension Shell Migration
**Shell Topbar Migration**
- Migrated Chat, Notes, and Schedules from legacy `sw.shell.Topbar` component to the v0.7.0 shell topbar contract (`sw.shell.topbar.setTitle/setSlot`)
- Eliminated double topbar (shell-injected + surface-owned) on all three extension surfaces
- Chat: reactive slot updates for thread title + People button when conversation changes
- Notes: slot content for + New Note, Import .md, and Graph toggle buttons
- Schedules: reactive slot with schedule count and + New Schedule button; removed legacy fallback branch
**Runner Test Updates**
- Added `shell-topbar` test suite to chat-runner, notes-runner, and schedules-runner
- Tests fetch surface JS and assert: no legacy `sw.shell.Topbar` reference, uses `sw.shell.topbar.setTitle/setSlot` API
- 6 new tests across 3 runners
**Package Versions**
- Chat surface v0.3.0, Notes surface v0.9.0, Schedules surface v0.2.0
- Chat runner v0.2.0, Notes runner v0.2.0, Schedules runner v0.2.0
**Roadmap**
- Headless E2E automation moved to v0.7.5 (independent from shell migration)
## v0.7.2 — Package Runners + CI Gate
**Package Runners (5)**
- Notes runner: `requires: ["notes"]`. 3 suites (crud, folders, tags-search), 12 tests
- Chat runner: `requires: ["chat", "chat-core"]`. 2 suites (conversations, messaging), 9 tests
- Schedules runner: `requires: ["schedules"]`. 1 suite (crud), 5 tests
- Workflow runner: `requires: ["content-approval"]`. 1 suite (lifecycle), 5 tests
- Renderer runner: `requires: ["mermaid-renderer"]`. 1 suite (contract), 4 tests
**Runner Result API**
- `POST /api/v1/admin/test-runners/results` — store structured run results
- `GET /api/v1/admin/test-runners/results` — retrieve latest results per runner
- In-memory store with 4 Go handler tests
**CI Integration**
- `test-runners` stage in Gitea CI pipeline
- Playwright driver launches headless browser, navigates to `/s/test-runners`, triggers run-all
- `wait-for-healthy.sh` polls `/healthz/ready` before test execution
- DinD networking fix: resolve container IP via `docker inspect` (port mapping not exposed to runner localhost)
## v0.7.1 — Surface Runner Framework
**`sw.testing` SDK Module**
- New kernel SDK module at `src/js/sw/sdk/testing.js`
- `sw.testing.suite(name, fn)` — register test suites with lifecycle hooks
- `sw.testing.run(name?)` — execute one or all suites, returns structured JSON
- Suite context: `s.test()`, `s.beforeAll/afterAll()`, `s.beforeEach/afterEach()`, `s.track()`, `s.skip()`
- Test context: `t.assert.ok/eq/neq/gt/match/throws/status/shape/arrayOf`, `t.warn()`, `t.skip()`
- Auto-cleanup: `track(type, id)` registers resources for LIFO deletion in afterAll
- Three result statuses: passed / failed / warned — warnings are never silent
**`test-runner` Manifest Type**
- New `"type": "test-runner"` in `ValidateManifest` — surface-like packages discovered by type
- Test-runner packages excluded from sidebar nav (not type "surface" or "full")
- Runner manifests support `"requires": [...]` — missing packages → clean skip
**ICD Runner Migration**
- Migrated from hand-rolled `T.test()`/`T.assert()` framework to `sw.testing.suite()`
- Stripped extension-dependent tests (channels, notes, personas, etc.) — those belong in v0.7.2 package runners
- Kernel-only suites: smoke, crud (admin, profile, notifications, teams, workflows, extensions, surfaces, packages), authz, security, providers, packaging, sdk
- Type changed to `"test-runner"`, standalone route removed, UI rendering delegated to registry
**SDK Runner Migration**
- Migrated from `T.dualTest()`/`T.domains` framework to `sw.testing.suite()`
- Dual-path validation preserved: SDK call + raw ICD fetch + verdict dispatch
- Stripped extension domains — kernel-only suites: misc, workflows, admin, packages, connections, dependencies, composition
- SHAPE_BUG verdict maps to `t.warn()`, SDK_BUG/ICD_BUG to `t.assert.ok(false)`
**Runner Registry Surface**
- New `test-runners` surface at `/s/test-runners` (admin-only)
- Discovers installed test-runner packages via admin packages API
- Dynamically loads each runner's JS to register suites
- Run All button, per-runner Run button, real-time results dashboard
- Suite/test results with pass/fail/warn/skip color coding, timing, error details
- Export Failures / Export Full Results buttons for JSON download
- `requires` checking with prominent skip display for missing dependencies
- Dark mode styling using kernel CSS variables
**Database Migration**
- SQLite migration 013: adds `test-runner` to packages.type CHECK constraint
- Postgres migration 014: same constraint update
## v0.7.0 — Shell Contract + Surface Audit + Rebrand
**Shell Infrastructure**
- Kernel-injected two-slot topbar for all surfaces (home, left slot, center slot, bell, user menu)
- `sw.shell.topbar` SDK API: `setLeft()`, `setSlot()`, `setTitle()`, `hide()`, `show()`
- `.sw-topbar__tabs` / `.sw-topbar__tab` CSS classes for consistent tab styling
- Shell topbar auto-mounts on extension surfaces via `#shell-topbar` div
**Backend WS Events**
- `package.changed` event broadcast on install/uninstall/enable/disable/update
- `auth.changed` event targeted to affected user on team/group membership changes
- `notification.all_read` event split from `notification.read` for cleaner badge sync
- `Hub.Broadcast()` method for untargeted all-client events
**User Menu + Bell Reactivity**
- User menu re-fetches surface list on `package.changed` and `auth.changed` events
- Notification bell syncs on `notification.read` and `notification.all_read` across tabs
**Surface Migrations**
- Settings: Pattern B (flat tabs in topbar, no sidebar, full-width content)
- Admin: Pattern C (category tabs in topbar, surface-owned sidebar below)
- Team Admin: Pattern B (flat tabs, no sidebar, Groups tab removed)
- Docs: Pattern A (shell topbar auto-renders, removed explicit Topbar import)
- All 4 surfaces now have notification bell and user menu via shell topbar
**Error Handling + Empty States**
- `.sw-inline-error` CSS primitive for inline error + retry pattern
- `.sw-empty-state` CSS primitive for guided empty states
- Admin Workflows, Packages, Groups: inline error on list fetch failure
- Admin Workflows, Groups: descriptive empty state guidance
**Announcement Global Dismiss**
- Announcement dismiss state persisted to localStorage keyed by content hash
- Dismissed once on any surface, dismissed everywhere
**Rebrand Assets**
- `favicon-light.svg` renamed to `wordmark.svg` (was a 520x80 wordmark, not an icon)
- New `favicon-light.svg`: actual square light-mode icon
- New `wordmark-dark.svg`, `wordmark-light.svg` for dark/light backgrounds
- New `favicon-light-32.png`, `favicon-light-256.png` raster icons
- Full icon library deployed to `src/icons/` (both b/e variants, animated SVGs)
- `manifest.json` description updated to "Self-hosted extension platform"
- Light-mode icon entries added to PWA manifest
**Bug Fixes**
- Docs: "On this page" outline links now scroll to headings (IDs were missing from rendered HTML)
- ICD security tier: tightened path traversal assertion (400/422, not 409), added `finally` cleanup
- Workflow demo: replaced silent catch with inline error + retry
- Team Admin: signoff panel shows display names instead of raw UUIDs
- Deleted `packages/hello-dashboard/` (dead package)
- Deleted `team-admin/groups.js` (37-line dead-end, no CRUD)
## v0.6.18 — CI Bundle Wiring
Wire `BUNDLED_PACKAGES` env var into the Gitea CI pipeline so each
environment gets the correct package set at boot.
### Changed
- **CI: dev deploys** — `BUNDLED_PACKAGES=*` (install all, matches docker-compose default).
- **CI: test deploys** — `BUNDLED_PACKAGES=notes,chat,chat-core` (core surfaces only).
- **CI: prod deploys** — `BUNDLED_PACKAGES=notes,chat,chat-core,mermaid-renderer,schedules`.
- **docker-compose.yml** — default `BUNDLED_PACKAGES` changed from empty to `*` (install all for local dev).
### Fixed
- **TestBundledInstall_DefaultAllowlist** — updated test assertions to match v0.6.17's empty default set (was still expecting `notes` in curated defaults).
## v0.6.17 — Bug Fixes & Welcome Logic
Fixes broken UI interactions (folder creation, team member add), dropdown
overflow, welcome surface auto-disable, and bare-install default behavior.
### Fixed
- **Notes "Add folder" button** — `prompt()` replaced with `sw.prompt()`
so the dialog renders correctly in the extension iframe sandbox.
- **Admin "Add team members"** — user list API returns `{data:[…]}`; handler
now unwraps the envelope (`Array.isArray(u) ? u : u.data`) so the user
picker populates.
- **Package filter dropdown overflow** — removed `right:0` constraint on
`.sw-dropdown__list`, added `min-width:max-content` and `overflow-x:hidden`
so option labels ("Extension", "Workflow") render fully without a scrollbar.
- **Admin actions cell wrapping** — switched `.admin-actions-cell` from
`white-space:nowrap` to flexbox with `flex-wrap:wrap; gap:4px` so buttons
don't overflow on narrow viewports.
### Changed
- **Welcome surface auto-disable** — welcome page now redirects to `/` when
any non-core extension surface is installed. Removed from the topbar
navigation surface list so it never appears alongside real surfaces.
- **Zero default bundled packages** — `defaultBundledPackages` map is now
empty. Fresh installs start bare; use `BUNDLED_PACKAGES` env var to control
what gets auto-installed per environment (`*` for all, comma-separated list
for selective).
- **Bundled filter logic** — `nil` (from `*`) means install all; empty map
(default) means install nothing. Previous code treated both as "install all".
- **User menu conditional items** — Docs, Settings, and Team Admin menu
entries only appear when those surfaces are actually enabled, not assumed.
- **SDK imperative host mount** — `ToastContainer` and `DialogStack` are now
auto-mounted by the SDK boot sequence for extension surfaces that lack an
AppShell, preventing missing toast/dialog hosts.
## v0.6.16 — Usability Survey Gate
Machine-auditable UI quality gate. Four new audit scripts, a structured survey
prompt, contrast and touch-target fixes, and Docker Hub documentation correction.
### Added
- **`scripts/generate-ui-inventory.sh`** — walks all kernel + package CSS,
extracts every class selector with surface, line number, responsive breakpoints,
spacing tokens, and font-size usage. Outputs `ui-inventory.json` (1567 entries).
- **`scripts/check-contrast.sh`** — parses `variables.css` dark/light token
pairs, computes WCAG AA contrast ratios for 24 semantic text-on-background
pairings per theme (48 total). Uses AA (4.5:1) for normal text, AA-lg (3.0:1)
for large/bold text contexts.
- **`scripts/generate-coverage-matrix.sh`** — 12 kernel primitives × all surfaces
markdown table. Flags any deprecated component usage (`.btn-primary`, etc.).
- **`scripts/audit-touch-targets.sh`** — static analysis for 44px minimum mobile
touch targets on close buttons and interactive elements.
- **`docs/USABILITY-SURVEY.md`** — structured 8-section prompt (viewport, banners,
responsive, styling, contrast, touch targets, focus indicators, component
uniformity) with pass/fail criteria and file paths for automated execution.
- **Focus indicators** — `:focus-visible` styles on `.sw-btn`, `.sw-input`,
`.sw-dropdown__trigger`, `.sw-menu__item`, `.sw-tabs__tab`.
- **Mobile touch targets** — `min-width/min-height: 44px` in `@media (max-width:
768px)` for `.sw-banner__close`, `.sw-toast__close`, `.sw-dialog__close`,
`.sw-drawer__close`, `.sw-tabs__arrow`, `.modal-close`, `.sw-tabs__tab`,
`.sw-dropdown__option`.
### Fixed
- **WCAG contrast violations** — dark-mode accent darkened from `#6c9fff` to
`#6493ed` (3.03:1 with white text), dark-mode success from `#22c55e` to
`#1dab51` (3.00:1). Light-mode `--text-3` darkened from `#8b8da3` to `#787a92`.
Light-mode `--success-light` and `--warning-light` darkened for badge contrast.
All 48 pairings now pass.
- **Docker Hub references** — `docs/DEPLOYMENT.md` and `docs/DISTRIBUTION.md`
corrected from `ghcr.io/armature/armature` to `gobha/armature` (Docker Hub).
Builder image corrected to `gobha/armature-builder`. GitHub URL corrected to
`github.com/gobha/armature`.
## v0.6.15 — User Display Audit
Every user-facing identity surface now shows human-readable names instead of
UUIDs, with the canonical fallback chain: `display_name → username → "Unknown"`.
### Added
- **`GET /api/v1/users/resolve?ids=...`** — batch endpoint returns identity
records (username, display_name, handle, avatar_url) for up to 100 user IDs.
Response keyed by ID for O(1) client lookups.
- **`sw.users` SDK module** — `resolve(id)`, `resolveMany(ids)`,
`displayName(user)` with 60-second local cache. Surfaces use this instead
of ad-hoc lookups or stale snapshots.
- **5 handler tests** for the resolve endpoint (single, multiple, missing,
empty, no-param).
### Changed
- **Admin users list** — shows `display_name || username` as primary
identifier, with username shown as secondary when display_name is set.
- **Admin teams/groups member lists** — replaced `username || user_id`
with `display_name || username || 'Unknown'`.
- **Team-admin members** — dropdown and list now show display_name.
- **Chat participants** — resolved from users table via `sw.users.resolveMany()`
instead of relying on creation-time snapshot. Message sender names, typing
indicators, and participant sidebar all use resolved names.
- **Dashboard greeting** — added `|| 'Unknown'` terminal fallback.
- **Team activity log** — capitalized fallback to `'Unknown'`.
### Deprecated
- **`participants.display_name` column** in chat-core — column retained for
backward compatibility but UI no longer relies on snapshot values. Comments
added to `packages/chat-core/script.star` noting deprecation.
## v0.6.14 — Visual Polish
Systematic cleanup of stale values, self-hosted fonts, and rendering fixes.
Final visual pass before the v0.6.15 usability survey gate.
### Fixed
- **v0.6.13 spacing regressions** — added half-step tokens (`--sp-1h` 6px,
`--sp-2h` 10px) and restored correct padding on `.sw-btn--sm`, `.sw-input`,
`.sw-menu__item`, `.sw-dropdown__option`, `.sw-tabs__tab`.
- **Theme settings toggle** — showed resolved theme ("Dark") instead of stored
mode ("System"). Changed `appearance.js` to read `sw.theme.mode`.
- **Notes surface scrollbar** — added `overflow: hidden` to `.surface-inner`
in `base.html`, preventing spurious scrollbar at any scale.
- **Chat input clipped at high scale** — same `overflow: hidden` fix prevents
zoomed content from overflowing the surface container.
- **User menu drift at scale > 100%** — `menu.js` now divides
`getBoundingClientRect()` coords by the CSS zoom factor, fixing position
for `position: fixed` menus inside a zoomed ancestor.
- **Undefined variables** — `--text-secondary` (login), `--text-muted`
(user picker), `--text-1` (settings toggle) replaced with correct tokens.
### Changed
- **Stale fallback colors purged** — removed ~65 hex/rgba fallback values
from `var()` calls across 9 kernel CSS files and 3 extension packages.
Old gold theme color `#b38a4e` fully eliminated (9 instances).
- **Self-hosted fonts** — bundled DM Sans and JetBrains Mono woff2 files
in `src/fonts/`. Replaced Google Fonts `@import` and login.html ``
with local `@font-face` declarations. Zero external font dependencies.
- **Consistent border-radius** — added `--radius-sm: 4px` token. Migrated
~60 hardcoded `border-radius` values across all kernel CSS and 12 extension
packages to three tokens: `--radius-sm` (4px), `--radius` (8px),
`--radius-lg` (12px).
### Updated
- `docs/EXTENSION-CSS.md` — added `--sp-1h`, `--sp-2h` half-step tokens
and `--radius-sm` to the public CSS contract.
## v0.6.13 — Responsive & Spacing
Spacing token scale and tablet breakpoint. All kernel CSS and extension
packages migrated from hardcoded values to design tokens.
### Added
- **Spacing tokens** (`--sp-1` through `--sp-12`) — 4px-grid scale in
`variables.css`. Nine stops: 4, 8, 12, 16, 20, 24, 32, 40, 48px.
Numeric naming (`--sp-N`), rem-based for zoom/font-size respect.
- **Tablet breakpoint** (`max-width: 1024px`) — new responsive tier
between mobile (768px) and desktop. Secondary workspace pane narrows
to 360px, admin sidebar to 120px, settings/admin/editor navs shrink.
- **Breakpoint documentation** in `EXTENSION-CSS.md` — Mobile (768px),
Tablet (1024px), Desktop (default).
- **Spacing guidelines** in `EXTENSION-CSS.md` — token table with
computed pixel values and usage examples.
### Changed
- **8 kernel CSS files** migrated to spacing tokens — `sw-primitives.css`,
`modals.css`, `surfaces.css`, `layout.css`, `sw-shell.css`,
`primitives.css`, `user-menu.css`, `sw-login.css`. Hardcoded padding,
margin, and gap values replaced with `var(--sp-N)`.
- **12 extension packages** migrated — chat, dashboard, editor,
git-board, hello-dashboard, icd-test-runner, notes, schedules,
sdk-test-runner, tasks, team-activity-log, workflow-demo.
- **Login hero breakpoint** normalized from 900px to 1024px (tablet).
- **Notes mobile breakpoint** normalized from 700px to 768px (standard).
## v0.6.12 — Extension CSS Isolation
Prefix enforcement prevents extension CSS from leaking into the kernel or
sibling extensions. All 12 in-tree packages migrated to `.ext-{slug}-*`
naming convention.
### Added
- **`data-ext` attribute** on extension mount container — enables scoped
selectors like `[data-ext="chat"] .ext-chat-app`.
- **CSS linter** (`scripts/lint-package-css.sh`) — validates that the first
class selector in every extension CSS rule starts with `.ext-{slug}`.
Exempts `:root`, `@keyframes`, `@font-face`, `@media`, kernel `.sw-*`
classes, and CodeMirror `.cm-*` classes.
- **Kernel CSS contract** (`docs/EXTENSION-CSS.md`) — documents stable
public classes and CSS variables that extensions may reference. Everything
else is internal kernel CSS.
### Changed
- **12 packages migrated** — all class selectors renamed to `.ext-{slug}-*`:
chat, dashboard, editor, git-board, hello-dashboard, icd-test-runner,
notes, schedules, sdk-test-runner, tasks, team-activity-log, workflow-demo.
CSS and JS files updated in lockstep.
- **`icd-test-runner`** — ID selectors (`#extension-mount`) converted to
class-based selectors with proper prefix.
- **`editor` cross-references** — compound selectors referencing notes
classes updated to new `.ext-notes-*` names.
- **`chat` kernel overrides** — `.sw-dialog:has(...)` override scoped under
`[data-ext="chat"]` instead of global.
## v0.6.11 — CSS Deduplication
One class per concept. The old `primitives.css` button, toast, popup-menu,
dropdown, and tabs systems are retired. `sw-primitives.css` is the single
source of truth for all Preact component styles.
### Changed
- **Buttons**: All 29 files migrated from `.btn-primary` / `.btn-small` /
`.btn-danger` / `.btn-ghost` / `.btn-md` / `.btn-sm` to the BEM-style
`.sw-btn .sw-btn--{variant} .sw-btn--{size}` system.
- **Toasts**: Old `.toast-container` / `.toast` CSS deleted. SDK's
`sw.toast()` API already used `.sw-toast-*` classes — no JS changes.
- **Popup menus**: Old `.popup-menu` / `.popup-menu-item` CSS deleted
(unused — `.sw-menu` is the active system).
- **Dropdown collision resolved**: Old `.sw-dropdown` (styled `