A primed draft from a single URL
Async Digital Ltd Cardiff, UK
Most deep links open a screen and stop. This closing case study in the Deep Link Kit series follows one URL past that point: from a cold launch to a compose draft already typed, sitting beneath a templates sheet two layers deep, with the chosen template highlighted and no loading state anywhere in between. Two URLs racing for the same surface resolve to a single winner on both the highlight and the draft body, the same last-writer-wins contract that governs every other racing surface in the kit. The mechanism is one effect case whose apply both highlights the row and selects the template, so the depth costs one case statement rather than a parallel navigation path. The cost is a composite trace event, a visible mid-flight handover under races, and a parse-time existence gate that has to fail closed before any of it is allowed to run.
The point of a deep link is not the URL scheme. The point is that anything able to fire one (a script, a Shortcut, a remote system, an LLM driving the phone) can deliver a user-ready, mid-edit state in one call. Not a search result. Not a context-free link to a screen. A draft already typed, sitting in the right place, ready to be reviewed and sent.
The bar is a draft, not a doorway
Deep linking grew up as wayfinding. A URL stood for a place in the app, and the app’s whole obligation was to take you there. Open the right screen, maybe select the right tab, done. Whatever happened next was the user’s problem.
Automation changes what the caller wants. A script staging a demo, a Shortcut wired into a daily routine, an agent driving the phone on someone’s behalf: none of them wants a doorway. They want to hand over finished context, a task already half done, so the human’s first touch is a decision rather than a search. The useful question stops being “can a URL open the compose screen?” and becomes “can a URL leave a draft in it?”
Deep Link Kit is built to answer yes to the second question without giving up any of the discipline the rest of this series describes. The racing behaviour below leans on the guarantees measured in the stress-test article; the depth this piece adds is what makes those guarantees worth having. The further a URL is allowed to reach past the front door, the more has to be true of the architecture carrying it.
Five beats from a cold launch
The claim is easiest to show. One URL, fired at an app that is not running:
botmessages://compose/bot/design/templates/design-feedback
The app cold-launches and resolves it into five beats, applied in order as one continuous motion:
- Push the bot’s preview onto the compose navigation stack.
- Ease the preview sheet’s detent from
.mediumto.large, making room for an active compose state. - Present the templates sheet over the preview.
- Highlight the named template’s row, with an animated scroll that centres it.
- Prime the underlying draft with that template’s body, left visible above the sheet’s edge.
The recording below is the kit’s demo consumer, cold-launched by the URL above. The chosen row sits highlighted in the sheet, and the draft it primed (“Could you take a pass on the latest mocks…”) peeks above the sheet, already typed. The resolution plays as a single animation pass of roughly 200 milliseconds. No loading state, no second tap, and the user has not touched the screen.
Two URLs, one winner, on both surfaces
A primed draft earns the same scrutiny as any other surface a URL can touch. The first article in this series stress-tests what happens when URLs arrive faster than the UI can animate; the short version is that racing URLs collapse to last-writer-wins, with cooperative cancellation and no half-applied state left behind. The compose prime joins that contract rather than negotiating its own. Fire two template URLs back to back:
botmessages://compose/bot/design/templates/release-notes
botmessages://compose/bot/design/templates/hero-copy-review
The first gets as far as scrolling its row into view. Mid-animation the second cancels it, re-runs the same flow with its own template, and lands. Both surfaces reflect the new winner: the highlighted row and the partially visible draft above the sheet agree about which URL won. That agreement is the point. The highlight and the draft body are primed by one effect, so there is no window in which the row names one template and the draft holds another.
The prime is one effect case
Inside the consumer, the flow that owns the compose surface maps this intent to a sequence drawn from the same closed set of operations every other entry point uses. Reaching a primed draft two sheet layers deep did not need a new kind of navigation. It needed one new effect case:
case .openBotPreviewTemplate(let pid, let tid):
return [
.nav(.popToRoot),
.effect(.clearTemplateHighlight),
.nav(.dismissSheet),
.nav(.push(.botPreview(pid))),
.nav(.present(.templates(pid))),
.effect(.highlightTemplate(tid))
]
And the effect’s apply, which does two things rather than one:
case .highlightTemplate(let id):
highlightedTemplateID = id
if let template = DemoData.template(id: id) {
selectTemplate(template)
}
Selecting the template sets it active and pre-fills the draft. The user sees the highlighted row first because the templates sheet is on top; dismissing the sheet reveals a compose surface that was ready before they looked. The depth of the result is composed entirely from operations the dispatcher already knew how to order, cancel, and replay.
highlightTemplate effect does two things in one apply: it sets the highlighted row and selects the template, which pre-fills the draft.The composite prime is a coupling, and it shows up in three places. The trace records one event where two things happened, so a test that asserts the highlight is silently asserting the draft as well; if the two ever need to move independently, this case splits. Under a race the coupling is visible: the first template’s scroll begins before the second URL wins, so a user watching closely sees a mid-flight handover rather than a clean cut. And the deeper a URL is allowed to reach, the more the existence gate at parse has to guarantee. A primed draft against a template that does not exist must fail closed at parse, before any of the five beats run, because a sequence that half-applies would leave the sheet open over a draft it never filled.
What one call hands over
This is the closing piece of the series. The other four claims (the races, the footprint, the state-aware resolution, and the input surface) each have an article of their own, and the series page holds the map.
The pattern they add up to is the one this page started with. A URL is a call into the app, and the measure of the architecture behind it is what that call is allowed to hand over. Here it hands over a draft already typed, two sheets deep, waiting on a human decision. The screen it opened was the least of it.