Files
kandev/docs/plans/bare-markdown-wrapper/analysis.md
T
2026-09-12 16:29:59 +01:00

173 lines
5.7 KiB
Markdown

# Bare-wrapper ambiguity analysis
## Finding
No content-only heuristic can guarantee both requested outcomes for every valid
Markdown message. A bare code block can contain headings and tagged-looking fence
lines as literal text. Those characters do not encode the author's wrapper intent.
The work remains blocked. A thematic-break boundary does not establish wrapper
intent: the same bounded range can contain two complete independent blocks. No
safe content-only discriminator is available for this task.
## Source evidence
- `apps/web/lib/markdown/normalize-cache.ts`:
`strengthenMarkdownWrapperFence` rejects a bare first opener through
`MARKDOWN_WRAPPER_OPEN_RE`.
- The same helper uses `firstNonBlankLineIndex` and `lastNonBlankLineIndex`.
Introductory and trailing prose also prevent detection.
- `markdownWrapperInnerFenceInfo` is a heuristic scan, not evidence of author intent.
`FENCE_OPENER_LINE_RE` can match text inside an already open code block.
- A boundary search without an enclosing-fence parser also sees rules and fence
candidates inside a valid five-backtick code block.
- `apps/web/components/shared/memoized-markdown.tsx` passes
`normalizeCached(content)` directly to `ReactMarkdown`.
- Existing tests preserve Markdown samples followed by separate blocks and
preserve tagged wrappers with trailing prose outside the wrapper.
## Compact reproduction
Construct the raw string from the following lines. `BT3` means exactly three
backticks, and `BT3go` means those backticks immediately followed by `go`.
`EMPTY` means an empty line. These labels are not part of the input.
```text
Copy everything between the lines.
EMPTY
---
EMPTY
BT3
Open a pull request.
## Why
BT3go
func first() {}
BT3
More prose.
BT3go
func second() {}
BT3
Final prose.
BT3
EMPTY
---
EMPTY
That is self-contained.
```
Desired interpretation: one raw Markdown code block between the rules.
Observed interpretation: three code blocks. The first ends after `func first()`.
The second contains `func second()`. The third consumes the final rule and outro.
The pure transform leaves this input unchanged. This establishes both the
reported symptom and the missing interior-range behavior.
## Counterexample A: two complete legitimate blocks
```text
BT3
## Fence syntax
BT3go
func first() {}
BT3
This paragraph separates two examples.
BT3
func second() {}
BT3
```
The first block shows a Markdown syntax fragment, including a heading and a
literal tagged opener. It ends at the first pure fence after `func first()`.
The paragraph is normal text. The second bare block contains `func second()`.
Both blocks have explicit closing fences.
This input satisfies all three suggested signals:
1. Its first and last nonblank lines are matching bare fences.
2. A same-length tagged opener and pure closer occur strictly between them.
3. It contains an ATX heading and prose between code samples.
Strengthening the outermost fences merges two intended code blocks and their
separating paragraph. Adding the reproduction's introduction and rules does not
remove this ambiguity from an interior-range detector.
## Counterexample A2: rule-bounded independent blocks
```text
---
BT3
## Fence syntax
BT3go
func first() {}
BT3
This paragraph separates two examples.
BT3
func second() {}
BT3
---
```
This input has the proposed thematic-break boundaries, but it is also two
complete bare code blocks separated by a paragraph. The first code block contains
the literal heading and tagged opener. Strengthening the first and last fences
would merge both blocks and the paragraph, so the boundary is not a safe repair
signal.
A valid five-backtick text block can contain the complete compact reproduction as
literal content. A raw-line scan would find its rules and inner fences and rewrite
bytes that Markdown already treats as code text.
## Counterexample B: requiring two tagged pairs
```text
BT3
## Fence syntax
BT3go
func first() {}
BT3
More prose.
BT3go
func second() {}
BT3
Final prose.
BT3
```
Under GFM, this is a bare syntax sample, a separate Go block, and an empty final
code block. A fence without an explicit closer extends to the end of the input.
Such input is valid Markdown, including during streaming.
The alternative intended interpretation is one wrapper containing two tagged
examples. Both interpretations use exactly the same bytes. Requiring two tagged
pairs or prose between them cannot distinguish those intentions.
Headings in these examples are meaningful literal documentation. They are not
evidence that sibling blocks are nonsensical. Natural-language introduction text
and horizontal rules also occur in legitimate example documents.
## Read-only verification
Installed `normalizeMarkdown`, React, and `react-markdown` produced the following
results on 2026-09-11:
| Input | Normalizer changes bytes | Rendered code blocks |
| --- | --- | --- |
| Compact reproduction | No | 3 |
| Counterexample A | No | 2 |
| Counterexample B | No | 3 |
| Two independent tagged Go blocks | No | 2 |
For counterexample A, the emitted HTML has a paragraph between two `pre`
elements. The first code text contains the literal heading and tagged opener.
Counterexample B and the independent Go case also ran with all production remark
plugins: GFM, breaks, and gemoji.
## Disposition
The proposed boundary contract is rejected. Counterexample A2 proves that it
violates the preservation guarantee, and the five-backtick case proves that a
boundary scan can rewrite literal code content. The normalizer therefore keeps
the existing tagged-wrapper behavior and leaves bare wrapper candidates unchanged.
The work order remains blocked until a producer marker, parser-level intent
signal, or other product decision supplies information that raw Markdown lacks.