From 49a1b89ff43064cdf34e5354169ce6399eb30165 Mon Sep 17 00:00:00 2001 From: feruz Date: Sat, 27 Jun 2026 10:43:37 +0300 Subject: [PATCH 1/3] fix(editor): fall back missing caret to end + cancel pending caret write on unmount Review follow-ups to the draft-caret feature: - When no caret is saved (legacy draft, cross-device draft, first open after this shipped), restore to the end of the body instead of position 0. Keeps the feature purely additive and avoids dropping the auto-focused cursor at the top (where immediate typing would prepend to the post). - Cancel the debounced caret persister on unmount so a late write can't re-create a caretMap entry that publish/first-save just cleared. --- .../markdownEditor/view/markdownEditorView.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/markdownEditor/view/markdownEditorView.tsx b/src/components/markdownEditor/view/markdownEditorView.tsx index 82c2510b68..7fd4e94a76 100644 --- a/src/components/markdownEditor/view/markdownEditorView.tsx +++ b/src/components/markdownEditor/view/markdownEditorView.tsx @@ -139,8 +139,13 @@ const MarkdownEditorView = ({ // Resume at the user's last caret position instead of jumping to the end // of the body (which scrolls a long draft to the bottom). Clamp to the // current length in case the body shrank since the caret was saved. - const savedCaret = store.getState().editor.caretMap?.[_caretKey] ?? 0; - const caret = Math.min(savedCaret, draftBody.length); + // When no caret was saved — a legacy draft, one created on another device, + // or the first open after this shipped — fall back to the end of the body + // (the long-standing behavior). This keeps the feature purely additive and + // avoids dropping the auto-focused cursor at position 0 (prepend-on-type). + const savedCaret = store.getState().editor.caretMap?.[_caretKey]; + const caret = + typeof savedCaret === 'number' ? Math.min(savedCaret, draftBody.length) : draftBody.length; _setTextAndSelection({ selection: { start: caret, end: caret }, text: draftBody, @@ -241,6 +246,10 @@ const MarkdownEditorView = ({ [], ); + // Cancel any pending caret write when the editor unmounts so a late debounce + // can't re-create a caretMap entry that publish/first-save just cleared. + useEffect(() => () => _persistCaret.cancel(), [_persistCaret]); + const _handleOnSelectionChange = async (event) => { bodySelectionRef.current = event.nativeEvent.selection; _persistCaret(event.nativeEvent.selection.start); From 68322b1dd1a9bc37d38e402bdc93912b196af4f9 Mon Sep 17 00:00:00 2001 From: feruz Date: Sat, 27 Jun 2026 11:39:44 +0300 Subject: [PATCH 2/3] fix(waves): align the wavy-dash tab icon with the other bottom-tab icons react-native-svg's ignored the shared paddingTop the vector-font tab icons honor on their , so the Waves icon sat ~15px too high. Apply the layout style (the tab bar's top offset) to a wrapping instead, which honors padding; the parent tab cell centers it horizontally. Keeps the same footprint as the other icons across phones and tablets on iOS and Android. --- src/assets/svgs/wavy-dash-icon.tsx | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/assets/svgs/wavy-dash-icon.tsx b/src/assets/svgs/wavy-dash-icon.tsx index 7be0c80a01..5dc94424b2 100644 --- a/src/assets/svgs/wavy-dash-icon.tsx +++ b/src/assets/svgs/wavy-dash-icon.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleProp, ViewStyle } from 'react-native'; +import { StyleProp, View, ViewStyle } from 'react-native'; import Svg, { Path } from 'react-native-svg'; interface Props { @@ -12,14 +12,23 @@ interface Props { * Wavy dash (〰️) glyph used as the Waves bottom-tab icon. Single Twemoji path, * tinted via `color` so it follows the active/inactive tab tint like the other * (vector-font) tab icons. + * + * The layout `style` (the tab bar's shared paddingTop offset) is applied to a + * wrapping View, not the : react-native-svg does not honor padding the way + * the vector-icon glyphs do, so applying it to left the icon ~15px + * too high. The wrapper sizes to the SVG and inherits the same top offset, and + * the parent tab cell centers it horizontally — keeping the Waves icon aligned + * with the other icons across phones and tablets on both iOS and Android. */ const WavyDashIcon = ({ color = '#000000', size = 26, style }: Props) => ( - - - + + + + + ); export default WavyDashIcon; From d37b0346911132a124a2b1346e2c203b3d01d824 Mon Sep 17 00:00:00 2001 From: feruz Date: Sat, 27 Jun 2026 11:48:55 +0300 Subject: [PATCH 3/3] fix(editor): flush (not cancel) pending caret + body debounces on unmount Review follow-up: cancelling _persistCaret on unmount dropped the caret when the user moved the cursor and left within the 600ms window (stale caret on reopen), and the body debounce (_debouncedOnTextChange) could still fire after unmount. Flush both on unmount so the latest caret and body are committed synchronously before teardown, addressing both the dropped-caret and late-body-write windows. --- .../markdownEditor/view/markdownEditorView.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/markdownEditor/view/markdownEditorView.tsx b/src/components/markdownEditor/view/markdownEditorView.tsx index 7fd4e94a76..35e181a276 100644 --- a/src/components/markdownEditor/view/markdownEditorView.tsx +++ b/src/components/markdownEditor/view/markdownEditorView.tsx @@ -246,9 +246,18 @@ const MarkdownEditorView = ({ [], ); - // Cancel any pending caret write when the editor unmounts so a late debounce - // can't re-create a caretMap entry that publish/first-save just cleared. - useEffect(() => () => _persistCaret.cancel(), [_persistCaret]); + // On unmount, flush (not cancel) the pending debounced writes so the latest + // caret and body are committed synchronously before the editor tears down. + // Cancelling would drop the last cursor move / keystrokes within the debounce + // window — leaving a stale caret and body on reopen — and letting them fire + // late would write after unmount. Flushing both closes that window cleanly. + useEffect( + () => () => { + _persistCaret.flush(); + _debouncedOnTextChange.flush(); + }, + [_persistCaret, _debouncedOnTextChange], + ); const _handleOnSelectionChange = async (event) => { bodySelectionRef.current = event.nativeEvent.selection;