Skip to content

feat(core,html): HTML rules — M5 5.2 - #37

Merged
aenawi merged 1 commit into
mainfrom
html-rules
Sep 5, 2026
Merged

aenawi merged 1 commit into
mainfrom
html-rules

Conversation

@aenawi

@aenawi aenawi commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Four defects the web can write and the other two formats cannot: an order imposed by <bdo>, a run left un-isolated where <bdi> belonged, an inset measured from a physical edge, and a layout reversed by hand to fake right to left. Closes PLAN §5.2.

They are rules in mirsam-core, not in mirsam-html

That is the design question this item had to answer. §5.2 called them "HTML's own", and putting them in the adapter would have followed that description straight past the architecture: a rule living in a format adapter is one the next format re-implements or quietly lacks. So the shared vocabulary grew and the formats that cannot speak it say so. Alignment::Distributed is the precedent, and four more refusals now sit beside it in the committed list.

The model gained a part of a paragraph

Whether a range is isolated from its neighbours, and whether its order was imposed rather than resolved, are properties of a range, and neither is recoverable from the characters — a run boundary is something only the document knows. TextUnit::spans is that, as byte offsets into the text a report shows, the same coordinates tatweel-padding already gives its offenders. Empty for both OOXML adapters, and empty means nothing was said rather than one plain run.

The four rules

rule severity proved by
bidi-override error / warning the imposed order against what the run resolves to under the direction the override itself names
isolation-missing warning the paragraph resolved twice, once with the run isolated, isolates stripped before comparing
inset-physical warning an asymmetric physical inset under right-to-left text
order-reversed warning flex-direction: row-reverse on a flex container

<bdo dir="rtl"> and an embedded U+202E give the same instruction and both replace the algorithm instead of informing it, which is invariant 4 from the other end — so bidi-override and bidi-control are one defect in two spellings. The isolates isolation-missing uses are computed and never written: invariant 4 forbids a document holding a bidi control, and asking what the algorithm would have produced had one been there is what turns "this looks risky" into a finding with two orders attached.

isolation-missing is a warning and not from doubt. Nothing is mis-rendered; the algorithm did what it is specified to do. What is wrong is that the order of text outside the run is a function of text inside it, so the line is laid out correctly for this owner's name and undefined for the next.

Two arguments worth reading before review

inset-physical reports a value stated elsewhere, and alignment-incoherent does not. An absent alignment is a real state a template fills in, which is why the alignment rules split into a written tier and an inherited one. An absent inset is not a state anything fills in: a box with no inset is simply not indented. A physical left inset under Arabic therefore exists because somebody wrote it, and the only open question is where — which the evidence answers by naming the declaration.

order-reversed's provable form is narrower than its name. DOM order reversed by hand, stating nothing, leaves no trace any tool can read; a container of Arabic in some order carries no evidence of which order was intended, and claiming otherwise would be the assertion ADR 0004 forbids. The declarative form is readable and is also the common one. This limit is written into AGENTS.md rather than left implied.

Neither of the last two is fixable — a claim about repairs, not severity. Both repairs are edits to markup or to a stylesheet, not new values in the shared Fix vocabulary, and a variant nothing lowers would be inferred capability.

What the adapter had to be rebuilt for

Whitespace used to be collapsed at the end, over the finished string, and a span recorded against the raw characters names a range of a string nobody sees once three spaces became one. Text is now collapsed as it is gathered. The first version put a run's start one byte early, on the space a collapsing box had not yet written, and a <pre> of nothing but newlines crossed its own trim offsets — the adapter's own tests caught both.

Checks

  • make verify green: fmt, clippy -D warnings, full suite, release build.
  • 34 conformance cases (was 29). Four new refusals: a run that is a bidi boundary, an inset from a named edge in either spelling, and a reversed layout.
  • tests/fixtures/quarterly-page.html carries one instance of each new defect beside the <bdi> line that proves the silence; quarterly-page-correct.html is still reported clean.
  • 17 rules registered, up from 13.

Four defects the web can write and the other two formats cannot: an order
imposed by `<bdo>`, a run left un-isolated where `<bdi>` belonged, an
inset measured from a physical edge, and a layout reversed by hand to
fake right to left.

They are rules in `mirsam-core`, not in `mirsam-html`, and that is the
design question this item had to answer. §5.2 called them "HTML's own",
and putting them in the adapter would have followed that description
straight past the architecture: a rule living in a format adapter is one
the next format re-implements or quietly lacks. So the shared vocabulary
grew and the formats that cannot speak it say so.
`Alignment::Distributed` is the precedent, and four more refusals now sit
beside it in the committed list.

The model gained one thing it genuinely lacked: a part of a paragraph.
Whether a range is isolated from its neighbours, and whether its order
was imposed rather than resolved, are properties of a range, and neither
is recoverable from the characters — a run boundary is something only the
document knows. `TextUnit::spans` is that, as byte offsets into the text
a report shows, the same coordinates `tatweel-padding` already gives its
offenders. Empty for both OOXML adapters, and empty means nothing was
said rather than one plain run.

`bidi-override` and `bidi-control` are one defect in two spellings.
`<bdo dir="rtl">` and an embedded U+202E give the same instruction and
both replace the algorithm instead of informing it, which is invariant 4
from the other end. Two tiers on the flagship rule's test: the imposed
order against what the same run resolves to under the direction the
override itself names. They differ, and it is an error with both
renderings as evidence; they agree, and it is a warning at the fragile
tier, because nothing has moved today and the algorithm is still off for
whatever is typed there tomorrow.

`isolation-missing` is proved rather than asserted, and its precondition
is narrow on purpose: the wide version of the question reports ordinary
markup. A run is only asked about when it carries a strong character of
the opposite direction — the interpolated name `<bdi>` was added to the
language for — and then the paragraph is resolved twice, once as it
stands and once with U+2068…U+2069 around the run, the isolates stripped
back out before the two are compared. The finding is the difference. A
warning, and not from doubt: nothing is mis-rendered, the algorithm did
what it is specified to do. What is wrong is that the order of text
outside the run is a function of text inside it, so the line is laid out
correctly for this owner's name and undefined for the next.

The isolates are computed and never written. Invariant 4 forbids a
document holding a bidi control; asking what the algorithm would have
produced had one been there is what turns "this looks risky" into a
finding with two orders attached.

`inset-physical` is `alignment-incoherent` for the other property, and it
reports a value stated elsewhere where that rule does not. The difference
is argued rather than assumed. An absent alignment is a real state a
template fills in, which is why the alignment rules split into a written
tier and an inherited one. An absent inset is not a state anything fills
in: a box with no inset is simply not indented. So a physical left inset
under Arabic exists because somebody wrote it, and the only open question
is where — which the evidence answers by naming the declaration. Only an
asymmetric inset counts; equal on both sides is a page gutter.

`order-reversed` is the one whose provable form is narrower than its
name. DOM order reversed by hand, stating nothing, leaves no trace any
tool can read — a container of Arabic in some order carries no evidence
of which order was intended, and claiming otherwise would be the
assertion ADR 0004 forbids. What is readable is the declarative form,
which is also the common one: `flex-direction: row-reverse`, the boxes
displayed backwards while the direction says nothing. Reported whichever
direction is declared, because both are wrong and wrong differently —
without one the layout and the reading order disagree, with one the two
reversals cancel. `display` is checked, because `flex-direction` on a
block box is a declaration nobody applied.

Neither of the last two is fixable, which is a claim about repairs and
not about severity: both repairs are edits to markup or to a stylesheet,
not new values in the shared `Fix` vocabulary, and a variant nothing
lowers would be inferred capability.

One thing the adapter had to be rebuilt for. Whitespace used to be
collapsed at the end, over the finished string, and a span recorded
against the raw characters names a range of a string nobody sees once
three spaces became one. Text is now collapsed as it is gathered. The
first version put a run's start one byte early, on the space a collapsing
box had not yet written, and a `<pre>` of nothing but newlines crossed its
own trim offsets; the adapter's own tests caught both.

Thirty-four conformance cases pass. `quarterly-page.html` carries one
instance of each new defect beside the `<bdi>` line that proves the
silence, and `quarterly-page-correct.html` is still reported clean.
@aenawi
aenawi merged commit 7f0b879 into main Sep 5, 2026
7 checks passed
@aenawi
aenawi deleted the html-rules branch September 5, 2026 09:07
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