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

# Resolving and rendering

> The learner-app contract: turn bindings into values, render fragments, and grade responses.

<Info>
  Canonical format, version 1. The resolver and evaluation shapes below match
  `src/core/course/resolve-binding.ts` and the exercise types.
</Info>

A learner app does three things: resolve [bindings](/data-structure/bindings) to values,
render each [fragment](/data-structure/bindings#render-fragments) by its role, and grade the
response against the exercise's `evaluation`.

## Resolving bindings

Build a resolver from the loaded course (a map of records and assets), then resolve any
binding to a `ResolvedValue`. Resolution never throws: an unresolved reference returns
`{ kind: "missing", reason }` so a broken reference degrades instead of crashing.

```ts theme={null}
type ResolvedValue =
  | { kind: "text"; text: string; locale?: string }
  | { kind: "asset"; asset: Asset; label?: string; locale?: string }
  | { kind: "record"; record: ContentRecord }
  | { kind: "list"; items: readonly ResolvedValue[] }
  | { kind: "literal"; value: PortableValue }
  | { kind: "missing"; reason: string };

interface BindingResolver {
  resolve(binding: Binding): ResolvedValue;
}

const resolver = createBindingResolver(course);
const value = resolver.resolve(fragment.binding);
```

How each binding resolves:

| Binding | Resolves to |
| - | - |
| `literal` | `text` when the value is a string or `{ type: "text", text }`; otherwise `literal`. |
| `asset` | `asset` (or `missing` if the ID is unknown). |
| `record` | `record`. |
| `field` | The field value: `text`, `asset`, or `list`. |
| `item` | One item of a `list` field: `text` or `asset` (or `missing` if the field is not a list or the item ID is unknown). |

## Rendering fragments

A `RenderFragment` pairs a binding with a `role` (`primary`, `supporting-text`, `visual`,
`audio`, `translation`, and so on). Resolve the binding, then render by role and value kind:
`text` values as text, `asset` values by asset `kind` (image, audio player, video), and a
`record` value with its presentation. Roles are open strings; render the ones you know and
fall back sensibly for the rest.

## Grading

Every exercise carries one `evaluation`. Grade against stable IDs or accepted-value
bindings, never against displayed text.

| `evaluation.kind` | Grade by |
| - | - |
| `selected-options` | The chosen option IDs equal `correctOptionIds` (respect `select: "one" \| "many"`). |
| `ordered-tokens` | The submitted token-ID order equals `correctOrder`. Unused `distractors` must stay unused. |
| `matched-pairs` | Every submitted `{ leftId, rightId }` is in `pairs`. Extra right items are distractors. |
| `filled-blanks` | For each blank, the filled option is in `correctOptionIds`, or the typed value passes `accepted` under `normalize`. |
| `typed-answer` | The typed value matches an `accepted` value binding under `NormalizationRules`. |
| `spoken-response` | On-device speech scoring at the given `strictness`. Not gradable off-device. |

### Accepted values and normalization

Typed grading resolves `accepted` bindings (usually a field such as
`field_alternate_answers`) and compares under normalization, so "a cat" and "cats" pass
without duplicating text in the exercise.

```ts theme={null}
interface NormalizationRules {
  ignoreCase?: boolean;
  ignoreWhitespace?: boolean;
  ignorePunctuation?: boolean;
  ignoreScript?: boolean; // e.g. accept kana for kanji
}
```

<Warning>
  `speaking` grades only in the learner app: it needs a microphone and on-device speech
  recognition. The exercise authors the target phrase and `strictness`; nothing is recorded
  or uploaded by Studio. If you build a learner app, you own the recognition and scoring for
  this type.
</Warning>

## Localized authored text

Titles, instructions, and literal text fragments may be a plain string or a map of locale
tag to string. Resolve against the course's `defaultLocale`, or against the learner's
language if you offer one: try an exact tag, then the base language (`pt-BR` to `pt`), then
any available entry.

```ts theme={null}
function resolveLocalized(value: string | Record<string, string>, locale: string): string {
  if (typeof value === "string") return value;
  return value[locale] ?? value[locale.split("-")[0]] ?? Object.values(value)[0] ?? "";
}
```

Taught content is not localized this way. It lives in records, where each language is its
own field and list items carry their own `locale`.

## Media playback

An `asset` resolution gives you the `Asset` record. Load the binary from the asset's own
directory using its `file` (when `availability` is `ready`); a `placeholder` asset has no
file yet. Use `mimeType` to choose the player.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.