feat(bin): add verified local Markdown previews#906
Open
gavinsomers wants to merge 16 commits into
Open
Conversation
gavinsomers
force-pushed
the
fm/fm-agents-markdown-preview
branch
from
July 23, 2026 13:23
944cfcd to
31918aa
Compare
Author
|
Hi @kunchenguid — this is ready for review. The Markdown preview helper and workflow rules passed the full automated review and test suite, with low residual risk. Thanks. |
4 tasks
gavinsomers
force-pushed
the
fm/fm-agents-markdown-preview
branch
from
July 23, 2026 18:46
81c26f3 to
71bd620
Compare
gavinsomers
force-pushed
the
fm/fm-agents-markdown-preview
branch
from
July 24, 2026 19:16
71bd620 to
6bf2f8c
Compare
4 tasks
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.
What Changed
Risk Assessment
✅ Low: The latest changes correctly preserve existing preview failures while normalizing descriptor I/O, close, and loopback server-construction errors without introducing a material source risk.
Testing
Baseline diff/status inspection, focused preview and related regression tests, and a live end-user rendering all completed successfully; the screenshot shows formatted Markdown with raw script text escaped and the remote image omitted, the helper reported success, and its loopback server was closed afterward. The in-app browser was unavailable, so the visual evidence was captured with local headless Chrome instead.
/tmp/no-mistakes-evidence/01KY7DACYHC79Z810G6TNF0G39/markdown-preview.png)Evidence: Successful preview and lifecycle evidence
ok - previewed preview-fixture.md preview server closed after delivery (curl exit 7)Pipeline
Updates from git push no-mistakes
⏭️ **intent** - skipped
✅ No issues found.
🔧 **Rebase** - 1 issue found → auto-fixed ✅
AGENTS.md- merge conflict rebasing onto origin/main🔧 Fix applied.
✅ Re-checked - no issues remain.
🔧 **Review** - 2 issues found → auto-fixed (6) ✅
AGENTS.md:49- The mandatory security-sensitive preview workflow has no repository-owned renderer or verification helper. Each harness must independently interpret sanitization, remote-resource blocking, current-file verification, and cleanup, producing inconsistent guarantees or making delivery impossible on a clean host. Decide whether to add one pinned, fail-safe preview helper or skill and reference it here.tests/fm-pi-primary-types.test.sh:9- Thetsc --versionprobe runs before checking whether the Pi package exists. A host without Pi but with a broken, wrapped, or unrelatedtscnow fails instead of taking the documented Pi-not-installed skip path. Resolve and validatePI_PACKAGE_DIRbefore probing the compiler.🔧 Fix: Captain: Add fail-safe previews and package-first Pi checks
3 issues (1 error, 2 warnings) still open:
bin/fm-markdown-preview.py:569- After a verified browser fetch, the helper still requires the opener process to exit within one second and then terminates it.xdg-opencan run its selected handler in the foreground, so a successfully loaded preview may be reported as failed and its newly launched browser terminated. Decide whether verified delivery should constitute success independently of opener lifetime.bin/fm-markdown-preview.py:176- Every unmatched[scans the remainder of the line for](, with similar repeated scans for unmatched emphasis delimiters. Bracket-heavy input is therefore quadratic, while rendering occurs before the timeout and accepts files up to 8 MiB. Use a single-pass or otherwise bounded delimiter scan.bin/fm-markdown-preview.py:515- Server verification reads at mostMAX_SOURCE_BYTES + 1, but HTML escaping can expand an accepted source to several times that size. A valid source below the 8 MiB limit can therefore be truncated during verification and falsely rejected as a content mismatch. Read against the bounded rendered-body length or enforce an explicit rendered-output limit before serving.🔧 Fix: Captain: Harden preview delivery and rendering bounds
6 issues (1 error, 5 warnings) still open:
bin/fm-markdown-preview.py:538-urllib.request.urlopenhonors proxy environment variables, so local verification can send the tokenized loopback URL to an external proxy and falsely fail when loopback is not exempted. Use a proxy-disabled opener or a direct loopback connection for verification.bin/fm-markdown-preview.py:643- Ifthread.start()raises, cleanup callsserver.shutdown()even thoughserve_forever()never started;BaseServer.shutdown()can then wait indefinitely. Track whether the thread started and call onlyserver_close()otherwise.bin/fm-markdown-preview.py:178- The delimiter budget does not cover counting a backtick run. For a large unmatched run, each successive backtick recounts most of the remaining run, making rendering quadratic before the serving timeout begins. Consume the whole unmatched run at once or include this scan in the budget.bin/fm-markdown-preview.py:73- The fence regex combines overlapping whitespace-capable groups before an end anchor, causing quadratic backtracking for a tilde fence followed by a long whitespace tail and a final backtick. Parse the fence tail separately or make the groups non-overlapping.bin/fm-markdown-preview.py:357- Nested blockquotes recursively invokerender_blockswithout a depth bound, so roughly 1,000 nested markers can raise an uncaughtRecursionErrorfrom a small accepted file. Render iteratively or reject excessive nesting withPreviewError.bin/fm-markdown-preview.py:432- The 64 MiB rendered-output limit is checked only after all block and table output has been accumulated, joined, and encoded. A wide accepted table can therefore allocate hundreds of megabytes before rejection. Enforce an output or cell budget during rendering.🔧 Fix: Harden preview verification and rendering bounds
6 warnings still open:
bin/fm-markdown-preview.py:350- Each fenced-block line rebuilds a marker-sized closing prefix. An accepted file with a multi-megabyte opening fence and many short body lines therefore performs quadratic allocation and work. Reusemarkerdirectly instartswithor cache the prefix once.bin/fm-markdown-preview.py:76- The lazy heading body followed by overlapping\s*#*\s*suffixes can backtrack quadratically on a heading containing a long whitespace run before a non-matching suffix. Parse trailing heading markers in one pass or make the groups non-overlapping.bin/fm-markdown-preview.py:100-RenderBufferlimits bytes but not part count. Millions of tiny accepted blocks can remain below 64 MiB of output whilesplit()andpartsretain millions of Python objects, consuming hundreds of megabytes. Add a line/block/part budget or coalesce output incrementally.bin/fm-markdown-preview.py:166-urlsplitraisesValueErrorfor malformed targets such as an unmatched IPv6 bracket, so one malformed Markdown link produces an uncaught traceback instead of being rendered without an unsafe href. Catch parser errors and returnNone.bin/fm-markdown-preview.py:310- Table detection applies the column limit before confirming that the following line is a divider. Ordinary prose containing|followed by a non-table line with over 1,024 delimited fields is therefore rejected as an oversized table. Enforce the limit only after recognizing a valid divider row.bin/fm-markdown-preview.py:414- The 64-level quote bound still copies the nearly complete captured line at every recursive level. A valid 8 MiB line with 64 nested quote markers can retain roughly 512 MiB before output budgeting applies. Strip quote prefixes iteratively or include retained input size in the budget.🔧 Fix: Bound Markdown parser work and retained memory
2 warnings still open:
bin/fm-markdown-preview.py:132-read_source()performs a blockingos.open(..., O_RDONLY)before validating that the descriptor is regular. A FIFO, including a path swapped to one, can therefore hang the supposedly bounded helper indefinitely. Open nonblocking and then validate the descriptor type.bin/fm-markdown-preview.py:194- The new byte and part budgets apply only afterrender_inline()has accumulated and joined its private lists. One accepted 8 MiB line containing dense short markup can retain millions of fragments and substantial object overhead before a singleRenderBuffer.append()occurs. Apply incremental byte/fragment limits inside inline rendering or coalesce bounded chunks.🔧 Fix: Bound source opening and inline rendering
2 warnings still open:
bin/fm-markdown-preview.py:180- Onlyos.openis translated toPreviewError; failures fromos.fstat,os.read, oros.closeescape as raw tracebacks instead of the documented controlled preview failure. Normalize descriptor I/O errors while preserving existingPreviewErrorexceptions.bin/fm-markdown-preview.py:812- Loopback socket creation or binding can raiseOSErrorfromPreviewServer(state), butmain()does not catch it, so an expected serving failure produces a traceback. Wrap server construction asPreviewErrorand retain cleanup for partially initialized state.🔧 Fix: Normalize preview I/O and server failures
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
Inspectedgit diff 68ed7ed2355619749c6ee2bb91b7298dee420df8..37b94216fea0b795e92676b45b1c85362c638d98to establish intent and affected test scope.tests/fm-markdown-preview.test.shtests/fm-bootstrap.test.shtests/fm-pi-primary-types-order.test.shtests/fm-pi-primary-types.test.sh(expected environment skip because installed TypeScript is older than version 5)bin/fm-markdown-preview.py --timeout 30 --opener /tmp/no-mistakes-evidence/01KY7DACYHC79Z810G6TNF0G39/capture-url-opener.sh /tmp/no-mistakes-evidence/01KY7DACYHC79Z810G6TNF0G39/preview-fixture.mdRendered the live loopback URL usinggoogle-chrome --headless --window-size=1440,1000 --screenshot=...and visually inspected the resulting PNG.Verified the loopback URL was unreachable after delivery usingcurl --noproxy '*' --silent --show-error --max-time 1 "$preview_url".Checked cleanup withgit status --short.✅ **Document** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.