studio-engine 0.74.12 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4fe6cb6d0167ad680d5d6e9dc577a623c87e83d839847be79950877b922d576
4
- data.tar.gz: 07d63386f006d3bfba60556910097f0f0797e52f28f6bcde41e38c37e4af9326
3
+ metadata.gz: 1a731a8298b49ab26be4ffc3a218a36d5a07d3a831362316750c9573ad656bd5
4
+ data.tar.gz: ed601d4cb20aa672e858d79b036e5392bbdb7018c7ef5bb8a95929d144f69b1f
5
5
  SHA512:
6
- metadata.gz: a46c78e389a2776d874522186127a1a16ae5bca04429ee3879a3bd0b1a8ce4e669d382803d94a2f3becb127c8735cd40255f62ef848cdfec3a76e912997d347d
7
- data.tar.gz: caab23ce4c8cd6788ad50bc28391302b9b388015dd33e19a80855ba708f335f38653a449af6afd3b4afe43dd92a7cd87233ed51874f831c7cf175d6109ebe76e
6
+ metadata.gz: 00ec03c34856f17a57fb661824041e6df304e53e384096645673790bdb790048bb2f40f40af2ea3a8548372d338dc370328210dbff69c52d8a70c1f267d39607
7
+ data.tar.gz: f9caf61a39dafe832bd50fdc8668e4dda3dd4fd5ce4faf34e767278906a2db777ce42c24cb95ff67733178245b1027b64fccf00cbb8db6c6b0b7b79fb6e26a9a
data/CHANGELOG.md CHANGED
@@ -4,6 +4,83 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.75.0 — 2026-09-17
8
+
9
+ ### Added
10
+
11
+ - **Session drift: every page now knows when the browser's session changed
12
+ underneath it.** Another tab signs in or out, the session expires, or the
13
+ server revokes it; the page notices, repairs itself in place when the host
14
+ allows, and tells a deliberate switch from an accidental one. Generic by
15
+ design: it knows sessions, accounts and fingerprints, and an app or companion
16
+ gem binds anything else to the session through an identity-source plug-in.
17
+ Contract: [`docs/SESSION_DRIFT.md`](./docs/SESSION_DRIFT.md).
18
+ - **The stamp, on every page, with no wiring.** `layouts/studio/_head` renders
19
+ `studio/_session_stamp`: `<meta name="studio-session">` carrying the
20
+ session's state, fingerprint, issue and expiry times, rehydrate URL and bound
21
+ identities, plus the store script. It renders only for a controller that
22
+ includes `Studio::ErrorHandling` (every consumer's `ApplicationController`);
23
+ any other head is byte-identical to before.
24
+ - **`Studio::SessionState`**: `STATES` (anonymous, authenticated, stale,
25
+ changed, rehydrated, signed_out), `SERVER_STATES`, `#state`, `#anonymous?`,
26
+ `#authenticated?`, `#fingerprint` and `#to_stamp`, built from the viewer
27
+ alone. `SessionContext` exposes the same by delegation; its constructor and
28
+ `#to_h` are unchanged.
29
+ - **`Studio::SessionFingerprint`**: an HMAC of the user's id, `session_token`
30
+ and bound identities, keyed from `secret_key_base`. Rotating the token or
31
+ re-binding an identity changes it; it never carries either readably.
32
+ - **`Studio::SessionDrift`**, included by `Studio::ErrorHandling`: helpers
33
+ `studio_session_stamp`, `studio_session_page_stamp` (logs to `ErrorLog` and
34
+ omits the stamp in production rather than failing a page),
35
+ `studio_session_state`, `studio_session_rehydrate_url`, and the host hook
36
+ `studio_session_identities`.
37
+ - **`GET /session/state`** (`Studio::SessionStatesController`), OFF by default:
38
+ `config.draw_session_routes = true`. Returns the stamp, the host's
39
+ `client_session_payload` and a fresh CSRF token. Anonymous gets a 200; a host
40
+ filter's 401 reads as revoked.
41
+ - **`window.StudioSession`** (`studio/session.js`, precompiled): `current`,
42
+ `subscribe`, `refresh`, `expectChange(scope)`, `registerIdentitySource`,
43
+ `observed`, `configure`; `session:changed` and `session:mismatch` events on
44
+ `document`; `Alpine.store('studioSession')`. Ships built-in sources for other
45
+ tabs (BroadcastChannel `studio-session`), expiry (after a grace, so probing
46
+ never renews a sliding session), and a server probe on returning to a hidden
47
+ tab or a bfcache restore. Back to a cached page of the same session keeps
48
+ what the tab already learned. Holds are capped at 10 minutes. It never
49
+ touches a host's own stores.
50
+ - Tested by executing the store under node, one vm context per tab, every
51
+ scenario asserted by name (`test/views/studio_session_store_test.rb`); two
52
+ browser-lane specs for real delivery and real cross-tab messaging
53
+ (`e2e/session_drift.spec.js`, lane contract 138 -> 140); a dummy-app
54
+ integration suite (`test/integration/session_drift_test.rb`); unit suites for
55
+ the state, the context delegation and the fingerprint; and a vocabulary guard
56
+ over the primitive's code, docs, tests and this entry
57
+ (`test/lib/session_drift_vocabulary_test.rb`).
58
+ - **`docs/USER_CONTRACT.md` documents two methods the engine already called
59
+ without listing:** `#session_token` and `#regenerate_session_token!`.
60
+
61
+ ### Fixed
62
+
63
+ - **The toast halo and the success card's explorer link now paint their
64
+ tints.** Both used the legacy `rgba(var(--color-primary-500-rgb), A)` form.
65
+ `Studio::ThemeResolver` emits every `-rgb` var as a space-separated list, and
66
+ that list makes the comma form invalid, so browsers dropped each declaration
67
+ without an error. `layouts/studio/_flash`'s `.dark .toast-blur-glow` (the
68
+ `blurShadow: true` halo) painted no halo in dark mode. The Solana explorer
69
+ link in `blocks/_success_card` (`tx_solana: true`) lost its tinted backing in
70
+ both themes. Both now use the slash form, `rgb(var(--x-rgb) / A)`.
71
+ - Measured in headless Chromium on a page built from the engine's
72
+ consumer-style Tailwind build and the resolver's real theme vars. Before,
73
+ both elements computed `rgba(0, 0, 0, 0)`. After, the dark halo computed
74
+ `rgba(142, 130, 254, 0.12)` and the link `rgba(142, 130, 254, 0.06)`.
75
+ - Guarded by `test/views/legacy_rgba_var_guard_test.rb`. It refuses the
76
+ legacy form in every code file the gemspec packages, in the
77
+ `Studio::UiPrimitives` CSS strings, and in a compiled consumer build. It
78
+ also pins the premise that every `-rgb` triple the engine defines is a
79
+ space-separated list.
80
+ - `RUNBOOK.md` gains the symptom and its fix. Its palette entry no longer
81
+ says a nil or blank primary skips palette generation; on the
82
+ `studio_theme_css_tag` path, both fall back to a default.
83
+
7
84
  ## 0.74.12 — 2026-09-16
8
85
 
9
86
  ### Fixed
data/README.md CHANGED
@@ -31,6 +31,7 @@ resolved.
31
31
 
32
32
  - **Authentication**: Passwordless magic-link auth, optional password auth, Google OAuth via OmniAuth, Solana wallet sign-in, and optional one-way SSO patterns
33
33
  - **Error handling**: `Studio::ErrorHandling` concern with `rescue_and_log`, `ErrorLog` model with `capture!`, error log viewer at `/error_logs`
34
+ - **Session drift**: every page carries a session stamp and loads `window.StudioSession`, which notices when another tab signs in or out, the session expires, or the server revokes it; `session:changed` / `session:mismatch` events, cross-tab sync, an identity-source plug-in interface, and `expectChange` holds for deliberate switches. The rehydrate endpoint (`GET /session/state`) is opt-in. See [`docs/SESSION_DRIFT.md`](docs/SESSION_DRIFT.md).
34
35
  - **Theme system**: Dynamic CSS custom properties generated from 7 role colors (primary, dark, light, success, accent, warning, danger). Dark/light mode toggle. Admin theme editor at `/admin/theme`.
35
36
  - **UI primitives**: Shared component partials and CSS primitives such as `components/emoji_swap` for nav/sidebar emoji hover transitions.
36
37
  - **Operator tooling**: Shared `studio/banners/environment` banner with Dev Mode + email connector controls, `studio/banners/impersonation`, and an opt-in `Studio::Impersonation` concern for Act As session conventions.
@@ -120,6 +121,8 @@ end
120
121
 
121
122
  This draws the enabled auth routes (`/login`, `/signup`, `/logout`, `POST /magic_link` to request a link, `GET`/`POST /l/:token` for the link itself, Solana routes), OAuth callbacks, optional SSO routes, `/error_logs`, and `/admin/theme`. Set `Studio.draw_geo_routes = true` to add the geo manager (`/admin/geo`) and its public probe (`/geo/check`) — off by default because turf-monster owns those helper names until its adoption lands. Magic-link emails point at the inert `GET /l/:token` confirmation page; the single-use token is burned only by the CSRF-protected `POST` to `link_consume_path`.
122
123
 
124
+ Set `Studio.draw_session_routes = true` to add the session-drift rehydrate endpoint (`GET /session/state`) — off by default because it inherits the host's filters, which an app should check first ([`docs/SESSION_DRIFT.md`](docs/SESSION_DRIFT.md)).
125
+
123
126
  **Magic links need the `studio_links` table.** Install it with `bin/rails studio_engine:install:migrations && bin/rails db:migrate` (install all of them) before enabling `:magic_link` — never by hand-copying the migration, which collides with the task's own copy on `class CreateStudioLinks`. Without the table, the first sign-in raises `Studio::Link::MissingTable`.
124
127
 
125
128
  In non-production local requests, this also draws `/_studio/local_emails`, a local email inbox for agent/worktree proof flows. Set `LOCAL_EMAIL_CAPTURE=1` or run with `AGENT_WORKTREE=1` to record outbox rows without sending real email.