Skip to content

feat(text): markdown formatting with floating format bar - #175

Merged
SunkenInTime merged 10 commits into
mainfrom
devin/1789961672-text-markdown-format-bar
Sep 22, 2026
Merged

SunkenInTime merged 10 commits into
mainfrom
devin/1789961672-text-markdown-format-bar

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Text widgets now understand a small Markdown subset and get a floating format bar while editing, so people who don't know the syntax can still bold, bullet, number, and head their notes. Always on, no setting. PlacedText.text stays a plain String (the source with markers), so nothing changes for Hive, migrations, or .ica round-trips; old strategies render exactly as before unless they happen to contain markers.

Supported: **bold**, *italic*/_italic_, - /* /• bullets, 1. /1) numbered, #–### headings. Unclosed markers stay literal. No Markdown dependency; the parser is ~200 lines in text_markup.dart.

How it fits together

text_markup.dart                 parseMarkup(String) -> List<MarkupLine>
                                 MarkupEditing.toggleInline / toggleLineKind / continueList
markup_text_editing_controller   TextEditingController.buildTextSpan: dims markers, styles bold/italic/headings in the raw source
formatted_text_view.dart         non-editing render: markers hidden, • glyphs, computed list numbers
text_format_bar.dart             B / I / bullets / numbered / H, active glyph in violet
text_widget.dart                 editing ? TextField(controller) : FormattedTextView; bar shown through OverlayPortal
  • Not editing → FormattedTextView. Tap → TextField(decoration: null) with MarkupTextEditingController, both inside the same 12px vertical padding, so the card is the same height in either state (hint is drawn by hand for the empty case); focus loss commits the draft and returns to the formatted view. Same swap for drag feedback, screenshots, and page transitions, so every non-editing path renders formatted.
  • The bar lives in an OverlayPortal (not scaled by map zoom), centred under the widget's transformed bounds, flips above when there's no room below, clamped to the viewport. Its taps share a TapRegion group with the field so clicking a button doesn't end editing.
  • Line-kind toggles apply to the lines touched by the selection only; numbered toggles renumber the run. Enter on a list item continues it (ListContinuationFormatter), Enter on an empty item exits the list. Ctrl/Cmd+B and +I toggle inline marks; with a collapsed caret they wrap the word under the caret, or insert **** and park the caret inside.
  • Geometry is untouched: the widget's measured box is still the frame (_TextBoxFrame), and the overlay is outside it, so resize, drag, and defense-side mirroring behave as before.

EditorToolbarButton gained an active flag: the glyph turns violet (DESIGN.md: violet on an icon means checked), no fill.

One test removed: canonical_coordinates_test asserted the live card height equals PlacedMediaGeometry.legacyTextFootprintInWorld. That formula only feeds the one-time canonical-coordinates migration and models the old TextField card; the runtime mirrors from live measurement, and the new card (text + 12px padding, no Material chrome) is a different height at the same font, so the two can't stay equal by design. Shout if you'd rather I pin the card to the old footprint instead.

Screenshots (Linux run)

Editing, bar below the widget, markers dimmed, active kind in violet:

editing

Same widget not editing (same card height):

rendered-same-size

Rendered after focus loss, after a resize:

rendered

Heading + numbered list + inline bold rendered:

rendered-list

Defense side (mirrored), formatting intact:

defense

Exported screenshot (camera button) crop:

export

Video walkthrough is in the first comment.

Tests

test/text_markup_test.dart, test/formatted_text_view_test.dart new; test/text_widget_resilience_test.dart extended (enter editing, draft, commit on blur). fvm flutter analyze clean for touched files; targeted suite +42 passed.

Link to Devin session: https://app.devin.ai/sessions/dc7ec45e6e12464494017e5ba546e542
Open in Devin Desktop: https://app.devin.ai/desktop/session/dc7ec45e6e12464494017e5ba546e542?variant=devin
Requested by: @SunkenInTime

RetriggerConfidence Score: 5/5

The verified formatting and text-editing behaviors are safe to merge.

What we checked:

  • Checked formatting control has wrong treatment: The documented selected-control treatment is a raised primary surface with a primary-foreground glyph. The active toolbar button applies that treatment and the focused editing interaction passed. T-Rex
  • Draft lost on focus change: Losing focus commits the draft before editing closes. Disposal also schedules a commit for any remaining draft, and persistence clears the draft only after committing its text. T-Rex
  • Empty list item cannot exit: The continuation handler removes an empty list prefix and restores the caret to the former line start. Focused bullet and numbered-list tests passed. T-Rex
  • Numbered continuation leaves stale ordinals: A numbered continuation is passed through the full renumbering routine. The verified insertion-before-later-items flow produced sequential values 1, 2, 3, and 4 and retained the expected caret position. T-Rex
  • Non-editing views expose markers: Both non-editing feedback paths use FormattedTextView, which excludes marker spans and renders list bullets. Focused widget rendering checks passed for screenshot and drag feedback. T-Rex
  • Compared the documented checked-control treatment with the active toolbar and ran the editing flow on baseline and current revision; started a real web app for a Chromium rendering attempt and observed no invalid capture after a Flutter layout assertion. T-Rex
  • Focused text editing and blur commit: the draft is committed on blur, disposal with a remaining draft persists changes, and the Flutter draft-state checks passed. T-Rex

Summary

This change adds Markdown-style formatting for text notes while retaining the original plain-text source for editing. Verified text editing flows save drafts on blur and disposal, list editing exits and renumbers correctly, and drag-feedback and screenshot views render formatted content without exposing Markdown markers. No defects requiring changes were found.

Reviews (4) · Last reviewed commit: "fix(text): carry the caret past earlier ..."

SunkenInTime and others added 3 commits September 21, 2026 03:41
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: d34c97e5-1b61-4476-947e-bd48a204e741

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Video walkthrough, recorded in the Linux desktop build: heading, numbering only the selected lines, bullet continuation and exit, Ctrl+B, italic from the bar, click-outside rendering, then resize, drag, Defense mirroring and PNG export.

Markdown walkthrough

Editing Rendered
Editing Rendered
Defense mirroring, exported PNG

Defense

Export

Not covered by the video: persistence across reopen and .ica round-trip (the model is unchanged, so both are exercised by existing tests only).

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

Not safe to merge because numbered-list renumbering demonstrably misplaces selections after a prefix-width change.

Findings

  1. P1 Numbered-list renumbering loses downstream caret offsets ▶

Summary

This change adds Markdown-style formatting and a floating format bar for tactical text notes while retaining plain source text.

Draft text is preserved when an actively edited note is removed, and drag-feedback and screenshot rendering use the formatted view that hides supported Markdown markers. One editing defect remains in lib/widgets/draggable_widgets/text/text_markup.dart: when list numbering crosses from 9. to 10., later caret positions are left one character early after renumbering.

Merge safety: do not merge until numbered-list selection offsets are corrected and protected by a regression test.

Reviews (3) · Last reviewed commit: "fix(text): hug the text, and a format ba..."

Comment thread lib/widgets/draggable_widgets/text/text_widget.dart Outdated
@greptile-apps

This comment has been minimized.

SunkenInTime and others added 4 commits September 21, 2026 04:18
…heck

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… for active

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@SunkenInTime
SunkenInTime marked this pull request as ready for review September 21, 2026 16:30
The card's vertical padding drops from 12px to 4px and moves into the
shared frame, so editing, previews, and exports agree.

The format bar is sized for the five buttons it holds, evenly spaced,
with corners that sit against the text card. Active toggles are checked
tools: a raised violet surface under a white glyph. No tooltips, which
covered the text being written.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread lib/widgets/draggable_widgets/text/text_markup.dart Outdated
Renumbering shifted a caret only by its own line's prefix change, so when
an earlier item went from 9. to 10. a caret further down landed one
character early.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@SunkenInTime
SunkenInTime merged commit 8ae7af3 into main Sep 22, 2026
3 checks passed
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