Feat/formula editor md example - #2932
Conversation
…move insertAtCaret
…kage.json regen - The formula-confirm button's CSS selector still targeted the old data-testid attribute after Task 4 renamed it to data-test, silently breaking the confirm button's styling. - docs/package.json/pnpm-lock.yaml regenerated via `gen:docs` to add katex/mathlive/@types/katex deps needed for the docs example preview.
… and slash-menu scroll fix
|
Someone is attempting to deploy a commit to the TypeCell Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThe Markdown interoperability example becomes an editable formula editor with KaTeX and MathLive support. It adds formula schema nodes, Markdown import conversion, toolbar and modal editing, chemistry palettes, localized documentation, slash-menu styling, and end-to-end coverage. ChangesFormula editor example
Estimated code review effort: 4 (Complex) | ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment Warning |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
tests/src/end-to-end/formula/formula.test.tsx (1)
159-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFixed
sleep()calls risk CI flakiness; prefer polling on the actual DOM condition.Several tests (lines 159, 215, 261, 306, 319) use hardcoded
sleep(N)to wait for async state to settle instead of thewaitForSelector-style polling already used elsewhere in this file. Under CI load these fixed delays can be too short (flaky failures) or unnecessarily slow the suite.♻️ Example: replace a fixed sleep with a condition-based wait
- await userEvent.type(textarea, "\\frac{1}{2}"); - await sleep(150); + await userEvent.type(textarea, "\\frac{1}{2}"); + await vi.waitFor(() => expect(textarea.value).toBe("\\frac{1}{2}"));Apply the same pattern (wait for the specific expected DOM/value change) at the other
sleep()call sites.Also applies to: 215-215, 261-261, 306-306, 319-319
🤖 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 `@tests/src/end-to-end/formula/formula.test.tsx` at line 159, Replace the hardcoded sleep calls at the listed test sites with condition-based polling using the file’s existing waitForSelector-style pattern. Wait for each test’s specific expected DOM or value change before continuing, preserving the existing assertions and behavior while removing fixed timing dependencies.examples/05-interoperability/04-converting-blocks-from-md/src/markdown/postprocessBlocks.ts (1)
50-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSimplify by dropping the shared global-regex
.test()step.
matchWholeBlockTokencalls the module-level globalBLOCK_TOKEN_REGEX.test(), relies on itslastIndexmutation, then manually resets it before re-matching with a separate anchored regex. The anchored match alone is sufficient — no need to touch the shared, stateful regex at all, which removes a subtle footgun for future refactors (e.g. if this helper is ever invoked concurrently/reentrantly).♻️ Proposed simplification
function matchWholeBlockToken( block: AnyBlock, blockMap: Map<string, string>, ): string | null { if (!Array.isArray(block.content) || block.content.length !== 1) return null; const first = block.content[0] as { type?: string; text?: string }; if (first.type !== "text" || typeof first.text !== "string") return null; const text = first.text.trim(); - if (!BLOCK_TOKEN_REGEX.test(text)) return null; - // Reset regex state and re-match to grab the exact token - BLOCK_TOKEN_REGEX.lastIndex = 0; const m = text.match(/^⟪FML_BLOCK_\d+⟫$/); if (!m) return null; return blockMap.get(m[0]) ?? null; }🤖 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 `@examples/05-interoperability/04-converting-blocks-from-md/src/markdown/postprocessBlocks.ts` around lines 50 - 64, In matchWholeBlockToken, remove the shared BLOCK_TOKEN_REGEX.test() check and its lastIndex reset. Use the existing anchored match as the sole token validation, then continue returning the matching blockMap entry.
🤖 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 `@examples/05-interoperability/04-converting-blocks-from-md/src/App.tsx`:
- Around line 50-73: Update the inline formula editing flow spanning
FormulaInlineView, openEdit, FormulaTarget, and App.tsx's onUpdate so the target
carries stable identity or position information for the specific node. Use that
identifier to replace only the intended block.content entry, rather than
matching every formula by identical latex text; preserve block-level updates
unchanged.
In
`@examples/05-interoperability/04-converting-blocks-from-md/src/formula/FormulaEditorModal.tsx`:
- Around line 61-76: Update isConfirmDisabled in FormulaEditorModal to validate
the sanitized latex value, matching confirm’s sanitizePlaceholders(latex) check.
Keep the existing confirm behavior and ensure placeholder-only templates disable
the Confirm button before submission.
- Around line 80-122: Update the formula modal component around the dialog
container to add aria-modal="true", move initial focus into the modal when it
opens, trap Tab and Shift+Tab within its interactive elements, and close it on
Escape. Restore focus to the triggering element when api.close is invoked, while
preserving the existing backdrop and inner-click behavior.
In
`@examples/05-interoperability/04-converting-blocks-from-md/src/formula/MathFieldWrapper.tsx`:
- Around line 43-47: Update sanitizePlaceholders to remove complete \placeholder
groups with nested balanced braces, replacing the current non-nested regex with
a small scanner that tracks brace depth and skips each placeholder’s entire
argument. Preserve all non-placeholder LaTeX content and continue handling
multiple placeholders in the same input.
In `@examples/05-interoperability/04-converting-blocks-from-md/src/schema.tsx`:
- Around line 28-32: Update the formula editing flow around the inline editor
opener and FormulaTarget to retain a stable inline/range identity alongside the
LaTeX value. Pass that identity through formulaInline and use it in onUpdate to
replace only the targeted formula node, rather than matching every formulaInline
with identical props.latex.
---
Nitpick comments:
In
`@examples/05-interoperability/04-converting-blocks-from-md/src/markdown/postprocessBlocks.ts`:
- Around line 50-64: In matchWholeBlockToken, remove the shared
BLOCK_TOKEN_REGEX.test() check and its lastIndex reset. Use the existing
anchored match as the sole token validation, then continue returning the
matching blockMap entry.
In `@tests/src/end-to-end/formula/formula.test.tsx`:
- Line 159: Replace the hardcoded sleep calls at the listed test sites with
condition-based polling using the file’s existing waitForSelector-style pattern.
Wait for each test’s specific expected DOM or value change before continuing,
preserving the existing assertions and behavior while removing fixed timing
dependencies.
🪄 Autofix (Beta)
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fb8adfe6-18e1-4234-90ca-7bcb1d41471b
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (27)
docs/package.jsondocs/superpowers/plans/2026-07-29-formula-editor-for-md-example.mddocs/superpowers/plans/2026-07-30-formula-editor-wysiwyg-and-slash-menu-fix.mddocs/superpowers/specs/2026-07-29-formula-editor-for-md-example-design.mddocs/superpowers/specs/2026-07-30-formula-editor-wysiwyg-and-slash-menu-fix-design.mdexamples/05-interoperability/04-converting-blocks-from-md/.bnexample.jsonexamples/05-interoperability/04-converting-blocks-from-md/README.mdexamples/05-interoperability/04-converting-blocks-from-md/index.htmlexamples/05-interoperability/04-converting-blocks-from-md/package.jsonexamples/05-interoperability/04-converting-blocks-from-md/src/App.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/formula/FormulaBlock.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/formula/FormulaEditorModal.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/formula/FormulaInline.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/formula/MathFieldWrapper.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/formula/formulaContext.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/formula/katexRenderer.tsexamples/05-interoperability/04-converting-blocks-from-md/src/formula/palettes.tsexamples/05-interoperability/04-converting-blocks-from-md/src/markdown/initialMarkdown.tsexamples/05-interoperability/04-converting-blocks-from-md/src/markdown/postprocessBlocks.tsexamples/05-interoperability/04-converting-blocks-from-md/src/markdown/preprocessMarkdown.tsexamples/05-interoperability/04-converting-blocks-from-md/src/schema.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/styles.cssexamples/05-interoperability/04-converting-blocks-from-md/src/toolbar/CustomFormattingToolbar.tsxexamples/05-interoperability/04-converting-blocks-from-md/src/toolbar/FormulaButton.tsxplayground/src/examples.gen.tsxtests/src/end-to-end/formula/formula.test.tsxtests/src/examples.d.ts
| onUpdate(target, latex) { | ||
| if (target.kind === "block") { | ||
| editor.updateBlock(target.blockId, { | ||
| type: "formulaBlock", | ||
| props: { latex }, | ||
| } as any); | ||
| } else { | ||
| // Replace the inline node at the current selection. The simplest reliable | ||
| // path: replace the whole inline content of the containing block by | ||
| // rewriting the inline content list. | ||
| const block = editor.getTextCursorPosition().block; | ||
| if (!Array.isArray(block.content)) return; | ||
| const nextContent = block.content.map((node: any) => { | ||
| if ( | ||
| node.type === "formulaInline" && | ||
| node.props?.latex === target.latex | ||
| ) { | ||
| return { ...node, props: { latex } }; | ||
| } | ||
| return node; | ||
| }); | ||
| editor.updateBlock(block, { content: nextContent } as any); | ||
| } | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Inline formula update can silently overwrite sibling formulas with identical LaTeX.
onUpdate's inline branch matches nodes to replace purely by node.props?.latex === target.latex. If a block contains two or more inline formulas with the same LaTeX (e.g. the same $x$ referenced twice in one paragraph), editing one via FormulaInlineView's "click to edit" path (see the comment in FormulaButton.tsx acknowledging this route bypasses the "sole content" restriction) will rewrite every matching node in that block, not just the one the user intended to edit.
Root cause: FormulaTarget for the "inline" kind carries no positional/identity information (only { kind, latex }), so onUpdate has no way to disambiguate which specific node was targeted when duplicates exist.
Consider threading through a stable identifier (e.g. the node's index within block.content, or a generated id stored in props) from wherever openEdit is invoked for inline formulas, and using that to target the exact node instead of latex-text equality.
🤖 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 `@examples/05-interoperability/04-converting-blocks-from-md/src/App.tsx` around
lines 50 - 73, Update the inline formula editing flow spanning
FormulaInlineView, openEdit, FormulaTarget, and App.tsx's onUpdate so the target
carries stable identity or position information for the specific node. Use that
identifier to replace only the intended block.content entry, rather than
matching every formula by identical latex text; preserve block-level updates
unchanged.
| const isConfirmDisabled = latex.trim().length === 0; | ||
|
|
||
| const insertItem = (item: PaletteItem) => { | ||
| mfRef.current?.insert(item.insert); | ||
| }; | ||
|
|
||
| const confirm = () => { | ||
| const finalLatex = sanitizePlaceholders(latex); | ||
| if (finalLatex.trim().length === 0) return; | ||
| if (state.mode === "edit" && state.editTarget) { | ||
| handlers.onUpdate(state.editTarget, finalLatex); | ||
| } else { | ||
| handlers.onInsert(kind, finalLatex); | ||
| } | ||
| api.close(); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Confirm button can silently no-op on placeholder-only input.
isConfirmDisabled checks raw latex, but confirm() validates sanitizePlaceholders(latex). If a user inserts a palette template (e.g. \frac{#?}{#?}) and never fills the placeholders, the button stays enabled, but clicking it hits the early return at line 69 with no feedback — the modal doesn't close and nothing is inserted/updated.
🔧 Suggested fix: align the disabled check with the sanitized value
- const isConfirmDisabled = latex.trim().length === 0;
+ const isConfirmDisabled = sanitizePlaceholders(latex).trim().length === 0;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const isConfirmDisabled = latex.trim().length === 0; | |
| const insertItem = (item: PaletteItem) => { | |
| mfRef.current?.insert(item.insert); | |
| }; | |
| const confirm = () => { | |
| const finalLatex = sanitizePlaceholders(latex); | |
| if (finalLatex.trim().length === 0) return; | |
| if (state.mode === "edit" && state.editTarget) { | |
| handlers.onUpdate(state.editTarget, finalLatex); | |
| } else { | |
| handlers.onInsert(kind, finalLatex); | |
| } | |
| api.close(); | |
| }; | |
| const isConfirmDisabled = sanitizePlaceholders(latex).trim().length === 0; | |
| const insertItem = (item: PaletteItem) => { | |
| mfRef.current?.insert(item.insert); | |
| }; | |
| const confirm = () => { | |
| const finalLatex = sanitizePlaceholders(latex); | |
| if (finalLatex.trim().length === 0) return; | |
| if (state.mode === "edit" && state.editTarget) { | |
| handlers.onUpdate(state.editTarget, finalLatex); | |
| } else { | |
| handlers.onInsert(kind, finalLatex); | |
| } | |
| api.close(); | |
| }; |
🤖 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
`@examples/05-interoperability/04-converting-blocks-from-md/src/formula/FormulaEditorModal.tsx`
around lines 61 - 76, Update isConfirmDisabled in FormulaEditorModal to validate
the sanitized latex value, matching confirm’s sanitizePlaceholders(latex) check.
Keep the existing confirm behavior and ensure placeholder-only templates disable
the Confirm button before submission.
| return ( | ||
| <div className="formula-modal-backdrop" onClick={api.close}> | ||
| <div | ||
| className="formula-modal" | ||
| role="dialog" | ||
| aria-label="Soạn thảo công thức" | ||
| onClick={(e) => e.stopPropagation()} | ||
| > | ||
| <div className="formula-modal-header"> | ||
| <h2>Soạn thảo công thức</h2> | ||
| <div | ||
| className="formula-modal-mode" | ||
| role="tablist" | ||
| aria-label="Chế độ" | ||
| > | ||
| <button | ||
| type="button" | ||
| role="tab" | ||
| aria-selected={mode === "basic"} | ||
| data-test="formula-mode-basic" | ||
| onClick={() => setMode("basic")} | ||
| > | ||
| Cơ bản | ||
| </button> | ||
| <button | ||
| type="button" | ||
| role="tab" | ||
| aria-selected={mode === "advanced"} | ||
| data-test="formula-mode-advanced" | ||
| onClick={() => setMode("advanced")} | ||
| > | ||
| Nâng cao | ||
| </button> | ||
| </div> | ||
| <button | ||
| type="button" | ||
| className="formula-modal-close" | ||
| onClick={api.close} | ||
| aria-label="Đóng" | ||
| > | ||
| × | ||
| </button> | ||
| </div> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Modal lacks aria-modal, focus trapping, and Escape-to-close.
The dialog has role="dialog" and aria-label, but no aria-modal="true", no initial-focus management, and no keyboard trap — keyboard/screen-reader users can Tab out into the underlying editor while the modal is "open," and there's no Escape shortcut to dismiss it.
🔧 Suggested minimal fix
<div
className="formula-modal"
role="dialog"
+ aria-modal="true"
aria-label="Soạn thảo công thức"
onClick={(e) => e.stopPropagation()}
+ onKeyDown={(e) => e.key === "Escape" && api.close()}
>Combine with a focus trap (e.g. focus the first interactive element on open, restore focus to the trigger on close) for full keyboard support.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ( | |
| <div className="formula-modal-backdrop" onClick={api.close}> | |
| <div | |
| className="formula-modal" | |
| role="dialog" | |
| aria-label="Soạn thảo công thức" | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| <div className="formula-modal-header"> | |
| <h2>Soạn thảo công thức</h2> | |
| <div | |
| className="formula-modal-mode" | |
| role="tablist" | |
| aria-label="Chế độ" | |
| > | |
| <button | |
| type="button" | |
| role="tab" | |
| aria-selected={mode === "basic"} | |
| data-test="formula-mode-basic" | |
| onClick={() => setMode("basic")} | |
| > | |
| Cơ bản | |
| </button> | |
| <button | |
| type="button" | |
| role="tab" | |
| aria-selected={mode === "advanced"} | |
| data-test="formula-mode-advanced" | |
| onClick={() => setMode("advanced")} | |
| > | |
| Nâng cao | |
| </button> | |
| </div> | |
| <button | |
| type="button" | |
| className="formula-modal-close" | |
| onClick={api.close} | |
| aria-label="Đóng" | |
| > | |
| × | |
| </button> | |
| </div> | |
| return ( | |
| <div className="formula-modal-backdrop" onClick={api.close}> | |
| <div | |
| className="formula-modal" | |
| role="dialog" | |
| aria-modal="true" | |
| aria-label="Soạn thảo công thức" | |
| onClick={(e) => e.stopPropagation()} | |
| onKeyDown={(e) => e.key === "Escape" && api.close()} | |
| > | |
| <div className="formula-modal-header"> | |
| <h2>Soạn thảo công thức</h2> | |
| <div | |
| className="formula-modal-mode" | |
| role="tablist" | |
| aria-label="Chế độ" | |
| > | |
| <button | |
| type="button" | |
| role="tab" | |
| aria-selected={mode === "basic"} | |
| data-test="formula-mode-basic" | |
| onClick={() => setMode("basic")} | |
| > | |
| Cơ bản | |
| </button> | |
| <button | |
| type="button" | |
| role="tab" | |
| aria-selected={mode === "advanced"} | |
| data-test="formula-mode-advanced" | |
| onClick={() => setMode("advanced")} | |
| > | |
| Nâng cao | |
| </button> | |
| </div> | |
| <button | |
| type="button" | |
| className="formula-modal-close" | |
| onClick={api.close} | |
| aria-label="Đóng" | |
| > | |
| × | |
| </button> | |
| </div> |
🤖 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
`@examples/05-interoperability/04-converting-blocks-from-md/src/formula/FormulaEditorModal.tsx`
around lines 80 - 122, Update the formula modal component around the dialog
container to add aria-modal="true", move initial focus into the modal when it
opens, trap Tab and Shift+Tab within its interactive elements, and close it on
Escape. Restore focus to the triggering element when api.close is invoked, while
preserving the existing backdrop and inner-click behavior.
| export function sanitizePlaceholders(latex: string): string { | ||
| // Remove unfilled MathLive placeholders like \placeholder{} or \placeholder{...} | ||
| // so downstream KaTeX rendering doesn't fail. | ||
| return latex.replace(/\\placeholder\{[^}]*\}/g, ""); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
sanitizePlaceholders regex doesn't handle nested braces.
/\\placeholder\{[^}]*\}/g can't match balanced nested groups, e.g. \placeholder{\frac{a}{b}} only strips up to the first }, leaving a truncated/invalid LaTeX fragment. renderLatex's catch-all .formula-error output prevents a hard crash, but the formula silently renders as an error instead of the intended stripped-placeholder content.
🔧 Suggested fix using a small balanced-brace scanner
-export function sanitizePlaceholders(latex: string): string {
- // Remove unfilled MathLive placeholders like \placeholder{} or \placeholder{...}
- // so downstream KaTeX rendering doesn't fail.
- return latex.replace(/\\placeholder\{[^}]*\}/g, "");
-}
+export function sanitizePlaceholders(latex: string): string {
+ // Remove unfilled MathLive placeholders like \placeholder{} or \placeholder{...},
+ // including any nested braces, so downstream KaTeX rendering doesn't fail.
+ const marker = "\\placeholder{";
+ let result = "";
+ let i = 0;
+ while (i < latex.length) {
+ const start = latex.indexOf(marker, i);
+ if (start === -1) {
+ result += latex.slice(i);
+ break;
+ }
+ result += latex.slice(i, start);
+ let depth = 1;
+ let j = start + marker.length;
+ while (j < latex.length && depth > 0) {
+ if (latex[j] === "{") depth++;
+ else if (latex[j] === "}") depth--;
+ j++;
+ }
+ i = j;
+ }
+ return result;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function sanitizePlaceholders(latex: string): string { | |
| // Remove unfilled MathLive placeholders like \placeholder{} or \placeholder{...} | |
| // so downstream KaTeX rendering doesn't fail. | |
| return latex.replace(/\\placeholder\{[^}]*\}/g, ""); | |
| } | |
| export function sanitizePlaceholders(latex: string): string { | |
| // Remove unfilled MathLive placeholders like \placeholder{} or \placeholder{...}, | |
| // including any nested braces, so downstream KaTeX rendering doesn't fail. | |
| const marker = "\\placeholder{"; | |
| let result = ""; | |
| let i = 0; | |
| while (i < latex.length) { | |
| const start = latex.indexOf(marker, i); | |
| if (start === -1) { | |
| result += latex.slice(i); | |
| break; | |
| } | |
| result += latex.slice(i, start); | |
| let depth = 1; | |
| let j = start + marker.length; | |
| while (j < latex.length && depth > 0) { | |
| if (latex[j] === "{") depth++; | |
| else if (latex[j] === "}") depth--; | |
| j++; | |
| } | |
| i = j; | |
| } | |
| return result; | |
| } |
🤖 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
`@examples/05-interoperability/04-converting-blocks-from-md/src/formula/MathFieldWrapper.tsx`
around lines 43 - 47, Update sanitizePlaceholders to remove complete
\placeholder groups with nested balanced braces, replacing the current
non-nested regex with a small scanner that tracks brace depth and skips each
placeholder’s entire argument. Preserve all non-placeholder LaTeX content and
continue handling multiple placeholders in the same input.
| onOpenEditor={() => | ||
| editor.openEdit({ | ||
| kind: "inline", | ||
| latex: props.inlineContent.props.latex, | ||
| }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C5 'onUpdate|target\.latex|formulaInline' \
examples/05-interoperability/04-converting-blocks-from-md/src/App.tsx \
examples/05-interoperability/04-converting-blocks-from-md/src/formula/formulaContext.tsx \
examples/05-interoperability/04-converting-blocks-from-md/src/schema.tsxRepository: TypeCellOS/BlockNote
Length of output: 7342
🏁 Script executed:
#!/bin/bash
set -euo pipefail
files=(
examples/05-interoperability/04-converting-blocks-from-md/src/App.tsx
examples/05-interoperability/04-converting-blocks-from-md/src/schema.tsx
examples/05-interoperability/04-converting-blocks-from-md/src/formula/formulaContext.tsx
examples/05-interoperability/04-converting-blocks-from-md/src/formula/FormulaInline.tsx
)
for f in "${files[@]}"; do
echo "===== $f ====="
sed -n '1,180p' "$f"
done
echo "===== package references ====="
fd -a 'package.json|pnpm-lock.yaml|yarn.lock|package-lock.json' examples/05-interoperability/04-converting-blocks-from-md | sed 's#^\./##' | while read -r f; do
echo "--- $f"
rg -n '`@blocknote/react`|`@blocknote/core`|`@blocknote/mantine`|`@blocknote/shadcn`' "$f" || true
doneRepository: TypeCellOS/BlockNote
Length of output: 8224
Use a stable inline-node identity when editing formulas.
formulaInline sends only latex, and onUpdate replaces every formulaInline in the current block whose props.latex matches. Duplicate formulas in the same block will be edited together; store a stable inline/range identity in FormulaTarget and target only that node.
🤖 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 `@examples/05-interoperability/04-converting-blocks-from-md/src/schema.tsx`
around lines 28 - 32, Update the formula editing flow around the inline editor
opener and FormulaTarget to retain a stable inline/range identity alongside the
LaTeX value. Pass that identity through formulaInline and use it in onUpdate to
replace only the targeted formula node, rather than matching every formulaInline
with identical props.latex.
Summary
Rationale
Changes
Impact
Testing
Screenshots/Video
Checklist
Additional Notes
Summary by CodeRabbit
New Features
Documentation
Tests