Skip to content

format: an HTML file can be a fragment instead of a whole page - #89

Merged
donislawdev merged 1 commit into
mainfrom
format/html-fragment
Sep 8, 2026
Merged

format: an HTML file can be a fragment instead of a whole page#89
donislawdev merged 1 commit into
mainfrom
format/html-fragment

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

#88 is merged, so this sits on main.

A new setting on html: structure, taking document or fragment. It
defaults to the whole page these files have always been.

A fragment is the same blocks without the skeleton - no doctype, no html
element, no head and no body. It is what a content field, an email body or a
partial render really holds, and it is what a system under test is handed when
something else owns the page around it.

Half of this is what was NOT built

The obvious next setting after XML was a third turn of the textenc work, since
HTML carries <meta charset="utf-8"> in band exactly the way XML carries its
declaration. The standard rules it out, checked at the source rather than
recalled:

  • "If the result of BOM sniffing is an encoding, return that encoding with
    confidence certain"
    - the BOM ends the algorithm before meta is looked at.
  • "If charset is UTF-16BE/LE, then set charset to UTF-8" - the same rule in
    three places.
  • "The actual character encoding used to encode the document must be UTF-8",
    and the attribute value "must be an ASCII case-insensitive match for the
    string utf-8"
    .

So a UTF-16 page is not a document whose declaration is ignored - it is a
non-conforming document. And measured here: html.parser takes text, not
bytes
, so a UTF-16 file carrying <meta charset="utf-8"> parses without
complaint, gives the same eight tags as the control and reports charset
utf-8. A mismatch nothing refuses.

Fidelity for HTML is Full, so rule 4 applies and a deliberately non-conforming
fixture belongs to the chaos lab, which does not exist. Verdict: O191. It cost
two questions to the standard rather than a day of code.

What the change cost

The bytes that close the body and the document sit in the last RECORD, not in
a footer:

tailLast   = paraClose + bodyClose
fixedWidth = len(paraOpen) + len(tailLast)

So the shape has to reach the block builder, not only the prologue. Swapping the
prologue alone gives a fragment ending in </body></html> - the right size,
deterministic, and nonsense. That is its own mutation, and it is caught.

Each shape answers for its own minimum: 118 B for a page, 8 B for a
fragment, which is <p></p> and a newline. The registry declares the default
shape's, the way it does for the JSON layouts.

The label rides in the heading alone in a fragment, since there is no head
to put a title in. The format still declares its label visible, which stays true
because a heading is.

The oracle had to be TOLD the shape. check_html hardcoded
startswith("<!doctype html>") and a closing </html>, so a fragment turned
both over - the same trap the JSON layouts sprang. A checker that worked the
shape out from the file would agree with a fragment produced where a page was
ordered and the other way round, which are exactly the two defects this
setting can cause.

What holds

  • Ten pinned hashes unchanged, whether a recipe says nothing or says
    document out loud - two different routes through the parser.
  • Five new guards, six new mutations, all caught.
  • 🔴 One of them was NOT CAUGHT first time and the guard was at fault: it
    asked whether a fragment starts lower than a page, and a fragment that
    inherited a page's prologue still does - so it passed on the very defect it
    names. It now derives the gap from the two files: the smallest fragment has to
    be the smallest page without its skeleton.
  • generator.Plan reached the crowding threshold, so the label decision was cut
    into its own function rather than the ceiling being raised.

Analysis written before the code, with what was not measured:
docs/HTML-STRUCTURE-2026-09-08.md. Measurement: MVP-FORMATS.md §5.2d.

This closes the structural format group - JSON, XML, SVG and HTML all have
settings now.

What is verified, and what is not

Verified on this branch: build, gofmt, go vet, staticcheck on the changed
package, every new guard, every one of the six mutations, the document
consistency guards, and the ten pinned hashes.

⚠️ Not yet confirmed on this exact branch: the full go test ./... and
preflight --quick.
Both were running when this was opened. They passed on
the same code before the rebase onto merged main, and the rebase moved no
content - but that is a reason to expect green, not evidence of it. Read CI.

⚠️ TestARunStoppedPartWayNamesEveryFileThatFinished is flaky and it is not
this branch's doing
- it fails about one run in four on merged main too,
checked in a separate worktree, and this change does not touch it (it uses
explicit sizes, so drawSizes never runs). Written down as O192. If CI goes
red on that one, it is the flake.

🤖 Generated with Claude Code

A new setting on html: structure, taking document or fragment. It defaults to
the whole page these files have always been, so the ten pinned hashes are
unchanged whether a recipe says nothing or says document out loud.

A fragment is the same blocks without the skeleton - no doctype, no html
element, no head and no body. It is what a content field, an email body or a
partial render really holds.

Half of this change is what was not built. The obvious next setting after XML
was a third turn of the textenc work, since HTML carries a meta charset in band
the way XML carries a declaration. The standard rules it out, checked at the
source rather than recalled: the document encoding must be UTF-8, and the
charset attribute must match "utf-8". So a UTF-16 page is not a document whose
declaration is ignored, it is a non-conforming document. Measured beside that:
html.parser takes text rather than bytes, so a UTF-16 file announcing utf-8
parses without complaint and reports charset utf-8. A mismatch nothing refuses.

What the change cost. The bytes that close the body and the document sit in the
last RECORD rather than in a footer, so the shape has to reach the block builder
and not only the prologue - swapping the prologue alone gives a fragment ending
in </body></html>, the right size and nonsense. That is its own mutation.

Each shape answers for its own minimum, 118 B against 8 B, and the registry
declares the default shape's the way it does for the JSON layouts. The label
rides in the heading alone in a fragment, since there is no head to put a title
in, and the format still declares its label visible because a heading is.

The oracle had to be told which shape to expect. check_html hardcoded a doctype
prefix and a closing html tag, so a fragment turned both over - and a checker
that worked the shape out from the file would agree with a fragment produced
where a page was ordered and the other way round.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit 9c8e468 into main Sep 8, 2026
18 checks passed
@donislawdev
donislawdev deleted the format/html-fragment branch September 8, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant