Package-level declarations
Types
The subset of DesignTokens that should breathe with the surface. Everything else — shapes, elevation, motion and the a11y-constant Size.minTouch — deliberately stays static in DesignTokens: a 48dp touch target is 48dp on every device, and corner radius is brand identity, not a function of viewport width.
Design tokens — the single source of spacing / shape / size values. Consume these instead of hard-coded dp so the whole fleet shares one visual rhythm (4dp scale).
Viewing posture and input model. Not derivable from width — it comes from the platform, via rememberFormFactor.
Default seam: holds the choice for the process lifetime only.
Which navigation affordance a surface should show.
How a single OTP cell is drawn.
Resolved visual state of one cell, in precedence order.
App-wide theme state holder (Mileway ThemeController idiom). Dark-first: defaults to true when the user hasn't chosen. Reads the persisted choice from store at construction and writes every change back, so the Settings toggle survives process death once a persistent ThemeStore is bound. Wasm-safe. Bound as a Koin singleton so the shell and the Settings screen share one instance.
Persistence seam for the theme choice. The default is in-memory; core:data binds a DataStore-backed implementation in Koin (same interface → no shell/settings changes), which is how the dark-mode choice survives process death.
How much room there is, within a given FormFactor. The thresholds differ per form factor (see windowTypeFor) — Expanded means "a big TV" on FormFactor.Tv and "a tablet in landscape" on FormFactor.Handheld.
Properties
ponytail: defaults to Compact handheld rather than error("no AdaptiveTheme"). A hard error is louder, but it also breaks every @Preview and Compose UI test that renders a leaf composable without the app shell around it. Compact is the safe wrong answer — phone metrics on a television look cramped, not broken. Switch to error() if a real screen ever ships un-wrapped by accident.
Screen-level enter transition: a fade with a barely-there scale-up. Deliberately plain EnterTransition values rather than the AnimatedContentTransitionScope<NavBackStackEntry>.() -> lambdas the idiom usually ships as — that signature drags in androidx.navigation, which this module does not depend on and which has no Compose Multiplatform equivalent on every target.
Screen-level exit transition. Mirror of screenEnter.
Functions
Provides AdaptiveTokens for the current surface. Wrap the app shell once, then read LocalAdaptiveTokens.current.screenPadding anywhere below.
Escape hatch for consumers that already resolve a window size class upstream (Android currentWindowAdaptiveInfo(), a desktop window listener) and don't want a second BoxWithConstraints subcomposition at the root.
byFormFactor against the ambient LocalFormFactor.
Picks a per-surface value, overriding only the surfaces that actually differ.
byWindow against the ambient LocalWindowType.
Picks a per-bucket value. This is the whole object FooDefaults { fun width(w: WindowType) } idiom compressed to one call — the source this came from wrote that when block out longhand thirty-one times across ten files (MovieCardDefaults, BannerDefaults, ButtonDefaults, CastCardDefaults, …), which is why none of those files were worth copying individually and this function is.
The single deferred-feature affordance. Every web feature not in v1 opens this instead of a real screen (see the "95% later" policy). One component so the message/styling stays consistent.
Caps a layout column at max and centres it. Wider than readableWidth on purpose: a grid of cards or a row of stat tiles stays legible at widths where a paragraph would not.
The succeeded-but-nothing-here sibling of LoadingState. Distinct from ErrorState on purpose: "no search results" is a normal outcome and must not be painted in the error colour.
The failed sibling of LoadingState. Retry is optional — a permanently failed screen (404, revoked access) should not offer a button that will fail again.
Grows this element while it holds focus, by AdaptiveTokens.focusScale — the "which card am I on" affordance that a D-pad surface lives or dies by, since a television is read from three metres and a subtle border change is invisible at that distance.
Lightweight, hand-rolled Markdown renderer — headers (#/##/###), bold (x), inline code (x), links (u rendered as underlined brand text), bullet/numbered lists, blockquotes, --- rules, and GitHub-style pipe tables. Deliberately minimal (no new dependency) — enough to render the A–G evaluation reports faithfully. Not a full CommonMark parser; unknown syntax falls back to plain text.
Picks the navigation affordance for a surface.
Which cell the caret is on, and how each cell should be painted.
A one-time-code field: length cells backed by a single real text field.
Page header with eyebrow, title and optional subtitle + trailing actions.
Pill-style page indicator for a carousel, banner or onboarding pager: the active page's dot stretches into a pill, the rest stay round.
Caps a prose column at a comfortable measure and centres it.
Animates onto an already-masked value (e.g. "9f••••3a") by scrambling the visible characters through a few random glyphs before settling — makes the redaction happen visibly instead of the masked string just appearing, which is the whole point of showing it at all.
Android is the one target where the surface genuinely varies at runtime — the same codebase runs on a phone, a Wear OS watch and an Android TV. uiMode's type bits are the platform's own answer, and reading them through LocalConfiguration means a fold, a display switch or a move to an external monitor recomposes with the right tokens.
The platform's own answer for which surface this is. Android is the only target that genuinely varies at runtime (the same APK runs on a phone, a watch and a television), so it reads the system UI mode; the other targets are known at compile time.
iOS and iPadOS are both arm's-length touch surfaces, so the width axis alone separates iPhone from iPad — no runtime probe needed.
The browser is treated as Desktop. That is the posture, not the screen size — a phone browser is still a ~360dp viewport and resolves to WindowType.Compact, which is what actually drives the layout. Desktop and Handheld share a token ladder precisely so this call doesn't have to be right about the device, only about the viewing distance.
Digits only, capped at length. Applied to every edit, so pasting "OTP: 123 456" from a notification yields "123456" rather than being rejected — the single most common way a user actually enters a code.
Insets the content by the surface's unsafe regions and then by its adaptive screenPadding, in one call. Use this instead of padding(tokens.screenPadding).
Bordered surface card — the fundamental container: optional leading icon in a tinted rounded container, title + subtitle, and a trailing action slot, with the body rendered below in a Column.
Section label — the "// SECTION" mono caption idiom above grouped content.
A vertical, connected timeline. Each step renders a coloured node joined by a rail to the next; ACTIVE pulses in the primary colour, DONE shows a check, ERROR shows a cross, PENDING is muted. Any step's payload key/value rows render inline beneath its title.
Resolves the token set for a surface. Total coverage: every FormFactor x WindowType pair.
Resolves the width bucket, using breakpoints appropriate to formFactor.