> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Display Resolution

> How the Web SDK resolves the host display values (appDisplayName, trialPeriod, logoUrl) and header copy it renders on the deferred screens: the per-field chain (explicit header.* → local display.* → remote /config → default or required), which fields are required and when, and the CONFIGURATION_ERROR vs NETWORK_ERROR failure classes.

The deferred flow renders SDK-composed copy about **your** product: the offer sheet
headline, the primer screen, and the redemption screen all say things like
*"Get FitTracker free for 1 month"*. This page is the single authority for **where those
values come from**, which are required, and what happens when they don't resolve.

Three host display values (set locally via the `display` placement option, or remotely in
the Encore portal) feed every composed sentence:

| Value        | Local source (`display.*`)               | Remote source (`/config`)                               |
| ------------ | ---------------------------------------- | ------------------------------------------------------- |
| App name     | `display.appDisplayName`                 | The app name set in the portal                          |
| Trial length | `display.trialPeriod` (e.g. `"1 month"`) | Composed from the app's native entitlement value + unit |
| Logo         | `display.logoUrl`                        | The app icon set in the portal                          |

## The resolution chain

Each value resolves independently, first match wins:

1. **Explicit `header.*`**, for the header slots only: a `header.title` / `header.subtitle`
   you pass always wins over any composed default.
2. **Local `display.*`**: the [`display` placement option](/publishers/web/sdk-reference/placement#field-reference),
   a per-placement override of the remote values.
3. **Remote `/config`**: the app profile the SDK fetches at `configure()` time; the app
   name, the native entitlement value + unit (composed into a trial period), and the app
   icon URL.
4. **The row's terminal**: a composed default where one exists; otherwise the field is
   **required** and resolution fails (see [below](#when-resolution-fails)).

Empty and whitespace-only strings count as absent at every link.

### Trial-period composition

When `display.trialPeriod` is not passed, the trial period composes from the remote
entitlement value + unit, with singular units: value `1` + unit `months` renders
**"1 month"**; value `3` renders **"3 months"**. The composed string is display-only and
never parsed back into a number.

## What the resolved values render

Every SDK-composed sentence is built from the resolved values; screens never invent
copy. With `appDisplayName: "FitTracker"` and `trialPeriod: "1 month"`:

| Surface                              | Slot              | Composed default                                                                                                    |
| ------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| Offer sheet (`'list'` · deferred)    | `header.title`    | "Get FitTracker free for 1 month"                                                                                   |
| Offer sheet (`'list'` · deferred)    | `header.subtitle` | "Claim a partner offer to start"                                                                                    |
| Primer screen                        | Headline          | "Amazing! Your 1 month free trial of FitTracker is ready"                                                           |
| Primer screen                        | CTA               | "Activate FitTracker trial"                                                                                         |
| Redemption screen                    | Host row detail   | "Free until August 8, 2026", derived from the remote numeric trial length and the redemption moment; never settable |
| Payment-status header (`'thankYou'`) | `header.title`    | "Payment Successful"                                                                                                |

An explicit `header.title` / `header.subtitle` replaces the composed default for that
slot. On `'list'` placements in **immediate** mode, no copy is composed; your `header.*`
values (or the generic remote copy) apply unchanged.

## Which fields are required, and when

`appDisplayName`, `trialPeriod`, and `logoUrl` are **required only for `'list'`
placements in deferred mode**, because requiredness follows the UI that consumes the
value. The composed headline and the primer screen are `'list'` UI, so only that context
needs the values; a `'thankYou'` + deferred `show()` renders none of them, and immediate
mode composes nothing.

"Required" means *required to resolve*, not *required to pass*: most integrations set the
app name, entitlement, and icon once in the Encore portal and never touch the `display`
option, so the remote chain satisfies the contract.

## When resolution fails

The check runs as a **pre-flight** inside a deferred [`show()`](/publishers/web/sdk-reference/show),
before any analytics, exposure, or UI, so a broken integration has zero side effects.
`show()` waits (bounded, \~2.5 s) for the shared remote-config fetch when local values
alone don't satisfy the contract, then resolves one of two failure classes as a
`dismissed` result (never a throw):

| Error code            | Meaning                                                                                                                                                                                                                                        | What to do                                                                  |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `CONFIGURATION_ERROR` | The remote config **loaded**, but a required field is genuinely absent everywhere. The message names **each** missing field with its two fixes, e.g. `appDisplayName (pass display.appDisplayName, or set the app name in the Encore portal)`. | Fix the integration: set the value in the portal or pass it in `display.*`. |
| `NETWORK_ERROR`       | The remote config was **unreachable**, so requiredness can't be judged. Transient.                                                                                                                                                             | The next `show()` call retries the fetch.                                   |

Both surface as `{ status: 'dismissed', reason: { type: 'error', error } }`; see
[Error Handling](/publishers/web/sdk-reference/errors).

## Resolution is per call, from live sources

Resolution happens fresh at every `show()`, and **again at `redeem()`** from the
then-current sources. Nothing display-related is snapshotted onto the pending
transaction: if the portal values change between claim and redemption, the redemption
screen renders the new values. At `redeem()` the local link of the chain is the
[`host` option](/publishers/web/sdk-reference/redeem#redeemoptions) (name, logo);
unresolvable values there degrade the screen rather than erroring.

## Related

* [Redemption Modes](/publishers/web/concepts/redemption-modes): the deferred flow these values power, including the primer screen.
* [`placement()`](/publishers/web/sdk-reference/placement): the `display` and `header` options.
* [`redeem()`](/publishers/web/sdk-reference/redeem): the `host` override and the redemption-screen ledger.
* [Error Handling](/publishers/web/sdk-reference/errors): the error-as-value contract.
