Skip to content

feat(mobile, app-shell): open the mobile shell's native connection switcher from the header organization button - #29565

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-g0magq
Nov 2, 2026
Merged

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

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

Requested by Steedos · project thread
Fixes #29458

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:手机 App(原生壳)里点顶栏的组织按钮,现在打开 App 自带的「切换组织」面板,而不是网页下拉菜单;在 App 里只属于一个组织的用户也能看到这个按钮(只读的组织名标记随之让位,顶栏只显示一次组织);用户在 App 里移除某个连接时,Console 会及时应答,不再让 App 干等 10 秒。
  • 为什么改:原生面板能同时列出已保存的组织和公司服务器,网页菜单看不到这些;此前它只能从离线页打开。移除连接的应答是推送令牌注销的接口位,等设备注册(Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042)落地后在这里填上注销动作。
  • 风险与回滚:只在 App 声明了该能力时生效,普通浏览器和旧版 App 行为不变;回滚即还原本 PR,无数据与服务端改动。
  • 先看:ui/packages/app-shell/src/layout/WorkspaceSwitcher.tsx(按钮分支,位于单组织判断之前)、ui/packages/app-shell/src/layout/CurrentOrganizationIndicator.tsx(App 内让位)、ui/packages/mobile/src/useNativeHost.ts(connectionRemoved 应答)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/layout/__tests__/WorkspaceSwitcher.nativeConnectionSwitcher.test.tsx packages/mobile/src/__tests__/nativeHost.test.tsx,应全部通过(7 + 64)。
  • 需要你做的:无,席位落地。

What changes

The bridge contract is the mobile repository's docs/HYBRID-SHELL.md (objectstack-ai/mobile#173, merged at 678fbe9). This session cannot read that repository, so the contract below is taken from the card's quoted text:

window.ObjectStackNative.showConnectionSwitcher(), feature-detected with has('showConnectionSwitcher'). It opens the native 切换组织 sheet: the saved organizations and company servers, plus an add action.

objectstack-native:connectionRemoved, a window event the shell sends to a loaded Console when the user removes its connection. The Console should: 1. call event.waitUntil(promise); 2. unregister this device's push token with its own session, by the existing device-registry unregister path; 3. resolve. […] If the Console does not answer within 10 s, the shell records the token as left registered.

The header organization button is WorkspaceSwitcher (data-testid="workspace-switcher"), which AppHeader draws at every width (the badge alone below sm). CurrentOrganizationIndicator draws no control by design, and OrgBadge is only the mark, so neither is the button.

  • WorkspaceSwitcher: when nativeHostCan(useNativeHost(), 'showConnectionSwitcher'), the trigger is a plain button that calls nativeShowConnectionSwitcher() and no dropdown is mounted. In the shell, with the capability, the button renders whenever there is an active organization, at any membership count: the native branch sits after the no-active-organization guard and before the single-membership guard. orgList counts this server's memberships only, while the sheet lists the shell's saved connections across company servers, so a user with one organization on each of two servers has two connections. Without the capability (plain browser, older shell) every rule is unchanged, the single-membership guard included. A failed answer from the shell is logged at warn.
  • CurrentOrganizationIndicator renders nothing when the shell announces the capability, so a walled deployment at one membership draws the organization once, on the native button. The name shows at the same widths the indicator showed it (badge always, name from sm up, the same hidden truncate sm:inline span); a test pins that equality.
  • Why the button renders at one membership. The seat chose this (option B) over option A in the first report ("leave as is (card scope)"), on review of this PR: the card's beneficiary is "a phone user with several organizations or company servers, who switches with one tap from the header", and single-organization users are the majority of them.
  • useNativeHostRouting (already mounted by the Console's NativeShellRouting) listens for objectstack-native:connectionRemoved while the bridge is present and calls event.waitUntil() with the promise its new optional onConnectionRemoved returns. When the option is omitted, as the Console does today, the promise is already resolved. The latest handler is read through a ref, so the listener is installed once per bridge.
  • @object-ui/app-shell now depends on @object-ui/mobile (which depends only on @object-ui/types; no cycle). Vitest and the Console's Vite config already alias @object-ui/mobile to src.

Which unregister case holds: no registration exists, so there is nothing to unregister. No Console path registers a device: the client SDK's device methods were removed (its notifications namespace comment says the /notifications/devices routes were never built), the server routes are #29041 (pm:blocked), and the registration card is #29042 (pm:blocked). So the Console has no token and no device id. The handler answers waitUntil with a resolved promise, and onConnectionRemoved is the seam where #29042 adds the unregister call. No registration is invented here.

Widening (@object-ui/mobile, all new exports):

  • 'showConnectionSwitcher' joins NativeHostMethod, and showConnectionSwitcher?(): Promise<null> joins ObjectStackNative as an optional method, the way scan? is: an older shell does not carry it.
  • nativeShowConnectionSwitcher(): the typed wrapper, same unavailable | ok | failed answer as the others.
  • nativeHostCan(host, method): new export. It is the wrappers' own feature-detect (the former private callable), so a control can pick its native or web path before it renders.
  • NATIVE_CONNECTION_REMOVED_EVENT and the NativeConnectionRemovedEvent type.
  • UseNativeHostRoutingOptions.onConnectionRemoved.

No server change, no metadata key, no new i18n key.

Tests

  • New WorkspaceSwitcher.nativeConnectionSwitcher.test.tsx (7 cases, real Radix dropdown and a real click).
    • Two memberships: a fake host with the capability makes the button call showConnectionSwitcher once, with no menu role, no organization row and nothing switched. Two controls, with no host and with a host without the capability: the web menu opens with its rows and showConnectionSwitcher is never called.
    • One membership on a walled (isolated) deployment, WorkspaceSwitcher and CurrentOrganizationIndicator rendered side by side as AppHeader draws them. With the capability, the native button renders and opens the sheet, the indicator is absent, and the organization name is drawn once. The native button's name span carries the same classes as the indicator's. Two controls, with no host and with a host without the capability: no switcher and the read-only indicator, as today.
  • nativeHost.test.tsx extended: showConnectionSwitcher joins the unavailable / missing-method / ok wrapper rows; nativeHostCan cases; a connectionRemoved block. With no unregister step, waitUntil gets a promise that resolves at once. With one, the shell is held until the step settles and the step runs once. The latest handler runs without a second listener. An event without waitUntil is left alone. Outside shell mode no listener is installed. The listener is removed on unmount.

Acceptance notes

  • No unsaved-edits prompt before the native sheet opens. Opening the sheet leaves nothing; the switch itself happens in the shell, which the page does not see.

Verification

Final head c962ab9e. Its last commit changes three code files against dec7cd0b: WorkspaceSwitcher.tsx, CurrentOrganizationIndicator.tsx and the new test file. It also updates the changeset text. All CI check-runs on c962ab9e concluded success or skipped: Test UI (1/16 … 16/16), Type Check UI, Lint UI, Build & E2E UI, Live E2E UI, Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard and Check Changeset. Before that, main was merged three times. Two of those merges brought in #29475 and the shell code-scanner card, which had edited nativeHost.ts / useNativeHost.ts; their conflicts were import and union lines only. showConnectionSwitcher is optional on ObjectStackNative, matching scan?.

Tests, ablation and type-check

On c962ab9e:

  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/mobile/ + WorkspaceSwitcher.nativeConnectionSwitcher, WorkspaceSwitcher, WorkspaceSwitcher.noActiveOrg-12698, WorkspaceSwitcher.organizationNoun-12851, CurrentOrganizationIndicator, CurrentOrganizationIndicator.noActiveOrg-12698: Test Files 15 passed, Tests 168 passed.
  • Ablations (ablation-replace.mjs, each restored to the HEAD blob with git diff HEAD empty; vitest aliases @object-ui/* to src, so no rebuild is involved):
    • if (nativeSwitcher) { to if (nativeSwitcher && false) { in WorkspaceSwitcher.tsx: 3 failed, 4 passed (every capability case red, every control green).
    • if (nativeSwitcher) { to if (nativeSwitcher && orgList.length > 1) { (the old placement after the single-membership guard): 2 failed, 5 passed (the one-membership capability cases).
    • if (nativeSwitcher) return null; to if (nativeSwitcher && false) return null; in CurrentOrganizationIndicator.tsx: 2 failed, 5 passed.
  • turbo run type-check --filter=@object-ui/app-shell: 65/65 tasks. eslint on the three changed files: exit 0.

On 33a962e6 (the bridge half, unchanged since): the mobile + native-shell console suites 18 files / 181 tests; the connectionRemoved ablation (the waitUntil guard forced to return) red 3 cases and restored; turbo run type-check for mobile + app-shell 66/66; pnpm check:new-line-citations 0 new.

Derived gates

node scripts/pm/dispatch-gates.mjs --commands at c962ab9e derived the same 54 families as on 33a962e6. On c962ab9e the 53 non-vitest families were run with their exit codes recorded before any pipe, plus pnpm check:ci-filter-parity at the seat's request:

  • 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-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-osv-exemptions.mjs: exit 0
  • node scripts/check-osv-exemptions.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/osv-base-relative.mjs --self-test: exit 0
  • node scripts/pm/release-rehearsal-clone.mjs --self-test: exit 0
  • node scripts/release-pending-publish.mjs --self-test: exit 0
  • pnpm --filter @objectstack/spec run check:duration-unit-keys: exit 0
  • pnpm --filter @objectstack/spec run check:llms-txt: exit 0
  • pnpm check:changeset-fixed: exit 0
  • pnpm check:changeset-gate-self-tests: 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:driver-memory-census: exit 0
  • pnpm check:dts-closure: exit 0
  • pnpm check:dual-build-cjs-loads: exit 3
  • pnpm check:engine-double-contract: exit 0
  • pnpm check:gitlink-declared: exit 0
  • pnpm check:lean-entry-closure: exit 0
  • pnpm check:manifest-repository-directory: exit 0
  • pnpm check:merge-driver: exit 0
  • pnpm check:nul-bytes: exit 0
  • pnpm check:override-consistency: exit 0
  • pnpm check:pm-changeset-deadline-census: exit 0
  • pnpm check:pnpm-flag-spellings: exit 0
  • pnpm check:published-files: exit 0
  • pnpm check:query-options-erasure: exit 0
  • pnpm check:refd-timer-probe: exit 0
  • pnpm check:skill-identifier-liveness: exit 0
  • pnpm check:sourcemap-no-sources-content: exit 0
  • pnpm check:test-source-alias: exit 0
  • pnpm check:tier-file-adoption: exit 0
  • pnpm check:turbo-task-graph: 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:vendor-export-contract-resolve: exit 0
  • pnpm check:watch-hint-literal: exit 0
  • pnpm check:where-matcher: exit 0
  • pnpm check:workspace-manifest-cycles: exit 0
  • pnpm check:ci-filter-parity: exit 0

check:dual-build-cjs-loads exited 3 on c962ab9e (PREREQUISITE NOT MET: the re-created worktree had no built dists). It is NOT MEASURED locally there. It measured exit 0 on 33a962e6 (115 require entry points across 70 packages), and CI's Lint & Repo Gates on c962ab9e is green. pnpm --dir ui exec vitest run scripts/ was not re-run locally on c962ab9e; the CI runs listed above for that head are green. It ran green locally on 33a962e6 (169 files) and on dec7cd0b (169 files, plus check-doc-json-fences.test.ts 42/42 after a stale-build rebuild).


Generated by Claude Code

…oved in the mobile shell

The header organization button opens the shell's native switcher when the
bridge announces showConnectionSwitcher; useNativeHostRouting answers the
shell's connectionRemoved event through event.waitUntil.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTVYu9vDSx28X2zi7h
…-g0magq

# Conflicts:
#	ui/packages/mobile/src/__tests__/nativeHost.test.tsx
…-g0magq

# Conflicts:
#	ui/packages/mobile/src/__tests__/nativeHost.test.tsx
#	ui/packages/mobile/src/index.ts
#	ui/packages/mobile/src/nativeHost.ts
@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tooling labels Nov 2, 2026
… too

In the mobile shell the sheet lists the shell's connections across company
servers, so the button no longer waits for a second membership on this
server; the read-only organization indicator steps aside so the header draws
the organization once.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jTVYu9vDSx28X2zi7h
@github-actions github-actions Bot added size/l and removed size/m labels Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Contract-review-claim: session_01QsR2jV5sBSzCYNEDgpYLJw · head c962ab9 · 2026-11-02T19:19Z


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c962ab9e1e85349f397f161a307dd6f7420ce861
Local-runs: none
Scope: the PR diff at c962ab9e (11 files, +496/−25: .changeset/29458-native-connection-switcher.md, pnpm-lock.yaml, ui/packages/app-shell/{package.json, src/layout/WorkspaceSwitcher.tsx, src/layout/CurrentOrganizationIndicator.tsx, src/layout/__tests__/WorkspaceSwitcher.nativeConnectionSwitcher.test.tsx}, ui/packages/mobile/{README.md, src/index.ts, src/nativeHost.ts, src/useNativeHost.ts, src/__tests__/nativeHost.test.tsx}), the PR body, the os-dev report on #29458 (6585524224), the card body, the seven commits of the PR with their dates, and on origin/main: ui/packages/mobile/package.json, ui/packages/mobile/src/index.ts, ui/apps/console/src/App.tsx, scripts/pm/clause2-line.mjs L111–117, scripts/check-changeset-no-major.mjs L40–41

Read-only; nothing was built or run here. CI on c962ab9e: 48 success, 8 skipped, 0 failed (check-runs API).

① Derived judgments

  • The widening list is the diff, nothing more and nothing less. nativeHost.ts adds 'showConnectionSwitcher' to the NativeHostMethod union, showConnectionSwitcher?(): Promise<null> as an optional member of ObjectStackNative (the scan? shape), the NATIVE_CONNECTION_REMOVED_EVENT constant, the NativeConnectionRemovedEvent interface, nativeShowConnectionSwitcher() and the export of the former private callable under the name nativeHostCan (same body: has is a function, host[method] is a function, has(method) === true inside try). useNativeHost.ts adds the optional onConnectionRemoved option. index.ts re-exports exactly those six names. The changeset and the body name all of them; the dev report's widening_exports has the same seven rows. Nothing is removed or renamed, so no accept-set narrows: Clause-②: yes (widening) is the truthful form (clause2-line.mjs L111–117 takes bare yes and yes (widening) as the widening spellings). No NativeHostMethod consumer on origin/main switches exhaustively over the union (only the barrel and the tests name it), so widening the union breaks no compile.
  • No dependency cycle. @object-ui/app-shell gains @object-ui/mobile (package.json + lock link:../mobile); on origin/main @object-ui/mobile depends only on @object-ui/types and nothing under ui/packages/mobile/src imports app-shell.
  • The connectionRemoved answer matches the card's three steps for the case that exists. The listener installs only while host is non-null, ignores an event without a function-valued waitUntil, and calls waitUntil.call(event, handler ? Promise.resolve().then(handler) : Promise.resolve()): a throwing handler becomes a rejection, so the shell is never held past the step; the handler is read from a ref so the listener is installed once per bridge. The Console's one caller (ui/apps/console/src/App.tsx L155) passes no onConnectionRemoved, so the shell is released at once. The claim "no Console path registers a device" holds: on origin/main the only getPushToken references under ui/apps and ui/packages/app-shell are the two native-shell test fakes; Push channel: build the device registry routes (/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041 (server routes) and Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042 (Console registration) are both open and pm:blocked. The option is the seam Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042 fills.
  • The header button. WorkspaceSwitcher keeps the hook order (nativeHostCan(useNativeHost(), …) is unconditional), returns before the single-membership and multiOrgDisabled rules when the shell announces the capability, and renders a plain <button> (no aria-haspopup, no dropdown mounted) that calls nativeShowConnectionSwitcher() and logs a failed answer at warn. CurrentOrganizationIndicator returns null under the same predicate, so the organization is drawn once. In a plain browser, or a shell without the capability, both files take their old branches byte-for-byte (the trigger markup was lifted into triggerClassName/triggerContent and reused by the dropdown trigger).
  • Tests pin each arm. The new app-shell file has 7 cases, not the 3 the body counts: 3 for the two-membership switcher (native path; no-bridge control; bridge-without-capability control, with the real Radix menu opened by a real click) and 4 for one membership on a walled posture (native button renders and opens the sheet while the indicator steps aside; same name-span class as the indicator; no-bridge control; no-capability control). The mobile test adds the wrapper rows, four nativeHostCan cases and seven connectionRemoved cases (resolved-at-once, held-until-settled, latest handler without a second listener, event without waitUntil, no listener outside the shell, removed on unmount). All of these ran green in CI's Test UI shards on c962ab9e.

② Semver level

  • @object-ui/mobile patch, @object-ui/app-shell patch, Clause-②: yes (widening), Metadata-protocol: none. Right: by check-changeset-no-major.mjs L40–41 the level follows the metadata protocol alone, and this PR adds no spec key, metadata key or server route; a pure widening of two @object-ui/* package entries ships patch. No !, no BREAKING banner, no ADR-0087 marker needed.

③ Boundary flags

  • Not Tier H: no governed path in the file list.
  • The record is behind the head, on a point the record says it did not change. Commit c962ab9e (18:55Z, three code files + one changeset line, titled "the native switcher button renders at one membership too") lands after the os-dev report (18:52Z, head: dec7cd0b), which lists this exact choice as its one open question (A leave as is / B show the button at one membership) and recommends A. The PR body still reads A at head: Acceptance notes say "Single-membership users have no button … WorkspaceSwitcher still renders nothing at one organization … Changing that visibility rule is beyond this card", Verification says "Final head dec7cd0b … touches none of this PR's files", and Tests/速读 count the new file at 3 cases. At c962ab9e the button renders at one membership in the shell, the indicator steps aside, and the file has 7 cases. B is inside the card's own reason ("a phone user with several organizations or company servers"), and the changeset at head already says it, so the code is not the problem; the body a maintainer reads is.

④ Design judgment

  • Showing the shell button at one membership is the better reading of the card: the sheet lists connections across company servers, which this server's membership count cannot see, and the web rules stay untouched outside the shell. Lifting the indicator out of the way keeps the header single-sourced. No concern with the ref-held handler or with exporting callable as nativeHostCan: it is the same predicate the wrappers use, so a control's pre-render choice cannot disagree with the call it then makes.

Must-fix (1):

  1. Bring the PR body to head c962ab9e: Acceptance notes must state that in the shell the button renders at one membership and the indicator steps aside (and who chose B over the report's A); Verification must name c962ab9e as the head with the evidence that covers its three code files (the green Test UI shards on c962ab9e, or a local run of the two test files there); Tests and 速读 must count the new file at 7 cases. Until then the record a maintainer reads contradicts the head on user-facing behaviour.

Should-fix (1): the os-dev report on #29458 names head: dec7cd0b and answers its open question with A; an addendum or re-stamp for c962ab9e closes that gap for the card's reader.

Implemented-by: claude/project-thread-g0magq
Reviewed-by: session_01QsR2jV5sBSzCYNEDgpYLJw

VERDICT: FAIL


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c962ab9e1e85349f397f161a307dd6f7420ce861
Local-runs: none
Scope: the PR body as replaced at 2026-11-02T19:37Z (13958 bytes, read back and diffed against the body record 6586314740 reviewed), the os-dev report addendum on #29458 (6586599851), the check-runs on c962ab9e after the body edit, and record 6586314740 (the review of the same head, whose code findings carry over unchanged)

Re-review of the one must-fix in record 6586314740. Read-only; nothing was built or run here. The head is the same commit, so ① (widening list, dependency, connectionRemoved answer, button branches, tests), ② (patch + Clause-②: yes (widening) + Metadata-protocol: none) and ④ of that record hold byte-for-byte.

① Derived judgments

  • The body now describes the head. The diff of the two bodies touches only the record: 速读 says a single-organization user in the App sees the button and the read-only mark steps aside, and counts the new file at 7 cases; "What changes" places the native branch after the no-active-organization guard and before the single-membership guard, names CurrentOrganizationIndicator's step-aside and the pinned span classes, and states that the seat chose B over the first report's A, with the card's beneficiary as the reason; the old Acceptance note ("beyond this card") is gone; Tests describe the 3 + 4 cases as the file holds them; Verification names c962ab9e as the final head with the three code files of its last commit, the green CI contexts on that head, a local run of 15 files / 168 tests covering the mobile, switcher and indicator suites, and three ablations (native branch forced off 3/4, old placement nativeSwitcher && orgList.length > 1 2/5, indicator hide forced off 2/5), each restored to the HEAD blob. Each of these matches the diff at c962ab9e as read in record 6586314740.
  • The local-gate ledger is now honest about its gaps. check:dual-build-cjs-loads is listed at exit 3 on c962ab9e and declared NOT MEASURED locally (no built dists in the re-created worktree), with its exit 0 on 33a962e6 and the green Lint & Repo Gates on the head cited in its place; vitest run scripts/ is listed as not re-run locally on this head. Neither is a code-content gap: the last commit touches app-shell source and test files only.
  • The should-fix is done. Addendum 6586599851 names head: c962ab9e, the four files of the round, the same tests and ablations as the body, 53 of 54 dispatch-gates families run locally with the one exit 3 named, and records the open question as resolved by the seat as B.

② Semver level

  • Unchanged: @object-ui/mobile patch, @object-ui/app-shell patch, right for a pure widening with no protocol change.

③ Boundary flags

  • Not Tier H.
  • The body edit at 19:37Z re-ran the PR-body guards (success) and Check Changeset (in_progress when this was written; its run on the same head at 18:55Z was green and the changeset is unchanged). The seat's pre-landing checks need that run green as usual.

④ Design judgment

  • Unchanged from record 6586314740.

No must-fix. No should-fix.

Implemented-by: claude/project-thread-g0magq
Reviewed-by: session_01QsR2jV5sBSzCYNEDgpYLJw

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

席位核对 — ACCEPT · domain:ui#5 · head c962ab9e · 2026-11-02T20:34Z

  • 形态: draft PR on main. The body has Fixes #29458 and no other card number next to a closing keyword. 11 files, +496/−25, Tier S: check-governed-merges --pr 29565 finds no governed path and 521 lines, under the 3000 limit. The body (replaced by the seat's issue_patch at 2026-11-02T19:37Z) carries 维护者速读 and both declaration lines (Clause-②: yes (widening), Metadata-protocol: none), copied from claim 6582128451 and its amendment.
  • 前提实测: at base, the Console had no reader for showConnectionSwitcher or objectstack-native:connectionRemoved (git grep 0 hits under ui/). The header's organization button is WorkspaceSwitcher (drawn by AppHeader at every width). No Console path registers a device (Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042 is pm:blocked, the client SDK's device methods are gone, the Push channel: build the device registry routes (/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041 routes are unbuilt). The dev's three ablations on c962ab9e each red their own cases: native branch off 3/7, old single-org placement 2/7, indicator hide off 2/7.
  • diff read:
    • @object-ui/mobile gains optional showConnectionSwitcher?() on ObjectStackNative, the NativeHostMethod member, nativeShowConnectionSwitcher, nativeHostCan (the former private callable), NATIVE_CONNECTION_REMOVED_EVENT with its event type, and an optional onConnectionRemoved on useNativeHostRouting. The listener calls waitUntil with the handler's promise, or with a resolved one when the option is omitted.
    • In the shell with the capability, WorkspaceSwitcher renders a plain button that opens the native sheet, at any membership count after if (!current) return null. The seat asked for this (B over the first report's A), because the sheet lists the shell's connections across servers, which orgList cannot see. CurrentOrganizationIndicator steps aside there, so the badge is not drawn twice, and the name shows at the same widths as before (hidden sm:inline on both). The web path is unchanged.
    • app-shell gains a workspace dependency on @object-ui/mobile, with no cycle. Surface additions approved in thread: the mobile barrel, the README's "Native shell mode" section, and CurrentOrganizationIndicator.tsx.
  • 门禁 check-runs (pinned to head c962ab9e1e85349f397f161a307dd6f7420ce861): 50 success, 10 skipped, 0 failed, 0 pending (Check Changeset re-ran after the body edit and is green, 19:38Z). mergeable_state clean. Dev local gates: 53 derived, all exit 0. dual-build-cjs-loads was NOT MEASURED locally on this head (prerequisite); it was exit 0 on 33a962e6, and CI's Lint & Repo Gates is green here. Local: 15 files, 168 tests.
  • Contract review: director CONTRACT_REVIEW_TIER review on this head. The first verdict (6586314740) was a FAIL on the record only: the body described dec7cd0b. After the body replacement and report addendum 6586599851, the delta re-review (6586653587) is a PASS, with no must-fix and no should-fix. Its landing note, that Check Changeset re-ran after the body edit, is satisfied above.
  • 接受的偏差: connectionRemoved releases the shell at once, because there is no registered token to unregister. onConnectionRemoved is the seam Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042's registration fills. This is declared in the body.
  • 范围外去向: none new. The unregister half rides with Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042.
  • 落地条款: widening with a same-head PASS. Not Tier H. Land by pr_ready + automerge_enable.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 20:35
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 20:35
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit 2420778 Nov 2, 2026
61 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-g0magq branch November 2, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console: open the mobile shell's native organization switcher, and unregister the push token when the shell removes a connection

2 participants