3 Commits

Author SHA1 Message Date
Lucien
1ae50826b1
fix(frontend): isolate workspace query caches by authenticated owner (#7146)
* fix(frontend): isolate query caches by authenticated owner

* fix(frontend): share auth owner across isolation boundaries

* docs(frontend): keep agent guidance within size budget

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-20 10:26:48 +08:00
Hyeonsang Cho
3f71b2e13f
fix(frontend): return to the opened workspace page after signing in (#6681)
* fix(frontend): return to the opened workspace page after signing in

Opening a workspace page without a valid session sent the user to a bare
/login: the workspace layout called redirect("/login"), and a layout
cannot read the request URL, so the path and query were both lost. A
scheduled-task notification link (/workspace/scheduled-tasks?task_id=X)
opened after the session expired therefore landed on the default
workspace after sign-in.

For an unauthenticated verdict the layout now renders
WorkspaceLoginRedirect, which replaces the page with
/login?next=<path+query> from the browser. It renders nothing else, so
the workspace pages under it never mount.

AuthProvider.refreshUser and useRequireAuth kept the path but dropped
the query. They now share currentReturnPath() with the fetcher, which
already kept both.

* docs(changelog): reference #6681

* fix(frontend): keep the fragment in the sign-in return path

currentReturnPath() kept the path and query but not location.hash, so
an anchored deep link (/workspace/chats/x#section) lost its anchor across
the login round trip. It is now the single source for all four bounce
sites, so include the fragment. The server never receives the fragment,
so only the browser-side bounce can keep it. validateAuthNextPath, the
backend's validate_next_param and its quote() into /auth/callback all
carry a '#' through unchanged.

The models API test stubbed window.location without hash; a real
Location always has one, so the stub now carries it.

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-11 19:05:03 +08:00
hataa
2f4f4d4b6c
feat(authz): admin user-management UI and disabled-account copy (#4063 gap 3) (#6641)
* feat(authz): admin user-management UI and disabled-account copy (#4063 gap 3)

Frontend slice of the account lifecycle backend (#6566):

- Settings gains an admin-only Users page (core/auth/admin-users.ts ->
  GET/PATCH /api/v1/admin/users): email, read-only role, disabled state,
  and a suspend/restore toggle per row; the Gateway's rejection detail
  (last-active-admin 409) renders verbatim. The nav entry is hidden for
  non-admins and the page guards independently; ?settings=users joins the
  deep-link whitelist (now an exhaustive Record over SettingsSection).
- A session that turns out disabled maps account_disabled to a login
  redirect that states the reason on all three surfaces: the SSR
  AuthResult tag (consumed exhaustively by the workspace/artifacts/auth
  layouts), the shared fetcher's 401 interception, and
  AuthProvider.refreshUser. No next target is preserved - a disabled
  account cannot sign back in. Login failures surface the localized
  account_disabled copy when the code is present.
- userSchema stays disabled-optional (absent = not disabled); zh/en
  locales and docs updated in the same change.

* fix(authz): address review on admin user UI and disabled copy (#4063 gap 3)

Review follow-ups on PR #6641:

- Direct /login visits with a suspended session now surface the SSR
  account_disabled verdict through AuthDisabledContext instead of
  discarding it (willem-bd): the auth layout wraps pages with the
  provider and the login form seeds its reason from it — no redirect,
  so no loop. Regression tests cover both verdict states.
- admin-users.ts parses both endpoints against userSchema /
  z.array(userSchema) (zhfeng): malformed payloads fail the load
  instead of rendering phantom rows; toggle error copy is scoped to
  GatewayApiError with a localized fallback for anything else.
- The localized account_disabled copy is login-only (zhfeng): sign-up
  keeps the backend message verbatim so a suspended account's email
  cannot be probed through the register surface.
- Settings page folds the load banner and toggle error into a single
  alert region (zhfeng); adds coded-envelope 409, malformed-row, and
  load-failure tests; fetcher.ts documents the 401 body-consumption
  contract.
- prettier across touched files (CI format gate).

The admin-users query key keeps user?.id, matching the
model-settings-page precedent (converge later per review).
2026-10-11 12:54:51 +08:00