From af01cd807a00420e8abb3f7360f5eb6efb517738 Mon Sep 17 00:00:00 2001 From: stanlyzoolo <51911715+stanlyzoolo@users.noreply.github.com> Date: Sat, 8 Aug 2026 15:16:32 +0300 Subject: [PATCH 1/5] docs: add research note comparing panel [3] preprocessor with glow Co-Authored-By: Claude Fable 5 --- docs/research/glow-comparison.md | 80 ++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 docs/research/glow-comparison.md diff --git a/docs/research/glow-comparison.md b/docs/research/glow-comparison.md new file mode 100644 index 0000000..b3553dc --- /dev/null +++ b/docs/research/glow-comparison.md @@ -0,0 +1,80 @@ +# Panel `[3]`'s preprocessor vs glow: what each side does to a README + +This is a research snapshot, not a design doc: it records how keepkit's markdown +preprocessing (`internal/model/readme_clean.go`, `cleanReadmeMarkdown`) differs from what +glow does before glamour, and why the two tools made opposite choices. It answers one +question — "why does keepkit carry ~650 lines of cleanup that glow lives without?" — and +documents nothing the code must keep true. Verified against glow master, glamour v1.0.0 +(the version in `go.mod`) and keepkit as of 2026-08-08. + +## The architectural difference + +**glow has no content preprocessor.** Its entire pre-glamour surface is +`utils.RemoveFrontmatter` (the CLI path strips a YAML frontmatter block) and +`utils.WrapCodeBlock` (a non-markdown file is wrapped in a fence so it renders as one code +block). Everything else is glamour *options* — `WithWordWrap`, `WithPreservedNewLines`, +`WithBaseURL`, a style — i.e. decisions about how to show constructs, not rewrites of the +document. + +That works because glow's decisions run **after parsing, on the goldmark AST**: glamour's +ansi renderer maps each node kind to an element and styles it. keepkit's pass is the +opposite — a **pre-parse string/regex rewrite** whose output is then handed to glamour. +The trade is explicit: + +- keepkit pays for parser guarantees by hand: `rcSegments` re-derives fenced-block + boundaries, `rcMaskSpans` NUL-masks inline spans, and the "code is never rewritten" rule + needs its own machinery. glow gets all of that free — a rule that only exists as node + styling can never see the inside of a code node. +- In exchange, keepkit can do what node styling cannot: delete a construct *differently + depending on content* (unwrap `[text](url)` but keep `` autolinks, which + glamour renders through the same Link primitive — the reason a style-level route was + rejected in [`readme-pipeline.md`](../design/readme-pipeline.md)), drop whole lines + (badge headers, link-reference definitions, the title block), and collapse the blank + runs the removals leave behind. + +## Construct by construct + +"glow" below means bare glamour v1.0.0 behavior, since glow adds nothing of its own. + +| Construct | glow (= glamour v1.0.0) | keepkit | +|---|---|---| +| `![badge](url)` | alt text **plus the full URL** (`ansi/image.go` renders the `ImageText` format, then the destination) — a ten-badge header becomes a stack of shields.io URLs | removed whole, every form incl. reference/shortcut (`mdImageRe`, `rcRefRe`, `rcShortcutRe`): no image can render in a TTY | +| `[text](url)` | text **plus the href** (`ansi/link.go`; `SkipHref` exists but only fires for table footnote links) | unwrapped to the text, href dropped — panel `[3]` links are not clickable; autolinks and bare URLs kept (there the URL *is* the content) | +| Raw HTML | `HTMLBlock`/`RawHTML` nodes pass through `bluemonday.StrictPolicy` (`ansi/context.go`) — tags stripped, inner text kept | own pass: comments whole, ``/`