Async Digital / Iris
Case study

A primed draft from a single URL

The agentic bar

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, or an agent) can deliver a user-ready, mid-edit state in one call. Not just a screen. A draft already typed, sitting in the right place, ready for the human to review and send.

Abstract

Most deep links open a screen and stop. This final study follows one URL much further: a cold launch that lands on a compose draft already typed, sitting beneath a templates sheet two layers deep, with the chosen template highlighted and no loading states.

Two such URLs fired back-to-back still resolve cleanly. Last writer wins on both the highlighted row and the draft body. The depth comes from one extra effect step inside the same ordered flow every other URL uses. The cost is explicit: highlight and draft are now coupled, a race can show a brief mid-flight handover, and the link must validate the template at parse time.


Bar

The bar is a draft, not a doorway

Deep linking started as wayfinding: a URL meant a place, and the job was to open the right screen. Whatever came next was the user’s problem. Automation flips the expectation. Scripts, Shortcuts, and agents don’t want a doorway. They want context already prepared. A task half-done so the human’s first action is a decision, not a search. The real question becomes: can one URL leave the app in a ready state? Iris answers yes while keeping every guarantee from the rest of the series. The depth shown below is possible only because the racing and state rules already work. The further a URL reaches, the stronger the architecture underneath must be.


Sequence

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:

  1. Push the bot’s preview onto the compose navigation stack.
  2. Grow the preview sheet to make room for an active compose state.
  3. Present the templates sheet over the preview.
  4. Highlight the named template’s row, with an animated scroll that centres it.
  5. Prime the underlying draft with that template’s body, left visible above the sheet’s edge.

The recording below is Iris’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.

Fig 1 One URL, cold launch. The preview pushes, the sheet grows, the templates sheet presents, the row highlights, and the draft above it is already primed.

Races

Two URLs, one winner, on both surfaces

A primed draft earns the same scrutiny as any other surface a URL can touch. The stress-test article measures 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 set together in one step, so there is no window in which the row names one template and the draft holds another.

Fig 2 Two URLs racing for the same draft. The second cancels the first mid-scroll and wins both the highlight and the draft body.

Design

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 Iris already knew how to order, cancel, and replay.

Fig 3 One intent, six ordered operations. The highlightTemplate effect does two things in one apply: it sets the highlighted row and selects the template, which pre-fills the draft.
Caveat

The combined prime is a coupling, and it shows up in three places. Because the highlight and the draft are set in one step, a test that checks the highlight is implicitly checking the draft too; if the two ever need to move independently, the step has to be split in two. 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 brief mid-flight handover rather than a clean cut. And the deeper a URL is allowed to reach, the more has to be true before it runs. A primed draft against a template that does not exist has to be rejected when the URL is parsed, before any of the beats run, because a sequence that half-applies would leave the sheet open over a draft it never filled.


Series

What one call hands over

This is the closing piece of the series. The other five claims (the races, the footprint, the state-aware resolution, the input surface, and tap-and-URL parity) 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.