You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c322421
Browse filesBrowse the repository at this point in the historyBrowse files
fix(console): after a sign-out, the next person to sign in lands on their own home, not the previous person's page (#30490)
domain:ui#5 · os-sales · session_01L6bhgUtwT9qwYbCjRDVu5Y
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuHF6g83xPLnHoDsxEw6wKQ6)_
Part of #30125 (item 4 fixed here; item 3 measured and not reproduced on
main, no change; items 1, 2 and 5 stay on the card).
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:在控制台里点「退出登录」之后,登录页不再带 `?redirect=`
回到上一个人打开的页面,下一个人登录后落到自己的首页。会话过期或被吊销(不是主动退出)时仍保留原路径,同一个人重新登录后回到原处。
- 为什么改:管理员在「系统设置」页面退出后,新成员登录直接落到那个页面,看到「你没有访问此应用的权限」(卡片 item 4,main
的生产构建上已复现)。
- 风险与回滚:只改登录跳转的一个分支;退出时少带一个参数,其它跳转(邀请链接、深链接、过期)不变;回滚即还原本 PR,无数据迁移。
- 先看:`ui/packages/auth/src/AuthProvider.tsx`(`sessionEndedBySignOut`
的升起与落下)、`ui/apps/console/src/components/ProtectedRoute.tsx` 的
`LoginRedirect`、测试文件 `ProtectedRoute.signOutListState-29174.test.tsx`
新增的 objectstack#30125 describe。
- 两分钟验证:`pnpm --dir ui exec vitest run
apps/console/src/components/ProtectedRoute.signOutListState-29174.test.tsx`,预期
7 个用例全绿。
- 需要你做的:无,席位落地。
## What changes
Card #30125, **item 4**: after a sign-out, the next person lands on the
previous person's page.
- **`@object-ui/auth`**: `AuthContextValue.sessionEndedBySignOut` (new,
boolean). `AuthProvider.signOut()` raises it in the same batch that
clears `user`/`session`, so the first render without a session already
reads it; it is lowered during render as soon as a session exists again,
whoever it belongs to. A session that ends any other way (`loadSession`
re-resolving to nothing: expiry, revocation, a sign-out in another tab)
leaves it `false`. The outside-provider default in `useAuth.ts` answers
`false`.
- **`@object-ui/console`**: `LoginRedirect` sends the visitor to
`/login` with no `?redirect=` when `sessionEndedBySignOut` is true.
Otherwise it is unchanged: the path and params are kept, list `uf_*`
state stripped (#29174).
- All three sign-out call sites (`AppHeader`, `UserMenu`,
`RemediationOverlay`) go through `AuthProvider.signOut()`, so the change
is at that one source; `AppHeader.tsx` is not touched.
- **Changeset**: `.changeset/30125-sign-out-no-return-link.md`,
`@object-ui/auth` and `@object-ui/console` patch.
Branch: the harness-assigned `claude/project-thread-guv7bd`, not
`claude/issue-30125-*`; the card is #30125.
## Tests
In the existing harness file
`ProtectedRoute.signOutListState-29174.test.tsx` (real `AuthProvider`
over a scripted client, real `ProtectedRoute`, real Console
`LoginPage`), a new top-level describe for #30125, 3 cases:
- A signs out of a Setup page; the login URL is exactly `/login`, no
`redirect`; B signs in and lands on `/`.
- A's session expires on the same page (the provider's own
`refreshSession` re-resolves to no session); the login URL keeps the
path; A signs back in and lands on it.
- The sign-out is forgotten once someone signs in: A signs out, B signs
in in the same tab, B's session then expires on the page, and the path
is kept again.
The #29174 block's first case now ends A's session by **expiry** instead
of by sign-out: an explicit sign-out carries no return link at all, so
the list-state stripping it pins is only reachable on the expiry path.
Its assertions are otherwise unchanged (moved pin, declared).
## Acceptance notes
- **Item 3 (time zone dialog over a toast): not reproduced on a
production build of main, no change.** Measured with `os start` (Mode:
production) on `examples/app-todo`, console built by `pnpm
console:build` at `989ae42d`, Chromium 1440x900, zh-CN, Asia/Shanghai, a
freshly registered first administrator. The dialog opened 0.2 to 0.3 s
after the first entry into any `/apps/...` route (Setup), before any
action was possible; after it was answered, inviting a member raised the
invitation toast and no dialog opened over it. The prompt is mounted
only under `/apps/:appName/*` and asks once per org and user, so on main
it cannot first appear after an in-app action. Residual, not measured:
an action OUTSIDE `/apps` that raises a toast and then navigates into an
app within the toast's 4 s could still open the dialog over it; no such
path was found in the first-run flow.
- Items 1, 2 and 5 are out of this PR (server and product call / held by
#30157 PR #30243 / devx template) and stay on the card.
- `AuthContextValue` gains a required member. Code that builds its own
value of that type (a test double typed as `AuthContextValue`) must add
it; in this tree the only such builders are `useAuth.ts` (updated) and
`inbox-arrival-preview.tsx`, which casts through `unknown`. Declared
`Clause-②: yes (widening)` for this, as PR #29338 did for its
`AuthContextValue` / `AuthClient` members.
- Two browser tabs: a sign-out in tab 1 reaches tab 2 as a session that
re-resolved to nothing, so tab 2 keeps its path. That is the expiry
reading, by design: this provider cannot tell who signed out over there.
## Verification
Tests, typecheck and the derived gates ran on `aa800e72` (the fix) or
earlier as noted; the head adds evidence commits and their removals, the
tree is byte-identical to `aa800e72` (`git diff aa800e7 HEAD` is
empty). Base `989ae42d`.
<details><summary>Live rig and screenshots (production mode, zh-CN, 1440
wide)</summary>
`pnpm build` + `pnpm console:build`, then `objectstack start -p PORT
--home TMP --database file:TMP` in `examples/app-todo` (banner: `Mode:
production`). First administrator registered on `/register`; a member
invited from Setup, Invitations, then signed up and accepted through the
auth API.
Item 4, before (main `989ae42d`): the administrator signs out of
`/apps/com.objectstack.setup/sys_user`; the login page is
`/login?redirect=%2Fapps%2Fcom.objectstack.setup%2Fsys_user`; the member
signs in and lands on that page: 你没有访问此应用的权限.


Item 4, after (console rebuilt at `aa800e72`): the login page is
`/login`; the member lands on `/`, which opens the member's own default
app.


Item 3 (main `989ae42d`): the dialog at first entry into Setup, and the
invitation toast uncovered after it was answered.


</details>
<details><summary>Tests, ablation, typecheck, lint</summary>
- The harness file: Test Files 1 passed (1), Tests 7 passed (7), lock
VERDICT command-exit 0.
- `pnpm --dir ui exec vitest run --maxWorkers=3 packages/auth/
apps/console/
packages/app-shell/src/providers/__tests__/MetadataProvider.crossPrincipalSeed.test.tsx`
on `aa800e72`: Test Files 341 passed (341), Tests 3315 passed (3315),
exit 0.
- Ablation 1 (`scripts/ablation-replace.mjs`, anchor x1 to x0):
`LoginRedirect`'s sign-out branch disabled: 2 failed / 5 passed (the
sign-out case and the forgotten-sign-out case). Restored, blob equals
HEAD, `git diff HEAD` empty.
- Ablation 2, on `aa800e72`: the flag's reset in `AuthProvider`
disabled: 1 failed / 6 passed (the forgotten-sign-out case). Restored,
blob equals HEAD.
- `pnpm --filter @object-ui/auth run type-check`,
`@object-ui/app-shell`, `@object-ui/console` (after rebuilding
`@object-ui/auth`): each exit 0. `pnpm --dir ui run check:spec-symbols`:
exit 0.
- eslint on the five changed files: 0 errors; the three remaining
warnings are on `AuthProvider.tsx` lines this PR does not change.
</details>
<details><summary>Derived gates (35/35 exit 0)</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 35 families; the list is identical
at the final head. Every one ran and exited 0; the sweep ran while the
head moved from `0ddd2bd3` to `7a94e786`, and the later source commits
change only lines inside `ui/packages/auth/src` that no family reads.
`--ran` reconciliation: 35 derived, 35 run, 0 NOT-MEASURED (a derived
zero, every line carries an exit code). Also `pnpm --dir ui run
check:new-line-citations` and `check:control-bytes`: exit 0. CI runs the
rest.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgUtwT9qwYbCjRDVu5Y)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgUtwT9qwYbCjRDVu5Y
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(console,auth): after an explicit sign-out, whoever signs in next lands on their own home, not on the page the previous person had open
7
+
8
+
Clause-②: yes (widening)
9
+
Metadata-protocol: none
10
+
11
+
-**`@object-ui/auth`: `AuthContextValue.sessionEndedBySignOut`**, `true` from a `signOut()` until a session is established again in the tab, whoever it belongs to; `false` on load, while signed in, and after a session that ended any other way (expired, revoked, signed out in another tab). It is a new required member of the exported `AuthContextValue` type: code that builds its own value of that type (a test double, a custom provider) must add `sessionEndedBySignOut: false` (or its own reading) to type-check.
12
+
-**`@object-ui/console`: `LoginRedirect`** sends the visitor to `/login` with no `?redirect=` after an explicit sign-out. A new member signing in after the administrator signed out of a Setup page used to land on that page and read "you do not have access to this app". A session that expires or is revoked under the person still keeps the path (list state stripped as before), so that person signs back in where they were.
0 commit comments