Skip to content

fix(recipe): stop the rich-text editor writing an undeletable <br> into recipe text - #534

Open
plusmobileapps wants to merge 2 commits into
mainfrom
claude/directions-ingredients-input-bugs-833bad
Open

fix(recipe): stop the rich-text editor writing an undeletable <br> into recipe text#534
plusmobileapps wants to merge 2 commits into
mainfrom
claude/directions-ingredients-input-bugs-833bad

Conversation

@plusmobileapps

Copy link
Copy Markdown
Collaborator

Fixes two reported problems with the ingredients and directions inputs.

The undeletable <br>

PlusMarkdownEditor kept its RichTextState two-way bound to value no matter which mode was on screen, and compose-rich-editor serializes a run of consecutive blank paragraphs to a literal <br> line:

setMarkdown("Step one\n\n").toMarkdown() == "Step one\n\n<br>"

In Markdown mode that made pressing enter twice at the end of a field paste a <br> into the raw text field — the hidden rich state parsed "…\n\n" and echoed "…\n\n<br>" back through onValueChange. Deleting the tag put the text back into the exact state that re-created it, so it could never be removed.

In rich-text mode (the default) the tag was invisible in the editor but reached storage, where the detail and cook screens render inline markdown only — no HTML — and printed it as a literal line.

The fix:

  • Both sync effects now run only while rich text is the active editor, so the inactive one never rewrites the value.
  • withoutLineBreakTags() normalizes <br> back to the blank line it stands for on the way out of the rich editor. A test pins that normalization is a fixed point through the library's parser, so the two halves of the sync can't fight each other.
  • Recipes already carrying a <br> render clean: the tag is dropped when splitting lines on the detail and cook screens, in the markdown preview, and in the add-to-grocery-list split, where it had been turning into a grocery item.

Mobile selection

The resize handle was overlaid on the text field's bottom-end corner with detectDragGestures, swallowing drags in exactly the spot where the caret and selection handles land on the last line. It now sits in its own row below the field. The mode gating removes a second source of touch jank: Markdown mode had been running a full markdown parse and serialize on the hidden rich state on every keystroke.

This does not fully fix selection in rich-text mode on phones, and that part is upstream. compose-rich-editor 1.0.0-rc14 ships adjustTextIndicatorOffset as its own workaround for BasicTextField selecting the wrong text in multi-paragraph content — but wires it up on desktop only; the Android and iOS actuals return the modifier unchanged (= this). Ingredients and directions are exactly that multi-paragraph case, so tap and selection offsets stay wrong there until the library fixes it. Maven shows nothing newer than rc14 to upgrade to. Markdown mode is a plain OutlinedTextField and selects normally, so it's the workaround for now; an upstream issue is worth filing.

Testing

  • New LineBreakTagsTest covers the helper and pins the library's <br> serialization plus round-trip stability.
  • client:ui:public, client:recipe:core:impl and client:cook:impl suites pass.
  • Screenshots: 249/249 pass. The 11 re-recorded in the second commit (6 markdown editor previews, 5 EditRecipe) all grow by the height of the handle's new row.

Reviewer notes

  • Opening a recipe that already contains a <br> deliberately does not mark it dirty — the collector compares against the normalized caller value, so you don't get a spurious "discard changes?" prompt just for viewing one. The stored text is repaired the next time that field is genuinely edited and saved, and renders clean in the meantime.
  • Blank lines beyond two collapse when re-seeded from storage, because the markdown source can't represent three consecutive blank paragraphs. It converges in one step rather than looping.

🤖 Generated with Claude Code

plusmobileapps and others added 2 commits September 6, 2026 11:38
…to recipe text

PlusMarkdownEditor kept its RichTextState two-way bound to the value no matter
which mode was on screen, and compose-rich-editor serializes a run of
consecutive blank paragraphs to a literal `<br>` line. In Markdown mode that
made pressing enter twice at the end of a field paste a `<br>` into the raw
text field, and deleting it put the text back into the state that re-created
it — the tag could never be removed. In rich-text mode the tag was invisible in
the editor but reached storage, where the detail and cook screens render inline
markdown only and printed it as a literal line.

Gate both sync effects on richTextMode so the inactive editor never rewrites
the value, and normalize `<br>` back to the blank line it stands for on the way
out of the rich editor. Recipes already carrying one now render clean: the tag
is dropped when splitting lines on the detail and cook screens, in the markdown
preview, and in the add-to-grocery-list split, where it had been turning into a
grocery item.

Also move the resize handle out of the text field's bottom-end corner into its
own row below the field. Overlaying it there let detectDragGestures swallow the
drags that move the caret and selection handles on touch platforms, which land
in exactly that corner on the last line. The gating removes a second source of
touch jank: Markdown mode had been running a full markdown parse and serialize
on the hidden rich state on every keystroke.

This does not fix selection in rich-text mode on phones. compose-rich-editor
1.0.0-rc14 ships adjustTextIndicatorOffset as a workaround for BasicTextField
selecting the wrong text in multi-paragraph content, but wires it up on desktop
only — the Android and iOS actuals return the modifier unchanged. Ingredients
and directions are exactly that multi-paragraph case, so the offsets stay wrong
there until the library fixes it; Markdown mode is a plain OutlinedTextField
and selects normally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The markdown editor previews and the EditRecipe screens all grow by the height
of the resize handle's own row, now that the handle no longer overlays the text
field's bottom-end corner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant