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 +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +3 -0
- data/app/assets/javascripts/studio/session.js +647 -0
- data/app/controllers/concerns/studio/error_handling.rb +10 -3
- data/app/controllers/concerns/studio/session_drift.rb +99 -0
- data/app/controllers/studio/session_states_controller.rb +43 -0
- data/app/models/session_context.rb +32 -0
- data/app/views/layouts/studio/_flash.html.erb +3 -1
- data/app/views/layouts/studio/_head.html.erb +3 -0
- data/app/views/studio/_session_stamp.html.erb +32 -0
- data/app/views/studio/modals/blocks/_success_card.html.erb +1 -1
- data/lib/studio/engine.rb +1 -0
- data/lib/studio/session_fingerprint.rb +109 -0
- data/lib/studio/session_state.rb +109 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +37 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a731a8298b49ab26be4ffc3a218a36d5a07d3a831362316750c9573ad656bd5
|
|
4
|
+
data.tar.gz: ed601d4cb20aa672e858d79b036e5392bbdb7018c7ef5bb8a95929d144f69b1f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|