Recognize Markdown tables on paste and while typing (#339) #344

Merged
fable-5 merged 1 commits from issue-339-markdown-table-paste into main 2026-08-16 05:22:43 +02:00
Collaborator

Root cause of the paste half: the Markdown conversion (#30) already handled tables, but ANY text/html clipboard flavor bypassed it -- and VS Code (copyWithSyntaxHighlighting) ships the plain text a second time as styled div/span HTML. Clipboard HTML without a single structural element is now treated as equivalent to the plain text; rich-text sources keep going through ProseMirror's HTML paste.

  • pasting inside a code block never converts (text is code there)
  • hand-typed tables: Enter at the end of a | --- | --- | separator row below a pipe row converts the two paragraphs into a real table; body rows are then typed cell-wise with Tab appending rows (#338)
  • unit tests for the HTML heuristic, three new e2e cases in the markdown pack

Stacked on #343 (fast-forward chain #340 -> #341 -> #342 -> #343 -> this).

Closes #339

Root cause of the paste half: the Markdown conversion (#30) already handled tables, but ANY text/html clipboard flavor bypassed it -- and VS Code (copyWithSyntaxHighlighting) ships the plain text a second time as styled div/span HTML. Clipboard HTML without a single structural element is now treated as equivalent to the plain text; rich-text sources keep going through ProseMirror's HTML paste. - pasting inside a code block never converts (text is code there) - hand-typed tables: Enter at the end of a `| --- | --- |` separator row below a pipe row converts the two paragraphs into a real table; body rows are then typed cell-wise with Tab appending rows (#338) - unit tests for the HTML heuristic, three new e2e cases in the markdown pack Stacked on #343 (fast-forward chain #340 -> #341 -> #342 -> #343 -> this). Closes #339
fable-5 added 5 commits 2026-08-15 19:12:20 +02:00
Gap cursor for block-edge positions (#335)
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m52s
CI / Build container images (pull_request) Successful in 1m13s
CI / Auth e2e pack (pull_request) Successful in 9m25s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CI / Lint, typecheck, test (push) Has been cancelled
CD / Build and push images (push) Has been cancelled
69563348ca
A table (or any other block node without a text position of its own) as
the page's first, last, or only block was unreachable from before/after:
neither mouse nor arrow keys could place the cursor there, so no
paragraph could be created around it.

- add the prosemirror-gapcursor plugin as a TipTap extension (via
  @tiptap/pm, no new dependency; schema-neutral, so the editorSchema
  drift fence is unaffected)
- style the gap cursor bar in base.css -- the upstream package does not
  ship its stylesheet through our import path; the blink animation
  honors prefers-reduced-motion
- e2e: keyboard-only round trip that creates paragraphs before and
  after a lone table

Closes #335

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aoPvnakfBP28nAfijgUY9
Unambiguous delete row/column toolbar icons (#336)
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m52s
CI / Build container images (pull_request) Successful in 1m12s
CI / Auth e2e pack (pull_request) Successful in 9m24s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
b1165a37e6
The delete buttons paired the minus-box with a double arrow (bidirectional
arrows next to the symbol) which reads as "resize/expand", not "delete".
Replace them with axis stripes plus the x delete marker that deleteTable
already established: vertical stripes with x for delete column, horizontal
stripes with x for delete row. Labels/tooltips were correct all along and
stay unchanged.

Closes #336

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aoPvnakfBP28nAfijgUY9
Merge and split table cells (#337)
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m51s
CI / Build container images (pull_request) Successful in 3m57s
CI / Auth e2e pack (pull_request) Failing after 5m22s
CI / Import/export fidelity gate (pull_request) Has been skipped
d76c731473
prosemirror-tables already ships mergeCells/splitCell and the schema
(tableNodes) already carries colspan/rowspan -- only the controls were
missing. Adds the two commands, toolbar buttons whose enabled state
follows the selection (merge needs a multi-cell selection, split a
merged cell), and de+en labels.

Both render paths now carry the spans: docToHtml emits colspan/rowspan
(read mode, exports via the HTML path), and the markdown serializer pads
a colspan with empty cells so every row keeps the table's column count
-- rowspan stays lossy there, GFM cannot express it.

e2e drives merge and split through the toolbar; the cell selection is
made per Shift+Click because a keypress in the same tick as the
preceding click races the editor's post-click rendering (keyboard cell
selection itself works, verified interactively with a settled editor).

Closes #337

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aoPvnakfBP28nAfijgUY9
Word-style Tab navigation in tables with an accessible exit (#338)
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m56s
CI / Auth e2e pack (pull_request) Failing after 2m26s
CI / Import/export fidelity gate (pull_request) Has been skipped
CI / Build container images (pull_request) Successful in 5m47s
753338f1be
Tab used to fall through to the browser's focus navigation everywhere.
Inside tables it now moves cell-wise (Shift-Tab backwards) and appends a
new row from the last cell, Word-style. Outside tables every branch
returns false, so Tab keeps leaving the editor.

Capturing Tab inside tables needs a documented way out (WCAG 2.1.2):
Escape places the cursor after the table -- unlike the arrow keys, which
reach the gap cursor (#335) only from the table's edge cells, it works
from every cell, including from a cell selection. When no textblock
follows the table it falls back to the gap cursor position. The
mechanism is announced to assistive tech via an aria-describedby hint
on the editor surface (visually hidden, de+en).

e2e: cell round trip per Tab/Shift-Tab with typed markers, row append
from the last cell, and the full keyboard-only exit (Escape, then Tab
leaves the editor). The table specs now settle briefly after the insert
-- right after it the collab sync can swallow a click's selection
update, which had the markers landing in stale selections.

Closes #338

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aoPvnakfBP28nAfijgUY9
Recognize Markdown tables on paste and while typing (#339)
Some checks failed
CI / Lint, typecheck, test (pull_request) Failing after 2m6s
CI / Auth e2e pack (pull_request) Has been skipped
CI / Import/export fidelity gate (pull_request) Has been skipped
CI / Build container images (pull_request) Has been skipped
b7360dd48e
The paste conversion (issue #30) already handled tables, but any
text/html flavor on the clipboard bypassed it. Code editors (VS Code
with copyWithSyntaxHighlighting) ship the plain text a second time as
styled div/span HTML, so a Markdown table copied there arrived verbatim
while the same text from a plain editor converted fine. Clipboard HTML
without a single structural element (table/list/heading/link/emphasis/
code...) is now treated as equivalent to the plain text; anything from a
rich-text source keeps going through ProseMirror's HTML paste.

Pasting inside a code block never converts anymore -- text is code
there, and the conversion would have split the block around rich nodes.

Hand-typed tables: pressing Enter at the end of a GFM separator row
whose previous sibling is a pipe row replaces the two paragraphs with a
real table (input rules cannot express this -- they see only one
textblock). Conversion is refused inside existing tables; body rows are
then typed cell-wise, with Tab appending rows (#338).

Closes #339

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aoPvnakfBP28nAfijgUY9
fable-5 force-pushed issue-339-markdown-table-paste from b7360dd48e to f142289813 2026-08-15 19:32:32 +02:00 Compare
fable-5 merged commit f142289813 into main 2026-08-16 05:22:43 +02:00
Sign in to join this conversation.
No description provided.