feat: preserve markdown tables and add raw ProseMirror ingest flags (#42)#48
Merged
Conversation
…ngest (#42) Path B from #42: expose raw ProseMirror ingest on `documents describe` so agents and humans can write arbitrary ProseMirror JSON (tables, colspan, task lists, custom nodes) without passing through the markdown parser. Two new flags on `documents describe`: - `--set-raw JSON_STRING` inline ProseMirror doc, validated then written verbatim via `client.set_content` / `client.create_content`. - `--set-raw-file PATH` same, but read from disk. Mutual exclusion now covers all four write flags (`--set` / `--set-file` / `--set-raw` / `--set-raw-file`) with a single clear error. Invalid JSON and non-doc root structures are rejected before any network IO. Command-layer regression tests cover: verbatim write, file read + write, missing file, invalid JSON, non-doc root, missing-content root, and all six mutual-exclusion pairs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Path A from #42: add GFM table support to `markdown_to_prosemirror` so `documents describe --set` / `--set-file` (and every other markdown → PM call site) preserves table structure instead of storing rows as literal `|`-pipe paragraphs. Rules: - first row after a `| --- | --- |` separator → `tableHeader` cells - body rows → `tableCell` cells - alignment row consumed but not stored (1x1 cells per the issue non-goal) - cells default to `{"colspan": 1, "rowspan": 1}` attrs - escaped pipes (`\\|`) inside cells remain literal Round-trip: `prosemirror_to_markdown` already emits GFM via `_table_to_md`, so markdown → PM → markdown now reproduces the table intact. Parser-level tests cover header/body distinction, attrs, alignment-row variants, inline marks inside cells, escaped pipes, round-trip equality, followed-by-paragraph, regression guards for non-table markdown, and false-positive guards (single `|` row with no separator; `|` in prose). Command-layer integration test: `documents describe --set "| a | b |..."` intercepts the markup handed to `create_content` and asserts it contains a `table` node with `tableHeader` / `tableCell` children. README and skill updated to describe markdown-table round-trip and the `--set-raw` / `--set-raw-file` escape hatches added in the preceding commit. Closes #42. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…own-tables # Conflicts: # skills/huly-cli/SKILL.md
…own-tables # Conflicts: # skills/huly-cli/SKILL.md
…own-tables # Conflicts: # README.md # src/huly_cli/commands/documents.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #42. Markdown tables sent via
huly documents describe --set/--set-filenow parse into real ProseMirrortable/tableRow/tableHeader/tableCellnodes instead of being stored as paragraphsfull of literal
|pipes. A raw-ingest escape hatch is also added forProseMirror features the markdown parser does not cover.
Changes
Path B — raw ProseMirror ingest (shipped first)
documents describe:--set-raw JSON_STRING— pass arbitrary ProseMirror JSON straight toclient.set_content/client.create_content.--set-raw-file PATH— same, but read from disk.--set,--set-file,--set-raw,--set-raw-file).{"type": "doc", "content": [...]}. Invalid JSON or wrong shape isrejected with a clear error before any network IO.
src/huly_cli/commands/documents.pytouched on the command side.client.pyis untouched —set_content/create_contentalreadyaccept arbitrary ProseMirror JSON.
Path A — GFM table parsing in
markdown_to_prosemirrormarkdown_to_prosemirrorinsrc/huly_cli/markup.pyrecognises GFMpipe tables:
tableHeader, body rows →tableCell, alignment rowconsumed but not stored (1x1 cells per the issue non-goal).
paragraphso inline marks (bold,italic, links, code) survive.
\|) inside cells are preserved as literal pipes.prosemirror_to_markdownalready emits GFM tables via_table_to_md,so markdown → PM → markdown round-trips; a test asserts exact
equality after
.strip().README.mdandskills/huly-cli/SKILL.mdupdated.Test plan
uv run pytest -x -q— 293 passing (up from 278: +15 new tests)uv run ruff format . && uv run ruff check .cleantests/test_markup.py:tablenode{"colspan": 1, "rowspan": 1}attrs:---/---:consumed\|) cells|row with no separator stays a paragraph|in plain prose stays a paragraphtests/test_issue_write_path.py:--set-rawwrites verbatim (markup payload handed tocreate_contentis the exact JSON, parsed result contains a
tablenode)--set-raw-filereads and writes--set-raw-filewith missing file exits 1--set-rawwith invalid JSON exits 1, no network call--set-rawwith non-docroot exits 1--set-rawwith adocroot missingcontentexits 1--set "" --set-fileregression updated to the newfour-flag error wording
--setwith a markdown table emits a PMtablenodewith
tableHeaderheader row andtableCellbody rowCloses #42