Repository navigation
refactor(mobile): unify the sheet, dialog, list, image and video surfaces - #7110
Conversation
… map Add the no-restricted-imports rule set that bans every element the UI unification replaces, with a ratchet override listing the files not yet migrated. Add 'Library First' and 'Unified Elements' to apps/mobile/AGENTS.md. Bump @shopify/flash-list 2.3.2 -> 2.3.3 (EngagedIndicesTracker scroll-window fix) and exempt it from minimumReleaseAge. Delete the stale 'FlashList v2 does not support inverted' comment.
…aces Migrate every surface the plan named, and land the guardrails with it. - Sheets: add `@/components/ui/sheet` (`Sheet` over `@expo/ui`'s native BottomSheet). Migrate the account picker, the reaction picker, the session filter sheet and the markdown table reader. They are no longer RN `Modal`, so they stack above a `formSheet` route. - Dialogs: add `@/components/ui/dialog` (`ConfirmDialog` over `@rn-primitives/alert-dialog`, plus `useConfirmDialog`). Migrate all 40 destructive `Alert.alert` sites. The default presentation is the RN `Modal` confirm, which is visible above a native sheet; `presentation: 'screen'` selects the portal dialog for a path nothing can be presented over. - Lists: migrate the last 9 `FlatList` sites to FlashList. - Images: replace the hand-rolled gesture stack with `react-native-zoom-toolkit`; `image-viewer-modal` becomes `ui/image-viewer`. - Video: add `expo-video` and render a video file part inline. - Lint: enable `no-destructive-alert`, which forbids a destructive-style `Alert.alert` button.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Add `react-native-keyboard-controller` 1.22.6 and wrap the app in its `KeyboardProvider`. The session list, session history, quick chat and session detail screens each rendered a platform fork: an app-aware JS padding container on Android (React Native's `KeyboardAvoidingView` is inert there under edge-to-edge) and RN's `KeyboardAvoidingView` on iOS. All four now render one native `KeyboardAvoidingView`, so the IME is measured once in native code. - Delete `session-keyboard-container-state.ts` and its test: the fork is gone. - Delete the `.oxlintrc.json` ratchet. No file imports a banned element now. - Rewrite the two suites that asserted the removed JS padding. The three surfaces that correct their own bottom-inset double-count keep `AppAwareKeyboardPaddingView`, and the chat-scroll schedulers stay. Each correction came from a device finding and moves only with a device pass.
Every keyboard lift now comes from `react-native-keyboard-controller`'s
`KeyboardProvider`, so the app keeps one keyboard engine:
- The new-session footer, the kilo-chat conversation screen and the PR-review
comment bar replace `AppAwareKeyboardPaddingView` with the native
`KeyboardAvoidingView`. Each passes
`keyboardVerticalOffset={keyboardInsetOffset(bottom)}`: the provider's Android
height spans the translucent navigation bar, so a surface whose content pads
the inset itself must reduce the lift by it.
- The session-list bands, the manual-review footer and the toast offset read
`useKeyboardState` instead of React Native's keyboard events.
- The login screen reads the provider; `resolveKeyboardBottomPadding` loses its
platform argument, because the provider's metric reaches the screen bottom on
both platforms.
- The kilo-chat message list's scroll container is `KeyboardChatScrollView`, so
the IME scroll is native. `message-list-keyboard-scroll.ts` keeps only the
newest-message follow and is renamed `message-list-newest-scroll.ts`.
Delete `app-aware-keyboard-padding.tsx`, `app-aware-keyboard-padding-state.ts`
and their three test files.
`use-composer-reveal-scroll` stays: it reveals a NON-focused element (the
new-session composer card's pill row), which the native chat scroll view has no
equivalent for.
The global `react-native-keyboard-controller` stub returned the whole state object whatever the argument, so a reader asking for `state.height` got an object and every numeric lift derived from it was NaN. Match the real hook's signature: apply the selector when one is passed.
…-import gap `use-composer-reveal-scroll` scrolled the new-session composer card's bottom row above the IME. Delete it, its test, and the two form tests that pinned it. The card's own `onLayout` still feeds the prompt's input floor, so the frame height and card top survive; only the reveal's scroll wiring goes. Ban a direct `@expo/ui/community/bottom-sheet` import. `ui/sheet.tsx` is the app's one bottom sheet, and its allowlist entry was dead because nothing banned the module it imports — the entry now carries the rule that makes it the only home, and a probe confirms the ban fires. Bounded verification: the 46 suites that render a keyboard reader or carry a keyboard-controller mock, 661 tests.
Both were RN `Modal` surfaces a native sheet can already take: `Sheet` renders `@expo/ui`'s native bottom sheet, which presents its own window, so it stacks above a `formSheet` route and above another native sheet just as `Modal` did. - `Sheet` takes an optional `background`, so a surface that owns its whole window paints its own colour instead of the card surface. - `SessionPageSheet` is a `Sheet` at the `100%` detent with no drag indicator. Its `topInset="ios-page-sheet"` contract is unchanged: iOS presents the sheet below the status bar, Android expands it over the status bar and the surface pads the top inset itself. - `ImageViewer` is a `Sheet` at the `100%` detent with no drag indicator. It drops `supportedOrientations`: a native sheet rotates with the activity instead of being locked portrait like an RN full-screen `Modal`, which is what e11 viewer-landscape was about. `session-page-sheet.tsx` and `ui/image-viewer.tsx` leave the RN-`Modal` allowlist. Rewrite the ten suites that asserted the old `Modal` host against the shell's `BottomSheet` host.
Delete the last three RN `Modal` surfaces. No file imports `Modal` from `react-native` any more, so the ban carries no exception. - `DestructiveConfirmDialog` is a native sheet: content-sized, no drag indicator. A confirm is reached from inside `formSheet` routes and from content already presented in `SessionPageSheet`, and a portal dialog renders behind a presented sheet, so the confirm must present natively. - `ui/dialog.tsx` loses the portal `ConfirmDialog` and the `presentation: 'screen' | 'sheet'` split: one confirm element, no opt-in. - `DialogCard` (new, on `@rn-primitives/dialog`) hosts a form in a portal card with its own backdrop pressable — the primitive owns Android Back and the accessibility escape but not the backdrop. `RenameModal` and `FeedbackPromptDialog` move onto it, so both keep their shape and their backdrop-dismiss. - `profile-screen` sends its sign-out confirm through `useConfirmDialog`; `use-sign-out-confirmation.ts` and the `@rn-primitives/alert-dialog` dependency are deleted. The dialog allowlist is now `ui/sheet.tsx`, `ui/dialog.tsx` and the test globs.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryThe incremental delta since Files Reviewed (14 files)
Scope NoteThe two-dot diff Previous Review Summaries (18 snapshots, latest commit 8834d3b)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 8834d3b)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental changes resolve all three prior findings - the mixed setext-underline matcher, Files Reviewed (3 files)
Previous review (commit 886f09d)Status: 3 Issues Found | Recommendation: Address before merge Executive SummaryThe previous setext-underline finding is fixed, but the new Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (10 files)
Fix these issues in Kilo Cloud Previous review (commit ebe0973)Status: 1 Issues Found | Recommendation: Address before merge Executive SummaryThe code-region scanner fix resolves both prior findings, but its new paragraph-end detection still misses Overview
Issue Details (click to expand)WARNING
Files Reviewed (2 files)
Fix these issues in Kilo Cloud Previous review (commit 46d9c9b)Status: 2 Issues Found | Recommendation: Address before merge Executive SummaryThe new HTML-to-markdown converter is well-tested, but its code-region scanner diverges from CommonMark in two places, so markup inside what markdown renders as code can be converted and the displayed code changes. Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (19 files)
Fix these issues in Kilo Cloud Previous review (commit 4e4f73e)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe Mermaid script is no longer inlined and the per-diagram memory duplication is fixed, but the host page is cached by Mermaid version alone, so a future edit to the app-owned page can be served stale. Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Previous review (commit 60fefc2)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe Mermaid port is correct and removes the runtime CDN dependency, but inlining the full ~2.7 MB library into every diagram's WebView HTML multiplies memory across multiple fences. Overview
Issue Details (click to expand)WARNING
Fix these issues in Kilo Cloud Files Reviewed (6 files)
Previous review (commit 7b18ce0)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental changes upgrade mobile dependencies (AppsFlyer 7, Reanimated 4.7.1 / Worklets 0.13.0, TypeScript 7, Sentry 8.29.0, intl-durationformat 0.11.1, expo-speech-recognition 57.1.0) and port Files Reviewed (11 files)
Previous review (commit b8f417f)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental changes add a periodic Files Reviewed (17 files)
Previous review (commit ab78107)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental change caps the image gate's whole-value lexer probes at eight per pass and falls back to rewriting every copy, which removes the quadratic re-lex on untrusted markdown without weakening the rewrite guarantee; the added regression test covers the budget path. Files Reviewed (2 files)
Previous review (commit 23385a0)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe prior gate-bypass and Mermaid injection findings are fixed; the one remaining concern is a quadratic re-lex in the new image/literal disambiguation on untrusted markdown. Overview
Issue Details (click to expand)WARNING
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Previous review (commit 89da13d)Status: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
SUGGESTION
Files Reviewed (29 files)
Fix these issues in Kilo Cloud Previous review (commit 0c39bde)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental commit since Files Reviewed (1 file)
Previous review (commit 97a253b)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental commit removes the redundant advanced-config profile selector and active-profile indicator, migrates the typing indicator and composer bottom-padding to Files Reviewed (incremental commit 97a253b, 148 files)
Previous review (commit 25be731)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental fix adds the Android top inset ( Files Reviewed (2 files)
Previous review (commit 978d619)Status: 1 Issues Found | Recommendation: Address before merge Executive SummaryIncremental review of Overview
Issue Details (click to expand)WARNING
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Previous review (commit ad708c7)Status: 1 Issues Found | Recommendation: Address before merge Executive SummaryChanging the session filter sheet to a single Overview
Issue Details (click to expand)WARNING
Files Reviewed (1 files)
Fix these issues in Kilo Cloud Previous review (commit 888d46f)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of Files Reviewed (2 files)
Previous review (commit dbd216f)Status: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of the native sheet/dialog/input/keyboard fix commit Files Reviewed (39 files)
[Snapshot truncated.] Additional previous summary content was truncated to keep this comment within platform limits. Reviewed by deepseek-v4.1-flash · Input: 106.8K · Output: 25.6K · Cached: 1.7M Review guidance: REVIEW.md from base branch |
The diagram page loaded mermaid.min.js from jsDelivr at runtime. The same 11.12.0 bytes (SHA-256 07e37dfa…3c4b) now ship as an app asset that is read once and inlined, so a diagram makes no network request and works offline.
Kilobot found that every diagram inlined the 2.7 MB library into its page, so N fences kept N copies in the JS heap and sent them over the bridge. The script and one static host page now sit in the cache directory; each diagram WebView loads the page by file URL and receives only its own source. The vendored build is marked generated so reviews collapse it.
Fixtures stay dev-only, mirror actual usage, fall back to real backend state when they cannot, and are committed for reuse.
Common tags (emphasis, strikethrough, code, links, HTTPS images, breaks, paragraphs, headings, lists, quotes, pre, rules, simple tables) become markdown; code spans, fences, indented code, and comments are never touched, and anything without a lossless markdown form stays HTML. An element still streaming in converts as if it closed at the end.
MarkdownText converts HTML to markdown before the split, so converted images and links go through the markdown image gate and link confirmation. The rest renders with @native-html/render 1.0.3, which replaces the deprecated react-native-render-html, with renderers for details/summary (collapsible), kbd, sub, and sup. A details element with a blank-line markdown body renders as one HTML block, and the incremental split reuses its head when only the tail was rewritten.
pnpm dev:seed app:mobile-markdown-fixture <email> seeds one agent turn covering markdown, Mermaid, and HTML in markdown (converted tags, details, kbd, sub/sup, an unknown tag, a literal tag in a fence) and returns its deep link.
The HTML engine has no model for an unknown tag and draws nothing, dropping its text; read it as a span, as a browser does. Drop the subscript a little lower.
An inline view's bottom sits on the baseline, which lifts its glyphs. The subscript moves down further, and kbd keys move down so their labels share the line's baseline. Tuned on an Android emulator.
Expo's react-native-web augmentation (expo-env.d.ts, written by expo start) narrows TextStyle keys that @native-html/render types loosely, so the sub/sup/kbd renderers failed typecheck on any machine that had run Expo. They now restate the library's style once. The tag-name lowercasing in markdown-html-tree.ts joins the case-guard allowlist.
# Conflicts: # pnpm-lock.yaml
…fences only at their own indent
iOS clipped the offset inline views used for <sub> and <kbd>. Keys now convert to inline code and digits/signs/simple letters in <sub>/<sup> to Unicode script characters, so the native markdown draws them on the line. Anything else stays HTML and draws as smaller or code-colored text without an offset. Also from kilobot and CI: a dash setext underline ends a paragraph for the code scan, and the details label applies the RTL writing direction (its file joins the raw-Text exemptions; two stale entries go).
Implements the element map and the guardrails from
mobile-ui-element-unification-plan.md.Landed: phases 1–7, the native device pass, and the visual repair pass. The earlier native pass records revision
25be7315a1. Current boundaries appear below. CI owns the full test suite.What landed
no-restricted-importsbans every element the work replaces (one entry per module), including a direct@expo/ui/community/bottom-sheetimport. The ratchet override is deleted — no file inapps/mobile/srcimports a banned element outside the ten sanctioned paths. A new oxlint plugin,tools/oxlint/no-destructive-alert.mjs, forbids a destructive-styleAlert.alertbutton.apps/mobile/AGENTS.mdgains## Library Firstand## Unified Elements.@/components/ui/sheetwraps@expo/ui/community/bottom-sheet. The account picker, the reaction picker, the session filter sheet and the markdown table reader use it, and so do the full-window surfaces:SessionPageSheet(the session page's 14 detail sheets) andImageViewer. They present a native window, so they stack above aformSheetroute.@/components/ui/dialogholds two surfaces:useConfirmDialog(a native sheet) andDialogCard(a portal card on@rn-primitives/dialog, for a form). All 40 destructiveAlert.alertsites moved to the confirm;RenameModalandFeedbackPromptDialogmoved to the card. The 33 remainingAlert.alertcalls are the non-destructive confirms, which stay by design.FlatListsites are FlashList.@shopify/flash-listmoves 2.3.2 to 2.3.3.react-native-zoom-toolkitreplaces the hand-rolled gesture stack;image-viewer-modalbecomesui/image-viewer.expo-videorenders a video file part inline.react-native-keyboard-controller1.22.6 and oneKeyboardProviderat the app root.app-aware-keyboard-padding.tsx,app-aware-keyboard-padding-state.ts,use-composer-reveal-scroll.tsand their tests are deleted; every lift is a nativeKeyboardAvoidingView, the session-list bands and the toast offset readuseKeyboardState, the login screen reads the provider, and the Kilo Chat list follows the newest message when the conversation screen'sKeyboardAvoidingViewshrinks its viewport. Nothing subscribes to React Native's keyboard events any more.Execution boundaries
Temporary VM-only endpoint changes do not enter this PR. No production changes or real purchases ran.
Initial implementation verification
pnpm format, repo-wideoxfmt --list-different .,git diff --check,pnpm lint(0 errors, 497 rules),pnpm typecheck,pnpm check:unused— all clean.expo exportbundles iOS and Android Hermes bytecode (38 MB each), so every new import resolves under Metro.expo prebuildsucceeds for both platforms;expo-videoand@expo/uiautolink on both.expo-doctoris 19/21, the same two failures asmain.Decisions worth a reviewer's attention
formSheetroutes (pr-merge-sheet,pr-review-submit,comment-composer,comment-edit,conversation-comment) and frommessage-details-sheet, which is content insideSessionPageSheet. A sheet presents its own window and stacks, so one confirm element serves every path. The cost is the shape: a destructive confirm is now a bottom sheet, the platform's own idiom on iOS, not a centred alert.keyboardInsetOffset(@/lib/keyboard-inset-offset).react-native-keyboard-controllerlifts by the keyboard's overlap with the view's own bottom edge, and on Android edge-to-edge its height is the full IME inset —KeyboardAnimationCallback.getCurrentKeyboardHeightsubtracts the navigation bar only whenhasTranslucentNavigationBaris false, andanimated.tsxforces that flag on for an edge-to-edge app. A surface whose content pads the platform inset itself therefore passes-bottomso the lift excludes it. The plain surfaces pass nothing, because their previous Android value already equalled the full IME. This is the per-site derivation the four old flags encoded.SessionPageSheetandImageViewermoved ontoSheetat the100%detent with no drag indicator, soui/sheet.tsxholds both the picker sheets and the full-window surfaces.ImageViewerdropssupportedOrientations: a native sheet rotates with the activity. No file importsModalfromreact-nativeany more, so the ban carries no exception: the allowlist isui/sheet.tsx,ui/dialog.tsxand the test globs.DialogCardis a portal card and stays behind a presented sheet. That is fine forRenameModalandFeedbackPromptDialog: every call site is a plain screen, a tab, or a portal overlay. It is recorded inAGENTS.mdas a rule, with the alternative named (aformSheetroute, or aSheet) for a future surface that must stack.@expo/uistays at 57.0.17. Itsbuild/communityand native sheet sources are byte-identical to 57.0.21 (compared the published tarballs), so the bump would buy aminimumReleaseAgeexemption and nothing else.platform-filter-modalandmarkdown-tablechanged surface shape. Both use full-height native sheets. The filter uses one100%detent to keep Cancel and Apply visible without dragging. Both add the Android top inset to clear the status bar.use-composer-reveal-scrollis deleted. The new-session composer uses the library keyboard lift. The native device pass fixes keyboard clearance and verifies Start remains reachable with a filled prompt.@rn-primitives/alert-dialog@1.5.2ships untranspiled JSX indist. Metro parses it for the app; no Node-side test transformer does.vitest.setup.tsstubs the primitive with the same contract so the app's dialog stays under test. The same file stubsreact-native-keyboard-controllerandexpo-videofor the same reason.minimumReleaseAgeExcludeentry and an exactpackage@versioninminimum-release-age-policy.test.ts:@shopify/flash-list@2.3.3(one day old at adoption; carries the EngagedIndicesTracker scroll-window fix) andreact-native-keyboard-controller@1.22.6(the SDK pins 1.21.9, which predates theKeyboardChatScrollViewfixes).min(screenHeight − keyboardTop, frameHeight)(keyboardPaddingFromEvent);react-native-keyboard-controllerreports the raw frame height and lifts by the keyboard's overlap with the view's own frame. The two agree for a docked keyboard and for a floating one that is fully on screen; they differ only when the keyboard frame extends past the screen bottom. Worth a device check on iPad.minimum-release-age-policy.test.ts,single-line-input-guard.test.ts(a pinned raw input moved three lines), andalert-dialog-platform-parity.test.ts(the Profile screen's destructive confirms are the in-app dialog now).Native device pass
Fix commits:
3ed1ff9a67,fd87eaeff0,888d46fc3d,ad708c743f,978d619ce5,25be7315a1. Main merge:4906071b08. Android runs on the development VM. iOS runs locally against the same backend.Input. Enforced zero vertical padding, Android center alignment, and iOS single-line clipping. Multiline defaults now use physical padding that Android applies.Observed final checks: 292 tests in 22 related files pass. Mobile typecheck, lint, unused-code check, and formatting pass. The final source join resolves every native proof identifier. Android has 63 semantic groups; iOS has 134 checks. All 1,176 screenshot/accessibility references exist and contain data.
Mobile CI on
25be7315a1passes 17,183 tests in 1,111 files. The main CI workflow passes after one failed-job retry. Kilobot recommends merge; all nine valid review findings are fixed, replied to, and resolved.The initial
cloud-agent-nextjob hit a workerd segmentation fault. The retry passed on the same head. PR #7110 changes no files in that service. The iOS build, package, and artifact upload pass. All active checks pass on25be7315a1.Input proof: 22 actual class variants pass on each platform. Matching placeholder/value glyphs stay centered. Native focus/blur does not move their baseline.
Evidence:
/Users/igor/Projects/.worktrees/cloud-pr7110-device-pass/.device-pass/. The 99-row source checklist covers 83 visual source files and 16 sheet consumers. It links individual platform evidence. Artifacts are not product code. The throwaway input route is removed.The three former execution blockers are resolved. Six additional native records are in
.device-pass/continuation-native-checks.json. Their related subset passes 101 tests in seven files. The iOS native Files picker remains stuck; document preview uses the existing share-staging path.Account deletion proof covers local PENDING request enrollment and signout, not physical purge. Remediation proof covers a persisted cancellation request, not terminal job completion.
The two new analysis sessions were intentionally stopped after Restart/navigation proof, not reported as completed assessments. All temporary endpoint source/configuration and environment keys are restored. The local store and owned runtimes are stopped. The four model keys and synthetic token cache are restored.
The cached-search preview Delete path exposed a late defect.
978d619ce5refreshes search queries through the existing session invalidation helper. Actual Android and iOS searches now remove a deleted row without manual reload. A behavioral regression fails before the fix and passes after it.No defect reached five failed attempts. The Android slash label repair passed on attempt four. Earlier dated checks retain their tested revisions; the rebuilt iOS binary and final changed-path native smokes pass.
Complete report:
.device-pass/final-report.md. Per-surface results:.device-pass/surface-results.md. Machine evidence:.device-pass/final-source-row-mapping.jsonand.device-pass/verification-manifest.json.Visual repair pass —
97a253baf1Observed related verification: 368 tests in 22 files pass. Mobile typecheck, lint, unused-code, i18n, 87 catalog comparisons, formatting, and
git diff --checkpass. New-head mobile and repository CI pass on0c39bdea7b. Kilobot reports no issues and recommends merge.Current boundaries
Evidence:
.device-pass/visual-reaudit/finding-dispositions.json, platform*-fix-proof.json, andevidence-integrity.json. All 319 native evidence references exist and contain data. Artifacts remain outside the product commit.CI contract cleanup —
0c39bdea7bThe first visual-repair CI run passed 17,093 tests and failed five mock-only foreground assertions. Its React Native mock lacked
Platform. The wiring-only suite is removed rather than re-pinned. The retained logout and push reconciliation suites pass 45 behavior tests. Mobile typecheck, lint, unused-code, formatting, and diff checks pass. No product behavior changes in this cleanup.The first repository CI run also hit a workerd SIGSEGV in
sandbox-control-v2-runtime-proxy.test.ts. The preceding native pass observed the same crash. This PR changes no service source. New-head CI passes, including the cloud-agent-next job. No service code changes were needed.Final current-head result —
0c39bdea7bCI evidence: mobile run, repository run, and review.
Library follow-up (2026-10-06)
Igor's library review chose these items. Each one was tried on device; a trial stays only if the device run proved it.
react-native-enriched-markdown1.1.0. Native md4c rendering replaces thereact-native-markedchain (renderer, table card and reader, render cache: ~6,900 lines deleted). Tables render inline. Fences get native highlighting and a header copy button. Long-press on a fence still opens Message details (enableBlockContextMenu={false}).ImageViewer.minimumReleaseAgeExcludeentry and a policy-test entry, like flash-list and keyboard-controller.react-native-webview. A closed```mermaidfence renders as a diagram; a load or parse failure falls back to the code block. An unclosed (streaming) fence stays code. The Mermaid 11.12.0 script ships with the app (assets/vendor/, MIT, byte-identical to the published file, markedlinguist-generated). Once per launch it is copied next to a static host page in the cache directory; each diagram WebView loads that page by file URL and receives only its own source, so the 2.7 MB never enters the JS heap. No network request: checked with internet blocked on Android and with a packet capture on iOS.@bsky.app/react-native-uitextview. A selectable fence in a tool detail sheet now gives native range selection and the iOS callout. Android is unchanged.expo-background-task. A periodic task (WorkManager on Android, BGTaskScheduler on iOS, 15-minute floor) reads the tray once and republishes the widgets, the ongoing notification and the Live Activity when the OS drops a data-only push. Signed out, it does nothing. A failed read keeps the last snapshot. The publisher is built before the read, so a sign-out during the read wins the surface. Android device run: the surfaces updated with no push, kept their state with the backend down, and stayed empty after sign-out.patches/expo-background-task@57.0.21.patchstops a cold-start run from cancelling itself; the package builds from source on Android so the patch applies. iOS simulators cannot run BGTaskScheduler; the Info.plist keys are in place.apps/mobile/AGENTS.mdnow tells agents to use agent-device.expo-glass-effectand@callstack/liquid-glassare inno-restricted-imports; the element map namesexpo-blur.expo-quick-actions. The app ships launcher shortcuts and an Android Quick Settings tile inmodules/kilo-launcher-surfaces. The only maintained library has no npm release for SDK 57 (6.1.0 is only onmain), has an open cold-start bug (quick actions doesn't work when app is in killed state EvanBacon/expo-quick-actions#54), and does not cover the tile.Device verification
.mdbody,.mdfile preview, Kilo Chat messages, Security Agent analysis panel, image gate, link confirm, fence copy, Mermaid, the 1,500-line fence, iOSUITextViewselection.ResizeObserver), and the library block menu opening over Message details.<details>HTML block renders nothing in both the old and the new chain.Related checks: mobile typecheck, i18n catalogs, format, and lint on every changed file pass. 165 tests in the markdown and code-block suites pass. Local full
pnpm lintreports tRPCerror-typed values at this PR's previous head too; CI is the judge.Dependency upgrades (2026-10-06)
Mobile-only upgrades that pass the pnpm release-age gate, each checked on an iOS simulator and an Android emulator against a local backend.
@formatjs/intl-durationformat0.10.18 → 0.11.1: feature release; the polyfill formats durations in the app runtime on both platforms.@sentry/react-native8.28.0 → 8.29.0: SDK 8.29.0 starts on both platforms with no native error.expo-speech-recognition56.0.4 → 57.1.0 (SDK 57 line): Android reaches "Listening..." and stops cleanly. The iOS simulator cannot capture audio, so iOS needs a physical-device check.typescript(mobile only) 6.0.3 → 7.0.2: mobile typechecks withtsgo; Expo's config loader detects TS 7 and uses its own transpiler. Lint, knip, the Expo config contract and bundle export pass.react-native-reanimated4.5.5 → 4.7.1 andreact-native-worklets0.10.4 → 0.13.0: one exactminimumReleaseAgeExcludeentry for reanimated 4.7.1 (4 days old), with a policy-test entry. No crash, RedBox or[Reanimated]error. Covered: sheets, dialogs, navigation, tabs, the skeleton, and the keyboard lift.react-native-appsflyer6.18.0 → 7.0.2-v2:src/lib/appsflyer.tsmoved to the v7 API (init+ session-ready +start, object params,setConsentData({...})). Consent, stop/resume and the late-callback invalidation are kept, and consent, stop and start now run one at a time. On both platforms: no SDK traffic before consent, and init → start → launch after consent; revoke stops it, and re-grant starts it again.patches/react-native-appsflyer@7.0.2-v2.patch.create()still drops its promise. The patch also adds a Kotlin stdlib fallback for RN 0.86's Kotlin 2.1.20, and a@ts-ignoreon the package'stypeof jest.isUserSubjectToGDPR: truetogether with the user's consent choices. v6 sent "undetermined".Not upgraded:
jotai3: the app hands its store to@kilocode/cloud-agent-sdk, which builds atoms with jotai 2.18.1 (156 type errors). Fixing it means moving the SDK, which web and the extension also use.nativewind5.0.0-rc.0: on device,leading-Nbecame N × 14 × fontSize, which hid the tab labels and made code lines ~150pt tall. rc.0 drops preview.4's unitlessleading-*override, andreact-native-css3.1.0-rc.0 multiplies the length by the font size. Reverted to the preview.4 pair, and re-checked on device.pnpm patch review: all 11 other patches are still needed on the newest release of the same version line. The react-native view-assert guard is fixed upstream only on
main.HTML in markdown and fixtures (2026-10-06)
markdown-html-convert.ts+ scan/tree/inline/block/script helpers). HTML that markdown can express becomes markdown before the native renderer, so it gets one style, native selection, and the image gate.b/strong,i/em,s/del/strike,code,kbd(inline code),a,img(https only, through the image gate),br,p,h1–h6, lists,blockquote,pre,hr, plain tables.sub/supwith digits, signs, parentheses or simple letters become Unicode script characters (H₂O, x²).@native-html/render1.0.3, which replaces the deprecatedreact-native-render-html(now inno-restricted-imports).<details>/<summary>is a collapsible row, collapsed by default; before, it rendered nothing. Unknown tags keep their text. Asub/sup/kbdthat markdown cannot express draws as smaller or code-colored text on the baseline, because iOS clips offset inline views.pnpm dev:seed app:mobile-markdown-fixture <email>seeds the transcript used for these checks: markdown, tables (wide, long-cell), fences, Mermaid (four diagrams), HTML cases. It returns the deep link.apps/mobile/AGENTS.md: new "Fixtures for End-to-End Testing" rules. Fixtures are dev-only (__DEV__, entered through+native-intent.tsx), faithful to actual usage, fall back to real backend state when they cannot be faithful, and are committed for reuse.