v0.23.0: server-side sign-in state resume (roadmap #29)
Track each authenticated user's last-viewed route + light view state server-side, and on next sign-in redirect them to that state, falling back to the empty-state home only when there's no recorded state. Backend: - migration 022_user_session_state.sql: one row per user (user_id PK/FK, last_route, last_route_state JSON-as-TEXT, resume_enabled default 1, last_updated_at). - PUT /api/me/last-state (require_user): upserts route + light state; no-ops when resume_enabled=0. Localized in the /me region. - /api/auth/me payload now carries resume_enabled + last_route + decoded last_route_state (no extra round-trip). - test_session_resume_vertical.py: auth-required, upsert/read-back, per-user isolation, resume_enabled=0 disable. Frontend: - lib/useLastState.js: debounced (~1s) route-change PUT for authenticated users; one-time resume redirect on sign-in, gated on identify having fired (preserves #21 Part C identify-then-track). - api.js: putLastState() client call. - App.jsx: import + call the hook; set identifyReady after identify. Header region untouched. Per-user (not per-device); profile-settings opt-out toggle UI deferred (column + default-on behavior ship now). Stored state is route + light view state ONLY, never draft buffers — documented in SPEC §6.8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,25 @@ export async function getMe() {
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// ── v0.23.0: sign-in state resume (§6.2, roadmap item #29) ───────────────
|
||||
//
|
||||
// The route-change hook (useLastState) debounce-posts the user's current
|
||||
// route + a small bag of *light* view state here for authenticated users.
|
||||
// The next sign-in reads `last_route` off `/api/auth/me` and redirects.
|
||||
// Privacy: `state` carries ephemeral view state ONLY — never draft-buffer
|
||||
// contents (see SPEC §6.2). Best-effort: callers ignore failures (an
|
||||
// offline/401 POST must never disrupt navigation).
|
||||
export async function putLastState(route, state) {
|
||||
const body = { route }
|
||||
if (state != null) body.state = state
|
||||
const res = await fetch('/api/me/last-state', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
// ── v0.7.0: email + one-time-code sign-in (§6.2) ─────────────────────────
|
||||
//
|
||||
// The legacy /auth/login → /auth/callback OAuth flow remains during the
|
||||
|
||||
Reference in New Issue
Block a user