Version: @markout-lang/core 0.3.0
A <:define> can give a parameter a default, and that is the whole of what it
can say about it. It cannot say a parameter is required, that it accepts
one of a set, or that its parameter list is closed. So a usage site has
several ways to be wrong that all compile clean and render something.
Repro
<:define tag="ui-btn:a"
:variant=${'primary'} // primary | outline | ghost -- a comment, not a rule
:label=${''} // required in practice, no sensible default
class=${`btn btn-${variant}`}
><:slot /></:define>
<ui-btn id="ok" :variant="outline">Fine</ui-btn>
<ui-btn id="typo-v" :variant="primry">Misspelled value</ui-btn>
<ui-btn id="typo-n" :varaint="outline">Misspelled parameter name</ui-btn>
<ui-btn id="missing">No label passed</ui-btn>
<ui-btn id="wrongty" :variant=${42}>Wrong type</ui-btn>
Actual
Compiles clean, five pages of nothing said:
| id |
rendered class |
what went wrong |
ok |
btn btn-outline |
— |
typo-v |
btn btn-primry |
a class with no rule; button renders unstyled |
typo-n |
btn btn-primary |
asked for outline, got primary |
missing |
btn btn-primary |
no label, and nothing wanted one |
wrongty |
btn btn-42 |
a number where a variant name goes |
typo-n is the worst of them: the author wrote a value, the page ignored it,
and the element silently took its default.
What narrows it
:varaint is not ignored — it creates a value. With :aka="b" on the
usage site, ${b.varaint} reads outline. So a usage site adding a value is
a real capability and unknown attributes cannot simply be rejected; what is
missing is a way for a definition to say its list is closed.
- This is the compiler's own standard, unmet. An unknown reference is a
compile error anywhere it is written, handler bodies included. An unknown
parameter at a usage site is accepted in silence. Same class of mistake,
opposite treatment.
- It fits
silent-failures.md's admission rule exactly — a correct-looking
page produces wrong output and nothing anywhere says why — but it sits at the
component-contract level rather than between two framework features, which
may be why it has not surfaced there.
Why this language is unusually well placed to fix it
Reactive values are declared in markout rather than in JavaScript, so the
compiler already owns every declaration and already resolves every name — the
same property that lets it catch Unknown reference: "cuont" inside a handler
body. Constraining a parameter is an extension of machinery that exists, not a
type layer bolted alongside. A JS-hosted framework needs TypeScript and a build
story to get the same thing.
Possible spellings, not a proposal
Recorded only so the trade-offs are visible; the shape is yours to choose.
- Required: a parameter declared with no default is required, and omitting
it is an error. Cheap, but it changes what :x alone means today.
- A set:
:variant=${'primary'} :variant-oneof=${['primary','outline','ghost']},
checked when the usage site passes a literal and skipped when it passes an
expression. Catches typo-v and wrongty, and only where it can be sure.
- Closed list: an opt-in on the definition — every
:name at a usage site
must be a declared parameter. Catches typo-n, which is the one that changes
what renders. Opt-in because adding values at a usage site is deliberate
elsewhere.
The three are independent and the third is the one with the highest ratio of
bugs caught to syntax added.
Version:
@markout-lang/core0.3.0A
<:define>can give a parameter a default, and that is the whole of what itcan say about it. It cannot say a parameter is required, that it accepts
one of a set, or that its parameter list is closed. So a usage site has
several ways to be wrong that all compile clean and render something.
Repro
Actual
Compiles clean, five pages of nothing said:
okbtn btn-outlinetypo-vbtn btn-primrytypo-nbtn btn-primarymissingbtn btn-primarywrongtybtn btn-42typo-nis the worst of them: the author wrote a value, the page ignored it,and the element silently took its default.
What narrows it
:varaintis not ignored — it creates a value. With:aka="b"on theusage site,
${b.varaint}readsoutline. So a usage site adding a value isa real capability and unknown attributes cannot simply be rejected; what is
missing is a way for a definition to say its list is closed.
compile error anywhere it is written, handler bodies included. An unknown
parameter at a usage site is accepted in silence. Same class of mistake,
opposite treatment.
silent-failures.md's admission rule exactly — a correct-lookingpage produces wrong output and nothing anywhere says why — but it sits at the
component-contract level rather than between two framework features, which
may be why it has not surfaced there.
Why this language is unusually well placed to fix it
Reactive values are declared in markout rather than in JavaScript, so the
compiler already owns every declaration and already resolves every name — the
same property that lets it catch
Unknown reference: "cuont"inside a handlerbody. Constraining a parameter is an extension of machinery that exists, not a
type layer bolted alongside. A JS-hosted framework needs TypeScript and a build
story to get the same thing.
Possible spellings, not a proposal
Recorded only so the trade-offs are visible; the shape is yours to choose.
it is an error. Cheap, but it changes what
:xalone means today.:variant=${'primary'} :variant-oneof=${['primary','outline','ghost']},checked when the usage site passes a literal and skipped when it passes an
expression. Catches
typo-vandwrongty, and only where it can be sure.:nameat a usage sitemust be a declared parameter. Catches
typo-n, which is the one that changeswhat renders. Opt-in because adding values at a usage site is deliberate
elsewhere.
The three are independent and the third is the one with the highest ratio of
bugs caught to syntax added.