Skip to content

Live Preview editor: inline callouts/code/tables/math/mermaid + Edit/Read modes - #114

Merged
ddutchie merged 17 commits into
mainfrom
ddutchie/editingfixes
Aug 5, 2026
Merged

ddutchie merged 17 commits into
mainfrom
ddutchie/editingfixes

Conversation

@ddutchie

@ddutchie ddutchie commented Aug 5, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

Makes the note editor's Live Preview a complete story. It fixes the callout widget that shipped broken in 2.6.2, extracts a reusable base for block widgets, and builds the rest of the Tier 2 set (code, tables, math, Mermaid) on it — plus editor-mode simplification and a follow-the-cursor preview panel. Targets v2.6.3.

Every block uses the same click-to-edit model: it renders inline (matching reading mode) and, when your cursor enters its range, folds back to the raw Markdown for editing. No edit-round-trip inside the widget.

1. Callout fixes — the 2.6.2 widget was mis-behaving (7221285)

Two real bugs found in use:

  • Height / cursor drift: <Callout> has my-3; the widget container was display:block, so those child margins collapsed through it — CodeMirror measured a border-box ~24px shorter than the callout actually painted and mapped clicks/cursor a line or two too low. Fixed with display: flow-root (establishes a BFC → margins contained → measured height == painted height). A ResizeObserver also re-measures when height settles after the async mount.
  • Arrow keys skipped the callout: a block:true replace decoration is atomic, so ArrowUp/Down jumped over it and it never unfolded. A Prec.high keymap now steps the cursor into a folded block from the adjacent line.

2. Shared base — BlockPreviewWidget (33efdc3)

Extracted the hard-won lessons (sync flushSync mount, ResizeObserver re-measure, estimatedHeight=-1, ignoreEvent=false, flow-root container) into a base so each new widget only supplies render() + eq(). Callout refactored onto it.

3. Code / Tables / Math / Mermaid inline (33efdc3, 21df71a1)

  • Code blocks — via the existing <CodeBlock> (highlighting + copy).
  • Tables — GFM Table node → NoteMarkdownPreview; isTableSource guards against lone-pipe prose.
  • Math — $$…$$ isn't a distinct lezer node (it lands in a Paragraph), so a separate line-scan (findMathBlocks) detects delimiter pairs.
  • Mermaid — ```mermaid renders as a diagram (was a code listing), mirroring read-mode routing.

4. Editor modes: Edit / Read + a Live Preview toggle (3ca8f098)

Collapsed the old three-mode bar (Write/Raw/Read) — Raw was just "Edit with Live Preview off". Now Edit | Read, with a Live Preview toggle shown while editing. Legacy persisted write/raw migrate to edit. The toggle persists.

5. Follow-the-cursor preview panel (cd599f8, a2472298)

The bottom preview now renders the structured block your cursor is in (table/callout/code/math) live as you type, so you can watch it form and instantly spot broken Markdown. Selection preview still wins; plain prose doesn't trigger it. Added an S / M / L size toggle (S also densifies the text via a scoped --preview-scale).

6. Fixes surfaced along the way

  • flushSync crash toggling Live Preview (91bb7c2): the compartment reconfigure ran inside a React effect, where flushSync is illegal. Deferred to a microtask (runs in CM's own context) + a try/catch fallback for the mount path.
  • Uncontrolled→controlled checkbox warning (60588ed): GFM task-list checkboxes now render as stable read-only inputs.
  • Preview lingered after switching notes (9bbba15): cleared on note change.
  • No-blank-line parser quirk (21df71a1): a callout/table with no trailing blank line no longer swallows the following paragraph (range clamped to the actual block; overlapping candidates dropped defensively).

Type of change

  • Bug fix
  • New feature
  • Refactor / code quality
  • Docs / changelog
  • Tests

Screenshots / recording

To attach. Worth showing: a table/callout/code/mermaid rendering inline and clicking into raw; the Edit/Read + Live Preview toggle; the follow-cursor preview panel (incl. S/M/L) rendering a table live as it's typed and breaking when a pipe is removed.

Checklist

  • npm run type-check:all passes
  • npm run lint passes
  • npm test passes — 1,861 passed / 12 pre-existing skips
  • npm run compile (esbuild) + npm run test:e2e (17/17) pass
  • Major user-facing feature → What's New entry added (v2.6.3-live-preview-editor) + regenerated
  • Changelog in changelogs/v2.6.3.md (v2.6.2 already shipped; restored byte-identical)
  • No hardcoded colours (CSS variables); no text-[Npx] classes — preview font-scaling uses a --preview-scale CSS var + calc()
  • No new DB migrations, IPC handlers, or dependencies

Notes for reviewer

Focus: src/lib/livePreview.ts (findBlockWidgets — the one place block types are detected; buildBlockWidgetDecorations — sorts + drops overlaps so a stray overlap can't throw a RangeSet error) and src/lib/block-preview-widget.tsx (the base every widget inherits).

Deferred on purpose: the task-checkbox inline widget (card Zs8LYKyGjYN9) is the one remaining Tier 2 item. It's a different interaction — an inline toggle that writes [ ]↔[x] back to the source — not the block click-to-edit base, so it's kept out of this branch to stay focused and low-risk.

Honest testing caveat: the component tests run in jsdom, which has no layout engine — element heights are 0 and coordsAtPos throws there. So the widget tests verify wiring (widget mounts, cursor-inside folds to raw, arrow-nav decision, parse/guard functions) and the fixes are argued by mechanism (BFC margin containment, microtask ordering), not by pixel/coordinate assertions. The layout-dependent behaviour (click mapping, cursor position around widgets) needs a real-app pass in Electron/Chromium — done informally during development (mouse mapping + arrow-nav confirmed by the author), but a reviewer should click through table/math/mermaid rendering, click-to-edit, and arrow navigation on the built app.

Board: tables (ZZcXhMqwhHHO), mermaid+math (GPnfcv9l0YBI), and the parser quirk (Qw0hhwXnqixP) are in Review; the audit note "Live Preview Editor — Tier 2 Block Widgets" is updated with the full implementation record.

Summary by CodeRabbit

  • New Features
    • Added inline Live Preview rendering for code blocks, tables, math, callouts, and Mermaid diagrams.
    • Introduced simplified Edit/Read modes with a persistent Live Preview toggle.
    • Added cursor-following previews with adjustable small, medium, and large sizes.
  • Bug Fixes
    • Improved callout navigation and preview updates when switching notes.
    • Fixed task-list checkbox warnings and kept preview checkboxes read-only.
  • Tests
    • Expanded coverage for previews, navigation, Markdown blocks, resizing, and checkbox behavior.

ddutchie added 14 commits August 4, 2026 22:07
- calloutWidgetTheme: display:flow-root on .cm-lp-callout establishes a block
  formatting context so <Callout>'s my-3 margins are contained in the measured
  box. Without it those child margins collapsed through the container, so CM
  measured a height ~24px shorter than the callout actually paints and mapped
  clicks/cursor a line or two too low.
- ResizeObserver on the widget container re-measures when height settles after
  the synchronous mount (async markdown/image resolution, fonts, collapsible
  toggle); guarded for environments without ResizeObserver.
- Prec.high ArrowDown/ArrowUp keymap: a block replace decoration is atomic, so
  default vertical motion skipped the whole callout and it never unfolded. The
  handler redirects a move from the adjacent line into the callout's first line,
  which unfolds it via the existing activeLines mechanism.
- foldedCalloutFromCursor extracted + tested; ResizeObserver stub added to the
  component test setup.
Tier 2 block widgets, continued (callouts already shipped).

- BlockPreviewWidget: shared base encoding the callout POC's hard-won lessons —
  synchronous flushSync mount, ResizeObserver re-measure, estimatedHeight=-1,
  ignoreEvent=false, and a flow-root container (contains child margins so the
  measured height matches the painted height). Each widget only supplies its
  React element + an eq() check.
- CalloutWidget refactored onto the base (behavior unchanged).
- CodeBlockWidget: renders fenced code (```lang) inline via the existing
  <CodeBlock> component (syntax highlighting + copy); parseFencedCode handles
  ```/~~~, info strings, and unclosed-while-typing fences.
- livePreview.ts generalized: findCalloutBlocks -> findBlockWidgets (callouts +
  FencedCode), calloutField -> blockWidgetField, and the arrow-key nav
  (foldedCalloutFromCursor -> foldedBlockFromCursor) now enters any folded block
  widget. CALLOUTS_ENABLED -> BLOCK_WIDGETS_ENABLED kill-switch.
- Tests: parseFencedCode unit cases; code-block render/cursor-inside/arrow-nav.
Adds a third mode between Write and Read: Raw turns Live Preview off
(livePreview={false}) so you edit the plain markdown source — no inline
block widgets or marker hiding — without leaving the editor. Useful when a
callout/code-block widget gets in the way of editing the underlying syntax.

- EditorMode: 'write' | 'raw' | 'read'; Raw button (Code icon) in the mode
  toggle, persisted like the other modes.
- Reuses the existing livePreviewCompartment (livePreview prop already drives
  it) — Raw is just Write with live preview off.
- AI/format toolbar and the wikilink picker now show in any editing mode
  (mode !== 'read') rather than write-only, so they work in Raw too.
Toggling the Live Preview compartment dispatched synchronously inside a
useEffect; that rebuilds decorations and mounts block widgets, whose toDOM
uses flushSync for a synchronous first measure. flushSync is illegal while
React is still flushing the effect, so it threw
('flushSync was called from inside a lifecycle method').

- Defer the compartment reconfigure to a microtask so the decoration rebuild
  (and toDOM) runs in CodeMirror's own context, where flushSync is legal.
- Guard toDOM's flushSync in try/catch as a safety net for any other
  render-phase path (e.g. initial mount, which also builds the view in an
  effect): on failure, fall back to a plain async render — the ResizeObserver
  still corrects the height once React paints.
The bottom preview panel now follows the cursor into a structured block
(table, callout, fenced code, math) and renders it live, so you can see a
table/callout render — and immediately spot broken markdown — while typing,
without needing a selection.

- extractStructuredBlockAtOffset (pure, tested): given raw markdown + cursor
  offset, returns the enclosing table/callout/code/math block, or null for
  plain prose (paragraphs/headings/lists don't trigger the panel).
- note-editor: on cursor move (write or raw mode), preview the enclosing block.
  Selection preview still takes priority; nothing shows in Read mode.
- MDPreviewPanel: optional label prop so the header reads e.g. 'table preview'.
The selection/block preview only refreshed on cursor/selection activity, so
after switching notes the previous note's preview lingered until you clicked
into the new one. Clear previewText + blockPreview in the note-id change
effect (alongside the existing title reset).
…trolled warning)

GFM task-list checkboxes came through NoteMarkdownPreview as bare
<input type=checkbox> where `checked` is present only when checked. When the
previewed text changed (e.g. the editor's live preview updating as you
drag-select across checkboxes), the same input flipped checked undefined→true,
tripping React's 'changing an uncontrolled input to be controlled' warning.

Add an `input` component override that renders a stable read-only, disabled
checkbox with an always-boolean `checked`. Non-checkbox inputs pass through.
Regression test asserts no controlled-ness flip across content changes.
The preview panel's height was hardcoded to 30vh. Add an S/M/L toggle in the
panel header that sets its max-height (15/30/50vh), persisted like the editor
mode. Small also densifies the text: the panel sets --preview-scale, and
globals.css scales the rem-based prose-cairn sizes under [data-md-preview-portal]
by it (S=0.82, M/L=1) so a smaller panel shows more at once.
Raw and Write were both editing modes differing only in whether Live Preview
was on, so collapse the top bar to Edit | Read and move Live Preview to its own
toggle (shown while editing, beside the mode switch).

- EditorMode: 'edit' | 'read' (was write/raw/read). Legacy persisted
  'write'/'raw' migrate to 'edit'; 'read' unchanged.
- livePreviewOn state, default on, persisted separately (NOTE_LIVE_PREVIEW_KEY).
  livePreview prop = mode === 'edit' && livePreviewOn.
- Toolbar/wikilink/preview gating already keyed on mode !== 'read', so still
  correct for the merged edit mode.
v2.6.2 already shipped (package.json is 2.6.2, released), so the inline
code-block / Edit-Read-LivePreview / current-block-preview entries I'd appended
to changelogs/v2.6.2.md were editing a released changelog. Per the changelog
rule, move them to a new changelogs/v2.6.3.md (one patch ahead) and restore
v2.6.2.md byte-identical to the release. Also add a 'Live Preview Editor'
What's New feature entry (v2.6.3) for the headline editor work.
…im fix

Completes the Tier 2 block widgets so the editing story needs no follow-up:

- Tables: GFM Table nodes render inline via NoteMarkdownPreview (matches read
  mode); isTableSource guards against lone-pipe prose.
- Math: $$…$$ display math renders inline. It isn't a distinct lezer node
  (lands in a Paragraph), so findMathBlocks scans delimiter pairs separately.
- Mermaid: ```mermaid fences render as a diagram via MermaidDiagram (mirrors
  read-mode's code-fence routing) instead of a code listing.
- Parser range-trim: a callout/blockquote with no trailing blank line no longer
  swallows the following paragraph — countLeadingQuoteLines clamps the widget to
  the actual `>` lines. buildBlockWidgetDecorations now sorts + drops
  overlapping candidates defensively so a stray overlap can't throw.

All three reuse BlockPreviewWidget (sync mount, ResizeObserver, flow-root,
click-to-edit) and the existing arrow-key nav. Tests cover render/cursor-inside
for each, the range-trim, and isTableSource.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ddutchie, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bc10d332-8af8-4491-90c3-1f0a5e616a1f

📥 Commits

Reviewing files that changed from the base of the PR and between a1be9a1 and 48d6161.

📒 Files selected for processing (3)
  • src/components/notes/NoteMarkdownPreview.tsx
  • src/components/notes/note-editor-utils.test.ts
  • src/components/notes/note-editor-utils.ts
📝 Walkthrough

Walkthrough

The editor adds inline previews for code, tables, math, and Mermaid blocks. It separates Edit, Read, and Live Preview controls, adds cursor-following previews, persists preview size, and fixes callout, note-switching, and task-list behavior.

Changes

Live Preview Editor

Layer / File(s) Summary
Shared block preview widgets
src/lib/block-preview-widget.tsx, src/lib/callout-widget.tsx, src/lib/code-block-widget.tsx, src/lib/math-block-widget.tsx, src/lib/table-block-widget.tsx
A shared CodeMirror widget lifecycle renders and measures callouts, code, tables, and math blocks.
Generic Live Preview flow
src/lib/livePreview.ts, src/lib/livePreview.component.test.tsx, vitest.setup.components.ts
Live Preview detects supported blocks, suppresses conflicting decorations, supports cursor navigation into folded blocks, and tests widget lifecycle and parsing behavior.
Editor modes and cursor previews
src/components/notes/note-editor-utils.ts, src/components/notes/note-editor.tsx, src/components/notes/markdown-editor.tsx, src/lib/constants.ts
The editor uses Edit and Read modes, persists Live Preview separately, extracts structured blocks at the cursor, resets previews when notes change, and defers Live Preview reconfiguration.
Preview sizing and Markdown rendering
src/components/notes/MDPreviewPanel.tsx, src/components/notes/NoteMarkdownPreview.tsx, src/app/globals.css, src/components/notes/*.test.tsx, changelogs/v2.6.3.md, scripts/features.config.js
Preview sizing and typography scaling are persisted. Inline Markdown rendering omits pane chrome. Task-list checkboxes use controlled read-only rendering. Tests and release metadata cover the changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NoteEditor
  participant MarkdownEditor
  participant LivePreview
  participant BlockPreviewWidget
  participant NoteMarkdownPreview
  NoteEditor->>MarkdownEditor: Enable Live Preview
  MarkdownEditor->>LivePreview: Install block decorations
  LivePreview->>BlockPreviewWidget: Create preview widget
  BlockPreviewWidget->>NoteMarkdownPreview: Render Markdown block
  BlockPreviewWidget-->>LivePreview: Report size changes
Loading

Possibly related PRs

  • ddutchie/cairn#92: Both changes modify the Live Preview editor plumbing and widget behavior.
  • ddutchie/cairn#113: Both changes modify callout widget and Live Preview behavior.
  • ddutchie/cairn#102: Both changes modify task-list checkbox rendering in NoteMarkdownPreview.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: inline Live Preview rendering and the new Edit/Read modes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ddutchie/editingfixes

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/components/notes/markdown-editor.tsx (1)

404-416: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Skip the redundant reconfigure on first mount.

The mount effect at Line 369 already initializes the compartment with livePreviewEnabled ? livePreview() : []. This effect then runs once on mount and dispatches a second reconfigure with a new, non-identical extension array. CodeMirror compares extensions by identity, so it tears down and rebuilds the whole live-preview extension set right after the first render. Every block widget unmounts and remounts, and Mermaid and math widgets restart their async rendering.

Track the applied value and dispatch only on a real change.

♻️ Proposed refactor
+    const appliedLivePreview = useRef(livePreviewEnabled);
     useEffect(() => {
+      if (appliedLivePreview.current === livePreviewEnabled && viewRef.current) return;
       let cancelled = false;
       queueMicrotask(() => {
         const view = viewRef.current;
         if (cancelled || !view) return;
+        appliedLivePreview.current = livePreviewEnabled;
         view.dispatch({
           effects: livePreviewCompartment.current.reconfigure(
             livePreviewEnabled ? livePreview() : [],
           ),
         });
       });
       return () => { cancelled = true; };
     }, [livePreviewEnabled]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/notes/markdown-editor.tsx` around lines 404 - 416, Update the
live-preview effect around livePreviewCompartment to track the value already
applied during mount initialization and skip the queued reconfigure when
livePreviewEnabled has not changed. Dispatch a compartment reconfigure only
after a real enabled-state transition, while preserving cancellation and the
existing livePreviewEnabled/on-off extension selection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/notes/note-editor.tsx`:
- Around line 61-77: Update the state initialization around NOTE_EDITOR_MODE_KEY
and NOTE_LIVE_PREVIEW_KEY so a legacy saved "raw" mode migrates to edit with
livePreviewOn initialized to false when the new preference is absent; otherwise
preserve the stored live-preview value and the default-on behavior. Add a
regression test covering this raw-mode migration and preference initialization.

In `@src/lib/livePreview.ts`:
- Around line 126-181: Optimize findBlockWidgets to avoid repeated full-document
scans: cache its BlockWidget[] result per EditorState with a WeakMap so
buildBlockWidgetDecorations, blockWidgetLineSet, and foldedBlockFromCursor reuse
the same computation within an update cycle. Preserve existing widget behavior
and ensure the cache is keyed by the exact EditorState instance; measure
performance with a 5000+ line document.
- Around line 530-541: Update moveIntoBlock so the target line uses
block.lineEnd when dir === -1 and block.lineStart otherwise. Keep the existing
dispatch, unfolding behavior, and return handling unchanged.

---

Nitpick comments:
In `@src/components/notes/markdown-editor.tsx`:
- Around line 404-416: Update the live-preview effect around
livePreviewCompartment to track the value already applied during mount
initialization and skip the queued reconfigure when livePreviewEnabled has not
changed. Dispatch a compartment reconfigure only after a real enabled-state
transition, while preserving cancellation and the existing
livePreviewEnabled/on-off extension selection.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b59fe7b3-905f-41d1-b8ea-468b93067bf3

📥 Commits

Reviewing files that changed from the base of the PR and between a43c31a and 7025379.

📒 Files selected for processing (20)
  • changelogs/v2.6.3.md
  • scripts/features.config.js
  • src/app/globals.css
  • src/components/notes/MDPreviewPanel.component.test.tsx
  • src/components/notes/MDPreviewPanel.tsx
  • src/components/notes/NoteMarkdownPreview.component.test.tsx
  • src/components/notes/NoteMarkdownPreview.tsx
  • src/components/notes/markdown-editor.tsx
  • src/components/notes/note-editor-utils.test.ts
  • src/components/notes/note-editor-utils.ts
  • src/components/notes/note-editor.tsx
  • src/lib/block-preview-widget.tsx
  • src/lib/callout-widget.tsx
  • src/lib/code-block-widget.tsx
  • src/lib/constants.ts
  • src/lib/livePreview.component.test.tsx
  • src/lib/livePreview.ts
  • src/lib/math-block-widget.tsx
  • src/lib/table-block-widget.tsx
  • vitest.setup.components.ts

Comment thread src/components/notes/note-editor.tsx Outdated
Comment thread src/lib/livePreview.ts
Comment thread src/lib/livePreview.ts
…arrow near-edge

- note-editor: a legacy saved 'raw' mode now migrates to Edit with Live Preview
  OFF (was defaulting on, losing the user's raw preference) when no explicit
  Live Preview preference exists. Extracted migrateEditorMode /
  initialLivePreviewOn as pure helpers + regression tests.
- livePreview: cache findBlockWidgets per EditorState (WeakMap) so the
  StateField, ViewPlugin, and arrow keymap don't each re-scan the full syntax
  tree within an update cycle (2–3 scans → 1 per state; states are immutable so
  instance-keyed caching is safe/self-evicting).
- moveIntoBlock: enter a folded block at its NEAR edge — lineEnd when moving up,
  lineStart when moving down — instead of always jumping to the first line.
- markdown-editor: skip the Live Preview compartment reconfigure when the
  enabled state hasn't changed since what's applied (notably on mount), avoiding
  a redundant decoration rebuild + widget remount.
…, inline chrome

Fixes reported for tables/callouts in the Live Preview editor:

- Huge empty gap above tables/callouts: the widget lives inside
  .cm-content.cm-lineWrapping (CM sets white-space: pre-wrap), which was
  inherited by the widget's prose-cairn. rehypeRaw leaves structural newlines
  between blocks as text nodes; under pre-wrap those rendered as real blank
  lines. Reset white-space: normal on the .cm-lp-block container so inter-block
  whitespace collapses like read mode. (Read mode was unaffected — its preview
  isn't inside .cm-content.)
- flushSync crash opening a note with a table / toggling Live Preview: dropped
  flushSync in the widget toDOM entirely (illegal when CM calls toDOM from
  inside a React commit). Height now settles via the ResizeObserver +
  estimatedHeight=-1.
- Callout/table/math rendered differently from read mode: NoteMarkdownPreview
  gains an  mode that drops the full-pane chrome (px-6 py-5, h-full,
  overflow) and zeroes first/last child margins, so embedded blocks sit compact.
- Trim widget ranges of trailing blank lines + trim raw before rendering.
- markdown-editor: skip the Live Preview reconfigure when unchanged (mount).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/notes/note-editor-utils.ts (1)

159-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make fenced-code closing fences match CommonMark rules.

extractStructuredBlockAtOffset currently accepts closing fences at FENCE_RE boundaries, so it will include shorter fences and mixed marker fences. Require the same marker character and a closing fence length ≥ the opener length before selecting the fenced-code block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/notes/note-editor-utils.ts` around lines 159 - 193, Update the
fenced-code handling in extractStructuredBlockAtOffset so a closing fence
matches CommonMark rules: require the same marker character as the opener and a
fence length at least as long as the opener before closing the block. Track the
opener’s full fence length alongside openFence, and only select the block when
the closing candidate satisfies both conditions; preserve unclosed-opener
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/notes/NoteMarkdownPreview.tsx`:
- Around line 195-201: Update the empty-content early return in
NoteMarkdownPreview so its container uses the same inline-dependent layout as
the main preview div: inline mode must avoid the standalone h-full and p-8
styling, while non-inline mode preserves the existing full-height padded layout.

---

Outside diff comments:
In `@src/components/notes/note-editor-utils.ts`:
- Around line 159-193: Update the fenced-code handling in
extractStructuredBlockAtOffset so a closing fence matches CommonMark rules:
require the same marker character as the opener and a fence length at least as
long as the opener before closing the block. Track the opener’s full fence
length alongside openFence, and only select the block when the closing candidate
satisfies both conditions; preserve unclosed-opener behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d82d55a3-d33c-468d-b012-6a07538713b5

📥 Commits

Reviewing files that changed from the base of the PR and between 7025379 and a1be9a1.

📒 Files selected for processing (12)
  • src/components/notes/NoteMarkdownPreview.component.test.tsx
  • src/components/notes/NoteMarkdownPreview.tsx
  • src/components/notes/markdown-editor.tsx
  • src/components/notes/note-editor-utils.test.ts
  • src/components/notes/note-editor-utils.ts
  • src/components/notes/note-editor.tsx
  • src/lib/block-preview-widget.tsx
  • src/lib/callout-widget.tsx
  • src/lib/livePreview.component.test.tsx
  • src/lib/livePreview.ts
  • src/lib/math-block-widget.tsx
  • src/lib/table-block-widget.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/components/notes/markdown-editor.tsx
  • src/lib/table-block-widget.tsx
  • src/lib/callout-widget.tsx
  • src/lib/math-block-widget.tsx
  • src/lib/livePreview.ts
  • src/components/notes/note-editor.tsx
  • src/components/notes/NoteMarkdownPreview.component.test.tsx
  • src/lib/livePreview.component.test.tsx

Comment thread src/components/notes/NoteMarkdownPreview.tsx
- NoteMarkdownPreview: the empty-content early return now honours inline mode
  (drops h-full/p-8) instead of always using the standalone padded layout.
- extractStructuredBlockAtOffset: a fenced-code block only closes on a fence
  that uses the SAME marker char AND is at least as long as the opener
  (CommonMark). A shorter/embedded same-char fence is content, not a close —
  fixes early-closing for nested code fences. Tracks the opener's fence length;
  unclosed-opener behaviour preserved. + regression test.
@ddutchie
ddutchie merged commit 3e44de7 into main Aug 5, 2026
6 checks passed
@ddutchie
ddutchie deleted the ddutchie/editingfixes branch August 5, 2026 14:08
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