When publish.py update overwrites a draft it replaces the entire body from Markdown, silently destroying any widgets inserted via the Substack editor. These widgets have no Markdown equivalent and should survive updates.
Widgets identified in a live draft:
| Type |
Key attrs |
polymarket |
eventSlug, marketSlug, fullEmbedUrl |
button |
url, text, action, class |
recipe |
id |
Possible implementation
Represent widgets as HTML comment blocks containing the raw server JSON, e.g.:
<!-- widget
{"type": "polymarket", "attrs": {"eventSlug": "gpt-6-released-by", ...}}
-->
markdown-it passes HTML comments through as html_block tokens, so _render_block can detect and parse these without any plugin. The JSON is whatever publish.py inspect <id> returns for that node — the renderer inserts it verbatim, with no schema knowledge required. The block renders invisibly in Markdown previewers.
Alternatively, it may also be possible to store the widgets as normal json-formatted code blocks, and have the parser check the "type", but they would need to be distinguishable from code blocks that were intended to be published as code blocks.
Seamless sync via update
Rather than requiring manual inspect + copy-paste, cmd_update can keep the local file in sync automatically:
- Before uploading, fetch the existing draft and find any widget nodes not already present in the local Markdown file as
<!-- widget ... --> blocks.
- Write missing widgets into the Markdown file.
- Render and upload the (now complete) Markdown.
Any widget added in the Substack editor is then written back into the local file on the next update run. The Markdown file stays in sync with the server and remains the source of truth for text content.
Image editing preservation
The Substack editor also lets authors set per-image properties (caption, alt text, alignment, full-width), as well as actually modify the image with a web-based editor, after a draft is created. A subsequent update currently stomps the changes because image nodes are rebuilt from scratch.
This is related to the widget problem and could be solved similarly — detect when a Markdown image URL matches an existing captionedImage src in the stored draft and merge editor-set attrs rather than replacing the node wholesale. It's worth investigating whether the editor saves image edits as a new upload or modifies the existing one, as that affects how src matching would work.
When
publish.py updateoverwrites a draft it replaces the entire body from Markdown, silently destroying any widgets inserted via the Substack editor. These widgets have no Markdown equivalent and should survive updates.Widgets identified in a live draft:
polymarketeventSlug,marketSlug,fullEmbedUrlbuttonurl,text,action,classrecipeidPossible implementation
Represent widgets as HTML comment blocks containing the raw server JSON, e.g.:
markdown-itpasses HTML comments through ashtml_blocktokens, so_render_blockcan detect and parse these without any plugin. The JSON is whateverpublish.py inspect <id>returns for that node — the renderer inserts it verbatim, with no schema knowledge required. The block renders invisibly in Markdown previewers.Alternatively, it may also be possible to store the widgets as normal json-formatted code blocks, and have the parser check the
"type", but they would need to be distinguishable from code blocks that were intended to be published as code blocks.Seamless sync via
updateRather than requiring manual
inspect+ copy-paste,cmd_updatecan keep the local file in sync automatically:<!-- widget ... -->blocks.Any widget added in the Substack editor is then written back into the local file on the next
updaterun. The Markdown file stays in sync with the server and remains the source of truth for text content.Image editing preservation
The Substack editor also lets authors set per-image properties (caption, alt text, alignment, full-width), as well as actually modify the image with a web-based editor, after a draft is created. A subsequent
updatecurrently stomps the changes because image nodes are rebuilt from scratch.This is related to the widget problem and could be solved similarly — detect when a Markdown image URL matches an existing
captionedImagesrc in the stored draft and merge editor-set attrs rather than replacing the node wholesale. It's worth investigating whether the editor saves image edits as a new upload or modifies the existing one, as that affects how src matching would work.