Skip to content

fix(sync): write code block edits to the right block without a regex - #9

Open
yoava wants to merge 2 commits into
a269ch:release/0.1.2from
yoava:fix/codeblock-sync
Open

yoava wants to merge 2 commits into
a269ch:release/0.1.2from
yoava:fix/codeblock-sync

Conversation

@yoava

@yoava yoava commented Oct 19, 2026 •

Copy link
Copy Markdown

Problem

Editing a cell in a table code block can fail to save, or save over the wrong part of the note. TableSyncService.syncCodeBlock builds a regular expression from the block's whole source and runs content.replace with it:

  1. Large tables never save. For a table of about 40 KB, V8 throws Regular expression too large. VaultService.modifyFile catches the error, so the cell shows the new value while the file stays unchanged, with no message.
  2. Editing a later block deletes earlier ones. The pattern starts at the first table fence in the note, so editing the second block replaces everything from the first block down to the second, including the first table and the text between them.
  3. $ in cell text corrupts the note. The replacement is a string, so $`, $& and $' expand. A cell containing `$` (a dollar sign in inline code) ends up holding all the text above the table.
  4. CRLF notes get mixed line endings, and the fallback overwrites the first block whenever the source doesn't match.

All four reproduce in Obsidian 1.12 on Linux.

Fix

  • Fences are found by scanning lines: backtick and tilde fences, closing fences at least as long as the opener, blocks nested in other fences skipped, list indentation and callout > prefixes stripped for matching and put back on write.
  • The target block is the one whose body matches the rendered source. Among identical tables, the line from ctx.getSectionInfo(el) decides, and a block at that line that no longer matches is never swapped for another.
  • Only the body lines are replaced, by splicing (no replacement patterns), and each line keeps its line ending.
  • syncCodeBlock returns whether it wrote. When no single block matches, nothing is written and a Notice appears. The view advances its expected source only after a successful save, and one view's saves run in order, so a quick second edit doesn't race the first.

Behavior change: the opening fence is kept, so tablebase and notion-table blocks are no longer renamed to table.

Known limits: a fence on the same line as a list marker (- ```table) is not recognized, so that save is skipped with a notice. Indented code is not told apart from list continuation.

Tests

npx vitest run: 350 passed. New cases in tests/table-sync-service.test.ts cover a large table, multiple blocks, $ patterns, no match, identical tables with and without a line hint, fences nested in other code blocks, tilde and longer fences, list and callout prefixes, and CRLF. Run against the old service, 10 of its 13 tests fail. The controller tests cover the return value and save ordering.

Checked manually in Obsidian with the built plugin: a 40 KB table saves two edits in a row with only the edited rows changed, editing the second block leaves the first untouched, and a cell with `$` saves literally.

🤖 Generated with Claude Code

Saving a table code block built a regular expression from the block's
whole source. On a large table (about 40 KB) V8 throws "Regular
expression too large", modifyFile swallows it, and the cell shows the
new value while the note never changes. The same pattern started
matching at the first table fence in the note, so editing a later
block replaced everything from the first block down, and
String.replace expanded $`, $& and $' in cell text into other parts of
the note.

Blocks are now found by a line scan of fences (backtick and tilde,
longer closing fences, blocks nested in other fences skipped, list and
callout prefixes kept) and matched on their source, with the
processor's section line telling identical tables apart. Only the body
is replaced and each line keeps its line ending. When no single block
matches, nothing is written and a Notice says so, instead of
overwriting the first table. The view advances its expected source only
after a successful save, and one view's saves run in order.

The opening fence is now kept: tablebase and notion-table blocks are no
longer renamed to table.

Known limits: a fence on the same line as a list marker is not
recognized (the save is skipped with a Notice), and indented code is not
told apart from list continuation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@a269ch
a269ch changed the base branch from main to release/0.1.2 October 19, 2026 23:14
@a269ch a269ch added the bug Something isn't working label Oct 20, 2026
@a269ch
a269ch self-requested a review October 21, 2026 07:07
@a269ch

a269ch commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Hi @yoava! Thank you again for your contribution to TableBase. I really appreciate the work on both PRs and am very glad that you are helping make the project more reliable.

Before merging, please bring both PR #7 and PR #9 in line with the project guidelines:

  • each PR should address one focused task;
  • each PR should contain exactly one commit after squashing;
  • commit messages should follow the format described in CONTRIBUTING.md;
  • the PR description should be concise and follow the pull request template;
  • include the results of npm test and npm run build;
  • remove unrelated changes, generated files, and unnecessary comments.

Please also see the related note in PR #7. Thank you again for helping improve TableBase!

Comment on lines +95 to +97
const lineEnding = lines[block.openLine].endsWith("\r") ? "\r" : "";
const bodyLines = newBody.map((line) => `${block.prefix}${line}${lineEnding}`);
lines.splice(block.openLine + 1, block.closeLine - block.openLine - 1, ...bodyLines);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for improving code block synchronization. Could we preserve the existing line ending for each replaced body line here? Using the opening fence line ending for every new line can normalize a body that contains mixed LF and CRLF endings, creating unrelated changes and noisy diffs. A regression test with mixed line endings would make the intended behavior explicit.

@a269ch a269ch self-assigned this Oct 22, 2026
@a269ch
a269ch force-pushed the release/0.1.2 branch 2 times, most recently from fc93874 to 9542798 Compare October 22, 2026 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants