You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Native host: make ObjectStackNative.platform an open set and pin it with an App-level test (follow-up to #29140) #29330
#29140 (card #29039) merged the Console's shell mode. An acceptance review checked it against the shell (objectstack-ai/mobile main 8c6f13a). The bridge contract matches item by item: the global, event name, methods, haptic kinds, argument shapes, push token, errors and events.
One acceptance item, from a maintainer comment on #29039, is met at runtime but has no test, and the type contradicts it: the platform is an open set. The desktop shell will report a value other than ios or android.
Runtime:getNativeHost has no platform check, the root attribute takes the value verbatim, and wrappers gate on has() only (ui/packages/mobile/src/nativeHost.ts:109-123, ui/apps/console/src/App.tsx:160-164).
Type:readonly platform: 'ios' | 'android' (nativeHost.ts:65) is a closed union. A consumer can write an exhaustive never switch that silently misses desktop.
Tests: both fake bridges hard-code ios/android (nativeHost.test.tsx:53, App.nativeShellMode-29039.test.tsx:105), and both announce every capability (has: () => true).
Ask
Widen the type to 'ios' | 'android' | (string & {}). Update the README and changeset wording (data-os-native-shell="ios|android").
Add an App-level test with platform: 'desktop' and a subset of capabilities. Assert that:
shell mode is on;
ready is posted once;
the root attribute is desktop;
a method that is not announced is never called.
Settle what ready means. Today ready() fires at the first commit of the router tree (App.tsx:244-245). On a cold start to a protected route, that commit is AuthGuard's loading fallback (ProtectedRoute.tsx:55-61). Either:
document ready as "the Console has painted its own UI, possibly a loading fallback", or
Version gate. Nothing reads ObjectStackNative.version. Treat a host with version > 1 as no shell (a safe browser fallback), or record why not.
README nit.ui/packages/mobile/README.md:314 changes button text on host presence (host ? 'Save' : 'Save changes'), which breaks the "viewport and capability, not host" rule. Use a capability check or drop the branch.
Acceptance
pnpm --filter @objectstack/mobile test and the console test pass, including the new desktop/subset case.
The type change compiles across ui/ with no new casts.
Read fresh. ObjectStackNative.platform is typed as a closed union, but the runtime treats it as an open set; widen the type and pin it with an App-level test. p3 kept. The card had no state (H13).
Claim: PM loop round 13 · 2026-11-02T13:21Z
Session: session_01QsR2jSpg4SQPs6XFLsrBhv (card thread, os-dev); dispatched by seat session session_01QsR2jR7ZFkpqmm4U8S1f9y
Account: zhuangjianguo
Branch: claude/project-thread-fggkhz (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-29330
Domain: domain:ui
Seat: domain:ui#4
File surface: ui/packages/mobile/src/nativeHost.ts and useNativeHost.ts, their tests, the console App-level shell-mode test (and App.tsx only for a test seam), ui/packages/mobile/README.md, .changeset/29039-mobile-native-shell-mode.md (wording only), and a new changeset (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: default
Clause-②: yes (widening)
Metadata-protocol: none
Responsibility: the native bridge types the platform as a closed ios/android union while the desktop shell reports another value, and nothing reads the bridge version | widen the type, gate unknown bridge versions to the browser fallback, document ready, pin all with App-level tests | every mobile and desktop shell user and every bridge consumer
Thread-read: 6577834108
Premise: holds (at origin/main after 45e9b2f1, nativeHost.ts L65 types platform as 'ios' | 'android' and no code reads version)
Serial constraints cleared: PR #29326 (#29155, Tier H) also edits mobile README.md and index.ts; build on main, second to land merges main
{
"issue": 29330,
"status": "done",
"branch": "claude/project-thread-fggkhz",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29475",
"head": "28cb3792",
"session": "session_01QsR2jSpg4SQPs6XFLsrBhv",
"premise_still_valid": true,
"summary": "ObjectStackNative.platform widens to 'ios' | 'android' | (string & {}); README and the pending 29039 changeset say data-os-native-shell carries the shell's platform string, an open set. nativeHost.ts gains NATIVE_BRIDGE_VERSION = 1 (module export only; index.ts untouched for the PR #29326 serial constraint) and getNativeHost() answers null for a version that is not a number (NaN included) or greater than 1, so every reader (shell mode, ready, routeChanged, navigate listener, main.tsx manifest and service-worker decisions) takes the browser path. ready is documented, not moved: it means the Console painted its own UI, possibly AuthGuard's loading fallback, written on the ready member, postNativeReady, useNativeHostRouting and the README bridge section. README SaveButton example keeps one label.",
"tests": "At head 28cb3792, from ui/: vitest run App.nativeShellOpenSet-29330 (new, 4 cases: desktop + capability subset with shell mode on, ready once, attribute 'desktop', unannounced getPushToken/navigate/resetServer never called; ready posted while AuthGuard's loading fallback is on screen; version 2 no shell, no ready; version 1 control), main.nativeBridgeVersion-29330 (new, 3 cases: version 2 starts the manifest step and the generated manifest lands; version 1 never starts it; no bridge lands it), App.nativeShellMode-29039, packages/mobile/ (nativeHost.test.tsx extended: desktop bridge, version 1 constant, newer/string/NaN/missing version null and uncalled, versions 1 and 0 accepted, routing posts nothing for version 2) and scripts/__tests__/check-vi-mock-inherit.test.ts: 13 files, 220 passed. Reverse check 1 (ablation-replace on the version-gate line, restored byte-identical to HEAD 467c156a): 7 red (main v2, App v2, 4 mobile detection, 1 mobile routing). Reverse check 2 (closed union restored on platform): console tsc --noEmit TS2322 at App.nativeShellOpenSet-29330.test.tsx(153,5) 'Type string is not assignable to type ios | android', restored byte-identical. Type-check @object-ui/mobile and @object-ui/console (both include tests): exit 0. eslint on the 5 touched ts/tsx files: exit 0. Test wall: the manifest file's tests take 127 ms after paying its module graph at module top.",
"gates": "dispatch-gates --commands at 28cb3792: 34 derived; all 34 run; --ran reconciliation exit 0 (34 run, 0 NOT-MEASURED). 33 exit 0 incl. check-sdui-manifest (and --self-test), check:cross-package-test-inputs, check:test-source-alias, check:type-check-debt (locked), check:type-check-coverage, check:published-files, pnpm --dir ui exec vitest run scripts/ (169 files, 4921 passed, locked). 1 exit 1: node scripts/check-empty-changeset.mjs --base origin/main on the ruled wording edit to .changeset/29039-mobile-native-shell-mode.md, deliberate-correction class, named in the PR body (not a required context). PR body gated before pr_create: check-changeset-no-major exit 0 (arm widening, Metadata-protocol none), check-adr-0087-registration exit 0, check-closing-keyword-parity exit 0, check-partof-closing-keyword exit 0. CI check-runs: in_progress at report time.",
"line_budget": "524 changed lines (+515 / -9) in 8 files, under 3000",
"files_changed": [
".changeset/29039-mobile-native-shell-mode.md",
".changeset/29330-native-host-open-platform.md",
"ui/apps/console/src/__tests__/App.nativeShellOpenSet-29330.test.tsx",
"ui/apps/console/src/__tests__/main.nativeBridgeVersion-29330.test.tsx",
"ui/packages/mobile/README.md",
"ui/packages/mobile/src/__tests__/nativeHost.test.tsx",
"ui/packages/mobile/src/nativeHost.ts",
"ui/packages/mobile/src/useNativeHost.ts"
],
"deviations": [
"surface addition: ui/apps/console/src/__tests__/main.nativeBridgeVersion-29330.test.tsx pins the manifest half of the version gate where it lives (main.tsx); seat 4 approved keeping it before pr_create",
"main merged twice (after #29326 and after #29405, which made the manifest step async; the test waits for it). The 13 main commits after a6441636 touch none of the 8 files and were not merged before pr_create",
"the first two WIP commits were made with --no-verify (pre-commit skipped); later commits and every push ran the hooks, and the pre-push trailer check passed on every push",
"the first ablation run executed its vitest command outside the verify lock; the head-28cb3792 ablation rerun ran under it"
],
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read get #29330, get_comments #29330 twice (reads only, no write tool)",
"api_writes": "2 — fleet-write relay pr_create (objectstack-ai/objectstack#29475, read back byte-identical, assignees none) and this post-stamped report comment on #29330",
"writes": 2,
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": []
}
Seat review · 2026-11-02T16:41Z · PR #29475 at 28cb3792: contract review (TIER_DEFAULT, seat) PASS 6582637070 and ACCEPT 6582667680 (on the PR). The one red, Check Changeset, is the named deliberate correction to the pending 29039 note. It goes to the merge queue now.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p3 kept ·
pm:queue· 2026-11-02T13:03Z⛔ Not a claim.
ObjectStackNative.platformis typed as a closed union, but the runtime treats it as an open set; widen the type and pin it with an App-level test. p3 kept. The card had no state (H13).Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim: PM loop round 13 · 2026-11-02T13:21Z
Session:
session_01QsR2jSpg4SQPs6XFLsrBhv(card thread, os-dev); dispatched by seat sessionsession_01QsR2jR7ZFkpqmm4U8S1f9yAccount:
zhuangjianguoBranch:
claude/project-thread-fggkhz(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29330Domain:
domain:uiSeat:
domain:ui#4File surface:
ui/packages/mobile/src/nativeHost.tsanduseNativeHost.ts, their tests, the console App-level shell-mode test (andApp.tsxonly for a test seam),ui/packages/mobile/README.md,.changeset/29039-mobile-native-shell-mode.md(wording only), and a new changeset (stop on breach; explain in the report)Container & model: S,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: defaultClause-②: yes (widening)
Metadata-protocol: none
Responsibility:
the native bridge types the platform as a closed ios/android union while the desktop shell reports another value, and nothing reads the bridge version | widen the type, gate unknown bridge versions to the browser fallback, document ready, pin all with App-level tests | every mobile and desktop shell user and every bridge consumerThread-read: 6577834108
Premise: holds (at
origin/mainafter45e9b2f1,nativeHost.tsL65 typesplatformas'ios' | 'android'and no code readsversion)Serial constraints cleared: PR #29326 (#29155, Tier H) also edits mobile README.md and index.ts; build on main, second to land merges main
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jSpg4SQPs6XFLsrBhv
os-dev-report
{ "issue": 29330, "status": "done", "branch": "claude/project-thread-fggkhz", "pr": "https://github.com/objectstack-ai/objectstack/pull/29475", "head": "28cb3792", "session": "session_01QsR2jSpg4SQPs6XFLsrBhv", "premise_still_valid": true, "summary": "ObjectStackNative.platform widens to 'ios' | 'android' | (string & {}); README and the pending 29039 changeset say data-os-native-shell carries the shell's platform string, an open set. nativeHost.ts gains NATIVE_BRIDGE_VERSION = 1 (module export only; index.ts untouched for the PR #29326 serial constraint) and getNativeHost() answers null for a version that is not a number (NaN included) or greater than 1, so every reader (shell mode, ready, routeChanged, navigate listener, main.tsx manifest and service-worker decisions) takes the browser path. ready is documented, not moved: it means the Console painted its own UI, possibly AuthGuard's loading fallback, written on the ready member, postNativeReady, useNativeHostRouting and the README bridge section. README SaveButton example keeps one label.", "tests": "At head 28cb3792, from ui/: vitest run App.nativeShellOpenSet-29330 (new, 4 cases: desktop + capability subset with shell mode on, ready once, attribute 'desktop', unannounced getPushToken/navigate/resetServer never called; ready posted while AuthGuard's loading fallback is on screen; version 2 no shell, no ready; version 1 control), main.nativeBridgeVersion-29330 (new, 3 cases: version 2 starts the manifest step and the generated manifest lands; version 1 never starts it; no bridge lands it), App.nativeShellMode-29039, packages/mobile/ (nativeHost.test.tsx extended: desktop bridge, version 1 constant, newer/string/NaN/missing version null and uncalled, versions 1 and 0 accepted, routing posts nothing for version 2) and scripts/__tests__/check-vi-mock-inherit.test.ts: 13 files, 220 passed. Reverse check 1 (ablation-replace on the version-gate line, restored byte-identical to HEAD 467c156a): 7 red (main v2, App v2, 4 mobile detection, 1 mobile routing). Reverse check 2 (closed union restored on platform): console tsc --noEmit TS2322 at App.nativeShellOpenSet-29330.test.tsx(153,5) 'Type string is not assignable to type ios | android', restored byte-identical. Type-check @object-ui/mobile and @object-ui/console (both include tests): exit 0. eslint on the 5 touched ts/tsx files: exit 0. Test wall: the manifest file's tests take 127 ms after paying its module graph at module top.", "gates": "dispatch-gates --commands at 28cb3792: 34 derived; all 34 run; --ran reconciliation exit 0 (34 run, 0 NOT-MEASURED). 33 exit 0 incl. check-sdui-manifest (and --self-test), check:cross-package-test-inputs, check:test-source-alias, check:type-check-debt (locked), check:type-check-coverage, check:published-files, pnpm --dir ui exec vitest run scripts/ (169 files, 4921 passed, locked). 1 exit 1: node scripts/check-empty-changeset.mjs --base origin/main on the ruled wording edit to .changeset/29039-mobile-native-shell-mode.md, deliberate-correction class, named in the PR body (not a required context). PR body gated before pr_create: check-changeset-no-major exit 0 (arm widening, Metadata-protocol none), check-adr-0087-registration exit 0, check-closing-keyword-parity exit 0, check-partof-closing-keyword exit 0. CI check-runs: in_progress at report time.", "line_budget": "524 changed lines (+515 / -9) in 8 files, under 3000", "files_changed": [ ".changeset/29039-mobile-native-shell-mode.md", ".changeset/29330-native-host-open-platform.md", "ui/apps/console/src/__tests__/App.nativeShellOpenSet-29330.test.tsx", "ui/apps/console/src/__tests__/main.nativeBridgeVersion-29330.test.tsx", "ui/packages/mobile/README.md", "ui/packages/mobile/src/__tests__/nativeHost.test.tsx", "ui/packages/mobile/src/nativeHost.ts", "ui/packages/mobile/src/useNativeHost.ts" ], "deviations": [ "surface addition: ui/apps/console/src/__tests__/main.nativeBridgeVersion-29330.test.tsx pins the manifest half of the version gate where it lives (main.tsx); seat 4 approved keeping it before pr_create", "main merged twice (after #29326 and after #29405, which made the manifest step async; the test waits for it). The 13 main commits after a6441636 touch none of the 8 files and were not merged before pr_create", "the first two WIP commits were made with --no-verify (pre-commit skipped); later commits and every push ran the hooks, and the pre-push trailer check passed on every push", "the first ablation run executed its vitest command outside the verify lock; the head-28cb3792 ablation rerun ran under it" ], "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read get #29330, get_comments #29330 twice (reads only, no write tool)", "api_writes": "2 — fleet-write relay pr_create (objectstack-ai/objectstack#29475, read back byte-identical, assignees none) and this post-stamped report comment on #29330", "writes": 2, "open_questions": [], "security_findings": "none", "out_of_scope_findings": [] }objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Seat review · 2026-11-02T16:41Z · PR #29475 at
28cb3792: contract review (TIER_DEFAULT, seat) PASS 6582637070 and ACCEPT 6582667680 (on the PR). The one red,Check Changeset, is the named deliberate correction to the pending29039note. It goes to the merge queue now.Generated by Claude Code