Skip to content

feat(ui): the signed-out invitation landing names the inviter and the organization from the invitation preview - #26554

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-26473-invitation-preview-landing
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-26473-invitation-preview-landing

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #26473
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: 未登录时打开邀请链接,登录页与注册页会读取服务端的匿名邀请预览(GET /api/v1/auth/organization/invitation-preview),卡片标题变为「林晓 邀请你加入 晓林装饰工作室」,并显示打码后的受邀邮箱作为提示。
  • 为什么改: 服务端读取已在 plugin-auth: a signed-out invitation preview read, so the invitation landing page can name the inviting organization and the inviter (server half of #25886) #26020 落地,但没有任何页面调用它;受邀人在注册前看不到是谁、邀请加入哪个组织。
  • 风险与回滚: 低。邮箱是打码值,只作提示,从不预填;预览 404、读取失败或旧服务端都保留现有通用文案,不抛错。回滚即 revert 本 PR。
  • 先看: ui/packages/app-shell/src/console/auth/invitationArrival.tsx 的 useInvitationPreview 与 invitationArrivalCopy;ui/packages/auth/src/createAuthClient.ts 的 getInvitationPreview。
  • 两分钟验证: pnpm --dir ui exec vitest run packages/app-shell/src/console/auth/__tests__/invitationPreview-26473.test.tsx packages/auth/src/__tests__/invitationPreview-26473.test.ts,预期全绿。
  • 需要你做的: 无,席位落地。

What changes

@object-ui/auth. getInvitationPreview(invitationId) is added to AuthClient, AuthContextValue, AuthProvider and useAuth(), along with the exported AuthInvitationPreview type (organizationName, inviterName: string | null, email). The client sends one anonymous GET {authUrl}/organization/invitation-preview?id=ID with the id URL-encoded. It handles the answers this way:

  • 200 with { success: true, data }: the three keys, checked at the boundary.
  • 404: null. The server sends one uniform not-found for an unknown, expired, accepted, rejected or cancelled id, so this is an answer, not a failure.
  • Any other status: a rejection that carries the server's code and the HTTP status.
  • A 200 of the wrong shape: a rejection named getInvitationPreview. The page never gets a half-filled sentence.

@object-ui/app-shell. invitationArrival.tsx gains useInvitationPreview(redirect) and invitationArrivalCopy(page, preview, t), and both are exported from the package entry.

  • The hook reads the id from ?redirect=/accept-invitation/ID, using the same segment isInvitationRedirect tests. It returns the preview for the current id only. It returns null while the read is pending, for a 404, and after any failure, including a useAuth() double that lacks the method. None of these throws into the page.
  • The copy helper returns the title and description for the login or register card. When there is a preview, the title is "INVITER invited you to join ORG", or "You are invited to join ORG" when the inviter has no name, and the description shows the masked address as the one to use. Without a preview it returns the generic copy each page shows today.
  • DefaultLoginPage and DefaultRegisterPage use both for an invitation redirect.

@object-ui/console. The console's own /login and /register (apps/console/src/pages/auth/) do the same. They are the pages the console routes.

The masked email. The preview's email is masked by the server (z***@example.com). It appears only in the card's description. No field is filled from it, and the pins assert that the email input stays empty.

@object-ui/i18n. Four keys are added: auth.login.invitationPreviewTitle, auth.login.invitationPreviewTitleNoInviter, auth.login.invitationPreviewDescription and auth.register.invitationPreviewDescription.

Locale packs: all ten, not en and zh only

The dispatch asked for new strings in en and zh only. That was measured, and it reds ui/packages/i18n/src/__tests__/all-locales-key-parity.test.ts. That test requires every pack to define every en key, and its only exception is the four outbound AI-message keys. The run under the lock with en and zh only: Tests 8 failed | 40 passed (48), one failure per other locale, for example ar is missing 4 key(s): auth.login.invitationPreviewDescription, auth.login.invitationPreviewTitle, auth.login.invitationPreviewTitleNoInviter, auth.register.invitationPreviewDescription. So the four keys are added to ar, de, es, fr, ja, ko, pt and ru as well, each on its own new lines next to the existing invitation keys.

Tests (red first)

New files:

  • ui/packages/auth/src/__tests__/invitationPreview-26473.test.ts (5 tests). Cases: found (exact keys, one GET, encoded id); found with a null inviter; 404 resolves null; 429 rejects with code and status; a wrong-shape 200 rejects.
  • ui/packages/app-shell/src/console/auth/__tests__/invitationPreview-26473.test.tsx covers DefaultLoginPage and DefaultRegisterPage, rendered through a real AuthProvider, a real createAuthClient and a real I18nProvider, with only fetch stubbed. Cases:
    • found: inviter and organization in the heading, the masked address shown, the email field empty;
    • found without an inviter;
    • 404: generic copy;
    • failed reads (500, a network failure, a wrong-shape body): generic copy, and the form still renders;
    • a login that did not come from an invitation sends no preview request.
  • ui/apps/console/src/pages/auth/__tests__/invitationPreview-26473.test.tsx runs found, 404 and 500 on the console's /login and /register.

The red leg was run under the lock: the 14 implementation files were restored to the base 00a4593a in the working tree only, the new files were run, and then the files were restored to HEAD. The restore was proved by hash comparison and an empty git diff HEAD.

Red, with the implementation at base:

  • auth file: 5 of 5 tests fail (client.getInvitationPreview is not a function).
  • app-shell file: 9 of 10 fail. The one that passes is the preservation pin (no preview request without an invitation redirect), which is expected to pass at base.
  • console file: 6 of 6 fail.

Green at head: see Verification.

Acceptance notes

  • invitationIdOf in invitationArrival.tsx reads the same /accept-invitation/ segment as isInvitationRedirect in signUpOffer.ts. That file is in open PR chore(ui)!: stop publishing @object-ui/app-shell; delete the two host-console examples and the extraction guide (#25922) #26483 and was not touched here. Once chore(ui)!: stop publishing @object-ui/app-shell; delete the two host-console examples and the extraction guide (#25922) #26483 lands, isInvitationRedirect could be expressed as invitationIdOf(r) !== null, leaving one parser.
  • The card also suggests a warning when the typed address does not match the masked pattern. The seat's route did not include it, so it is not done here. The masked address is shown, and the server still refuses a non-invitee's sign-up.
  • The card's optional "greet from ObjectOS Cloud" on the environment host is not taken. It is a matter of taste and outside the seat's route.
  • AcceptInvitationPage is unchanged. A signed-out visitor is redirected from it to /login before it renders, and a signed-in visitor already gets the organization through get-invitation.
  • The claim's file surface named invitationArrival.tsx, AcceptInvitationPage.tsx, the console sign-up page, createAuthClient.ts and the locale packs. This diff also touches four files the card itself lists (both LoginPage.tsx files and both RegisterPage.tsx files), the auth package's types.ts, AuthContext.ts, AuthProvider.tsx, useAuth.ts and index.ts (needed to reach the read from a page), the app-shell index.ts export, and all ten locale packs.

Verification

All runs are on head b848d2b1, in a worktree based on origin/main 00a4593a. Heavy runs went through scripts/pm/os-verify-lock.sh.

  • Closure: pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' build gave VERDICT command-exit 0.

  • vitest run (from ui/) over packages/auth/, packages/i18n/ and the 13 app-shell test files that import the touched pages (the new file included): Test Files 136 passed (136), Tests 1781 passed | 13 skipped (1794).

  • Console project, over the 21 console test files that import the two console pages (the new file included): Test Files 21 passed (21), Tests 110 passed (110).

  • pnpm --filter X run type-check exited 0 for each of @object-ui/auth, @object-ui/i18n, @object-ui/app-shell and @object-ui/console.

  • ui i18n gates check:i18n-keys (it checks every inline defaultValue against the en pack), check:i18n-drift and check:i18n-dead-keys: exit 0.

  • node scripts/check-sdui-manifest.mjs: exit 0. No registry component contract changed, so the manifest is not regenerated.

  • Derived gates (dispatch-gates.mjs --commands): 34 commands. 32 exited 0. Two were not measured, and CI runs both:

    • pnpm --dir ui exec vitest run scripts/ exited 99, a lock queue-timeout, three times.
    • pnpm check:type-check-debt exited 3, PREREQUISITE NOT MET, because its re-measure build never got a lock turn. The four touched packages' own type-checks are green above.

    The --ran reconciliation reads 34 accounted, 33 run, 1 NOT-MEASURED. The tool classifies exit 3 only, so the exit-99 vitest run counts as NOT MEASURED here as well.

  • pnpm check:changeset-fixed and pnpm check:nul-bytes: exit 0.


Generated by Claude Code

…gin and register pages (wip)

Claude-Session: https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup
Co-authored-by: Claude <noreply@anthropic.com>
… found / 404 / failure (wip)

Claude-Session: https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup
Co-authored-by: Claude <noreply@anthropic.com>
…key parity requires

Claude-Session: https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:未登录时打开邀请链接,登录页和注册页会读取服务端的匿名邀请预览(GET /api/v1/auth/organization/invitation-preview),标题变成「林晓 邀请你加入 晓林装饰工作室」,并把打码后的受邀邮箱作为提示显示。
为什么改:服务端的匿名预览已在 #26020 落地,但没有页面调用;受邀人注册前看不到是谁、邀请加入哪个组织(#26473)。
风险与回滚:邮箱只是打码提示,从不预填;预览 404、读取失败或旧服务端时保留现有通用文案,不抛错。回滚 = revert 本 PR。
先看:ui/packages/app-shell/src/console/auth/invitationArrival.tsx 的 useInvitationPreview 与 invitationArrivalCopy → ui/packages/auth/src/createAuthClient.ts 的 getInvitationPreview → 控制台 /login、/register 两页。
两分钟验证:在仓库根运行 pnpm --dir ui exec vitest run packages/app-shell/src/console/auth/__tests__/invitationPreview-26473.test.tsx packages/auth/src/__tests__/invitationPreview-26473.test.ts,预期全过。
需要你做的:无,席位落地。
席位意见:ACCEPT。新文案进了全部十个语言包(只加 en/zh 会让 all-locales-key-parity 红 8 个),与席位 1 已合并的 #26538 同改语言包,合并树干净。注册页「输入邮箱与打码不符时提示」未做,服务端本就拒绝非受邀人注册,不另开卡。

席位核对

seat 2 · 2026-10-27T08:23Z · head b848d2b1 (#26473)

item reading
form 26 files, +809/−46: @object-ui/auth getInvitationPreview(id) (one anonymous GET; preview, null on 404, rejects with code/status otherwise) plus its type and provider/hook plumbing; app-shell useInvitationPreview + invitationArrivalCopy and its default login/register pages; console /login and /register; four keys in all ten packs (added lines only); three red-first pins (auth, app-shell, console); root .changeset/ patch on auth, app-shell, i18n, console
premise measured the server route is on main (plugin-auth/src/auth-plugin.ts:3853, masked email per its CHANGELOG); no ui caller before this PR
gates (check-runs on b848d2b1) 45 success, 9 skipped, 0 failing (Test UI 16/16, Build & E2E UI, Type Check UI and debt ledger, Lint UI, Check PR Size, governed guard all green). Local: red 5/5 (auth), 9/10 (app-shell; the passing cell is the no-invitation preservation pin), 6/6 (console); green 136 files / 1781 tests (auth, i18n, 13 app-shell files) and 21 console files / 110 tests; type-check exit 0 for auth, i18n, app-shell, console; check-sdui-manifest exit 0; i18n key/drift/dead-key gates exit 0; 32 of 34 derived gates exit 0, ui scripts/ vitest and check:type-check-debt not measured locally (lock-starved), left to CI
accepted deviations locale keys in all ten packs instead of en + zh (parity test); file surface wider than the claim named (both login/register page pairs, auth types/context/provider/hook/index, app-shell index) because a page must reach the new read; AcceptInvitationPage.tsx untouched (a signed-out visitor is redirected before it renders)
out of scope after #26483 lands, isInvitationRedirect can reuse the new invitationIdOf (one parser for the invitation id); the register-page mismatch warning (server already refuses)
landing not governed (check-governed-merges: 855 changed lines, no hit paths); git merge-tree against origin/main a3009cb clean, after seat 1's #26538 on the same packs; ⛔ signUpOffer.ts untouched (open PR #26483)

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 08:24
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 319d6fa Oct 27, 2026
55 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-26473-invitation-preview-landing branch October 27, 2026 08:46
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/l tests tooling

Projects

None yet

2 participants