Repository navigation
fix(mobile): the native bridge platform is an open set, and a newer bridge protocol reads as no bridge - #29475
Conversation
Claude-Session: https://claude.ai/code/session_01QsR2jSpg4SQPs6XFLsrBhv Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jSpg4SQPs6XFLsrBhv Co-authored-by: Claude <noreply@anthropic.com>
…rrides Claude-Session: https://claude.ai/code/session_01QsR2jSpg4SQPs6XFLsrBhv Co-authored-by: Claude <noreply@anthropic.com>
… now runs after first paint Claude-Session: https://claude.ai/code/session_01QsR2jSpg4SQPs6XFLsrBhv Co-authored-by: Claude <noreply@anthropic.com>
… top Claude-Session: https://claude.ai/code/session_01QsR2jSpg4SQPs6XFLsrBhv Co-authored-by: Claude <noreply@anthropic.com>
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y Contract reviewServed-tier: Face: declaration. The PR body and the changeset declare ① Derived judgments
② Semver levelThe new changeset is ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y 席位核对 · ACCEPT · 2026-11-02T16:40ZHead-sha:
Generated by Claude Code |
|
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y Landed · 2026-11-02T17:20Z · PR #29475 merged through the merge queue as
The card closed through Generated by Claude Code |
Requested by Steedos · project thread
Fixes #29330
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
platform类型从ios | android放开为开放集合(桌面壳可报其它值);新增桥协议版本门:壳报的version大于 1 或不是数字时,Console 当作没有壳、走浏览器路径;写明ready的含义;README 示例去掉按壳切换文案的分支。desktop,旧的封闭类型会让消费方写出漏掉它的穷举分支;version从未被读,将来协议破坏性升级时旧 Console 会误调新协议;壳将依赖ready揭开加载遮罩(mobile#162),需要明确它在鉴权加载态就会发出。platform做穷举 switch 的代码编译不过,全仓只有 Console 一处把它原样写进属性。回滚即 revert 本 PR。ui/packages/mobile/src/nativeHost.ts(getNativeHost的版本门与platform类型)→ui/apps/console/src/__tests__/App.nativeShellOpenSet-29330.test.tsx→ui/apps/console/src/__tests__/main.nativeBridgeVersion-29330.test.tsx。ui/下跑pnpm exec vitest run apps/console/src/__tests__/App.nativeShellOpenSet-29330.test.tsx,预期 4 个用例全绿(桌面壳、鉴权加载态时的 ready、版本 2 不进壳模式及其对照)。What changes
ObjectStackNative.platformis an open set. The type widens from'ios' | 'android'to'ios' | 'android' | (string & {}), so the two known values still autocomplete and the desktop shell's value type-checks. The runtime already treated it as open:getNativeHostnever checked it, the Console writes it verbatim intodata-os-native-shell, and the call wrappers gate onhas()only. The README and the pending.changeset/29039-mobile-native-shell-mode.mdnow say the attribute carries the shell's platform string, an open set.version"bumps only on a breaking change".nativeHost.tsgainsNATIVE_BRIDGE_VERSION = 1(module export used by the tests; not added to the package entry, which this PR leaves untouched).getNativeHost()answersnullfor a host whoseversionis not a number (NaN included) or is greater than 1. Every reader goes through it, so such a host gets the browser behaviour: no shell mode, noready, norouteChanged, no navigate listener, the PWA manifest kept, and (since feat(console): installable desktop PWA with an offline app shell and a push-ready Service Worker #29326 merged) the service worker treated as in a browser.readymeans, decided: document it, don't move it.readymeans "the Console has painted its own UI", which may be a loading fallback such asAuthGuard's spinner on a cold start to a protected route. The shell lifts its loading cover onready, so the cover hands over to the Console's own spinner; waiting forAuthGuardwould hold the native cover through a network round-trip. Written on theObjectStackNative.readymember, thepostNativeReadyanduseNativeHostRoutingdocblocks, and the README's bridge section. The timing is unchanged and now pinned.SaveButtonexample dropshost ? 'Save' : 'Save changes'and keeps one label, following the "viewport and capability, not host" rule.Tests
ui/apps/console/src/__tests__/App.nativeShellOpenSet-29330.test.tsx(new, the realApp,ProtectedRouteandAuthGuardunder the realBrowserRouter):platform: 'desktop'bridge announcinghapticandopenExternalonly: shell mode on,readyposted once,data-os-native-shell="desktop", the two announced calls reach the shell andgetPushToken,navigateandresetServerresolveunavailableand are never called;/with the session read still open:readyarrives whileAuthGuard's loading fallback is on screen and before the route renders;version: 2bridge: no root attribute, noready, no route post, callsunavailable; control: the same bridge atversion: 1is the shell.ui/apps/console/src/__tests__/main.nativeBridgeVersion-29330.test.tsx(new, the realmain.tsxwith its boot calls and its manifest icon step stubbed): aversion: 2bridge starts the manifest step and the generated manifest replaces the static one; controls:version: 1never starts the step and leaves no manifest, no bridge behaves likeversion: 2.ui/packages/mobile/src/__tests__/nativeHost.test.tsx(extended): adesktopbridge is a bridge;NATIVE_BRIDGE_VERSIONis 1; a newer, string, NaN or missing version isnulland its calls areunavailableand uncalled; versions 1 and 0 are the bridge;useNativeHostRoutingposts nothing and installs no listener for a version 2 bridge.Acceptance notes
typeof versionline replaced): 7 cases red across the three files (main version 2, App version 2, four mobile detection cases, mobile routing case), restored byte-identical to HEAD. Old closed union restored onplatform:tsc --noEmitinui/apps/consolereds the desktop fixture with TS2322 "Type 'string' is not assignable to type '"ios" | "android"'", restored byte-identical.git grepof the bridge symbols acrossui/,packages/andapps/finds only@object-ui/mobileand the Console; the one reader ofplatformisNativeShellRouting, which writes it into an attribute. No exhaustive switch exists to break.check-empty-changeset. This PR edits the pending.changeset/29039-mobile-native-shell-mode.md, wording only:data-os-native-shell="ios|android"becomes the shell's platform string, an open set. That is the gate's deliberate-correction class: the old sentence is false after this PR, so it is corrected here rather than restored, by the card's ruling. The gate stays red by design; it is not one of the required contexts.main.tsx, so it has its own test file beside the App-level one. No source file outside the card's surface changed;App.tsxandindex.tsare untouched.mainafter feat(console): installable desktop PWA with an offline app shell and a push-ready Service Worker #29326 (service worker), which also editsui/packages/mobile/README.mdandmain.tsx; the merge was clean and the version gate now also covers its worker decision throughgetNativeHost(). A second merge brought fix(console): PWA manifest declares the branding icon's real type and size, with 192/512 PNG fallbacks #29405, which moved the manifest into an async step after first paint; the manifest test waits for that step and pins whether it starts.Verification
All readings at head
28cb3792(mainmerged ata6441636; the 13maincommits since touch none of this PR's 8 files).ui/:pnpm exec vitest runof the two new console files,App.nativeShellMode-29039.test.tsx,packages/mobile/andscripts/__tests__/check-vi-mock-inherit.test.ts: 13 files, 220 tests passed.pnpm --filter @object-ui/mobile run type-checkandpnpm --filter @object-ui/console run type-check(both cover the test files): exit 0.eslinton the five touched source and test files: exit 0.node scripts/pm/dispatch-gates.mjs --commandslists 34; all 34 run and reconciled with--ran(0 NOT-MEASURED). 33 exit 0, includingpnpm --dir ui exec vitest run scripts/(169 files, 4921 tests) andcheck-sdui-manifest;check-empty-changesetexits 1, the expected deliberate-correction red above.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
Generated by Claude Code