You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
Repeat UIElement — one template, N rows. Materializes its children once per row of a payload-authored props.data array, replacing the duplicated subtrees that made every copy or style change an N-fold edit. A renderWhen on the template gates per row, so Repeat also covers the "show exactly one of N" case — there is deliberately no Match element. Row fields read as {{item.<field>}} and as renderWhen variables (item.index always present); as renames the scope, keyField picks the row field used for each materialized element's id suffix (card → card__aries) and React key. data is authored in the payload rather than sourced from a variable holding JSON, and a translatable row string carries its own literal i18n key — key coverage is measured by scanning payloads for literal key strings, so a computed key ("zodiac_{{item.sign}}_title") would make the scanner find nothing, report the screen fully translated, and ship untranslated rows. New exports: RepeatElementProps.
animation.replayWhen on BaseBoxProps — a variable name. Re-fires entering whenever that variable's value changes, so an element can re-animate without disappearing first; previously the only way to replay an entrance was to toggle renderWhen, which coupled "animate again" to "change visibility". The element's subtree is remounted, so transient state inside it resets and a continuous effect restarts; the initial mount is not a replay.
Image.mode: "plain" | "expression" — expression enables {{variable}} interpolation in url, so one element serves a data-driven set instead of one duplicated subtree per case. References resolve to the variable's value, not its label (the inverse of Text): a URL segment is a machine identifier, so { value: "aries", label: "Aries" } must yield .../aries.png and not a 404 on .../Aries.png. Defaults to "plain", which stays fully static.
Carousel.progressVariableName — publishes the carousel's continuous swipe position as a screen-scoped animated variable, so siblings can gate renderWhen on the finger rather than the settled slide (variableName still writes only on snap). The published value is normalized to [0, childCount): the underlying absoluteProgress is clamped only when loop: false and is unbounded under loop: true (which is the default), so the raw value would leave every gate silently dead after the first lap.
TypewriterText.reserveSpace — lays the fully-resolved string out invisibly to establish the box and overlays the animating characters, so a reveal never pushes siblings down. Only meaningful with cursor: true (without a cursor every character is already mounted from frame 0 and the box is stable). Measures the real resolved string, so it stays correct per locale, unlike the hardcoded wrapper height it replaces.
inset on BaseBoxProps — { top?, left?, right?, bottom? }, number | string, honoured on ZStack children only, replacing hand-computed transform.translateX/Y for off-anchor layers. An omitted side inherits the stack's shared anchor for that axis rather than meaning 0; when an axis carries an inset, that axis drops both the opposite side's 0 and the shared anchor, so placement is correct at every anchor rather than only at flex-start.
configuration.progressHeader — typed studio-authored progress-header styling (colours, height, borderRadius, paddings, gap, trackFlex, back-button), plus a useProgressHeaderConfig() hook. No backend change: the edge function already returns the whole configuration blob. New exports: ProgressHeaderConfiguration, useProgressHeaderConfig. The block covers the back button's container as well as its glyph — backButtonBackgroundColor, backButtonBorderColor, backButtonBorderWidth, backButtonContainerSize, backButtonBorderRadius — because glyph fields alone were not enough to retire a fork, which is what the block exists for: the one host known to have forked the bar wraps the chevron in a 32x32 white circle with a 1px border, and with only backButtonColor/backButtonSize that had no reachable expression. A lone backButtonBorderColor implies width 1 (RN defaults it to 0, so a lone colour would draw nothing); all five unset render the previous bare chevron in a padding: 4 touchable exactly.
Non-fatal payload diagnostics for misplaced keys.collectUnknownElementKeys / collectUnknownKeysInSteps / formatUnknownElementKeys report keys sitting at an element's top level that the schema silently drops — classically animation outside props, which parses, renders, and never animates. Each finding carries a kind: misplaced (valid prop absent from props → "did you mean props.X?"), shadowed (valid prop already in props, so the top-level copy is inert, with a conflicts flag when the two values differ), or unknown. Allowed key sets are derived from UIElementSchema at runtime, so they cannot drift. OnboardingProvider runs the check once per payload under __DEV__. Deliberately not.strict(): rejecting unknown keys would turn already-published payloads carrying a stray key into hard parse failures. New exports: UnknownElementKey.
Fixed
Template URLs are no longer handed to the asset preloader.extractAssetUrls pushed any url prop verbatim, so an Image with mode: "expression" sent the literal https://cdn/{{sign}}.png to the prefetcher — a guaranteed 404 on every load. Such URLs are now skipped, and a Repeat's template is instead resolved against each row so repeated media genuinely preloads.