Skip to main content
This glossary defines every term used by Duet, Modaal’s cross-platform parity framework for native iOS and Android apps: the parts of a feature, the recording-and-checking vocabulary, the steps of a migration, and the work items a migration is planned in. Each entry is one paragraph with a stable anchor and a link to the page that treats the subject in full; where one of the nine Duet tutorials builds the thing by hand, the entry names that page too. The entry heading is the name the documentation uses. Where the tools, the parity manifest or the framework’s own pages use a different name for the same thing — fixture for behavior recording, gate for check, ledger for tracker, oracle for reference test — the entry states both names.

The parts of a feature

feature

A unit of behavior declared as state, actions, effect payloads and a pure reducer, carried as one row in the parity manifest and checked by its own behavior recordings. See how a feature gets built.

reducer

Where every decision happens: for each action the Store feeds it, the reducer alone runs the guards, writes state and returns effects. It is pure — no platform calls, no minted time or identity — so its behavior replays byte-for-byte on both platforms.

effect / effect payload

What a reducer returns beside the new state: a value naming work the platform must do, such as a wait on a clock, a port call or a delegate notification, never the work itself. The effect handler turns each payload into the actions it produces, and an effect id gives it cancel-in-flight semantics. Because an effect is data, a behavior recording pins it byte for byte. Tutorial 1 writes the first two; Tutorial 9 adds an analytics event as one more.

Store

Built once per mount: it holds the feature’s state, takes actions, runs the reducer, and hands returned effects to the effect handler. Nothing else runs feature logic. Tutorial 1 builds the first one and drives it through the kernel’s exhaustive test store.

kernel

The runtime every Store is built on: it runs each action through the reducer, tracks returned effects in flight, and owns the effect-leak check at teardown. One kernel shape serves both platforms; its contract ships with the framework.

shell

What stays on the platform side once logic has moved: three duties and no decisions — bind the view’s intents to the store, project state into the view, bracket the lifecycle. See what crossing one feature looks like; Tutorial 2 writes the first Swift and Compose shells.

host / host root

The level that mounts a feature: it builds the store, owns the slot the feature renders in, and takes its delegate events as data. In the Swift flavor a host root is the package whose resolved dependencies must contain the Duet family and nothing else. Tutorial 3 mounts a tree of features from state, one level hosting the next.

delegate events / the delegate seam

How a feature reports an outcome upward: the reducer emits a plain enum case, and the host receives it as data on the seam it handed in at build time. The event is a value, so a scenario asserts it byte-for-byte. Tutorial 3 receives each child’s delegate events as one case of the parent’s action type.

Interactor

The CombineRIBs type that owns a RIB’s business logic. In a migration every Interactor shrinks to a shell and is then deleted. See what happens to each piece of your code.

worker

Stateful, lifecycle-bound processing on the environment side, adopted at mount: its results re-enter feature state only through declared seams, and cancellation is its stop. Workers process, reducers decide; a worker has no golden recording. Tutorial 4 writes the first two and tests them with the worker harness.

package

In the Swift flavor a feature’s subtree holds two Swift packages: the gated feature half resolves the Duet family and nothing else, so the checks build it in isolation; the app-facing node half is where the app connects.

port

An interface the shared logic calls and each app implements, such as the auth, purchases, items and account ports in the tutorials’ app. Every operation starts work and returns, with the answer arriving through a callback that fires once, so a Swift class can implement it across the framework boundary; a stream on a port is a sticky value a worker observes. Tutorial 3 declares the four ports behind mock services; Tutorial 4 adds the streams and replaces the mocks with an on-device backend.

slice

A value a parent publishes downward out of its own state, delivered to a child as an action of the child’s own: the child reads it and never writes it, and nothing below the parent holds the parent’s Store. Tutorial 4 projects the entitlement to two tabs as a slice.

lateral state

A value that must cross between sibling features, neither of which holds the other’s store: one worker owned by their lowest common ancestor, with a void update on one side and a sticky observation on the other. Tutorial 5 builds one for the onboarding steps’ readiness.

@MainActor

The escalation a handler or worker takes to cross an isolation boundary. Marking a type unchecked-Sendable instead silences the compiler without proving anything, which is why the doctor pass flags it on a worker.

the fixture-authoring language

The four verbs a scenario is written in — given, when, then, then-effects — with a Swift and a Kotlin spelling. The framework’s pages call it the scenario DSL.

Recording and checking

scenario

One test in the fixture-authoring language, pinning one outcome. Mutually exclusive endings are branches over one starting state; a chain scenario pins a delegate seam that crosses features. Tutorial 1 writes the first scenario and records it.

behavior recording

What recording a scenario produces: a checked-in file replaying the scenario’s exact state and effect bytes — a build product, never hand-edited, and it wins any disagreement with spec prose. The tools, the manifest and the framework’s pages call these files fixtures.

golden recording

A behavior recording in its replay role: the golden test reads committed recordings at runtime and fails on any byte that differs — a role, not a second kind of file. A worker has no golden.

chain recording

A behavior recording that pins the seam between two features rather than one reducer: the delegate event one reducer emits, received by the next as its action, with each hop re-derived at verify time from the recorded bytes so an edited seam fails as structure drift. Tutorial 3 records the first four.

re-recording

Running the recorder again after a deliberate behavior change, then reading the recording diff like code. The run deletes before it harvests, so a leftover file means a stale run.

metadata-only

The pass condition for a re-record that must not change behavior: the diff touches scenario source locations and step labels, and zero state or effect bytes.

mutation drill

The negative control on the checks: break a reducer guard and watch the replay go red, then break a recording file and watch the test task go red — proof the checks are wired to what they claim to cover. Run in a crossing’s first half; Tutorial 6 runs it from a mutation table in both directions.

the checks

What must pass before work is done, run by one command locally and in CI: the manifest lint, the recording check, the cross-platform verification and the doctor pass. The framework’s pages call each one a gate. Tutorial 6 lists every check with what it proves and puts them in one CI workflow.

lane

One slice of a checks run, rooted at a specific package or module set, so a red names its platform and its root. Parity lanes derive from a feature’s manifest row; generation lanes produce the test doubles and DI components. Tutorial 6 reads the lanes report the tool derives.

the Swift and Kotlin lanes

A manifest row’s two implementation declarations; which of them a row carries tells the checks which platform lanes to run — one during ordinary work, both inside a coexistence window.

the manifest lint

The manifest’s own consistency checks — the fast red, run on every manifest edit. The declaration-parity and subtree-geometry checks join only while a row declares both implementations.

the headless test runner

A script every scaffolded project ships that runs the iOS test suite from the command line, wrapping Apple’s xcodebuild — for verifying outside the Modaal app and reproducing CI failures locally.

the run verb

The build-and-launch action in a Modaal session: one command that builds the selected target and launches it on a simulator, emulator or device. The action’s name differs between session types, so Duet’s documents say “the run verb”.

The migration, step by step

the readiness audit

A static census of a legacy app’s distance to the target shape, producing the readiness report, the migration tracker and a spec draft per feature. The counts are pointers to verify, not verdicts. See the stages, step 1.

the migration tracker

One row per surface: a derived status, four sizing columns, and the notes the per-feature plan is written into. The tools and the migration article call it the ledger — distinct from the divergence waivers the parity contract keeps.

updating the tracker

Re-running the readiness audit in write mode: it creates or merges the tracker and refreshes the computed fields. It is the route’s first step after the stop-digging edit.

the graft

The one operation that adds the Kotlin core plane, the Android app shell and the parity workspace beside an existing tree while editing none of its sources — the checks exist before any behavior can change. See the stages, step 2.

wiring the core

Run once the graft’s checks are green: it links the assembled Kotlin core into the iOS app’s build, so every later flip is feature work with no build wiring left to add. See the stages, step 2b.

feature crossing

One feature’s move onto the shared Kotlin core, end to end: spec, Swift twin, Kotlin twin, the Android mount, the flip. See what crossing one feature looks like.

the coexistence window

The stretch of a crossing in which a feature’s manifest row declares both implementations: both twins replay the same recordings and the parity lints run. A valid resting state for as long as the repository wants.

the flip

The one change that closes a coexistence window: delete the Swift twin, make the feature’s row Kotlin-only, verify both platforms.

twin

One of a feature’s two implementations while its coexistence window is open — the Swift twin inside the app, the Kotlin twin in the shared core — replaying the same behavior recordings.

single-source

The destination shape: one implementation of a feature’s logic, in the shared Kotlin core, consumed by both apps. The Swift flavor’s single-core is the same one-copy rule before a second platform consumes it. See the two Duet templates.

The work items and the field kit

surface

A tracker row’s unit of work: one RIB or one legacy worker in the app being converted, typed by the row’s kind.

reference test

The existing test a conversion is written against, ported one-to-one onto the deterministic test store or the worker test harness. A surface without one earns its reference test first; the audit’s sizing table and the tracker call it the oracle.

look-and-feel work

The part of a surface that does not move: tuned rendering, animation timing, gesture handling — re-authored on the other platform and sized by counting files. The audit calls it platform-manner mass; manner is the contract layer it lives in.

the note format

The shape every tracker note takes: a capitalized tag, an em dash, then clauses following the surface’s kind — a RIB’s note carries a crossing position; a worker’s names the crossing it rides with.

the stop-digging rule

From step 0 on, a new feature in the legacy app is authored in the target shape — a reducer plus a thin shell, never a new Router — and new background work is a Duet worker from day one. See the stages, step 0.

adoption seam

Where a converted feature’s SwiftUI view attaches to the legacy UIKit composition: a themed hosting controller takes the legacy child’s slot, and activating the shell replaces attaching the child.

coexistence kit

The migration package that stands for the migration’s duration — the worker shim and the dual-import discipline — and whose dependency edge is deleted at completion. See the finish line.

stage host

What the Android app’s root grows into when the app needs real navigation: one full-screen child chosen from route state, transitions as reducer edges, back handling in a documented order. The first crossing that needs a screen change grows it.

mount

Putting a feature into the app’s composition: its Store is built once at the mounting level, its screen renders from the mount row, and that level’s own workers are adopted there.

spine

The app’s navigation trunk, in two uses: the route state that survives process death, and the root and navigation surfaces that cross last, after the leaves. Tutorial 5 gathers a route spine and rebuilds the tree from it.

Common questions

They are the same file. The headings here use the documentation’s names; the tools, the parity manifest and the framework’s pages use the framework-side names — fixture, gate, ledger, oracle — and each entry states both: behavior recording, the checks, the migration tracker, reference test.
As long as the repository wants — the window is a valid resting state, and the parity lints keep both twins honest while it is open. Holding one open costs the dual-language authoring premium the migration article quantifies; the flip closes it.
Three articles carry the depth: the Duet overview for the feature shape and the two templates, handling platform-specific UI for the presentation contract, and migrating a CombineRIBs app for the route. The framework’s own reference lives in the Duet repository. The nine Duet tutorials build each feature-level term by hand, and the series index maps every concept to the step that treats it.
Pick one of the two Multiplatform cards in the Modaal new-project wizard — starting a Duet project walks through the choice.

Sources and further reading

Duet: one shared core, two native apps

The product overview: what a Duet project is, the two templates, and how a feature gets built.

Handling platform-specific UI

The five-layer presentation contract: kinds, manner, islands, and how deliberate divergence is recorded.

Migrating a CombineRIBs app

The route the migration cluster’s entries walk: audit, graft, crossings, the finish line.

Duet tutorials

Feature, reducer, shell, worker, scenario, recording and the checks, each built by hand across nine tutorials.
Last modified on September 8, 2026