The ask
navigation.open() takes a path and nothing else:
/** Open `path` as the shell would: a tab, a native screen, or the page pushed. */
open(path: string): booleanSo a list screen cannot hand the pushed screen the object it already holds. A typed second argument — open(path, props), readable as route.props — would close that.
This is ergonomics, not capability. Two channels already work, and I want to be straight about that before asking for a third.
What already works
Query strings survive and merge into route.params. Opening /judges/1?from=feed&name=Probe from one native screen arrives on the next as:
{"params":{"name":"Probe","from":"feed","id":"1","path":"/judges/1?from=feed&name=Probe"}}craft.snapshots is shared between screens. Writing {from:'reviews', judgeId:1} before open() and reading it on arrival works, and carries structured data rather than strings.
Both verified on an iPhone 17 Pro simulator, craft-native 0.0.187.
Why a props argument would still earn its place
Strings only, through a URL. Anything structured has to be encoded and parsed back, and the types are gone at the boundary — every field arrives as
string. A row object with a nestedcourtand a number rating becomes hand-rolled serialisation at every call site.Path params and query params share one flat namespace. In the payload above,
id(from the path) sits besidefromandname(from the query) in the same object. A screen mounted at/judges/:idthat is also passed?id=has two different things competing for one key. A separatepropschannel cannot collide with the route.The snapshot workaround is a side channel with no tie to the navigation. It is a shared key-value store, so the handoff is "write a key, navigate, hope the next screen reads it before anything else writes it". There is no scoping to the push, nothing clears it when the user goes back instead, and two opens in flight race on the same key. It works, but it is doing a job it was not designed for — it is the offline-snapshot store.
The fallback is an API endpoint per detail screen. Without a props channel the obvious move is to re-fetch. In our app that meant adding
GET /api/judges/{id}purely so a pushed screen could name the judge that the list screen was already displaying — a round trip for data that was on screen a moment earlier, and one that fails offline even though the data was in memory. (Query strings or a snapshot would have avoided the common case; the endpoint is still needed for a cold deep link, which is the honest argument for keeping it.)
Shape that would fit
open(path: string, props?: Record<string, unknown>): booleanwith the value surfacing as something distinct from route.params — route.props, untouched by the path matcher, typed by the caller, and dropped when the screen is popped.
Environment
craft-native0.0.187stacks0.75.187,@stacksjs/stx0.2.176- iPhone 17 Pro simulator, iOS 26.5, Xcode 26.6