Skip to content

fix(console): after a sign-out, the next person to sign in lands on their own home, not the previous person's page - #30490

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-guv7bd
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-guv7bd

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#5 · os-sales · session_01L6bhgUtwT9qwYbCjRDVu5Y

Requested by Sales · project thread

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 (Console sign-out: the login page's redirect carries the previous user's list filter, search and sort, so the next person who signs in on that browser opens their list state #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

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 aa800e72 HEAD is empty). Base 989ae42d.

Live rig and screenshots (production mode, zh-CN, 1440 wide)

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: 你没有访问此应用的权限.

before: login URL
before: member landing

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

after: login URL
after: member landing

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

item 3: dialog at first app entry
item 3: invitation toast uncovered

Tests, ablation, typecheck, lint
  • 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.
Derived gates (35/35 exit 0)

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.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgUtwT9qwYbCjRDVu5Y

…item 4)

After a sign-out, LoginRedirect kept the page the previous person had
open as `?redirect=`, so the next person to sign in landed there. The
auth provider now records that the session was ended by signOut() and
lowers the record once any session exists again; LoginRedirect sends that
case to /login with no return link. An expiry or revocation keeps the path.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgUtwT9qwYbCjRDVu5Y
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
…textValue member (#30125 item 4)

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgUtwT9qwYbCjRDVu5Y
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#5 · os-sales · session_01L6bhgVwQZbHRRM9CaXeKif

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 29ca8be831e1e2c64933186875c26c213f1947d2
Local-runs: none

Reviewed 2026-11-05T13:30Z. Face: the Clause-②: yes (widening) declaration alone (PR body and changeset). The committed tree touches no packages/spec/src/**, no governed path, and narrows nothing. This head differs from 85600dc8 only in the changeset's declaration lines (1 file, +2 −2).

① Derived judgments

② Semver level

③ Boundary flags

  • The dev raised the required member in Acceptance notes; answered above.
  • Author check: the PR's user.login is objectstack-fleet[bot] (relay pr_create).

Implemented-by: session_01L6bhgUtwT9qwYbCjRDVu5Y
Reviewed-by: session_01L6bhgVwQZbHRRM9CaXeKif

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#5 · os-sales · session_01L6bhgVwQZbHRRM9CaXeKif

维护者速读

  • 改了什么: 有人主动点了「退出登录」之后,下一个在这台浏览器登录的人会进入自己的首页,不再被带回上一个人当时打开的页面。会话过期或被吊销时,原来的人重新登录仍回到原页面。
  • 为什么改: First-run flow: small frictions a new deployment's first people meet #30125 item 4。管理员在设置页退出后,新成员登录直接落在设置页,看到「您无权访问此应用」。
  • 风险与回滚: @object-ui/auth 的 AuthContextValue 多了一个必填成员 sessionEndedBySignOut。自建这个值的第三方实现要补上它,和 fix(auth): a user with two-factor sign-in on is asked for their code after the password #29338 加成员时一样。控制台只在这个标志为真时去掉回跳链接。回滚就是撤销本 PR。
  • 需要您做的: 无。本 PR 不在受管面,共 172 行。席位会经合并队列落地。
  • 席位意见: ACCEPT。

席位核对 — ACCEPT · domain:ui#5 · head 29ca8be831e1e2c64933186875c26c213f1947d2

  • 形态: draft,作者 objectstack-fleet[bot](中继 pr_create),无 assignee,无 review request,目标 main,Part of #30125(只做 item 4,item 3 前提不成立)。共 6 个文件,+153 −19。check-governed-merges --pr 30490:NOT governed;172 行,未超 3000。mergeable_state 读为 clean。
  • 契约面: 不碰 packages/spec/**,不碰 governed 路径。首个 head 85600dc8 申报 Clause-②: no,但 AuthContextValue 由包入口导出(ui/packages/auth/src/index.ts:130),多一个必填成员就是公开类型的加宽。补丁轮 29ca8be8 只把 changeset 改为 Clause-②: yes (widening);PR 正文由席位经 relay issue_patch 改为同一申报,读回一致。TIER_DEFAULT 合同复核 PASS 6659259203,复核的就是这个 head。
  • 抽查:
  • 门禁 check-runs: 29ca8be8 上 59 个 run:46 success,13 skipped,0 失败。旧 head 85600dc8 上的红是被新推送取消的 rollup(Test UI、TypeScript Type Check、Lint & Repo Gates 读为 cancelled),以及一次 Live E2E UI 失败:studio-customize-journey.spec.ts 里 apiRequestContext.get: read ECONNRESET,那个用例走 Studio 定制路径,本 PR 不碰;在新 head 上 Live E2E UI 为 success。
  • 测试: ProtectedRoute.signOutListState-29174.test.tsx 扩到覆盖主动退出、过期两条路径。卡线程报告:341 个文件 3315 条用例全绿,两次消融转红后还原;main 生产构建实测前后截图在 PR 正文。
  • 接受的偏差:
    • 文件面多了同包的 ui/packages/auth/src/AuthContext.ts 和 useAuth.ts(类型和兜底值)。不加这两处,新成员无法成立,范围不外溢。
    • 证据提交在后续提交中删除,最终代码树与修复提交 aa800e72 逐字节相同;squash 合并不会把它带进 main。
  • 范围外去向: item 3(时区弹窗遮挡邀请提示)在 main 生产构建上实测不成立:弹窗在进入 /apps 路由 0.2–0.3 秒内弹出,早于任何操作,之后的邀请 toast 不被遮挡。落地时 First-run flow: small frictions a new deployment's first people meet #30125 随 Release 回 pm:queue,附这条读数。
  • 落地条款: Part of #30125。本席位按普通队列在这个 head 上落地:relay pr_ready + automerge_enable。MERGED 后在 First-run flow: small frictions a new deployment's first people meet #30125 写落地评论加 Release,摘 pm:dispatched,清 assignee。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 13:42
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 13:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit c322421 Nov 5, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-guv7bd branch November 5, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant