Summary
A product calling navigateTo("calculator.paseo") reaches the native HostBridge.navigateTo callback as https://calculator.paseo.
A DotNS product identifier is not an HTTPS origin. Converting it before the platform boundary loses the distinction between Web3 product navigation and explicit HTTPS navigation, forcing native hosts to infer and reverse the conversion.
Reproduction
Using Epoca iOS with TrUAPIHostRuntime from revision 3f9ba7368f52fc3c9e8930803cb71ca46a9b1ae7:
- Open
browse-testing.paseo as a verified product.
- Browse calls
navigateTo("calculator.paseo") when the Calculator product is selected.
- Trace the native
HostBridge.navigateTo(url:) callback.
- Observe
url == "https://calculator.paseo".
The product bundle calls the TrUAPI navigation API with the bare value. The HTTPS conversion occurs inside the shared Rust runtime.
Root cause
rust/crates/truapi-server/src/host_logic/dotns.rs correctly classifies the input as NavigateDecision::DotName, preserving:
identifier: "calculator.paseo"
path: ""
It also constructs canonical_url: "https://calculator.paseo".
rust/crates/truapi-server/src/runtime.rs then collapses DotName to that canonical_url before calling the platform:
NavigateDecision::DotName { canonical_url, .. }
| NavigateDecision::Localhost { canonical_url, .. } => canonical_url,
The native callback therefore receives the HTTPS-shaped value rather than the DotNS identity.
Platform divergence
The current Android UserAgentKit ProductView path preserves the original navigation string:
HostRequest::NavigateTo { url } becomes NeedsNavigate { request_id, url } unchanged.
ProductHostAdapter invokes configuration.onNavigate(outcome.url) unchanged.
The native host-rust-core path used by iOS instead normalizes the same bare product identifier to HTTPS. An Android host adopting the same native runtime would inherit this behavior.
Impact
The native host cannot distinguish these product requests after normalization:
navigateTo("calculator.paseo")
navigateTo("https://calculator.paseo")
Both can reach the platform as https://calculator.paseo.
This prevents a host from enforcing the intended routing contract:
- bare
.paseo / dot:// destination -> verified Web3 product container
- explicit
https:// destination -> external web browser
Epoca iOS currently requires a workaround that recognizes https://*.paseo in the native callback and converts it back to a bare product destination. That restores visible navigation but cannot recover whether HTTPS was explicit in the original request.
Expected behavior
navigateTo("calculator.paseo") reaches the platform as a DotNS product destination without an HTTPS scheme.
- Paths, queries, and fragments remain intact.
navigateTo("https://example.com") remains external HTTPS.
- Bare DotNS and explicit HTTPS requests remain distinguishable.
- Android and iOS native hosts receive equivalent navigation semantics.
A typed platform callback carrying NavigateDecision, or a string callback that preserves the bare DotNS identifier for DotName, would both retain the required information.
Acceptance criteria
- A Rust regression test asserts that
navigateTo("calculator.paseo") invokes the platform callback with calculator.paseo.
- DotNS path, query, and fragment components are preserved.
- External HTTPS remains HTTPS.
- Bare DotNS and explicit HTTPS input remain distinguishable at the platform boundary.
- Native Android and iOS conformance coverage asserts the same contract.
Summary
A product calling
navigateTo("calculator.paseo")reaches the nativeHostBridge.navigateTocallback ashttps://calculator.paseo.A DotNS product identifier is not an HTTPS origin. Converting it before the platform boundary loses the distinction between Web3 product navigation and explicit HTTPS navigation, forcing native hosts to infer and reverse the conversion.
Reproduction
Using Epoca iOS with
TrUAPIHostRuntimefrom revision3f9ba7368f52fc3c9e8930803cb71ca46a9b1ae7:browse-testing.paseoas a verified product.navigateTo("calculator.paseo")when the Calculator product is selected.HostBridge.navigateTo(url:)callback.url == "https://calculator.paseo".The product bundle calls the TrUAPI navigation API with the bare value. The HTTPS conversion occurs inside the shared Rust runtime.
Root cause
rust/crates/truapi-server/src/host_logic/dotns.rscorrectly classifies the input asNavigateDecision::DotName, preserving:identifier: "calculator.paseo"path: ""It also constructs
canonical_url: "https://calculator.paseo".rust/crates/truapi-server/src/runtime.rsthen collapsesDotNameto thatcanonical_urlbefore calling the platform:The native callback therefore receives the HTTPS-shaped value rather than the DotNS identity.
Platform divergence
The current Android UserAgentKit
ProductViewpath preserves the original navigation string:HostRequest::NavigateTo { url }becomesNeedsNavigate { request_id, url }unchanged.ProductHostAdapterinvokesconfiguration.onNavigate(outcome.url)unchanged.The native
host-rust-corepath used by iOS instead normalizes the same bare product identifier to HTTPS. An Android host adopting the same native runtime would inherit this behavior.Impact
The native host cannot distinguish these product requests after normalization:
Both can reach the platform as
https://calculator.paseo.This prevents a host from enforcing the intended routing contract:
.paseo/dot://destination -> verified Web3 product containerhttps://destination -> external web browserEpoca iOS currently requires a workaround that recognizes
https://*.paseoin the native callback and converts it back to a bare product destination. That restores visible navigation but cannot recover whether HTTPS was explicit in the original request.Expected behavior
navigateTo("calculator.paseo")reaches the platform as a DotNS product destination without an HTTPS scheme.navigateTo("https://example.com")remains external HTTPS.A typed platform callback carrying
NavigateDecision, or a string callback that preserves the bare DotNS identifier forDotName, would both retain the required information.Acceptance criteria
navigateTo("calculator.paseo")invokes the platform callback withcalculator.paseo.