spike: evaluate react-native-live-markdown for message input#5533
Draft
janicduplessis wants to merge 52 commits intojanic/expo-54from
Draft
spike: evaluate react-native-live-markdown for message input#5533janicduplessis wants to merge 52 commits intojanic/expo-54from
janicduplessis wants to merge 52 commits intojanic/expo-54from
Conversation
Major version upgrades: - Expo 52 → 54 - React Native 0.73 → 0.81 - React 18 → 19 - React Navigation 6 → 7 - PostHog and other dependencies updated for compatibility
- Delete AppDelegate.h and AppDelegate.mm - Add AppDelegate.swift with equivalent functionality - Remove main.m (Swift uses @main attribute) - Update Xcode project configuration
- Convert MainActivity.java to MainActivity.kt - Convert MainApplication.java to MainApplication.kt - Preserve all functionality including window insets handling
Mobile: - Update app.config.ts for Expo 54 plugins - Update babel.config.js for new architecture - Update metro.config.js with new resolver config - Update Android gradle and build configuration - Update iOS Info.plist and bridging headers Web: - Update Vite config for React Native Worklets
Expo 54 supports TypeScript entry files
- Move cosmos.imports.ts from root to apps/tlon-mobile/ - Add cosmos.config.json to tlon-mobile app - Remove root cosmos.imports.ts
- Refactor navigation logging to work with React Navigation v7 - Remove unnecessary React imports (React 19 auto-imports) - Update TypeScript config for React 19 - Fix navigation context usage
- Refactor PostHog initialization to synchronous pattern - Update telemetry provider for new PostHog API - Fix type compatibility with new PostHog version
- Update Cosmos exports for ES module compatibility - Fix TypeScript errors in signup context and API calls - Update contacts API for new Expo Contacts module - Fix attestation domain types
- Remove expo@52.0.47.patch (no longer needed in Expo 54) - Add .expo directory to .gitignore
- Update pnpm-lock.yaml with new dependency versions - Update iOS Podfile.lock - Update Android gradle.lockfile
- Rename patch from @tamagui__sheet@1.126.12 to @tamagui__sheet@1.126.18 - Update pnpm.patchedDependencies reference - Update lockfiles with new dependency resolutions - Minor dependency version bumps from pnpm install
- Replace 'react-native bundle' with 'expo export:embed' - Remove outdated entry-file path and dev flag - Expo CLI automatically handles entry point detection
- Replace all imports with expo-clipboard
- Update Clipboard.setString() to Clipboard.setStringAsync()
- Update Clipboard.getString() to Clipboard.getStringAsync()
- Update Clipboard.hasImage() to Clipboard.hasImageAsync()
- Update Clipboard.getImagePNG/JPG() to Clipboard.getImageAsync({ format })
- Update test mocks to use expo-clipboard mock
- Make callbacks async where needed for await usage
- Revert @tamagui/sheet patch from 1.126.18 to 1.126.12 - Lock all Tamagui dependencies to exact 1.126.12 (no range) - Update all package.json files to use strict version (removed ~) - Regenerate lockfiles with clean install - All Tamagui packages now on exact 1.126.12
- Change Tamagui versions from exact 1.126.12 to ~1.126.12 range - @tamagui/react-native-media-driver - @tamagui/babel-plugin - @tamagui/vite-plugin - Add @react-native-picker/picker@^2.11.4 as explicit dependency - Was peer dependency of react-native-phone-input - Now explicitly managed for Expo 54 compatibility - Update Android build scripts: productionDebugOptimized → productionDebug - Regenerate lockfiles
- TelemetryProvider: Remove null check, use disabled option for tests - AppInfoScreen: Format upload logs button - tsconfig: Remove expo/tsconfig.base extend, add JSDoc comment
Changes applied: - Android: Set DEFAULT_INTERVAL_MINUTES to 20 minutes (was 24 hours) - iOS: Set intervalSeconds to 15 minutes (was 12 hours) - iOS: Change from BGProcessingTaskRequest to BGAppRefreshTaskRequest - iOS: Change background mode check from 'processing' to 'fetch' - iOS: Set earliestBeginDate to nil for immediate scheduling - iOS: Add debug print statements for task lifecycle - Remove network/power requirements from iOS task requests Removed obsolete patches: - expo-background-task@0.1.4.patch - @react-navigation__drawer@6.7.2.patch - expo-localization@16.0.1.patch - react-native-reanimated@3.8.1.patch - react-native@0.73.4.patch These were automatically removed as those versions are no longer installed.
- Upgrade @tamagui/* packages from ~1.126.12 to ~2.0.0-rc.0 - Remove @tamagui/sheet patch (no longer needed in v2) Tamagui v2 API changes: - Replace Stack with View/YStack (Stack removed in v2) - Replace animation prop with transition on animated components - Replace tag prop with render (renamed in v2) - Replace editable prop with readOnly on TextArea/Input - Replace onHoverIn/onHoverOut with onMouseEnter/onMouseLeave - Remove textWrap/wordWrap non-existent props - Remove fontWeight from Button (stack-based, not text) - Fix TransitionProp by adding medium/slow animation keys to config React 19 type compatibility: - JSX.Element → ReactNode/ReactElement for children props - RefObject<T> → RefObject<T | null> for ref nullability - Fix generic types for forwardRef components React Native API updates: - BackHandler.addEventListener returns NativeEventSubscription - headerBackTitleVisible → headerBackButtonDisplayMode: 'minimal' - expo-contacts types: Contact → ExistingContact Component fixes: - Update ButtonContext/FloatingActionButton to use useStyledContext() - Fix Pressable navigation props (href/action typing) - Fix OverflowTriggerButton forwardRef generic type - Cast web-only outlineStyle in BareChatInput - Provide defaultTheme fallback in BaseProviderStack - Remove duplicate clipboard image format attempt - Update react-native-country-codes-picker patch for JSX.Element - Add fontFamily to ListItemTitle for consistency - Add position="relative" for absolute positioning contexts
Allow overriding the default hover background color by accepting a hoverStyle prop, defaulting to the original behavior if not provided.
- Fix useRef initialization to explicitly pass undefined - Update BackHandler event listener cleanup to use new API - Fix ts-expect-error placement in PhoneNumberInput - Add type cast for RawBottomSheetTextInput ref
Replace NodeJS.Timeout type with ReturnType<typeof setTimeout> to resolve compilation error when DOM types are present in tsconfig. This makes the type declaration work correctly in both DOM and Node.js environments.
- Add preview.proxy to vite config so vite preview proxies API requests to the Urbit ship (the urbit plugin only sets server.proxy for dev) - Add resolveId hook to reactNativeWebPlugin to prefer .web.ts index files for directory imports in node_modules (fixes Rollup resolving expo-modules-core polyfill/index.ts noop instead of index.web.ts) - Add explicit expo-polyfill.ts imported first in main.tsx to ensure globalThis.expo is set up before any expo modules load
- Add envPrefix ['VITE_', 'TAMAGUI_'] to vite config to prevent Tamagui plugin from overriding Vite's default VITE_ prefix - Fix ActionSheet dialog: add disableRemoveScroll to prevent z-index stacking issues, change ScrollView flex to flexShrink to fix 0-height content rendering - Patch @tamagui/dialog to remove render: 'dialog' on DialogPortalFrame which causes stacking context issues with native <dialog> element - Fix GroupTypeCard text overlap by disabling text trimming margins
The previous fix only addressed ActionSheet, but ConfirmDialog (used for delete group confirmation) had the same issue. Add pointerEvents="none" to Dialog.Overlay in both ActionSheet and ConfirmDialog so overlays never intercept clicks, regardless of whether the pnpm patch is applied. Also make e2e test cleanup more robust by pressing Escape to dismiss any lingering dialogs before attempting to interact with background elements.
Fixes any-ascii ESM crash during Tamagui static extraction on EAS. Node 22.12.0 had buggy require(esm) interop that caused uncatchable errors with esbuild-register.
React Nav v7 changed navigate() to push new screens instead of popping back to existing ones in a stack. This caused duplicate ChannelRoot screens (and 2 MessageInput textareas) when navigating back to a channel from GroupSettings via the sidebar. - Add pop: true to getDesktopChannelRoute nested params so navigate() pops back to existing ChannelRoot instead of pushing a new one - Add pop: true to useNavigateBackFromPost desktop path - Fix navigateToGroupSettings to navigate directly to Channel > GroupSettings in a single call instead of the broken 2-step approach (navigateToGroup + setTimeout with stale navigation ref)
…ade issues
React Navigation v7 changed how nested navigator state is handled:
in v6, navigating with `params: { screen, params }` would reset the
nested navigator state. In v7, it dispatches `CommonActions.navigate()`
which pushes onto the existing stack, causing stale screens to
accumulate.
The fix uses `params: { state: { routes: [...], index: 0 } }` which
triggers `CommonActions.reset()` to fully replace the nested state,
matching v6 behavior. This is applied to all GroupSettings stack
navigations.
Also adds `pop: true` to navigate calls that should pop back to
existing screens (restoring v6 popTo behavior), and fixes several
other issues from the Expo 54 / RN v7 upgrade:
- Port @tamagui/sheet patch to v2.0.0-rc.0
- Move ForwardPostSheetProvider inside NavigationContainer
- Fix ActionSheet Popover z-index behind modals
- Add navigation state debug logging
- Refactor e2e tests to use navigateBack helper
NativeStack on web renders all stacked screens in the DOM, creating many HeaderBackButton elements. The old helper only checked indices [2, 1, 0] which missed the correct button when 5+ screens were stacked. Now dynamically counts all back buttons and clicks the last visible one (the topmost screen). Also fix roles-management test back-navigation loops to stop once they reach the group channels view instead of blindly navigating back a fixed number of times.
- Sheet `animation` prop renamed to `transition` - Remove `estimatedItemSize` (FlashList 2.0 auto-calculates) - Remove unused `editorIsFocused` prop from DetailView - Fix expo-sensors version for Expo 54 - Update sheet patch for new version
52c19de to
156d323
Compare
Change packages/api and packages/shared db files to import from specific module paths instead of barrel re-exports to avoid circular dependency issues with Metro/Vite.
…bility report Add a second native input candidate using Software Mansion's react-native-enriched, which provides full formatting parity (bold, italic, strike, headings 1-6, lists, code blocks, blockquotes, links, inline images) via imperative API. - EnrichedNoteInput wraps EnrichedTextInput with a TlonEditorBridge adapter - FormattingToolbar is a standalone toolbar (no tentap dependency) - BigInput supports three editor modes behind feature flags - Import path cleanups in packages/api and packages/shared - Updated feasibility report with comparison and recommendation
…er notes - Both libraries can coexist: react-native-enriched for rich text, markdown mode as an alternative - Note react-native-enriched-markdown as future markdown input candidate - Document current custom PostContent renderer and potential for enriched-markdown as read-only renderer - Prioritize rich text mode (react-native-enriched) as first to ship
…itor The current WebView/TipTap editor can't reliably receive paste events from the native side. react-native-enriched solves this with a native onPasteImages callback. Added WebView column to comparison table.
ba44e31 to
3bf36e4
Compare
9c81e3b to
a988736
Compare
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.
Summary
Spike evaluating two native input candidates to replace the WebView/TipTap editor on mobile. Both can coexist — they serve different purposes:
react-native-enriched(Software Mansion) → rich text mode. Native rich-text input with full formatting parity (bold, italic, strike, headings 1-6, ordered/unordered/checkbox lists, code blocks, blockquotes, links, inline images) via imperative API. Priority to ship first. Key advantage: native paste support works reliably (the current WebView/TipTap editor can't receive paste events from the native side).@expensify/react-native-live-markdown→ markdown mode. Live syntax highlighting for raw markdown input. Limited formatting surface. In the future,react-native-enriched-markdown(also Software Mansion) is a better candidate here — same native engine, consistent behavior across both modes. Could also potentially be used as a read-only markdown renderer (the app currently uses a customBlockRenderer/InlineRenderersystem inPostContent/).Both are wired into
BigInputbehind feature flags (enrichedInput,liveMarkdownInput) alongside the existing TipTap WebView editor.What's included
EnrichedNoteInput— wrapper aroundEnrichedTextInputexposing aTlonEditorBridge-compatible adapter so existing toolbar actions workFormattingToolbar— standalone toolbar component (no tentap dependency) with expliciteditor+editorStatepropsLiveMarkdownInput— thin wrapper aroundMarkdownTextInputwithparseExpensiMarkBigInputintegration with three switchable editor modes and debug labelpackages/apiandpackages/shared(barrel → direct imports)docs/spikes/live-markdown-feasibility.mdNext steps
react-native-enrichedsend pathreact-native-enriched-markdownfor markdown mode and potentially read-only renderingLimitations (spike-level)
Tests
Not run (spike only). See feasibility report for manual validation steps.
Demo
Rich text:
Screen.Recording.2026-03-07.at.2.08.03.PM.mov
Markdown:
Screen.Recording.2026-03-07.at.2.19.33.PM.mov