Skip to content

fix(ui,platform-objects): invitation screens — labelled statuses, role help, resend as a confirmation, dialog focus and email check - #28224

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-yg03y1
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-yg03y1

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

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 走查(Setup and Studio: suggestions from the 18.3.0 RC browser walkthrough: Setup naming and machine values, invitation and user onboarding, permission-set authoring, dialog focus, Studio first run, validation and flow builders, and publish-panel wording #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.

Tests and type-checks
  • 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.
Ablations (each restored, with `git diff HEAD` empty)
  • 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.
Derived gates: 64 derived, 64 run, all exit 0; reconciled

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.

The full farm runs in CI.


Generated by Claude Code

…aram dialog checks and focus, joined state

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyekmZuiE5TVrwDWqH
…e help, resend as a confirmation, param dialog format check and focus

Invitation statuses carry display labels. Every role picker that grants an
organization role explains each role on its own line, and Change Role names the
member. Resend confirms with the row's email and role carried over. The
workspace cancel dialog dismisses with Keep invitation.

The console's param dialog checks email and url params against the form's
format rule and marks the field. The param, confirm and result dialogs open on
the first editable field and return focus to their opener. The sign-in page
tells an invitee without the link to open it, and the accept page says the user
joined before Continue enters the organization.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyekmZuiE5TVrwDWqH
…as it turned disabled

A browser blurs a focused button when it becomes disabled, so an action
button that disables itself while its action runs leaves focus on body by the
time its dialog opens. The dialog now takes the control the user last pressed
or clicked as its opener in that case.

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

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/platform-objects, touching 12 documentable anchor(s).

34 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 1ea3fe9eaa8cf43657cb66b119f6ed15ac43d19c.

⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 anchor(s) matched too much of the corpus to be a work list: sys_user (symbol, 48 pages), organization_id (literal, 38 pages)
  • 10 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1ea3fe9eaa8cf43657cb66b119f6ed15ac43d19c → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e660e5c39e4cc131898a8aac30b1aa07a530e1c0 — the merge of head 5b5f464ebfb64b208e1ef14f0971917ae689e12e into base 1ea3fe9eaa8cf43657cb66b119f6ed15ac43d19c, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e660e5c39e4cc131898a8aac30b1aa07a530e1c0 && git checkout e660e5c39e4cc131898a8aac30b1aa07a530e1c0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1ea3fe9eaa8cf43657cb66b119f6ed15ac43d19c 5b5f464ebfb64b208e1ef14f0971917ae689e12e && git checkout -B drift-repro 1ea3fe9eaa8cf43657cb66b119f6ed15ac43d19c && git merge --no-ff 5b5f464ebfb64b208e1ef14f0971917ae689e12e

node scripts/docs-audit/affected-docs.mjs --json 1ea3fe9eaa8cf43657cb66b119f6ed15ac43d19c

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1ea3fe9eaa8cf43657cb66b119f6ed15ac43d19c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 2582296ce06d02ba1b476da8b949b311a460971d · 2026-10-30T17:55Z

Contract review lane (Fable): declaration face (Clause-②: yes, no schema or governed path) at TIER_DEFAULT per record-recognisers.mjs --template --paths; reading the card, the PR diff and the head's check-runs only. The record follows on this head once its check-runs complete.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibyekmZuiE5TVrwDWqH

Test UI (5/16) on head 2582296 is red on one test only: ui/packages/app-shell/src/views/studio-design/AutomationsPillar.problemsAgree-27799.test.tsx expects the node config finding's node 'write' prose and receives the key path flows[0].nodes[1].config.fields.id. That is the main red tracked in #28219 (from #28192). This diff touches neither the test nor Studio's Problems path, so the red is not this PR's. The fix is owned elsewhere and is left alone here. Once it lands, this branch merges origin/main and the shard re-runs.


Generated by Claude Code

…k included

The console's pin evaluates this exact sentence through a real ActionRunner,
including the fallback for a row that does not expand user_id; holding the
whole declaration here keeps that copy from drifting.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 5b5f464ebfb64b208e1ef14f0971917ae689e12e · 2026-10-30T19:27Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 5b5f464ebfb64b208e1ef14f0971917ae689e12e
Local-runs: none
Scope: declaration face (Clause-②: yes (widening: @object-ui/app-shell exports InvitationLinkHint and @object-ui/plugin-form exports useInitialFieldFocus; no accept set moves)), the whole diff outside the prose faces: packages/platform-objects/src/identity/{sys-invitation,sys-member,sys-organization,sys-user}.object.ts, membership-role-help.ts and invitation-screens-copy.test.ts, the four generated translation bundles under src/apps/translations/, ui/packages/app-shell/src/views/{ActionParamDialog,ActionConfirmDialog,ActionResultDialog}.tsx and actionDialogReturnFocus.ts with their two new tests, ui/packages/app-shell/src/console/auth/{LoginPage,invitationArrival}.tsx, src/console/organizations/manage/{AcceptInvitationPage,InvitationsPage}.tsx, src/index.ts, ui/apps/console/src/pages/auth/LoginPage.tsx, ui/packages/plugin-form/src/index.tsx, the ten i18n packs and their test, the adapted auth/organization tests, .changeset/27521-invitation-screens.md.

Inputs: card #27521 body (rows 7, 8, 9, 10, 11, 23) and its thirteen comments (triage 6495068090, seat 3's split 6511102307 and claim 6511168770, seat 2's claim 6511270445, reports 6512376770 and 6513177556, seat 3's accept 6512877469, seat 2's landing 6513658747 and release 6524603067, ui seat 1 claim 6526176786 and amendments 6526355417 and 6529299111, os-dev-report 6529206941); PR #28224 body, 39-file list and the diff against main (+1247/-75); the check-runs on 2582296c (read 2026-10-30T20:06Z): check-runs on 5b5f464: 63 total, 55 success (Auto Label, Build & E2E UI, Build Core, Build UI Deps, Check Changeset, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Test Core, Test Core (all 8), Test UI, Test UI (all 16), Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter); 8 skipped path-filtered (18.x upgrade harness (opt-in), Build Docs, Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), UI Published Artifacts); 0 failure, 0 in progress Author user.login is objectstack-fleet[bot], so no login note is owed. The reviewer is not the drafter (session_01L9qibyekmZuiE5TVrwDWqH).

Delta since the 2582296 review (claim 6529782482; re-claimed on this head): two commits. fa9836b merges origin/main 39da1bd (which carries the #28227 main-red fix): the PR's own hunks against the new merge base are byte-identical to the reviewed ones (39 files, same names, no content difference) and the merge touches no file the PR touches. 5b5f464 is test-only: invitation-screens-copy.test.ts now pins the whole update_member_role description, fallback included, so the should-fix below is resolved on this head.

① Derived judgments

  • The declared widening is exact: @object-ui/app-shell's barrel adds InvitationLinkHint (a component rendered by both sign-in pages), and @object-ui/plugin-form's barrel re-exports the existing internal hook useInitialFieldFocus; neither name was exported on main. Nothing an existing importer sees changes.
  • Metadata-protocol: none holds against the platform-object diff: every edit uses an authorable key the spec already declares — option label on sys_invitation.status (Field.select({ options: [{ value, label }] }), values unchanged, rejected labelled "Declined"), helpText on the role param of the six pickers (one MEMBERSHIP_ROLE_PARAM_HELP derived from BUILTIN_MEMBERSHIP_ROLE_OPTIONS, typed on BuiltinMembershipRole so a new role fails to compile until explained), description on resend_invitation and update_member_role, carryOver: true beside the existing defaultFromRow: true on resend's two params (carryOver is a declared action-param contract, packages/spec/src/ui/action.zod.ts), and role added to the expired list's columns. No stored value, enum member, action name or param set changes.
  • Shared dialogs: ActionParamDialog now runs the object form's own format rule (buildValidationRules for email / url, via resolveParamWidgetType) after the required check and before the run, marking the field and skipping carried-over and empty values; help text keeps line breaks; the dialog opens on its first editable field and all three dialogs return focus to the opener through useActionDialogReturnFocus (document-level capture listeners that only record the last activated control; a disabled opener is waited for up to 10 s unless the user moves focus; no opener ⇒ Radix's default). These apply to every action's dialogs, which the changeset says.
  • Invitation pages: the sign-in hint where sign-up is invitation-only, the accept page's joined state with Continue, "Keep invitation" on the workspace cancel dialog; four new i18n keys in all ten packs, pinned.
  • Tests: invitation-screens-copy.test.ts pins labels, the help's one-line-per-role shape on all six pickers, the descriptions, resend's carried-over params and that every invitations list projects email and role, and the translated leaves' placeholders; the two app-shell test files (18 cases) pin the format check, required-over-format, carry-over skipping, line breaks, and the three focus-return openers with a control case. The ablations are the dev's report, read not re-run.
  • Order: rows 7–11 (the invite half of 10) and 23; remainders named with their reasons (row 9's admin dismiss label needs a spec member on the confirm; row 11's pre-fill is a needs_decision against ADR-0138; row 10's Create User half is another card's), which is why the PR carries Part of.

② Semver level

  • @object-ui/app-shell and @object-ui/plugin-form patch: additive barrel exports with no accept set moving (the fix(ui): list each failed validation rule as its own line in the form banner and toast #28151 precedent). @objectstack/platform-objects patch: labels, help text, descriptions and a projected column on built-in objects, no authorable key and no stored value. @object-ui/i18n and @object-ui/console patch: added keys, a rendered hint. Metadata-protocol: none holds.
  • The three carriers agree: PR body, claim amendment 6529299111 and the changeset.

③ Boundary flags

  • Behaviour reaching every declared action: the client-side format check refuses, before the run, an email / url param value the object form already refuses for the same field type; a server that accepted such a value for a custom action would now be reached only after the user fixes it. Disclosed in the changeset; the rule is the form's, so the two doors agree.
  • Focus handling installs two capture-phase listeners on document once per document; they store one element reference and a timestamp, nothing else.
  • Generated translation bundles regenerated and three leaves hand-translated; check-i18n-bundles reported clean in the dev's run.
  • Part of #27521: remainders stay on the card as listed. Governed: none. Tier: +1247/-75 over 39 files, Tier S. Author objectstack-fleet[bot] ⇒ no login note.

Should-fix, resolved by 5b5f464 on this head (the test now holds the whole sentence): update_member_role's description reads ${record.user_id?.name ?? 'this member'}, which depends on the list expanding user_id; where a future list shows the action without that expansion the fallback wording is the one users see, worth a pin on that list when it appears.

Implemented-by: claude/project-thread-yg03y1
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN

席位核对

ACCEPT · seat domain:ui#1 · head 5b5f464e · 2026-10-30T20:13Z

席位意见:Setup → Invitations 的六行建议中,这个 PR 做了能做的部分。邀请状态改显示标签(Pending / Accepted / Declined …),不再是原始小写值。所有授予角色的选择框共用一段说明,每个角色一行;Change Role 的说明会点名被改的成员。Resend 改为确认框,不再弹出可编辑的表单。控制台的动作对话框会先检查邮箱/网址参数,错误标在字段上;打开时焦点落在第一个可编辑字段,关闭后焦点回到打开它的按钮。在只能凭邀请注册的环境里,登录页会提示受邀人去打开邀请链接;接受邀请后,页面会告诉用户已加入哪个组织、担任什么角色。余下三项留在卡上(见第 3 条),所以本 PR 是 Part of #27521。

  1. Shape: Part of #27521, draft, base main, 39 files, +1251/−75. Metadata: sys-invitation / sys-member / sys-organization / sys-user objects plus the shared membership-role-help.ts, with the generated translation bundles. Console: ActionParamDialog, ActionConfirmDialog, ActionResultDialog, the new actionDialogReturnFocus.ts, console/auth LoginPage / invitationArrival / AcceptInvitationPage, and one render line in ui/apps/console/src/pages/auth/LoginPage.tsx. Ten locale packs; tests; root changeset. The surface matches claim 6526176786 and amendments 6526355417 / 6529299111.
  2. Declaration: Clause-②: yes (widening: @object-ui/app-shell exports InvitationLinkHint and @object-ui/plugin-form exports useInitialFieldFocus; no accept set moves), and Metadata-protocol: none (option labels, help text and action params on platform objects, no authorable key).
  3. Remainder on Setup and Studio: suggestions from the 18.3.0 RC browser walkthrough: Setup naming and machine values, invitation and user onboarding, permission-set authoring, dialog focus, Studio first run, validation and flow builders, and publish-panel wording #27521: row 9's dismiss label needs a spec member. Row 11's email pre-fill is needs_decision against ADR-0138's amendment, where the signed-out preview masks the invited address. Row 10's Create User half is not done. The shared dialog change was pinned on a non-invitation action, the row-menu Delete confirm.
  4. Contract review: PASS at this head, record 6532423369 (TIER_DEFAULT), by the contract-review lane. The lane's should-fix, the update_member_role whole-description pin, is folded in at 5b5f464e.
  5. Gates / check-runs: on 5b5f464e, 55 success, 8 skipped, 0 failure. mergeable_state clean. Dev's local run: 64 derived gates exit 0, check-sdui-manifest 0, every row live-checked.
  6. Landing: check-governed-merges --pr 28224: NOT governed, 1326 changed lines ≤ 3000. The seat readies the PR and arms auto-merge on the maintainer's 「按 pm-dispatch 审过的pr你可以自主落地」 (seat thread, 2026-10-30T14:19Z).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 20:25
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit f40b777 Oct 30, 2026
67 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-yg03y1 branch October 30, 2026 20:49
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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant