Skip to content

fix(deepnote): prevent endless save loop and backslash accumulation in text blocks#413

Merged
tkislan merged 2 commits into
mainfrom
tk/fix-deepnote-save-loop
Jun 11, 2026
Merged

fix(deepnote): prevent endless save loop and backslash accumulation in text blocks#413
tkislan merged 2 commits into
mainfrom
tk/fix-deepnote-save-loop

Conversation

@tkislan

@tkislan tkislan commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes an endless save loop and progressive backslash accumulation when saving .deepnote notebooks 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, stripMarkdown removed the type prefix and trimmed but never unescaped, so each save grew backslashes without bound (a_ba\_ba\\\_b → …).

Added a module-scope unescapeMarkdown — the exact inverse of the library's escapeMarkdown (identical character class) — 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. 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 counter Map with a one-shot Set (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/blocks library: a literal-backslash tripwire, double-round-trip stability, todo checked, bullet indent_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

    • Fixed markdown character escaping/unescaping in text blocks to ensure proper round-trip conversion
    • Improved file change detection to properly normalize and track self-write markers
  • Tests

    • Added comprehensive round-trip conversion tests for Deepnote notebook blocks with special characters
    • Added tests for normalized self-write marker handling in file change watcher

…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

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0%. Comparing base (9221e13) to head (d98dc52).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@     Coverage Diff     @@
##   main   #413   +/-   ##
===========================
===========================
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@tkislan

tkislan commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a2a5eb5f-75d9-429c-aa51-85dbe85ec5ce

📥 Commits

Reviewing files that changed from the base of the PR and between 923ec53 and d1b88f3.

📒 Files selected for processing (6)
  • src/notebooks/deepnote/blocks.md
  • src/notebooks/deepnote/converters/textBlockConverter.ts
  • src/notebooks/deepnote/converters/textBlockConverter.unit.test.ts
  • src/notebooks/deepnote/deepnoteDataConverter.unit.test.ts
  • src/notebooks/deepnote/deepnoteFileChangeWatcher.ts
  • src/notebooks/deepnote/deepnoteFileChangeWatcher.unit.test.ts

📝 Walkthrough

Walkthrough

This 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 stripMarkdown() with an unescapeMarkdown() helper to restore escape sequences that @deepnote/blocks produces, ensuring Deepnote text blocks survive conversion to/from VS Code cells. The watcher refactoring replaces per-URI reference-counting with normalized-key set membership, allowing multiple query-URI notebooks from the same file to coalesce under a single self-write marker. Both changes include unit tests validating round-trip stability and edge cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses both main defects: markdown escaping idempotency (backslash accumulation) and self-write URI normalization (endless save loop) that the PR objectives describe.
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.
Updates Docs ✅ Passed Primary OSS documentation (blocks.md) updated with unescapeMarkdown helper and round-trip logic. Private repo roadmap updates cannot be verified.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Stopped 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 @coderabbit review after the pipeline has finished.


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

@tkislan tkislan marked this pull request as ready for review June 11, 2026 06:00
@tkislan tkislan requested a review from a team as a code owner June 11, 2026 06:00
@tkislan tkislan merged commit ac66c74 into main Jun 11, 2026
13 checks passed
@tkislan tkislan deleted the tk/fix-deepnote-save-loop branch June 11, 2026 08:21
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.

2 participants