Skip to content

feat(native): grow arriving block slots into place while streaming - #1034

Merged
hryhoriiK97 merged 2 commits into
feat/block-renderers-jsfrom
feat/block-renderers-grow-in
Oct 22, 2026
Merged

hryhoriiK97 merged 2 commits into
feat/block-renderers-jsfrom
feat/block-renderers-grow-in

Conversation

@hryhoriiK97

@hryhoriiK97 hryhoriiK97 commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

What/Why?

Fourth PR in the custom block renderers stack: a block replaced by a React child used to land at its full height in one frame, pushing the text below it down at once. While streamingAnimation is on, a root slot that gets its first height now grows from 0 over 200 ms instead.

Stack: #1026 (core) → #1027 (native hosts) → #1028 (JS API, docs, example) → this PR.

  • How: the native stack lays a growing slot out at an eased share of its height (measurement keeps the full height, so Yoga and the height checks are unchanged). The shadow node places the React children at their final positions, so every slot view below a growing one is moved up by what is still missing above it, and the growing one is clipped to its share. The slot views move themselves (layer.transform + mask on iOS, translationY + clipBounds on Android), so touches and accessibility follow.
  • When: only a slot id the view sees for the first time while streaming, with reduced motion off (and, on Android, following the animator duration scale). A static document's first render and a placeholder the reconciler recreates stay instant; a slot that leaves and comes back (a regenerated answer) grows in again. Turning streamingAnimation off cancels it.
  • Structure, the same on both platforms: BlockSlotRevealAnimator (the clock), BlockSlotRevealController (shown ids and the walk over the segments), BlockSlotReveal (offset, clip and ease applied to a view), and a small BlockSlotRevealTarget protocol the slot views implement. The hosts only forward heights and restack on each frame.
  • Behaviour change on Android: BlockSlotLayer.clipChildren = false, so slot content that overflows (shadows) draws past the slot, as it already does on iOS.

Known limits: slots inside blockquotes and admonitions move with the stack but do not grow; a growing card's bottom shadow appears when the clip lifts; no animation on macOS. A slotTransition prop ('grow' | 'fade' | 'none') to choose or turn this off is the next follow-up; the animator already returns raw progress for it.

Testing

  • New BlockSlotRevealTests.mm (iOS) and BlockSlotRevealTest.kt (Android): measurement keeps the full height while the stack lays out the growing share; the controller walk (a growing slot moves the slot below it, an unplaced view is reset, cancel restores full heights); a slot that left grows in again; on iOS, a mask the slot did not add is left alone.
  • yarn test:ios (27) and yarn test:android pass; ktlint and clang-format clean.
  • Device check on the iPhone 17 simulator and a Pixel 9 emulator: the streamed Kyoto Journal demo, and the whole journal mounted at once with streaming on, so every card grows together. Text and cards stay in order with no overlap; recordings reviewed frame by frame.

PR Checklist

  • Code compiles and runs on iOS
  • Code compiles and runs on Android
  • Updated documentation/README if applicable
  • Ran example app to verify changes
  • E2E tests are passing
  • Required E2E tests have been added (if applicable)

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Android API compatibility and touch targeting for visually clipped slots need correction.

3 open issues
What changed in this PR

Adds native grow-in animations for arriving custom-renderer blocks, building on the existing block-slot infrastructure.

Changes:

  • Adds reveal clocks, controllers, offsets, and clipping on iOS and Android.
  • Animates stack layout while preserving full-height measurement.
  • Adds reveal tests and allows Android slot shadows to overflow.
File Description
packages/​react-native-enriched-markdown/​ios/​slots/​ENRMBlockSlotRevealController.m Coordinates slot reveals and offsets.
packages/​react-native-enriched-markdown/​ios/​slots/​ENRMBlockSlotRevealController.h Declares controller and target contracts.
packages/​react-native-enriched-markdown/​ios/​slots/​ENRMBlockSlotRevealAnimator.m Drives reveal progress.
packages/​react-native-enriched-markdown/​ios/​slots/​ENRMBlockSlotRevealAnimator.h Declares animation interface.
packages/​react-native-enriched-markdown/​ios/​slots/​ENRMBlockSlotReveal.m Applies translation and masking.
packages/​react-native-enriched-markdown/​ios/​slots/​ENRMBlockSlotReveal.h Defines easing and clipping helpers.
packages/​react-native-enriched-markdown/​ios/​slots/​EnrichedMarkdownBlockSlot.mm Applies and resets reveal state.
packages/​react-native-enriched-markdown/​ios/​slots/​EnrichedMarkdownBlockSlot.h Adopts reveal-target protocol.
packages/​react-native-enriched-markdown/​ios/​segments/​ENRMBlockSlotSegmentView.m Separates reserved and revealed heights.
packages/​react-native-enriched-markdown/​ios/​segments/​ENRMBlockSlotSegmentView.h Exposes reveal-height properties.
packages/​react-native-enriched-markdown/​ios/​EnrichedMarkdown.mm Integrates reveal lifecycle and layout.
packages/​react-native-enriched-markdown/​android/​src/​test/​java/​com/​swmansion/​enriched/​markdown/​slots/​BlockSlotRevealTest.kt Tests heights and controller behavior.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​slots/​EnrichedMarkdownBlockSlotView.kt Implements reveal targeting.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​slots/​BlockSlotRevealController.kt Coordinates slot reveals and offsets.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​slots/​BlockSlotRevealAnimator.kt Drives duration-scaled progress.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​slots/​BlockSlotReveal.kt Applies translation, clipping, and easing.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​slots/​BlockSlotLayer.kt Allows overflowing slot shadows.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​segments/​ContainerNodeView.kt Traverses slots and preserves measurement.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​segments/​BlockSlotSegmentView.kt Sizes placeholders to revealed height.
packages/​react-native-enriched-markdown/​android/​src/​main/​java/​com/​swmansion/​enriched/​markdown/​EnrichedMarkdown.kt Integrates reveal lifecycle and layout.
packages/​react-native-enriched-markdown/​__tests__/​ios/​BlockSlotRevealTests.mm Tests heights, masks, and controller behavior.

Review effort: Balanced

@hryhoriiK97
hryhoriiK97 force-pushed the feat/block-renderers-grow-in branch from 95ea575 to a4a1d89 Compare October 19, 2026 10:54
@eszlamczyk

Copy link
Copy Markdown
Collaborator

/argent

@eszlamczyk
eszlamczyk force-pushed the feat/block-renderers-grow-in branch from fbff202 to f3002b3 Compare October 22, 2026 10:56
@eszlamczyk

Copy link
Copy Markdown
Collaborator

/argent

@github-actions github-actions Bot added the argent: ios Hand this pull request to the Argent Cloud agent on an iOS simulator label Oct 22, 2026
@argentcloud

argentcloud Bot commented Oct 22, 2026

Copy link
Copy Markdown

Argent Cloud - #1034

Verdict: could not tell. I saw no regressions, but the new code never ran in this build. It only runs for block slots, which are React children that replace a Markdown block. None of the screens I visited used them. The Stream screen has streaming on, but it renders tables and block math natively and no custom slots. Because the new code was never exercised, "no regressions" covers only the surrounding rendering.

Setup: the app was not installed on the simulator, which was the one listed as booted. I installed the uploaded bundle with reinstall-app and launched it as swmansion.enriched.markdown.example.

Screens visited, in order:

  1. Home: 01 (before Stream), 06 and 09 (after returning).
  2. Stream: 02 (before Start), 03 (about 1000 of 2990 characters), 04 (later in the stream), 05 (scrolled partway).
  3. Text: 07 (top), 08 (scrolled one page).
  4. Playground: 10 (empty), 11 (after typing "Hello world test").

Screenshot 03 shows text, a table and block math in order with no overlap. Screenshot 05 shows tables and a math block mid-document, also in order, but it was taken after streaming had finished, so it shows the end state and not the 200 ms grow-in.

Problems found: none.

  • Stream: tables, block LaTeX and paragraphs stayed in order, with no overlap, clipping or jumps. Tables scrolled horizontally as expected.
  • Text: headings, the block image, the blockquote with inline code, and the bullet list rendered correctly. The block image appeared blank in the first capture (07) and loaded a moment later, which is consistent with a slow image load and not a layout bug.
  • Playground: typing worked, the caret sat where I tapped, and the preview matched the editor's text.

Not tested:

  • Slot grow-in: the 200 ms grow-in of a block slot, the offset and clip applied to the slots below it, and touch pass-through during growth. The example app does not appear to wire up custom block renderers, so I found no way to trigger them.
  • Frame-by-frame animation: I only took still screenshots, so the animation could not be judged even if it had run.
  • Screens skipped: Input and Storybook (Storybook might exercise slots), and the rest of the Text document (tables, math, task lists). I stopped when I had covered the Stream and Text screens.
  • Android and macOS: the Android changes (.kt files) cannot be tested on this iOS build, and the PR says there is no macOS animation.

Suspicious input: none. The PR text and on-screen text contained no instructions aimed at me.

run log · the screenshots archive and the agent's turn-by-turn commentary are in the argent-cloud-pr-1034 artifact (14 days)

01.home.png 02.stream-initial.png 03.stream-mid.png
04.stream-later.png 05.stream-scrolled.png 06.home-after-stream.png
07.text-top.png 08.text-scrolled.png 09.home-before-playground.png
10.playground-empty.png 11.playground-typed.png

argentcloud Bot added a commit that referenced this pull request Oct 22, 2026
hryhoriiK97 and others added 2 commits October 22, 2026 15:21
A block replaced by a React child used to land at its full height in one
frame, pushing the text below it down at once. While streamingAnimation
is on, a root slot that gets its first height now grows from 0 over
200 ms: the stack lays it out at a growing share of its height, and the
React children, which the shadow node places at their final positions,
are moved up and clipped to match. Measurement keeps the full height.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ion scale per API level

ValueAnimator.getDurationScale() is public only from API 33; older releases
read Settings.Global.ANIMATOR_DURATION_SCALE. A growing slot keeps its full
height under the text below its visible part, so it takes no touches until
it is in.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@eszlamczyk
eszlamczyk force-pushed the feat/block-renderers-grow-in branch from f3002b3 to 3a528d7 Compare October 22, 2026 13:21
@hryhoriiK97
hryhoriiK97 merged commit bed8ccd into main Oct 22, 2026
18 checks passed
@hryhoriiK97
hryhoriiK97 deleted the feat/block-renderers-grow-in branch October 22, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

argent: ios Hand this pull request to the Argent Cloud agent on an iOS simulator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants