Send a deep link
Pick a device, run a request, and read what came back.
You have a workspace open and a device listed. What is left is the part Ryno was built for: write one link, send it, and read an answer you can trust. This page goes from an empty request to a link on a screen, and it spends most of its length on the last step, because that is where a testing tool earns its keep.
Write your first request
Pick New request in the sidebar, or press collections/, inside your workspace, as one small file you can read, search and
review like the rest of your project.
Give it a name, then click into the URL field and type the link you want to test.
Press
A link is made of a scheme, a host and a path — myapp://profile/42, or
https://example.com/user/42 for a link that also has to work from a browser.
Anything you type after a ? is folded into the query parameters table below,
and stays in step with it from then on.
Fill the link in from an environment
Wrap a name in double braces and Ryno fills it in from the environment you have
selected: https://example.com/user/{{userId}}. The braces are highlighted as
you type, and the environment you are on is switched with
Underneath the field sits the Resolved deep-link panel, which shows the link as
it will actually be sent — braces filled in, disabled parameters gone, values
encoded. It is rebuilt on every keystroke, so what you read there is what leaves
the machine. Copy it with the button in its header, or with
A name no environment defines resolves to nothing at all. Ryno does not
guess, and it does not stop you either: the environment panel names the braces it
could not fill and asks you to define them, and the resolved link shows the hole
they left. Values you keep out of your repository work the same way — an
environment file whose name ends in .env.local.json is read like any other, and
stays on your machine.
Turn a query parameter off without deleting it
The Query params tab holds one row per parameter: a checkbox, then Key,
Value and Description. Add rows as you need them.
Unticking a row is the point of the tab. The parameter stays in the file, stays visible in the table, and is dropped from the resolved link — so it is also absent from what reaches the device. That is how you test the same link with and without a parameter without retyping either version, and how a colleague reading the file still sees the parameter you were trying. A row with no key is dropped the same way, which is why a half-typed row never breaks the link under your fingers.
Description is for you and your team. It is written into the file and travels
with it, and it never reaches the link nor the command Ryno runs. Say what a
parameter is for, and nothing you write can change what is sent.
Name the moving parts of a path
A path segment written :name — myapp://user/:userId/orders — becomes a row in
the Path params tab, with the same Name, Value and Description columns.
The rows are read from the path itself, so editing a row can never change the URL.
Rename a segment and an empty row takes its place: the value you had typed is set
aside rather than deleted, and comes back if you put the old name back.
A value may itself hold {{braces}}, which are filled in from the environment
like everywhere else. A segment left without a value is called out next to the
unfilled braces, because an empty segment in a resolved link is the kind of
mistake that looks like an application bug for twenty minutes.
Aim at a device, and the editor follows
The third tab belongs to the platform of the device you have selected. Select an
Android phone and it is Android intent; select a simulator or an iPhone and it
is iOS launch. There is only ever one, on purpose: the editor tells you what the
next run is aiming at, rather than listing everything it could aim at.
On Android the tab holds Package, Action, Category and Flags. Only the
action has a value out of the box, android.intent.action.VIEW, which is the
action a browser would send. Naming a package aims the link at one application
instead of letting the system route it.
On iOS the tab holds Bundle identifier, Launch mode, Launch arguments and
Environment, and the launch mode is also the picker at the left of the URL
field. It has three settings, and they test three different things:
| Mode | What it runs | What it tests |
|---|---|---|
OPEN |
simctl openurl |
System routing — whichever app declares the scheme or owns the domain wins. The default. |
LAUNCH |
simctl launch · openurl |
A named app, started with its launch arguments and environment, then handed the link once it runs. |
SAFARI |
simctl openurl · https |
The bounce from the browser, smart banner included. |
On a real device there is only LAUNCH, and it runs devicectl process launch:
the named app is started with the link as a payload, and routing is skipped
entirely.
The text fields of the tab take {{braces}} exactly like the URL: Package,
Action and Category on Android, Bundle identifier, Launch arguments and
Environment on iOS. They are highlighted and offered as you type,
filled in from the selected environment when the request runs, and a name the
environment does not define is called out next to the request like one in the
URL. That is how one request aims at {{appPackage}} and reaches the staging
build in one environment and the production build in the other. Flags and
Launch mode are fixed choices, and stay as written.
Launch arguments and Environment reach the app in LAUNCH mode, the only
one that starts it: on a simulator each variable is set for simctl launch as
SIMCTL_CHILD_ followed by its name, and the command Ryno shows says so. OPEN
and SAFARI only hand a link over, so the tab marks both fields launch only.
Bundle identifier is optional on a simulator and required on a real device.
A simulator can be asked to open a link and work out the rest by itself; a
physical iPhone cannot, so Ryno has to name the app it hands the link to. Select
a handset with the field empty and Ryno says so next to the request, before you
run anything, rather than letting the launch fail with a message from Apple’s
tooling that reads like a bug in Ryno.
Run it
The green Run button sits in the top bar, and
Three answers, and only one of them is a verdict
This is the part worth reading before you need it. A finished run is reported one of three ways, and the middle one is not a shade of the first.
A link that was handled. On an Android phone, an Android emulator or an iOS
simulator, the system tells Ryno whether anything claimed the link. If something
did, you get Link opened and a green mark; if nothing did, you get
Launch failed in red, in plain words, with what to check — and the tool’s own
words folded away underneath, for when you want them. Green means what it says,
here.
A link that was sent. On a physical iPhone or iPad, Apple’s tooling has no
door that asks iOS to open a link. Its only door starts an app you name and
hands it the link — without consulting the system, and reporting the same clean
success whether the app understood the link, ignored it, or was the wrong app
entirely. So Ryno refuses to call that a success. It says Link sent — RYNO can’t tell what the app did with it., in muted ink rather than green, and files the run
under that same sentence. Nothing failed, nothing is confirmed, and the answer is
on the phone in your hand.
A link that never left. Ryno can also refuse to run, which it does rather than
send something it knows is wrong — a temporary request still carrying braces
nothing can fill, for instance, since a scratch belongs to no workspace and has no
environment to read from. You get Launch refused and the reason, and no link is
sent.
Read what came back
Device logs sits under the editor and keeps the history of the request you are
looking at. Each entry carries the exact command line that was sent and the
output that came back, with a Copy button that takes both at once — which is
what you paste into a bug report.
The command shown is the command run. Values are quoted before they reach the shell, so a link with an ampersand or a space in it is sent whole rather than being cut in half by the shell, and the line in the journal is the line you can paste into a terminal yourself.
Two of the options below fill this panel further: a captured extract of the
device’s own log stream, attached to the run it belongs to. The panel’s menu also
clears the history, either for this request or with Clear logs for all requests.
Ask whether the domain declares your app
Next to the resolved link sits a small mark: App Links when you are aiming at
Android, Universal Links when you are aiming at iOS. It answers one question —
does the domain in your link declare this app, for this path? Each state has its
own shape as well as its own colour, so it still reads on a monochrome screen.
Click it and Ryno shows you everything the check read: what the domain declares,
the signing fingerprints, the paths it covers, and how the app installed on the
device compares. The mark only applies to https:// links, since a custom scheme
has no domain to interrogate.
Every option, and what it starts at
The Options tab is the same list on both platforms, in four sections: Before launch, Behavior, Encoding & delivery and Diagnostics. An option the device
you are aiming at cannot run is not hidden — it goes grey, keeps its stored
value, and is tagged with the platform that does run it. A request is shared with
your team, and a row that vanished would let the tab lie by omission to whoever
opens it from another machine.
| Option | Default | What it does |
|---|---|---|
| Boot simulator if needed | Off | Starts the selected simulator first, so a cold machine still runs. iOS only. |
| Force-stop app first | Off | Kills the app so the link arrives at a genuine cold start. |
| Clear app data | Off | Wipes the app’s data first, so the link lands on a signed-out, first-launch state. Android only. |
| Wait for launch | On | Blocks until the activity reports it is up. Android only. |
| Force-stop before start | Off | Clears the launch task only, not the whole app. Android only. |
| Wait for debugger | Off | Starts the activity paused until a debugger attaches. Android only. |
| Run timeout | No limit | Fails the run if the activity does not start in time. Android only. |
| Intent flags | None | Extra flags added to the launch intent. Android only. |
| URL-encode values | On | Percent-encodes query values; turn it off to send them raw. |
| Delivery method | am start |
Send the link directly, or wrap it as intent:// the way a browser would. Android only. |
| Screenshot on finish | Off | Grabs a screenshot once the launch settles and attaches it to the run. |
| Capture logcat | Off | Attaches a recent extract of the Android log stream. Android only. |
| Capture os_log | Off | Attaches an extract of the Apple log stream, narrowed to your bundle. iOS only. |
Reset to defaults at the top of the tab puts every row back to the values above.
Screenshot on finish, Capture logcat and Capture os_log are best effort.
They run after the launch, and a capture that fails is dropped quietly rather
than turned into a failed run: the outcome you read is the one the launch
earned. So a run with no image and no log extract attached is not a run that
went wrong — the absence is about the capture, and never a symptom of what the
device did with your link.
Three of these look alike and are not, and confusing them costs a session:
Force-stop before startclears the launch task of the activity you are opening. It is the lightest of the three.Force-stop app firstkills the whole app before the run, as a separate command. State the app kept in memory is gone; state it wrote to disk is not.Clear app datagoes further still and wipes what the app stored, which puts it back in a signed-out, first-launch state. Use it when you are testing what happens to somebody opening your link for the first time, and not before.
And one trap worth naming. Run timeout has no default, and no value means no
limit. That is harmless on its own, and it stops being harmless the moment you
turn Wait for debugger on: the launch then waits for a debugger that may never
attach, and nothing brings it back. Set a timeout whenever you pause for a
debugger — ten seconds is a reasonable start.
When the link does not land
Work down this list before suspecting the application.
Nothing runs at all. Check that a request and a device are both selected. The
Run button is deliberately out of reach otherwise.
Ryno refused to launch. Read the sentence next to the request. Aiming at a
physical iPhone with no Bundle identifier is the common one, and so is picking
SAFARI for a link that is not an https:// link — there is no browser to bounce
from.
The run failed and the journal names the tool. Copy the command and its output from the entry and run the same line in a terminal. It is the line Ryno sent, so whatever answers you there is what answered Ryno.
The link went out and the app did nothing. Check the resolved link rather than
the one you typed: an unfilled {{name}} leaves a hole, and a parameter you
unticked is genuinely gone. Then check the domain mark — a link the domain does
not declare for that path opens in the browser rather than in the app, which looks
exactly like the app ignoring it.
The run says the link was sent. That is a real iPhone, and it is not a failure. Ryno handed the link over and cannot see any further: look at the device’s screen for the answer, or reproduce the same request on a simulator, where a link nothing claims fails out loud.