Skip to content

fix(editor): persist a dragged table column width as a sidecar marker - #1949

Merged
h4yfans merged 6 commits into
mainfrom
table-column-widths-persist
Sep 2, 2026
Merged

fix(editor): persist a dragged table column width as a sidecar marker#1949
h4yfans merged 6 commits into
mainfrom
table-column-widths-persist

Conversation

@h4yfans

@h4yfans h4yfans commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Closes #1936. Part of #1934.

Why

Resizing a table column works, and nothing carries the result out of the session. BlockNote keeps a dragged width in the table content's columnWidths, GFM has no syntax for it, and blocksToMarkdownLossy writes | a | b |, so the width is gone the next time the note is parsed. Markdown is the storage, so the width needs somewhere on disk to live.

It goes next to the table as a comment line, the same shape the cell colour marker already uses. #1937 turned that pattern into a registry in packages/shared/src/block-markers.ts, and both serializers read and write through it, so this is a fourth registry entry rather than a fourth pair of code paths. Living in the note body also means the width syncs with the note, with no new synced type and no device-local state to go stale.

Scope

packages/shared/src/block-markers.ts gains TableLayout, extractTableLayout, serializeTableLayoutMarker, parseTableLayoutMarker and applyTableLayout, plus the registry entry that puts the marker after table-colors in on-disk order.

<!-- table-layout:{"columnWidths":[120,null]} -->
| Name | Status |
| ---- | ------ |
| Ship | Done   |

One slot per column. A column nobody has resized is null, and a table whose slots are all null writes no marker, so a table already in a vault keeps its bytes.

On read the parsed table's own column count wins, not the marker's. A marker naming fewer columns is padded with the widths BlockNote chose, one naming more is truncated. A columnWidths that is not an array of positive finite numbers or nulls is not claimed as a marker at all and stays a foreign comment.

Row heights are out, and not by omission. BlockNote's table content holds columnWidths, headerRows and headerCols, and nothing about row sizes, so there is no row height in the block model to persist. The issue title names both; only the column half exists to fix.

Tradeoffs

The width is visible to anyone reading the raw .md. The alternative, a per-note view-state record beside the note, keeps the file clean but makes the width device-local until it becomes a synced type, and leaves it to go stale when the table is edited. The marker travels with the note for free and reconciles against the table it sits above.

An older app version reading a marked file drops the comment on its next save and shows the table at default widths, which is the same degrade the colour and alignment markers already have.

Blast Radius

Both serializers, for tables only. A table nobody has resized writes no marker, so no note in any existing vault changes bytes on its next save; the golden vault fixture holds an untouched table next to a marked one to keep that true. The registry entry is additive, so the colour and alignment markers keep their bytes and their order.

apps/mobile/src/editor/generated/editor-web-asset.ts is a rebuild, not an edit. editor:check stamps the packed bundle against its sources and the shared registry is one of them.

Verification

pnpm --filter @memry/desktop test:main on blocknote-converter.test.ts, 6 of the 6 new #1936 cases green. With the registry entry removed by hand, 5 of the 6 go red and the "leaves a table nobody has resized byte-identical" guard stays green, which is the shape the fix should have.

Round-trip conformance and the golden vault on both pipelines: blocknote-converter.roundtrip.test.ts and byte-preservation.golden.test.ts 131 passed, renderer markdown-utils.test.ts and roundtrip-conformance.test.ts 114 passed with the 2 pre-existing expected failures. --project shared 2764 passed.

E2E table-column-widths-persist.e2e.ts green in 19.5s against a production build: drag the width, wait for the marker to reach the vault file, switch to another note and back, relaunch the app, then measure the rendered header cell so a width that reaches the block model but never reaches the layout still fails. This is the one check here that walks the whole user-facing path.

pnpm typecheck and pnpm lint clean. pnpm docs:impact --base origin/main --strict reports covered, pnpm docs:build builds.

… markdown

Red on both serializers. A table whose first column was dragged wider
serializes to plain GFM, and the width is gone the next time the note is
parsed. The same cases also pin the two ways a marker can disagree with
the table it sits above, a column added and a column removed, so the
degrade path is fixed before the marker exists.
A fourth entry in the marker registry, so both serializers pick it up
with no change of their own. `extractTableLayout` writes only a table
somebody has resized, which keeps every table already in a vault byte
identical, and `applyTableLayout` reconciles against the parsed table's
own column count so a marker that no longer fits pads or truncates
instead of corrupting the note.

BlockNote's table content has no row height, so column widths are the
whole of what there is to store.
… vault

Two corpus cases, one table carrying a width and one carrying cell
colours as well, which pins the on-disk order of the two table markers
on both pipelines at once. The golden vault fixture holds the same pair
next to an untouched table, so a regression that starts writing a marker
for every table fails on bytes.
Drives the width through the live editor, waits for the marker to land
in the vault file, then reopens the note and relaunches the app. The
last assertion measures the rendered header cell, so a width that reads
back into the block model but never reaches the layout still fails.
Records the on-disk shape, the registry order against the cell colour
marker, the pad and truncate rule, and that row heights are absent
because the editor has no row height to store.
`editor:check` compares the packed bundle against a stamp over its
sources, and the shared marker registry is one of them.
@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation test labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 90d22b3.

@h4yfans
h4yfans marked this pull request as ready for review September 2, 2026 11:09
@h4yfans
h4yfans merged commit 45c4eb1 into main Sep 2, 2026
20 checks passed
@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Table row and column sizes never survive leaving the note

1 participant