C.W.K.
Stream
Lesson 02 of 04 · published

One Resolver, Two Envelopes

~12 min · shared-code, divergence, ownership-boundary, design

Level 0Dead Zone
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Share the part that's identical; diverge exactly where ownership differs. Not one line sooner, not one line later."

The Shared Middle

Pippa Go has two flows that carry images: Prompt Macro (a utility that runs the composer without a conversation turn) and Ask (canonical streaming chat). Both begin with the exact same problem — a user picked an image, and it now sits in device-local staging as a blob with some metadata. Turning that staged blob into real, usable bytes plus a correct media type is identical work regardless of which flow you're in. So it's done once, in a single shared staged-media resolver. Duplicating that resolution in two places would be the classic drift bug: two copies that slowly disagree about encoding, orientation, or media-type detection.

The Deliberate Divergence

Then, having resolved the bytes, the two flows split — and the split is principled, not incidental. They diverge at the ownership boundary, because what happens to the image depends on who owns the result:

  • Prompt Macro → utility envelope. Macro is an apply-free utility, not a conversation. Its image rides in a lightweight utility envelope (inline encoded bytes) suited to a one-shot call that doesn't create canonical state.
  • Ask → canonical envelope. Ask creates a real conversation turn, so its image is uploaded as a canonical attachment and referenced by the chat message — owned by the backend like everything else in a canonical turn.

Same resolved bytes, two envelopes, because Macro's result is disposable and Ask's result is canonical. The divergence isn't a wart; it's the ownership model showing through the code.

The Rule: Share to the Boundary, Diverge at It

This is a reusable design instinct. When two flows share early work and differ later, factor the shared part into one component and let them diverge exactly at the point their requirements actually differ — here, the ownership of the resulting image. Share too little and you duplicate the resolver (drift). Share too much and you force Macro and Ask into one envelope that fits neither (a leaky abstraction). The staged-media resolver draws the line in the right place: everything up to "we have real bytes + media type" is common; everything about where those bytes live is per-flow.

Code

Shared resolver, then two envelopes at the ownership boundary·typescript
// Shared: identical for both flows — staged blob -> real bytes + media type.
async function resolveStagedImage(localId: string): Promise<ResolvedImage> {
  const blob = await readStagedBlob(localId);
  return { bytes: await blob.arrayBuffer(), mediaType: blob.type };
}

// Prompt Macro: disposable utility call -> inline utility envelope.
async function macroEnvelope(img: ResolvedImage) {
  return { inlineData: toBase64(img.bytes), mediaType: img.mediaType };
}

// Ask: canonical turn -> upload as a canonical attachment, reference it.
async function askEnvelope(convId: string, img: ResolvedImage) {
  const ref = await uploadCanonicalAttachment(convId, img); // backend-owned
  return { attachmentRef: ref };
}

// One resolver feeds both. They diverge exactly where ownership differs.

External links

Exercise

Take two features that share early processing but differ in what they produce (e.g. 'export as draft' vs 'publish live', both starting from the same document). Identify the exact boundary where their requirements diverge. Factor the shared work into one function and the divergent tails into two. Then check: did you share right up to the boundary, or did you accidentally share past it into a leaky one-size path?
Hint
The correct seam is the last point at which both flows want identical output. Everything before it is shared; the first difference in requirements is where you split. If your shared function has an 'if flow === A' inside it, you shared one step too far.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.