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; diff --git a/src/components/markdownEditor/view/markdownEditorView.tsx b/src/components/markdownEditor/view/markdownEditorView.tsx index 82c2510b68..35e181a276 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,19 @@ const MarkdownEditorView = ({ [], ); + // 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; _persistCaret(event.nativeEvent.selection.start);