Repository navigation
Conversation
arnaud-zg
force-pushed
the
feat/force-js-thread
branch
from
September 18, 2026 16:58
5f8a903 to
5f014a0
Compare
Drives the desired behaviour from the public component, stubbing only the packages outside this library so StreamdownText, useStreamdownMarkdown and remendWorklet all run for real. Three of the four cases fail: there is no way to reach the JS-thread branch on native yet. The fourth pins today's default behaviour so the next commit cannot change it by accident. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Arnaud Zheng <arnaud.zheng@doctolib.com> Co-Authored-By: Bacary Bruno Bodian <brunobacary.bodian@doctolib.com>
The worklet path hands the remend import to a worklet runtime, which needs Bundle Mode importForwarding. That option first exists in react-native-worklets 0.10, so apps pinned lower, for example through react-native-reanimated 4.3.x, hit a runtime error on the first streamed token. Everything else the worklet path needs already exists in 0.8.3, so let callers opt into the JS-thread branch this file already has for web. forceJsThread joins the existing web condition rather than adding a second branch. The default path, the module-scope runtime and the peer range are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Arnaud Zheng <arnaud.zheng@doctolib.com> Co-Authored-By: Bacary Bruno Bodian <brunobacary.bodian@doctolib.com>
Both reasons to skip the worklet runtime now read as one named idea rather than an inline boolean. Behaviour is identical and the tests from the first commit still pass untouched. Also documents the prop in the README, including how to install past the peer warning on older worklets, and adds a toggle to the example app so the two paths can be compared in a single build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Arnaud Zheng <arnaud.zheng@doctolib.com> Co-Authored-By: Bacary Bruno Bodian <brunobacary.bodian@doctolib.com>
arnaud-zg
force-pushed
the
feat/force-js-thread
branch
2 times, most recently
from
September 22, 2026 14:01
75634f5 to
dafbd9b
Compare
arnaud-zg
marked this pull request as ready for review
September 25, 2026 12:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The error, and why
On worklets below
0.10, the first streamed token throws:The worklet path hands the
remendimport to a worklet runtime. That needs Bundle ModeimportForwarding, andimportForwardingfirst exists in worklets0.10.0. On 0.8.x thebundle unpacker cannot resolve
remend, so the guard fires. Nothing else is missing.What changes
remendWorklet.tsalready has a JS-thread path, added for web in #28. This makes it reachable onnative too, behind one opt-in prop. Two lines of logic. Nothing existing changes.
flowchart LR MD([streaming markdown]) --> Q{forceJsThread?} Q -->|off · default| W["worklet runtime<br/><small>needs worklets ≥ 0.10</small>"] Q -->|on · new| J["JS thread<br/><small>works on 0.8.x</small>"] W --> R([rendered]) J --> R✅ 10 tests · lint · typecheck · build · iOS + Android ·
peerDependenciesunchangedBuilt test-first
Three commits, and the test output at each one is real, not retyped.
yarn test StreamdownTextyarn typechecktest:describe the behaviourfeat:implement itrefactor:clean it upThe three runs
🔴 red — the prop does not exist yet, so three cases fail and typecheck rejects the test.
The fourth pins today's default so the next commit cannot change it by accident.
🟢 green — the two-line change below, nothing else.
🔵 refactor — the two reasons to skip the runtime become one named condition. Same four
tests, untouched, still green.
The red commit is the only one that fails, which is the point of it.
lint,typecheck,testandprepareall pass at the head of the branch, which is what CI runs.Demo
react-native-worklets@0.8.3, same build, one tap apart.forceJsThreadScreen recording
03-forcejsthread-demo.mp4
How it works
export function processRemendInWorklet( markdown: string, onComplete: (result: string) => void, - config?: RemendOptions + config?: RemendOptions, + forceJsThread = false ) { - if (remendRuntime == null) { + // No runtime on web, and worklets before 0.10 cannot resolve `remend` inside one. + const runOnJsThread = remendRuntime == null || forceJsThread; + + if (runOnJsThread) { scheduleOnRN(onComplete, remend(markdown, mergedConfig)); return; }forceJsThreadjoins the existing web condition, so both reasons to skip the runtime share onebranch. The flag is threaded through
useStreamdownMarkdownand exposed as a prop onStreamdownText, where it is destructured out so it never reachesEnrichedMarkdownText.The module-scope
createWorkletRuntime()is deliberately left alone. Making it lazy was tempting,but it works fine on 0.8.3 and deferring it would have inverted the
creates a worklet runtime at module load on nativeassertion added in #31.Which worklets versions have what
Checked against the
worklets-*tags insoftware-mansion/react-native-reanimated:createWorkletRuntime,scheduleOnRuntime,scheduleOnRNbundleModeimportForwardingpeerDependenciesstays>=0.10.0, since that is still what the default path needs. Apps below itinstall past the peer warning and opt in themselves, which the README now documents.
How it was tested
forceJsThread4 new tests in one file, driven through the public component so
StreamdownText, the hook andremendWorkletall run for real; only packages outside this library are stubbed. No existingtest file was modified at all.
Reproduce: in
example/, pinreact-native-worklets@0.8.3andreact-native-reanimated@4.3.2,rebuild, then tap the remend runs on: toggle.
Why a prop, not an automatic fallback
remend re-parses the whole document on every update. On Hermes a 16 KB document costs around
340 ms per pass, so falling back silently would trade a loud error for a stall nobody opted into.
We would rather choose that trade-off explicitly, and document the cost.
If you would prefer something automatic, two options I have not implemented: a one-shot probe
worklet that checks
typeof remend === 'function'and reports back throughscheduleOnRN, or adev-only warning when the installed worklets version is below 0.10. Happy to add either.
AI assistance (Claude) was used for research and drafting. I reviewed and verified everything
myself, including the device testing.