diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a99a3..ac93034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,62 @@ All notable changes to Armature are documented here. +## v0.6.9 — Session Lifetime Config + +Admin-configurable session durations, "keep me logged in" opt-in, and +optional idle timeout. Completes the auth hardening started in v0.6.8. + +### Added + +- **Admin session settings**: `session.access_token_ttl` (default 15m, + clamp 5m–60m) and `session.refresh_token_ttl` (default 7d, clamp + 1h–90d) stored in `global_settings`. New `LoadSessionConfig()` helper + reads and clamps values with `parseDurationString()` supporting `m`, + `h`, `d` suffixes. +- **"Keep me logged in" checkbox**: Login form opt-in. Checked = full + `refresh_token_ttl`. Unchecked = capped at 24h. Cookie `max-age` + tracks whichever lifetime was chosen. `keep_login` flag stored on + refresh token row. +- **Config-driven token generation**: `generateTokens()` reads TTLs from + `global_settings` instead of hardcoded `15*time.Minute` / + `7*24*time.Hour`. Response includes `expires_in` and + `refresh_expires_in` (seconds) so the client schedules refresh + correctly. +- **Idle timeout (optional)**: Admin-toggleable (default off). Server + checks `last_activity_at` on refresh-token row; rejects if gap exceeds + `session.idle_timeout`. Client SDK pings `POST /api/v1/auth/activity` + on click/keydown (debounced, max 1/min). +- **Admin Settings > Session section**: Dropdowns for access TTL, refresh + TTL, and idle timeout with toggle. +- **7 new tests**: Duration parsing, clamping (low/high), defaults, + invalid values, empty idle timeout. + +### Changed + +- `CreateRefreshToken` store method now accepts `keepLogin bool` + parameter; both Postgres and SQLite implementations updated. +- `GetRefreshTokenInfo` returns `RefreshTokenInfo` struct with + `UserID`, `KeepLogin`, `LastActivityAt` for idle-timeout decisions. +- SDK `auth.login()` accepts optional third `keepLogin` parameter; + cookie max-age derived from server `refresh_expires_in` response. +- SDK boots activity tracking after successful auth boot. + +## v0.6.8 — Cookie Fix + UI Hardening Roadmap + +### Fixed + +- **Session cookie max-age bug**: `arm_token` cookie was set to 15 min + (matching access token) while refresh token lasted 7 days. Cookie now + matches refresh token lifetime so Go SSR middleware can serve page + shells while JS refreshes the access token client-side. + +### Added + +- **ROADMAP-UI.md**: Detailed UI hardening roadmap (v0.6.9–v0.6.15) + covering session config, viewport foundation, CSS deduplication, + extension CSS isolation, responsive layout, visual polish, and + automated usability survey gate. + ## v0.6.7 — Native mTLS End-to-end mutual TLS without a reverse proxy. Targets systemd+podman diff --git a/ROADMAP-UI.md b/ROADMAP-UI.md new file mode 100644 index 0000000..038e89c --- /dev/null +++ b/ROADMAP-UI.md @@ -0,0 +1,161 @@ +# Armature — v0.6.x UI Hardening Roadmap + +> **Goal**: Fix all viewport scaling, banner integration, and styling inconsistencies +> so Claude Code can run a meaningful automated usability survey against a +> stable, uniform UI. + +--- + +## Problem Inventory + +### P1 — Viewport & Scaling (blocks everything) + +| # | Issue | Where | Impact | +|---|-------|-------|--------| +| 1 | **Dual layout systems** — `base.html` template has its own banner→surface→footer column; `app-shell.js` Preact shell has a separate `sw-shell` → `sw-shell__body` → `sw-shell__surface` column. They never coordinate. | `server/pages/templates/base.html` ↔ `src/js/sw/shell/app-shell.js` | Every surface inherits an ambiguous viewport ancestor. | +| 2 | **Transform-based scaling is broken** — `transform: scale()` on `#surfaceInner` doesn't reflow layout. Breaks `getBoundingClientRect` (menu.js already carries a scale-correction hack at line 20), scroll containment, pointer events, and the inverse-width/height hack (`100/s%`) doesn't account for banners consuming viewport space. | `base.html:56-67`, `appearance.js:41-53`, `menu.js:20-33` | Menus, tooltips, dropdowns all mis-positioned at any non-100% scale. Click targets wrong. | +| 3 | **Banner height desync** — `base.html` hardcodes `--banner-h: 28px` and renders banners in-flow. `app-shell.js` ShellBanner measures `offsetHeight` into `--banner-top-height` / `--banner-bottom-height` and uses `position: fixed` + body padding. Two independent banner systems. | `base.html:31,72-75,103-107` ↔ `app-shell.js:25-42`, `sw-shell.css:22-37,63-69` | With banners enabled, content overflows by the banner height or gets double-inset depending on which code path is active. | +| 4 | **`100vh` on mobile** — `.sw-shell`, `.chat-app`, `.chat-loading`, `.login-shell` use `100vh` which doesn't subtract mobile browser chrome (address bar, toolbar). | `sw-shell.css:9`, `chat/css/main.css:15,25`, `sw-login.css:7` | Content overflows on iOS Safari, Android Chrome. | +| 5 | **Chat uses `100vh` not `100%`** — `.chat-app { height: 100vh }` ignores parent container (which already excludes banners/footer). Notes correctly uses `height: 100%`. Every extension surface that copies chat's pattern will inherit the bug. | `packages/chat/css/main.css:15` | Chat surface overflows behind banners. | + +### P2 — CSS Architecture + +| # | Issue | Where | Impact | +|---|-------|-------|--------| +| 6 | **Class name collisions** — `.settings-section` defined in both `modals.css:41-43` and `surfaces.css:75-83` with different padding, margin, and border-bottom rules. `.sw-dropdown` in `primitives.css:443` (styled `