Skip to content

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

Closed

Description

@objectstack-fleet

repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv

Filing category ③: a direct task from the maintainer's card objectstack-ai/mobile#152, ask 5. Quoted verbatim:

After acceptance, file the Console consumer card in objectstack-ai/objectstack: the header's organization button calls it when has('showConnectionSwitcher').

The shell side merged as objectstack-ai/mobile#173, into mobile main at 678fbe9. The source of truth for the bridge contract is mobile docs/HYBRID-SHELL.md.

What the shell offers

  • window.ObjectStackNative.showConnectionSwitcher(), feature-detected with has('showConnectionSwitcher'). It opens the native 切换组织 sheet: the saved organizations and company servers, plus an add action. Only pages on the configured server's origin can call it, the same as every method.

  • 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 (Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042 covers registration);
    3. resolve.

    The shell then clears that host's own cookies. If the Console does not answer within 10 s, the shell records the token as left registered. The shell itself never calls the API.

Ask (Console, ui/)

  • When has('showConnectionSwitcher'), the header's organization button calls showConnectionSwitcher() instead of the web organization menu. In a plain browser, or in an older shell, behaviour is unchanged.
  • Handle objectstack-native:connectionRemoved as above.

Business reason

  • Who benefits: a phone user with several organizations or company servers, who switches with one tap from the header. Removing a connection also stops its push notifications.
  • If not done: the native switcher is reachable only from the offline page, and removed connections keep receiving pushes.

Who acts

The domain:ui seat. Triage first-touch decides the grade.

Duplicate check (2026-11-02)

Open and closed issues in this repo:

Query Hits
is:issue showConnectionSwitcher 0
is:issue connectionRemoved 0

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · domain:ui · pm:queue · 2026-11-02T16:00Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

    Claim: PM loop round 4 · 2026-11-02T16:16Z
    Session: session_01QsR2jTVYu9vDSx28X2zi7h (card thread, os-dev); dispatched by seat session session_01QsR2jQjvWkZ7is6GXbC6DB
    Account: zhuangjianguo
    Branch: claude/project-thread-g0magq (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29458
    Domain: domain:ui
    Seat: domain:ui#5
    File surface: ui/packages/mobile/src/nativeHost.ts and/or ui/packages/mobile/src/useNativeHost.ts, the header organization button file the dev names before editing (candidates ui/packages/app-shell/src/layout/CurrentOrganizationIndicator.tsx, OrgBadge.tsx), new tests, and a changeset (stop on breach; a new i18n key goes to the seat first)
    Container & model: S, mode:thread, model: default tier (no Fable per the maintainer's standing rule), effort: medium
    Clause-②: yes (widening) — only if an exported member is added to @object-ui/mobile's NativeHost type or event constants; otherwise no, amended before pr_create
    Metadata-protocol: none
    Responsibility: a phone user in the mobile shell with several organizations or servers taps the header's organization button | the web menu opens, and the native switcher is reachable only from the offline page; a removed connection's push token is never unregistered by the Console | every mobile-shell user with more than one connection
    Thread-read: none
    Premise: holds on 04e0c2c0. The Console has no showConnectionSwitcher or connectionRemoved reader (git grep 0 hits under ui/), and @object-ui/mobile has the has() feature-detect (nativeHost.ts:119). Registration (#29042) is still pm:blocked, so the dev checks whether any Console path holds a device id before wiring the unregister step.
    Serial constraints cleared: #29330 (another seat's card) edits nativeHost.ts. The dev reports any open PR on the surface before editing and merges main right before pr_create.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

    Claim amended · 2026-11-02T16:18Z · File surface and Clause-② of claim 6582128451

    File surface: ui/packages/app-shell/src/layout/WorkspaceSwitcher.tsx (the header's organization button, drawn by AppHeader at every width; CurrentOrganizationIndicator and OrgBadge have no control), ui/packages/app-shell/package.json + pnpm-lock.yaml (a new workspace dependency on @object-ui/mobile, so the bridge reader is reused, not rewritten; mobile depends only on @object-ui/types, no cycle), ui/packages/mobile/src/nativeHost.ts, ui/packages/mobile/src/useNativeHost.ts (connectionRemoved handled inside useNativeHostRouting through an optional onConnectionRemoved option, so the Console's App.tsx is untouched), new tests, and a changeset (stop on breach; any .github/workflows/** or root turbo.json edit goes to the seat first, since it would make this PR Tier H)
    Clause-②: yes (widening) — confirmed: showConnectionSwitcher on ObjectStackNative and NativeHostMethod, nativeShowConnectionSwitcher, NATIVE_CONNECTION_REMOVED_EVENT and the hook option are new exported members of @object-ui/mobile. The director's contract review is ordered after the PR opens.
    Metadata-protocol: none
    Premise (unregister half): no Console path registers a device today. The client SDK's device methods were removed, the #29041 routes are unbuilt, and #29042 is pm:blocked. So waitUntil gets a promise that resolves at once, and the option is the seam #29042's registration fills.
    Serial constraints: #29475 (#29330, draft) edits nativeHost.ts and useNativeHost.ts. This card builds beside it and merges main right before pr_create. Whichever lands second resolves the overlap.

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jTVYu9vDSx28X2zi7h
    os-dev-report

    {
     "issue": 29458,
     "status": "done",
     "branch": "claude/project-thread-g0magq",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/29565",
     "head": "dec7cd0b",
     "session": "session_01QsR2jTVYu9vDSx28X2zi7h",
     "premise_still_valid": true,
     "summary": "WorkspaceSwitcher (the header org button; CurrentOrganizationIndicator and OrgBadge draw no control) opens the shell's native switcher via nativeShowConnectionSwitcher when nativeHostCan(useNativeHost(), 'showConnectionSwitcher'); otherwise the web dropdown is unchanged. useNativeHostRouting answers objectstack-native:connectionRemoved with event.waitUntil(promise from optional onConnectionRemoved, else already resolved). Unregister case: no Console path registers a device (client device methods removed, server routes #29041 and registration #29042 both pm:blocked), so waitUntil resolves at once and onConnectionRemoved is the seam #29042 fills. app-shell now depends on @object-ui/mobile.",
     "clause_2": "yes (widening)",
     "metadata_protocol": "none",
     "widening_exports": [
      "NativeHostMethod += showConnectionSwitcher",
      "ObjectStackNative.showConnectionSwitcher? (optional, like scan?)",
      "nativeShowConnectionSwitcher (new)",
      "nativeHostCan (new export; was private callable)",
      "NATIVE_CONNECTION_REMOVED_EVENT (new)",
      "NativeConnectionRemovedEvent (new type)",
      "UseNativeHostRoutingOptions.onConnectionRemoved (new)"
     ],
     "files_changed": [
      ".changeset/29458-native-connection-switcher.md",
      "pnpm-lock.yaml",
      "ui/packages/app-shell/package.json",
      "ui/packages/app-shell/src/layout/WorkspaceSwitcher.tsx",
      "ui/packages/app-shell/src/layout/__tests__/WorkspaceSwitcher.nativeConnectionSwitcher.test.tsx",
      "ui/packages/mobile/README.md",
      "ui/packages/mobile/src/__tests__/nativeHost.test.tsx",
      "ui/packages/mobile/src/index.ts",
      "ui/packages/mobile/src/nativeHost.ts",
      "ui/packages/mobile/src/useNativeHost.ts"
     ],
     "line_budget": "10 files, +399 / -22 vs merge base (dispatch-gates count at 33a962e6), under 3000",
     "tests": "33a962e6: vitest packages/mobile/ + ScanFieldButton.shellScan + 4 WorkspaceSwitcher files + 4 console native-shell files: 18 files / 181 tests passed. Ablation 1 (WorkspaceSwitcher 'if (nativeSwitcher) {' -> '&& false'): 1 failed, 2 controls passed, restored to HEAD blob. Ablation 2 (useNativeHost waitUntil guard forced return): 3 connectionRemoved cases red, restored. turbo type-check mobile+app-shell 66/66. eslint on 6 files exit 0. dec7cd0b (clean main merge, no touched files): mobile + switcher + 3 turbo-inputs test files 13 files / 280 tests passed.",
     "gates": "dispatch-gates --commands at 33a962e6: 54 derived, all exit 0 (dual-build-cjs-loads measured, exit 0); --ran: 54 run, 0 NOT-MEASURED. Extra: check:ci-filter-parity 0, check:new-line-citations 0 new. dec7cd0b: vitest run scripts/ 169 passed + check-doc-json-fences red on a stale build (zod 'fn is not a function'), 42/42 after rebuilding formula/sdui-parser/lint/client/client-react; ci-filter-parity, turbo-task-graph, undeclared-dep-imports, check-sdui-manifest exit 0. CI check-runs: in_progress at report time.",
     "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
     "deviations": [
      "Surface grew with seat approval before edit: WorkspaceSwitcher.tsx (the real button), app-shell package.json + pnpm-lock.yaml, mobile index.ts barrel, mobile README.md.",
      "showConnectionSwitcher declared optional on ObjectStackNative after main's scan card set that precedent (merge on 33a962e6).",
      "Changeset wording edit rode inside merge commit 33a962e6."
     ],
     "mcp_calls": "0",
     "api_writes": "2 — pr_create via fleet-write relay (PR 29565); this os-dev-report comment via post-stamped",
     "open_questions": [
      {
       "question": "Single-membership shell users get no header button (WorkspaceSwitcher renders nothing at one org), so a user with one org but several servers cannot reach the native sheet from the header. Widen?",
       "options": [
        "A leave as is (card scope)",
        "B show the button in shell mode when the capability exists, even at one org"
       ],
       "recommendation": "A now; B is a visibility-rule change for the seat to card if the mobile maintainer wants it"
      }
     ],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: seat · single-membership shell users have no header entry to the native switcher (noted in PR Acceptance notes, not filed)"
     ]
    }
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#5 · zhuangjianguo · session_01QsR2jTVYu9vDSx28X2zi7h
    os-dev-report

    Addendum for head c962ab9.

    {
     "addendum_to": "os-dev-report for PR 29565 (head dec7cd0b)",
     "issue": 29458,
     "status": "done",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/29565",
     "head": "c962ab9e",
     "session": "session_01QsR2jTVYu9vDSx28X2zi7h",
     "summary": "Seat-requested change (option B over the report's A): in the shell with showConnectionSwitcher, WorkspaceSwitcher renders the native button whenever there is an active organization, at any membership count (native branch moved above the single-membership guard). CurrentOrganizationIndicator renders nothing in that case, so the organization is drawn once; the name span keeps the indicator's widths (badge always, name from sm). Web path unchanged.",
     "files_changed_this_round": [
      "ui/packages/app-shell/src/layout/WorkspaceSwitcher.tsx",
      "ui/packages/app-shell/src/layout/CurrentOrganizationIndicator.tsx",
      "ui/packages/app-shell/src/layout/__tests__/WorkspaceSwitcher.nativeConnectionSwitcher.test.tsx",
      ".changeset/29458-native-connection-switcher.md"
     ],
     "line_budget": "11 files, +496 / -25 vs merge base (dispatch-gates at c962ab9e), under 3000",
     "tests": "c962ab9e: vitest packages/mobile/ + 4 WorkspaceSwitcher files + 2 CurrentOrganizationIndicator files: 15 files / 168 tests passed. New test file 7 cases. Ablations (restored to HEAD blob each): native branch forced off -> 3 failed / 4 passed; old placement (nativeSwitcher && orgList.length > 1) -> 2 failed / 5 passed; indicator hide forced off -> 2 failed / 5 passed. turbo type-check app-shell 65/65; eslint 3 files exit 0.",
     "gates": "dispatch-gates --commands at c962ab9e: same 54 families as 33a962e6; 53 non-vitest run locally, all exit 0 except check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET in the re-created worktree; NOT MEASURED locally; measured exit 0 on 33a962e6). check:ci-filter-parity exit 0. ui vitest scripts/ not re-run locally on c962ab9e (green on 33a962e6 and dec7cd0b). CI on c962ab9e: all check-runs success or skipped, including Test UI 1-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, Check Changeset.",
     "nonzero_local": [
      "pnpm check:dual-build-cjs-loads :: exit 3"
     ],
     "pr_body": "corrected body for c962ab9e at /mnt/project-files/issue-29458/pr-body-v2.md with the seat signature line; body gates (changeset-no-major, adr-0087-registration, closing-keyword-parity, partof-closing-keyword) exit 0; the seat applies it",
     "deviations": [
      "CurrentOrganizationIndicator.tsx added to the surface with seat approval before edit",
      "Open question from the first report resolved by seat as B"
     ],
     "mcp_calls": "0",
     "api_writes": "1 — this addendum via post-stamped",
     "security_findings": "none"
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions