Every museum is full of people who know which object in the room has the best story. A conservator knows the repair nobody notices. A host knows the thing children always stop at. A curator knows why an unremarkable piece of terracotta is in the case at all. Visitors almost never hear any of it, because the app only knows what the catalogue knows.
Staff picks are our attempt to fix that: profiles of real staff told as questions and answers, one set per venue, where each answer can point at real collection objects, events, shop products and links, with a way to walk you to the object. This post is less about the feature than how it was delivered: from a pitch on 24 September to iOS and Android builds on 21 October, through review and CI the whole way, across six repositories and four teams. I think the delivery pattern is the part worth keeping.
01 · THE PROBLEMAn idea with five owners
On paper a staff-picks feature is small: a list of people, a page per person, some linked objects. In practice it touches nearly every layer of a digital experience platform, and each layer has a different owner. Editorial content lives in a headless CMS. Published data reaches the platform through an integration hub behind an API gateway. The visitor app is a React Native app with its own .NET backend. Collection records, events and wayfinding come through a Content API. Releases go through a mobile pipeline that every feature shares.
A feature that crosses five owners has a predictable failure mode. It doesn't fail technically. It waits. Each team reasonably wants to know the shape of the thing before committing to its part, and the shape isn't known until someone builds part of it. Ideas like this tend to die in the gap between "everyone likes it" and "who goes first".
02 · THE WRONG TURNWrite the spec, then wait in line
The conventional sequence is easy to picture. Write requirements. Design the CMS schema. Wait for content. Ask the hub team for an endpoint. Wait for it to reach dev. Hand the app team a payload. Wait for screens. Release. Every step is sensible, every step blocks on the one before, and the first time anyone sees the feature working is weeks after the idea, by which point changing its shape is expensive.
There's a quieter problem too. A written spec gets reviewed as prose, and prose hides the hard questions. Can a pick really join to a live collection record? Does the object know which room it's in? Can the app get from that room to a map? None of those come up in a document. They come up the first time you try to render the screen.
The fastest way to agree on a feature is to put a working one in front of people, on the day of the idea, and argue about that instead.
03 · THE KEY INSIGHTPrototype the contract, not the screens
The Content API already fronts the integration hub, the collections data, indoor wayfinding and the event feeds, and it serves everything in one envelope that says where each response came from. That meant a new feature didn't need new plumbing, only a new contract. On 24 September, starting from a pitch for a weekly curator's pick, we built it in an afternoon:
The same afternoon our showcase web app gained an app-style preview. It was a phone frame drawn in the visitor app's own design language, with a new Curated lane on the Discover screen between On Now and On Display, plus pick, story, object and travel screens. Every screen footer listed the API calls that built it. That preview was the pitch: stakeholders could tap through the experience, with real objects and real wayfinding, on the day the idea was raised.
Decision register · the model changed, the plumbing didn't
The prototype then went to the content and app leads for review, and the idea changed shape. A weekly curator's pick became staff picks: staff profiles told as Q&A, one set per venue, with no weekly publishing rhythm to keep up. That is exactly what a prototype is for. Changing the editorial model cost a conversation, not a rebuild, because the expensive question, whether a pick can join to real objects, events and wayfinding, had already been answered in production.
04 · THE ARCHITECTUREAuthor once, publish through the hub, read through a proxy
Here's the shape that shipped. Editors write in the CMS studio. The integration hub publishes picks per venue behind Azure API Management. The visitor app never calls upstream directly: its .NET backend proxies the hub and caches it, and the React Native app only talks to that backend, through a generated client. The Content API serves the same picks to web consumers, reading the hub first and the CMS directly when the hub has nothing to say.
Who owned which layer
Each layer had one owner, and they met at one shape. Nobody waited on a document. They built against the contract and the stored model the hub already exposed.
05 · THE FLOWThirty hours, four lanes, one shape
Once the model was agreed, the build ran in parallel rather than in sequence. This is the record from the pull requests and the release pipeline, in local time, from the morning the CMS studio was created to the moment both store builds were uploaded.
The app's data-layer pull request is the one I would point to. It merged twenty-three minutes after it was opened, on the same afternoon the CMS schema landed. That only works when the payload shape isn't in dispute. The backend endpoints, DTOs, regenerated OpenAPI spec, generated client and React Query hooks were all written against a model that already existed in the hub. The screens followed the next afternoon, with simulator screenshots in the pull request and every required check green.
Thirty hours was not anyone typing fast. The hard questions were settled in September, so in October four teams could build side by side instead of in a queue.
06 · THE DETAIL THAT SEPARATES REAL FROM DEMOEvery response says where it came from
A prototype built on fixture copy is only safe if nobody can mistake the fixture for the real thing. Every Content API response carries a meta.source. The September prototype said curator-demo on every editorial field. The October endpoints say dxp-hub when the hub answered and picks-sanity when they read the CMS directly. A consumer, a tester or a support analyst can tell what they're looking at without reading code.
That labelling earned its keep within a day. When the Content API endpoints were first wired up, the development hub returned an empty list for every venue. The CMS's development dataset held six published profiles and its production dataset held none, so the development hub was reading the empty one. The staging hub, meanwhile, already served all six.
Decision register · fall back, don't block
Rather than wait for the development hub to be repointed, the Content API reads the hub first and falls back to the CMS's public CDN when the hub is unset, unreachable, unauthorised or empty, and labels the answer accordingly. Development carried on against real authored content, the upstream fix was logged, and nothing pretended to be the hub when it wasn't. Once the hub answers, the label flips back to dxp-hub by itself, with no release needed.
The app takes a stricter line on purpose. It never reads the CMS directly. It reads the hub through its own backend with a five-minute cache, so an editor's change reaches phones within minutes and a hub outage never turns into a stampede of retries from every device in the building.
07 · THE PIPELINENobody got a special deploy path
The speed came from the shape of the work, not from skipping anything. Every repository kept its own branch protection, review and checks, and the mobile app used the same release pipeline as every other feature.
The Content API follows the same discipline at its own scale. Work lands on a test branch, tests and type checks run in Docker, a pull request goes to main, and CI runs on a self-hosted runner to keep cost down. An endpoint isn't finished until it is in the OpenAPI spec and the interactive docs.
08 · THE REPEATABLE PARTThe workflow for the next feature
This is the first feature to travel the whole path end to end. It shouldn't be the last, so here is the pattern written down as steps another team can follow without me in the room.
- Prototype on the Content API on the day of the idea. Fixture editorial is fine as long as every response says so, and everything that can be real (objects, events, wayfinding) is real.
- Write the contract before the schema. A short contract document and an OpenAPI entry give the app, CMS and hub owners one shape to review.
- Author in the CMS, publish through the hub. Editors own the content in the studio, and the hub is the single published source behind the gateway.
- Generate clients, don't hand-write them. OpenAPI → orval in the app, typed DTOs in the backend. A field rename becomes a compile error, not a crash on a visitor's phone.
- Make the source visible at every hop.
meta.source, the hub admin panel and shared API collections let anyone answer "where did this come from?" - Fall back, and say so. Hub first, CMS second, always labelled, so an upstream gap degrades gracefully instead of showing an empty screen or blocking a team.
- Release through the shared pipeline only. A version bump goes to TestFlight and the Play internal track, and a tagged release goes to the stores. No feature gets its own deploy path.
09 · THE PROOFWhat shipped, and what comes next
This is what the commits, pull requests and pipeline runs showed on 21 October:
- Prototype contract, real objects and live wayfinding in production on 24 September, about three and a half hours after the first commit
- A CMS studio with a pick document type, and the first six real staff profiles authored
- Hub routes registered in the gateway route map, with shared API collections for each environment
- A hub admin panel that can list and inspect picks per venue, for operators only, with validated inputs and rate limiting
- The visitor app's data layer and React Native screens merged with every required check green
- A new app version built and uploaded to Apple TestFlight and the Google Play internal track through the shared release pipeline, with a release cut, ahead of the app's public store release
- Content API picks endpoints with new tests, a full suite of over five hundred passing, and OpenAPI documentation
The rest of the road is deliberate, not unknown:
- Promotion to the public App Store and Google Play listings happens through the release branch and a version tag. It is deliberately a human decision.
- Editors keep improving the profiles: titles, descriptions and image alt text.
- The web version moves from the September preview to the new design the native app follows.
What matters most to me about that list is that it is all content, configuration and promotion. None of it is architecture, and none of it needs another team to start before it can finish.