integration/
UI That Lives Inside Someone Else's Product - Delegated UI and Host Themes

Andrei Bespamiatnov
Author
The API was never the product
When teams talk about integrating two ALM tools, they start with REST or OSLC. That work is real. Users do not see it.
Users see a panel that opens inside Jira, Azure DevOps, or Polarion and either looks like a hostage page from 2014 or looks like a native part of the host. The second one is the product. I call that delegated UI: your app, their chrome.
I have shipped this shape enough times that the surprises are now boring. They are still the ones that burn a sprint if you ignore them.
You do not own the window
Your UI runs in a box the host created.
- Size is not yours. A “comfortable 1280 layout” will be clipped.
- Navigation is not yours. A full-page reload can dump the user out of the issue.
- Cookies and third-party storage are hostile. Assume the iframe is on a diet.
- The host may reload you when the issue changes. Local component state is not a database.
Design for a narrow, stateful fragment. Persist anything that matters on your server, keyed by the host issue id you were given. If the panel blinks, the user should not lose the mapping they just made.
Theme: host wins, browser is a hint
Dark mode is where delegated UI dies in code review.
The host has a theme. The browser has a theme. They disagree more often than you think. A developer with OS dark mode and a Jira site stuck on light will report your panel as “broken contrast” even when your tokens are correct for the wrong source.
The rule that saved me:
- Prefer an explicit theme signal from the host (their API, their CSS variables, their
data-themeon a parent). - Fall back to a documented default, not to
prefers-color-scheme, unless the host says that is the contract. - Bridge tokens once. Map host
--ds-background(or the Azure DevOps equivalent) to your own--color-base-100. Do not restyle every component by hand.
If your panel ignores the host, users will describe it as “the integration looks cheap.” They are right.
Auth in a sandbox is a feature, not a detour
Hosted extensions — Forge, Azure DevOps, Polarion gadgets — do not let you drop a client secret in the browser and hope.
Typical shape:
- The host already knows the user.
- Your backend needs a token to call the other system.
- The first run is a consent or OAuth dance that must survive a popup blocker and an iframe.
I keep a dedicated “connect” path that works in a sandbox project before any production site sees it. If auth only works on localhost with third-party cookies enabled, it does not work.
When a call returns 401, invalidate the session you understand and show a reconnect action in the panel. Silent retry loops inside an iframe look like a frozen host.
Native means vocabulary, not just color
A native-feeling panel uses the host’s words.
- “Work item” vs “issue” vs “work item type” is not pedantry. It is trust.
- Buttons should do one host-shaped thing: Save mapping. Open in the other tool. Refresh.
- Empty states should name the next click in the host, not “contact your administrator” unless that is truly the only path.
I have watched users reject a technically correct integration because the panel said “catalog” when the rest of the product said “project.” Rename is cheaper than a support queue.
A contract I write down now
Before the first pixel, I write a one-pager:
- How we receive the host context (issue id, project, locale).
- How we hear theme changes.
- How we start and refresh auth.
- What we persist, and what we refuse to persist in
localStorage. - What “success” looks like in the host (a field, a remote link, a backlink).
If that page does not exist, the UI will be a pile of special cases. The host will change. Your pile will not.
Why this is worth the pain
The alternative is “open in new tab.” That always works and always loses. People do not leave the issue they are in to maintain links in a second product.
Delegated UI is how an integration becomes a habit. Theme and auth are not polish. They are the load-bearing walls.