Repository navigation
Closed
Labels
bugSomething isn't workingSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3Medium: important, M3
Description
Context
The push delivery contract merged in #29218 (packages/spec/src/api/push-delivery.zod.ts, around lines 28–39 and 82–83). It says route is the notification's actionUrl:
- either app-relative,
/opportunity/123; - or a Console route,
/apps/crm/opportunity/123.
It also says the tap is resolved "as the inbox does, never verbatim": an app-relative path is hosted under /apps/APP/, and an /apps/… route passes through.
Nothing resolves it today:
- Mobile shell. It hands the route to the Console with the
objectstack-native:navigateevent, and on a cold start falls back to<server>/_console<route>. The shell has no app context and does no resolving. A search ofobjectstack-ai/mobilemainforresolveNotificationTargetand app-relative hosting finds nothing. - Console listener.
ui/packages/mobile/src/useNativeHost.ts:153-163checksisInAppRoute(target)and then callsnavigate(target)verbatim.
So a push for /opportunity/123 falls to the Console's catch-all and lands on the home page, not the record. This is the defect objectui#5179 fixed for the inbox (ui/packages/app-shell/src/utils/appRoute.ts:328-399, resolveNotificationTarget).
The bug is not visible yet, because no push channel delivers today (#29041). It will be on day one of push.
Ask
- In the native navigate listener, resolve the route with the same resolver every inbox door uses before navigating:
resolveNotificationTarget(route, hostAppSegment), asopenInboxNotification.tsdoes./apps/…passes through./{object}/{id}is hosted under the current app, or the default app when none is open.- A route that resolves to nothing leaves the user where they are, rather than going to the catch-all.
- The cold-start fallback in the shell (
location.assignof the raw route) must not bypass this. Once the shell waits forready(mobile 🔗 Broken links detected in documentation #162, landed), the event path is the normal one. Confirm that the cold-start tap goes through the listener, and add a Console-side guard that resolves an app-relative path arriving as the initial URL.
Acceptance
- Unit test on the listener:
/opportunity/123with host appcrm→/apps/crm/opportunity/123;/apps/x/y/1passes through; an unresolvable path does not navigate. - The same resolver function is imported, not re-implemented.
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3Medium: important, M3
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 kept ·
domain:uikept ·pm:queue· 2026-11-03T03:58Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Claim: PM loop round 12 · 2026-11-03T04:08Z
Session:
session_01QsR2jVL7fzYGjxa3ani6k8(card thread, os-dev); dispatched by seat sessionsession_01QsR2jRFuJz9QBZqPVHYVhJAccount:
zhuangjianguoBranch:
claude/project-thread-vrlmvd(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29750Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/apps/console/src/App.tsx(NativeShellRoutingand an initial-URL guard for an app-relative path),ui/packages/mobile/src/useNativeHost.tsonly if the design needs it, the@object-ui/mobileREADME line if behaviour docs change, tests, changeset(s). Design report to the seat before code; the seat amends this claim if a published member is added. Stop on breach; explain in the reportContainer & model: S,
mode:thread,model: default tier (dispatch-gates --tier on the final file list),effort: highClause-②: no (provisional: a new published
@object-ui/app-shellexport oruseNativeHostRoutingoption makes it yes (widening), amended before code)Metadata-protocol: none
Responsibility:
the native navigate listener navigates to a push route verbatim | an app-relative push route such as /opportunity/123 lands on the Console home instead of the record (visible from day one of push, #29041) | people opening Console notifications from the mobile shellThread-read: none
Premise: the seat's reading on
main6f094645:useNativeHostRouting(useNativeHost.ts ~L153–163) callsnavigate(target)afterisInAppRoute;@object-ui/app-shelldepends on@object-ui/mobile, so the resolver cannot be imported from mobile; the one caller is the Console'sNativeShellRouting.Serial constraints cleared: none known on these files; the os-dev checks open PRs touching useNativeHost.ts and App.tsx and merges
mainbeforepr_createobjectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Claim amendment (to 6595211090) · 2026-11-03T04:14Z · on the os-dev's design report; session, branch and seat unchanged.
Clause-②: no
Metadata-protocol: none
NativeShellRoutingwraps thenavigateit handsuseNativeHostRouting: with a host hint (currentAppName, else the URL's:appName) it navigatesresolveNotificationTarget(route, resolveHostAppSegment(apps, hint)); with none it hands the route on. Leg 2, the catch-all (path="*"): a path whose first segment is a Console root segment orappskeeps today's redirect to/; any other unmatched path waits for the app list under the same guard as/and redirects to the resolved target under the default app. Every unmatched non-root path is hosted (scope A), so the shell's initial-URL starts (recreate, the 8 s fallback, switch) and a cold-start push's login bounce all reach the record.ui/apps/console/src/App.tsx(NativeShellRoutingand the catch-all),ui/packages/app-shell/src/index.ts(root exportsresolveNotificationTarget,type NotificationTarget,CONSOLE_ROOT_SEGMENTS), tests, changeset.ui/packages/mobile/**unchanged.Clause-②staysno:@object-ui/app-shelland the Console are private packages, so the new root exports publish nothing. No new hook option, no server or spec change.objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jVL7fzYGjxa3ani6k8
os-dev-report
{ "issue": 29750, "status": "done", "branch": "claude/project-thread-vrlmvd", "pr": "https://github.com/objectstack-ai/objectstack/pull/29810", "head": "b1ed0110", "session": "session_01QsR2jVL7fzYGjxa3ani6k8", "premise_still_valid": true, "summary": "Two legs, one resolver. Leg 1: the Console's NativeShellRouting wraps the navigate it hands useNativeHostRouting; inside an app it navigates resolveNotificationTarget(route, resolveHostAppSegment(apps, URL :appName)); elsewhere it hands the route on. Leg 2: the catch-all is UnmatchedRoute; a first segment in CONSOLE_ROOT_SEGMENTS or apps keeps the redirect to /, any other unmatched path waits (ProtectedRoute, settled app list) and redirects to resolveNotificationTarget(path, resolveHostAppSegment(apps, currentAppName)). app-shell root exports resolveNotificationTarget, NotificationTarget, CONSOLE_ROOT_SEGMENTS (private package). Premise additions: the listener has no app list (outside every MetadataProvider); no in-app route is unresolvable (isInAppRoute refuses first); mobile main ddcf2d5 still delivers a route as the initial URL on recreate, the 8 s fallback and switch.", "clause_2": "no", "metadata_protocol": "none", "files_changed": [ ".changeset/29750-native-push-route-hosted.md", "ui/apps/console/src/App.tsx", "ui/apps/console/src/__tests__/App.agentChatLink-10640.test.tsx", "ui/apps/console/src/__tests__/App.nativeShellMode-29039.test.tsx", "ui/apps/console/src/__tests__/nativeShellRoute-29750.test.ts", "ui/apps/console/src/components/UnmatchedRoute.tsx", "ui/apps/console/src/nativeShellRoute.ts", "ui/packages/app-shell/src/index.ts" ], "line_budget": "8 files, +451 / -10 vs merge base (dispatch-gates count, generated included), under 3000", "tests": "b1ed0110: closure rebuild 70/70 tasks; turbo type-check @object-ui/console + @object-ui/app-shell Done; vitest 13 console files (every test mocking components/RootLandingRedirect or using @object-ui/mobile, plus nativeShellRoute-29750) 62/62 passed; eslint on the 7 touched source/test files exit 0. Ablation A1 (App.tsx wrapper navigates verbatim, anchor 1->0, blob restored == HEAD): 1 red, 14 green. Ablation A2 (UnmatchedRoute always redirects to /): 6 red (hosting + waiting), 9 green incl. controls; restored == HEAD. Live (app-crm --fresh + Vite Console, Playwright, fake bridge): /apps/com.example.crm/foo/bar shows the app's Page not found; first URL /foo/bar -> /apps/com.example.crm/foo/bar; /crm_opportunity/ID -> the record; /dev/nope -> Home; push tap from fresh Home, in-app, Home-after-app -> /apps/com.example.crm/crm_opportunity/record/ID.", "gates": "dispatch-gates --commands at 93b08baf: 35 derived, all exit 0 (incl. check-sdui-manifest, vitest run scripts/, check:type-check-debt, check:test-source-alias, check:cross-package-test-inputs, changeset gates); --ran: 35 run, 0 NOT-MEASURED. Re-run on b1ed0110 with the PR body as GITHUB_EVENT_PATH: check-changeset-no-major 0, check-adr-0087-registration 0, check-closing-keyword-parity 0, check-empty-changeset 0, check-sdui-manifest 0, check-partof-closing-keyword 0. dispatch-gates --tier: no path-derived mandate. CI check-runs: in_progress at report time (the seat reads them at ACCEPT).", "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm", "deviations": [ "Leg 1 host hint is the URL :appName only, not currentAppName ?? :appName as ruled: measured live, currentAppName is the app NAME (crm_app) and hosting under it made the app route re-address the page 4 times; the catch-all re-checks currentAppName against the app list instead. Reported to the seat before pr_create.", "Catch-all waits while the app status is idle/loading (not only while loading): measured live, a session-cached list ends loading while its re-read still reads loading, and the first build redirected to Home there. Only status error keeps the redirect to /.", "Surface additions approved by the seat before code: ui/apps/console/src/nativeShellRoute.ts, ui/apps/console/src/components/UnmatchedRoute.tsx, and a one-line importOriginal spread in App.agentChatLink-10640.test.tsx's RootLandingRedirect mock.", "Mobile cold-start path read from the public objectstack-ai/mobile main ddcf2d5 via a shallow clone in the scratchpad (read only)." ], "mcp_calls": "3: mcp__github__issue_read (get #29750, get_comments #29750 twice). No MCP write.", "api_writes": "2: pr_create via scripts/pm/fleet-write relay (objectstack#29810, read back byte-identical, assignees none); this os-dev-report comment via post-stamped. Plus git pushes of the branch (not REST writes).", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: none · noted, not filed: an /apps/NAME/... URL addressing an app by name rather than its _packageId segment lands right but re-addresses the page several times (alternating crm_app / com.example.crm replaces, live). Nothing in this PR produces that form." ] }objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Landed · 2026-11-03T07:05Z · PR #29810 merged through the queue as
b0ef25f5. The merge commit carries all 8 files of the accepted headb1ed0110:App.tsxNativeShellRouting,nativeShellRoute.ts,components/UnmatchedRoute.tsx, three app-shell root exports, tests, and the changeset. Delivered in full: a push route from the mobile shell is hosted the way the inbox hosts it, throughresolveNotificationTarget. That covers the in-app listener and a first URL or cold-start hand-off through the catch-all. Closed by the PR'sFixes;pm:dispatchedremoved.