Repository navigation
Commit f40b777
fix(ui,platform-objects): invitation screens — labelled statuses, role help, resend as a confirmation, dialog focus and email check (#28224)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoKKC66puRHhWEAxZHdQjYYQ)_
Part of #27521
Clause-②: yes (widening: `@object-ui/app-shell` exports
`InvitationLinkHint` and `@object-ui/plugin-form` exports
`useInitialFieldFocus`; no accept set moves)
Metadata-protocol: none (platform object metadata only, no authorable
key: `sys_invitation.status` options gain display labels, with
`rejected` read as "Declined"; the role param of the four `invite_user`
actions, `sys_member.add_member` and `sys_member.update_member_role`
gains one shared `helpText`; `sys_invitation.resend_invitation` gains a
`description` and carries its email and role over from the row;
`sys_member.update_member_role` gains a `description` naming the member;
the `expired` invitations list projects `role`)
This PR lands rows 7, 8, 9, 10 (the invite half), 11 and 23 of the
18.3.0 RC walkthrough, minus two pieces that need a decision or a spec
member (see Acceptance notes). It keeps the card open.
## 维护者速读
-
**改了什么**:邀请相关页面的六处体验问题。状态显示成标签(待接受、已接受、已拒绝…),所有分配组织角色的选择框都逐行解释每个角色,「更改角色」点名被改的成员;「重新发送」改成一句确认(写明发给谁、到期重新计算),不再弹可编辑表单;邀请时邮箱格式错误直接标在输入框下,不再横幅加
toast 报两遍;登录页提示受邀者打开邀请链接;接受邀请后显示「已加入 X 组织,角色为
Y」并有「继续」;所有操作弹窗打开时聚焦第一个输入框,关闭后焦点回到打开它的按钮。
- **为什么改**:18.3.0 RC
走查(#27521)在这些页面记下的问题:原始小写状态值、角色无解释、重发像是在改邀请、错误报两遍且字段不标红、受邀者不知道该点链接、加入后没有确认、键盘用户关弹窗后焦点丢到页面顶端。
- **风险与回滚**:参数弹窗新增的客户端格式校验对所有动作生效,email/url
类型参数填了表单本就拒绝的值时,现在提交前就被拦下;焦点归还在 document
上挂了两个捕获阶段监听(只记录最后一次点击/按键的控件)。无数据迁移,回滚即 revert 本 PR。
-
**先看**:`ui/packages/app-shell/src/views/actionDialogReturnFocus.ts`(焦点归还,尤其等待被禁用按钮恢复的那段);`ui/packages/app-shell/src/views/ActionParamDialog.tsx`(提交时先查必填再查格式);`packages/platform-objects/src/identity/sys-invitation.object.ts`(重发改为带入行上的
email/role)。
- **两分钟验证**:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/actionDialogFocus-27521.test.tsx
packages/app-shell/src/views/ActionParamDialog.invitationScreens-27521.test.tsx`,预期
2 个文件、18 个用例全过。
- **需要你做的**:无,席位落地。
## What changes
**Platform objects (`@objectstack/platform-objects`)**
- Row 7: `sys_invitation.status` options carry labels (Pending,
Accepted, Declined, Expired, Canceled), so list cells read the label
instead of the raw value. The `expired` list view also projects `role`,
as the other views do.
- Row 8: one help text, `MEMBERSHIP_ROLE_PARAM_HELP` in the new
`identity/membership-role-help.ts`, explains each built-in membership
role on its own line. It is built from
`BUILTIN_MEMBERSHIP_ROLE_OPTIONS`, so a role and its label cannot drift.
All six role pickers that grant an organization role use it: the four
`invite_user` actions (on `sys_invitation`, `sys_member`, `sys_user`,
`sys_organization`), `add_member` and `update_member_role`.
`update_member_role` gains a description that names the member, with a
fallback for a row that does not expand the user.
- Row 9: `resend_invitation` becomes a confirmation. Its description
names the invitee and says the expiry starts over, and its email and
role params carry over from the row, so the dialog shows no editable
inputs.
- Translations: the four generated bundles are regenerated, and the
zh-CN, ja-JP and es-ES leaves are translated by hand.
**Shared action dialogs (`@object-ui/app-shell`)**
The change moves three shared dialogs that every declared action uses:
`ActionParamDialog`, `ActionConfirmDialog` and `ActionResultDialog`.
`useConsoleActionRuntime` is unchanged.
- Row 10: `ActionParamDialog` checks an `email` or `url` param against
the same format rule the object form uses (`buildValidationRules` from
`@object-ui/fields`) before anything runs. A malformed value is marked
on its field (message under it, `aria-invalid`), and the run never
starts, so the old banner plus toast cannot appear. An empty required
field still reads "X is required". Carried-over values and empty
optional values are not judged.
- Row 8: help text under a param keeps its line breaks
(`whitespace-pre-line`), so the role help reads one role per line.
- Row 23, opening: the param dialog opens on its first editable field
instead of the footer's Cancel. It reuses the object form's
`useInitialFieldFocus`, now exported from `@object-ui/plugin-form`. A
dialog of carried-over values only opens on itself.
- Row 23, closing: the new `actionDialogReturnFocus.ts` gives all three
dialogs `useActionDialogReturnFocus`. When a dialog closes, focus goes
back to the control that opened it. This covers three openers that
defeated Radix's default:
- An action button that is disabled while its action runs. Chrome blurs
it before the dialog opens, so the opener is read from the last pointer
or key activation. Focus waits for the button to be enabled again,
unless the user moves focus elsewhere first.
- A row menu item, whose menu is gone by then. The opener is the menu's
trigger.
- A param dialog that hands over to a result dialog. The result dialog
returns to the first opener.
With no opener (focus on `body` and no recent activation), Radix's
default stays.
**Invitation pages (`@object-ui/app-shell`, `@object-ui/console`)**
- Row 9 (workspace page): the Invitations page's cancel dialog dismisses
with "Keep invitation".
- Row 11: the sign-in page shows "Have an invitation? Open the link in
the invitation email to accept it." where sign-up is by invitation only.
`InvitationLinkHint` lives with the other invitation-arrival pieces and
is rendered by both sign-in pages, the app-shell default and the
console's own. After accepting, the accept page shows "Invitation
accepted", "Welcome to ORG. You joined as ROLE." and a focused Continue
button that enters the organization, instead of a toast and an immediate
redirect.
- i18n: four new keys, in all ten packs.
## Tests
-
`packages/platform-objects/src/identity/invitation-screens-copy.test.ts`
(new) pins:
- the status labels, and that the en bundle carries them;
- that the help has one line per `BUILTIN_MEMBERSHIP_ROLE_OPTIONS`
label, and that all six pickers carry it;
- the Change Role description and its fallback;
- the resend description and its carried-over params, and that every
invitations list projects email and role;
- that the translated leaves keep their placeholders and one help line
per role.
-
`ui/packages/app-shell/src/views/ActionParamDialog.invitationScreens-27521.test.tsx`
(new, 10 cases), under a real English `I18nProvider`:
- a malformed email is marked and nothing runs, with no refusal banner;
- the mark clears on the next keystroke;
- required wins over format;
- a url is held to the url rule, and the control case is a text param;
- `paramFormatErrorKey` skips carried-over and empty values;
- help line breaks are kept;
- the Change Role description is interpolated by a real `ActionRunner`
from a list row.
- `ui/packages/app-shell/src/views/actionDialogFocus-27521.test.tsx`
(new, 8 cases):
- the dialog opens on the first field, and a carry-over-only dialog on
itself;
- Escape returns focus to a disabled action button, both when it kept
focus and when the browser blurred it;
- a user who moved focus keeps it;
- a row menu's Delete confirm returns to the menu trigger (the
non-invitation pin);
- a param dialog that hands over to a result dialog returns to the first
opener;
- control: with no opener, Radix's default stays.
- `ui/packages/i18n/src/__tests__/invitation-screens-27521.test.ts`
(new): all ten packs carry the four keys, translated, with the English
placeholders.
- Cases added to existing suites: the sign-in hint in both posture
suites (`defaultPagesFollowPosture-11705`,
`signUpFollowsPosture-11691`), "Keep invitation" in
`org-i18n-holdouts-4474`, and the joined state plus Continue in
`AcceptInvitationRoute` (its toast assertion is replaced).
## Acceptance notes
| Row | Producer | Fix | Test | Live-checked |
| --- | --- | --- | --- | --- |
| 7 | `sys_invitation.status` options had no labels | Labels; `rejected`
reads "Declined" | `invitation-screens-copy.test.ts` | y |
| 8 | Each role picker had its own one-line help; `update_member_role`
had no description | One shared help, one line per role, on six pickers;
`whitespace-pre-line`; Change Role names the member |
`invitation-screens-copy.test.ts`,
`ActionParamDialog.invitationScreens-27521.test.tsx` | y |
| 9 | `resend_invitation` declared editable params; the workspace cancel
dialog used a generic Cancel | Resend is a confirmation with
carried-over params; "Keep invitation" |
`invitation-screens-copy.test.ts`, `org-i18n-holdouts-4474.test.tsx` | y
|
| 10 (invite) | `ActionParamDialog` checked only required, so the
server's refusal showed twice | Client-side format check marked on the
field, before the run |
`ActionParamDialog.invitationScreens-27521.test.tsx` | y |
| 11 | No hint on the plain sign-in page; accepting toasted and
redirected | `InvitationLinkHint` on both sign-in pages; a joined state
with Continue | posture suites, `AcceptInvitationRoute.test.tsx`,
`invitation-screens-27521.test.ts` | y |
| 23 | The dialogs had no initial focus target and no return target |
First field on open; return to the opener on close |
`actionDialogFocus-27521.test.tsx` | y |
What is left on the card, so this PR says `Part of`:
- **Row 9, admin page.** The declared Cancel Invitation confirm still
reads "Cancel" and "Cancel Invitation". Its dismiss label needs a spec
member on the action's confirm, which this card does not own.
- **Row 11, pre-fill.** Pre-filling the invited email on the
registration form is a `needs_decision` against ADR-0138.
- **Row 10, Create User.** The "Either email or phoneNumber is required"
message and the missing asterisks belong to the Create User half, which
this card does not own. Create User's dialog does get the row 23 focus
fix, because the dialogs are shared.
Live check (private stack, `invite_only` posture, Chrome):
- **Invite User:** the dialog opens on Email. A malformed email shows
"Please enter a valid email address" under the field, with
`aria-invalid=true` and no toast. The role help reads one role per line.
Both Escape and a successful invite return focus to Invite User.
- **Invitations list:** the status cells read "Pending".
- **Resend:** the dialog reads "Resend the invitation to
ada@example.com? Its expiry date starts over." and has no editable
inputs. Escape returns focus to the row's "Open menu".
- **Admin Cancel Invitation:** the dismiss label is unchanged
(remainder), and focus returns to the trigger.
- **Sign-in:** the plain sign-in page shows the hint.
- **Accepting:** an invitee signs up and accepts, then reads "Invitation
accepted / Welcome to Default Organization. You joined as Member." with
focus on Continue, which lands on the root.
- **Create User:** the dialog opens on Email, and Escape returns focus
to Create User.
- **Change Role** (Members related list on the organization page): reads
"Choose the new organization role for Ada Lovelace." with the role help.
It opens on the role picker, and Escape returns focus to "Row actions".
## Verification
Local runs at the branch head before the last merge of `main`
(a78dee0). The last merge, at the head, brings 10 commits that touch
none of this diff's files. Both the platform-objects suite and the
touched ui suites were re-run after the previous merge.
<details><summary>Tests and type-checks</summary>
- `pnpm --filter @objectstack/platform-objects exec vitest run`: Test
Files 104 passed (104), Tests 1474 passed (1474).
- Affected ui suites (`pnpm --dir ui exec vitest run` over the packages
this diff moves and their consumers): Test Files 944 passed (944), Tests
10202 passed, 15 skipped.
- The touched dialog, auth, organization and i18n suites after the
merge: Test Files 62 passed (62), Tests 543 passed (543).
- Type-check passes for `@object-ui/i18n`, `@object-ui/plugin-form`,
`@object-ui/app-shell`, `@object-ui/console` and
`@objectstack/platform-objects`.
- `node scripts/check-sdui-manifest.mjs`: exit 0.
- `node scripts/check-i18n-bundles.mjs` after a platform-objects closure
build: OK, no file changes.
</details>
<details><summary>Ablations (each restored, with `git diff HEAD`
empty)</summary>
- Focus: with the dialogs' focus changes reverted, 5 of the 7 focus
cases then in the file went red, including the row-menu case. With only
the last-activation read removed (`node scripts/ablation-replace.mjs`),
the browser-blur case goes red: 1 failed, 7 passed (8).
- Email format: with the format check removed from submit, 3 of the 10
param-dialog cases went red.
- Sign-in hint: with the hint unrendered, the 2 hint cases went red.
</details>
<details><summary>Derived gates: 64 derived, 64 run, all exit 0;
reconciled</summary>
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--ran F` printed "64 derived famil(ies) accounted for — 64 run, 0
NOT-MEASURED".
- node scripts/check-adr-0087-registration.mjs --base origin/main · exit
0
- node scripts/check-adr-0087-registration.mjs --self-test · exit 0
- node scripts/check-changeset-no-major.mjs --base origin/main · exit 0
- node scripts/check-closing-keyword-parity.mjs · exit 0
- node scripts/check-closing-keyword-parity.mjs --self-test · exit 0
- node scripts/check-comment-mask-adoption.mjs · exit 0
- node scripts/check-comment-mask-adoption.mjs --self-test · exit 0
- node scripts/check-comment-mask-corpus.mjs · exit 0
- node scripts/check-dts-emitted.mjs --self-test · exit 0
- node scripts/check-empty-changeset.mjs --base origin/main · exit 0
- node scripts/check-empty-changeset.mjs --self-test · exit 0
- node scripts/check-issue-citations.mjs · exit 0
- node scripts/check-plugin-teardown-shape.mjs · exit 0
- node scripts/check-plugin-teardown-shape.mjs --self-test · exit 0
- node scripts/check-sdui-manifest.mjs · exit 0
- node scripts/check-sdui-manifest.mjs --self-test · exit 0
- node scripts/ci/assert-turbo-remote-cache.mjs --self-test · exit 0
- node scripts/ci/plan-test-core-shards.mjs --self-test · exit 0
- node scripts/ci/prune-turbo-cache.mjs --self-test · exit 0
- node scripts/docs-audit/check-affected-docs.mjs · exit 0
- node scripts/docs-audit/check-drift-comment.mjs · exit 0
- node scripts/pm/release-rehearsal-clone.mjs --self-test · exit 0
- node scripts/release-pending-publish.mjs --self-test · exit 0
- pnpm --dir ui exec vitest run scripts/ · exit 0
- pnpm --filter @objectstack/spec run check:duration-unit-keys · exit 0
- pnpm check:changeset-gate-self-tests · exit 0
- pnpm check:ci-filter-parity · exit 0
- pnpm check:closed-ceremony-text · exit 0
- pnpm check:cross-package-test-inputs · exit 0
- pnpm check:dispatcher-error-vocabulary · exit 0
- pnpm check:doc-authoring · exit 0
- pnpm check:driver-memory-census · exit 0
- pnpm check:dts-closure · exit 0
- pnpm check:dual-build-cjs-loads · exit 0
- pnpm check:engine-double-contract · exit 0
- pnpm check:gitlink-declared · exit 0
- pnpm check:i18n · exit 0
- pnpm check:i18n-stale-fill · exit 0
- pnpm check:issue-citations · exit 0
- pnpm check:keyed-text-bounds · exit 0
- pnpm check:lean-entry-closure · exit 0
- pnpm check:logger-receiver-detach · exit 0
- pnpm check:nul-bytes · exit 0
- pnpm check:objectql-double-limit · exit 0
- pnpm check:org-identifier · exit 0
- pnpm check:page-declaration-shape · exit 0
- pnpm check:platform-object-tenancy-census · exit 0
- pnpm check:pm-changeset-deadline-census · exit 0
- pnpm check:published-files · exit 0
- pnpm check:query-options-erasure · exit 0
- pnpm check:refd-timer-probe · exit 0
- pnpm check:registry-log-declared · exit 0
- pnpm check:rest-log-spy-declared · exit 0
- pnpm check:skill-identifier-liveness · exit 0
- pnpm check:slot-lookup · exit 0
- pnpm check:sourcemap-no-sources-content · exit 0
- pnpm check:system-context-census · exit 0
- pnpm check:test-source-alias · exit 0
- pnpm check:tier-file-adoption · exit 0
- pnpm check:type-check-coverage · exit 0
- pnpm check:type-check-debt · exit 0
- pnpm check:undeclared-dep-imports · exit 0
- pnpm check:watch-hint-literal · exit 0
- pnpm check:where-matcher · exit 0
`check:i18n` first exited 1 after merging `main`, because the
platform-objects dist was stale. After a closure rebuild it exited 0
with no file changes. The plugin-teardown-shape self-test needs its
pinned commit, which this shallow clone fetched first.
</details>
The full farm runs in CI.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibyekmZuiE5TVrwDWqH)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9e4f1cf commit f40b777
39 files changed
Lines changed: 1251 additions & 75 deletions
File tree
- .changeset
- packages/platform-objects/src
- apps/translations
- identity
- ui
- apps/console/src/pages/auth
- __tests__
- packages
- app-shell/src
- console
- auth
- __tests__
- organizations
- __tests__
- manage
- views
- i18n/src
- __tests__
- locales
- plugin-form/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| 149 | + | |
149 | 150 | | |
150 | 151 | | |
151 | 152 | | |
| |||
585 | 586 | | |
586 | 587 | | |
587 | 588 | | |
| 589 | + | |
588 | 590 | | |
589 | 591 | | |
590 | 592 | | |
| |||
676 | 678 | | |
677 | 679 | | |
678 | 680 | | |
| 681 | + | |
679 | 682 | | |
680 | 683 | | |
681 | 684 | | |
| |||
688 | 691 | | |
689 | 692 | | |
690 | 693 | | |
691 | | - | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
692 | 700 | | |
693 | 701 | | |
694 | 702 | | |
| 703 | + | |
695 | 704 | | |
696 | 705 | | |
697 | 706 | | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
698 | 712 | | |
699 | 713 | | |
700 | 714 | | |
| |||
748 | 762 | | |
749 | 763 | | |
750 | 764 | | |
751 | | - | |
752 | | - | |
753 | | - | |
754 | | - | |
755 | | - | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
756 | 770 | | |
757 | 771 | | |
758 | 772 | | |
| |||
816 | 830 | | |
817 | 831 | | |
818 | 832 | | |
| 833 | + | |
819 | 834 | | |
820 | 835 | | |
821 | 836 | | |
| |||
833 | 848 | | |
834 | 849 | | |
835 | 850 | | |
| 851 | + | |
836 | 852 | | |
837 | 853 | | |
838 | 854 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| 149 | + | |
149 | 150 | | |
150 | 151 | | |
151 | 152 | | |
| |||
585 | 586 | | |
586 | 587 | | |
587 | 588 | | |
| 589 | + | |
588 | 590 | | |
589 | 591 | | |
590 | 592 | | |
| |||
676 | 678 | | |
677 | 679 | | |
678 | 680 | | |
| 681 | + | |
679 | 682 | | |
680 | 683 | | |
681 | 684 | | |
| |||
688 | 691 | | |
689 | 692 | | |
690 | 693 | | |
691 | | - | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
692 | 700 | | |
693 | 701 | | |
694 | 702 | | |
| 703 | + | |
695 | 704 | | |
696 | 705 | | |
697 | 706 | | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
698 | 712 | | |
699 | 713 | | |
700 | 714 | | |
| |||
816 | 830 | | |
817 | 831 | | |
818 | 832 | | |
| 833 | + | |
819 | 834 | | |
820 | 835 | | |
821 | 836 | | |
| |||
833 | 848 | | |
834 | 849 | | |
835 | 850 | | |
| 851 | + | |
836 | 852 | | |
837 | 853 | | |
838 | 854 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| 149 | + | |
149 | 150 | | |
150 | 151 | | |
151 | 152 | | |
| |||
585 | 586 | | |
586 | 587 | | |
587 | 588 | | |
| 589 | + | |
588 | 590 | | |
589 | 591 | | |
590 | 592 | | |
| |||
676 | 678 | | |
677 | 679 | | |
678 | 680 | | |
| 681 | + | |
679 | 682 | | |
680 | 683 | | |
681 | 684 | | |
| |||
688 | 691 | | |
689 | 692 | | |
690 | 693 | | |
691 | | - | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
692 | 700 | | |
693 | 701 | | |
694 | 702 | | |
| 703 | + | |
695 | 704 | | |
696 | 705 | | |
697 | 706 | | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
698 | 712 | | |
699 | 713 | | |
700 | 714 | | |
| |||
816 | 830 | | |
817 | 831 | | |
818 | 832 | | |
| 833 | + | |
819 | 834 | | |
820 | 835 | | |
821 | 836 | | |
| |||
833 | 848 | | |
834 | 849 | | |
835 | 850 | | |
| 851 | + | |
836 | 852 | | |
837 | 853 | | |
838 | 854 | | |
| |||
0 commit comments