fix(deepnote): prevent endless save loop and backslash accumulation in text blocks#413
Conversation
…n text blocks Saving a .deepnote notebook containing text blocks (text-cell-*) entered an endless save loop and progressively added backslashes to the text. Two independent defects combined — one caused the corruption, the other made it self-perpetuating. Part 1 — Idempotent text round-trip (textBlockConverter.ts): On open, text blocks render to markdown via createMarkdown, which backslash-escapes a character class (including `\` itself). On save, stripMarkdown removed the type prefix and trimmed but never unescaped, so each save grew backslashes without bound. Add a module-scope unescapeMarkdown — the exact inverse of the library's escapeMarkdown — applied after stripMarkdown at the single choke point, restoring the round-trip invariant. Part 2 — Self-write suppression in the file watcher (deepnoteFileChangeWatcher.ts): Notebooks opened from the Deepnote sidebar carry a ?notebook=<id> query in their URI, but fs events deliver the bare file URI, so the self-write markers (keyed on the raw URI) never matched and the watcher's own saves re-entered the reload pipeline. Add selfWriteKey(uri) (strips query + fragment) used by every mark/consume/snapshot path, and replace the per-URI counter Map with a one-shot Set so coalesced writes cannot leave stale residue that swallows a genuine external change. Tests: round-trip suite through the real @deepnote/blocks library (incl. a literal-backslash tripwire, double-round-trip stability, todo checked, bullet indent_level, whitespace trim); text-cell round-trip in the data converter; and watcher query-URI consumption, coalesced-event and duplicate-event safety. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #413 +/- ##
===========================
===========================
🚀 New features to boost your workflow:
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR refactors two independent concerns: (1) markdown character escaping in text-block round-trip conversion, and (2) self-write URI normalization in the file-change watcher. The escaping fix wraps Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes 🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning Review ran into problems🔥 ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a Comment |
Summary
Fixes an endless save loop and progressive backslash accumulation when saving
.deepnotenotebooks that contain text blocks (text-cell-*). Two independent defects combined — one caused the corruption, the other made it self-perpetuating.Part 1 — Idempotent text round-trip (
textBlockConverter.ts)On open, text blocks render to markdown via
createMarkdown, which backslash-escapes a character class (including\itself). On save,stripMarkdownremoved the type prefix and trimmed but never unescaped, so each save grew backslashes without bound (a_b→a\_b→a\\\_b→ …).Added a module-scope
unescapeMarkdown— the exact inverse of the library'sescapeMarkdown(identical character class) — applied afterstripMarkdownat the single choke point, restoring the round-trip invariant.Part 2 — Self-write suppression in the file watcher (
deepnoteFileChangeWatcher.ts)Notebooks opened from the Deepnote sidebar carry a
?notebook=<id>query in their URI, but fs events deliver the bare file URI. The self-write markers keyed on the raw URI, so the watcher's own saves were never recognized and re-entered the reload pipeline.Added
selfWriteKey(uri)(strips query + fragment), used by every mark/consume/snapshot path, and replaced the per-URI counterMapwith a one-shotSet(mirrors the existing snapshot mechanism) so coalesced writes can't leave stale residue that swallows a genuine external change.Tests
TextBlockConverter— round-trip suite through the real@deepnote/blockslibrary: a literal-backslash tripwire, double-round-trip stability, todochecked, bulletindent_level, and whitespace-trim cases.DeepnoteDataConverter— text-cell round-trip without backslash accumulation.DeepnoteFileChangeWatcher— query-URI self-write consumption, coalesced-event safety, duplicate-event safety.All green: 71 / 26 / 31 passing.
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests