Architecture decision records
Why Modyra is built the way it is. Each record states the pressure that forced a decision, what was decided, what it costs, the alternatives that lost and why, and the check that fails if the decision is violated.
Every architectural or security decision belongs here. A decision recorded only in a commit message, a changeset, or a file version control ignores is a decision the next reader will relitigate from scratch — an untracked file is not a durable record at all.
Use TEMPLATE.md. A record is not complete without Verification and Security and privacy; where either is genuinely empty, say so explicitly rather than omitting the section — an absent section reads as an oversight, and “no security impact” is a finding.
The records
Section titled “The records”| 0001 | Project and contract model | What a Studio project is, and how it relates to the contract it compiles to |
| 0002 | Ids and paths | Stable node ids for editing, dotted paths for reading — and where each belongs |
| 0003 | Command engine | Every edit is a command, which is what makes undo, redo and grouping possible |
| 0004 | Target plugin API | How a code-generation target is added without changing the model |
| 0005 | Expressions and references | The portable operator set, and why a reference is an id rather than an accessor |
| 0006 | One UI contract, many consumers | @modyra/widgets is the UI contract; renderers consume it and never redefine it |
| 0007 | Expressions are data, never code | No eval on a document that arrives over a network. The security decision of the dynamic contract |
| 0008 | The preview has no privileged path | What a designer watches is what a designer exports |
| 0009 | Client validation is defence in depth | The trust boundary, stated plainly: the server is the authority |
| 0010 | Every claim has an executable check | Ratchets, gates, and why a check nobody has watched fail is only a claim |
| 0011 | A capability names its event | A boolean answers whether and leaves how to each renderer, which is a specification by accident — superseded by 0013 |
| 0012 | A choice is a radio, by tag or by role | All three renderers already chose radiogroup; the open question was whether the tag is required |
| 0013 | The dismissal names its gesture | One event cannot express where a gesture began and ended, which is what the dismissal rule turns on |
| 0014 | The contract names the responsible element | Naming the region and not the element inside it let three widgets conform that nobody could operate |
| 0015 | Light text while it is readable | The contrast ratio and a reader disagree about saturated colour, and the ratio was winning |
| 0016 | A multiselect is one kind, and its mode is not the contract’s | Decided on a premise that was false — the mode was already contract data — superseded by 0017 |
| 0017 | A varianted kind names its anatomy per configuration | A disjunction says something is operable; ADR 0014 asks which element, and only a variant can answer |
| 0018 | A select declares whether it filters | One widget, three behaviours and one of them broken, because the distinction was not contract data |
| 0019 | TypeScript 7 compiles the libraries | Two consumers pin the compiler and the rest of the repository was waiting for them |
| 0020 | A hidden native control is never painted | Paint on a clipped pixel is invisible to review and to screenshots, and one engine ended the page over it |
| 0021 | A dialog overlay is not a combobox | The combobox opening keys were declared for four kinds that hold no options, and no renderer implemented them |
| 0022 | A theme expresses its own colour model | Two themes could not state their own design system, so a brand colour produced white text at 1.85:1 |
| 0023 | A popup is positioned, not dressed | A container that paints is a wrapper around the thing it was meant to present |
| 0024 | An author-time check calls the parser | The contract already knows what is wrong with a document; an editor check that says so again is a second answer that drifts |
| 0025 | A tag publishes, and nothing else does | A release pipeline whose publish step was a dry run stayed green for four versions while npm served none of them |
| 0026 | A row exists because it was declared | A collection keyed by data, where what is mounted must not decide what exists |
| 0027 | A register and its summary are both checked | A maintainer’s defect register and the page consumers read, held to the same statuses |
| 0028 | A status sentence names a version | One “pre-1.0” line over a workspace whose packages have never shared a maturity |
| 0029 | A widget does not repair the model | A select erased a value it could not render, and the data that would have let a person fix it went with it |
| 0030 | A declared fact survives composition | compose(required(), …) produced a field that was not required, silently, and three copies of one rule disagreed |
| 0031 | A field name is a path, in a schema as everywhere else | A schema keyed by "shipping.city" described a shape no read could produce, so every flattened document mounted into a form that threw on getValue() |
| 0032 | A computed is a function of its inputs | The vanilla graph allowed a signal write inside a computed and Angular refuses it, so shared code could pass every test on one adapter and throw on another |
| 0033 | One engine in the tree | Exact sibling pins installed @modyra/core twice after a partial release, and a required() from one engine was not required to the other |
- ADR 0034: A draft is not a linked signal — a value derived from a handle is a computed; a draft resets on the event that starts it, never on its source
- ADR 0035: The colour arithmetic lives with the themes —
color-utilsandtheme-compilership with the stylesheets they generate, measured to be a leaf with no edge either way - ADR 0036: The UI contract lives in one package — icons, keyboard policy and the option filter move to
@modyra/widgets, which had been importing them sideways from the engine - ADR 0037: A vocabulary does not belong to a parser — the seventeen field kinds were the property of the JSON reader that happened to declare them; a shared vocabulary lives in a leaf owned by nobody who uses it
- ADR 0038: An adapter does not redeclare what it derives — narrowing an upstream type for a framework’s signals is legitimate; restating its members is drift with a delay
- ADR 0039: A breaking change shipped as a patch — eighteen subpaths removed at
2.1.2 → 2.1.3, why that was bounded rather than habitual, and the complete migration table - ADR 0040: A collection owns its subtree — nested collections: gates compose along the whole chain, ownership is explicit, and the eight semantics decided before a line of runtime
- ADR 0041: History crosses structural changes — undo acts on the value as it is now: a declared, removed or renamed row is undoable the moment it happens, at any depth; only the value is restored
- ADR 0042: An adversarial suite attacks from outside —
battle-tests/consumes published entry points only and exists to falsify public claims: every battle cites a registered claim, proves it attacked, and writes a replayable artefact when it breaks something - ADR 0043: A collection nests without a limit — an array may hold an array, a form may nest as deep as it needs, and a document is bounded by the caller rather than by a number; supersedes ADR 0040’s one-positional-level rule
- ADR 0044: A binding belongs to the row — what a control said about a cell travels with the row across a rename or a move, as its value and its marks already did
- ADR 0045: A declaration is all or nothing — a row whose value raises while it is read is not declared; a row reads the object it was given, prototype chain included; a schema is read by its own properties
- ADR 0046: An adapter states no less than the engine — a capability the engine gains ships only when every package that restates it has it, checked by a consumer program rather than by a green suite; a nested collection’s value has the same type as a top-level one
- ADR 0047: An expression reads what a field could name — a document’s predicate passes the engine’s path guard and answers from the value’s own properties;
""stays the root reference - ADR 0048: A panel does not print what it masks — a masked field’s value is taken out of the errors beside it, and a snapshot describes a file rather than handing it over
- ADR 0049: A released custodian owes no focus —
release()ends the borrow, so a widget being torn down places no focus; a named target is still honoured, and the fallback keeps the case it was written for - ADR 0050: A document cannot make the form stop answering — a pattern whose shape backtracks exponentially is refused like one that will not parse, and the field survives the refusal
- ADR 0051: An option is recognised by what it holds —
oneOfcompares an object option by its members, so a draft’s round trip is not tampering, and every forgery it refused it still refuses - ADR 0052: A widget announces only the states it has —
readonlyleaves the kinds whose contract never had it, in both halves, andaria-checkedholds one of the three values the standard allows - ADR 0053: A widget id is refused where it is used, not only where it is asked about — the part-id builders throw on an id that cannot be referenced; the joining factory does not, and nothing is repaired silently
- ADR 0054: A list shows the choice it will not erase — an option is keyed by what it holds, a survivor keeps the label it was painted with and gets a part, and a radio group stops being the exception
- ADR 0055: A runtime that cannot recompute is not the one to run on — Solid’s server build freezes every derived value and reported an invalid form as valid; the adapter probes the graph and falls back to one that answers
- ADR 0056: A project file does not decide what the generated module does — an operand outside the expression vocabulary was printed unquoted into generated source; refused at the compiler and reported where the project is read
- ADR 0057: An argument is refused where it arrives — seven public entry points took a value they could not use and left the form to fail later; the reactive setters, the whole-value write and the initial are checked at the call
- ADR 0058: A rename moves a key, not a row — a renamed row was appended, and the value and the handle kept two different orders for one list
- ADR 0059: A step of history is a state the form was in — a bulk write cost one undo per row and a restored row came back last, so the path back passed through states the form was never in
- ADR 0060: A refusal reaches somebody — three spellings of a server’s refusal were dropped by the guard that drops a hostile path, so a person saw nothing and believed it went through
- ADR 0061: A rule that says nothing says nothing — the validator everybody writes has no
else, returnsundefined, and made the form unreadable from the first question asked of it - ADR 0062: The form says what no field can — three renderers had nowhere to show a refusal that names no field, which made it a missing part rather than three oversights
- ADR 0063: A value a control cannot read stays where it can be corrected —
14:30typed into a 12-hour picker was erased with nothing said; the judgement moves into the controller and the entry is kept and explained - ADR 0064: A typed form refuses a path it does not declare — one transposed letter attached a rule nothing could satisfy and the Submit button stopped working, with nothing said
- ADR 0065: What is said about a path is said about what is under it — the three interactivity setters reached only leaves, so a section a consumer excluded stayed editable and stayed in the payload
- ADR 0066: A bound beside the field is a rule — two spellings of a limit rendered identically and only one was enforced, so a tampered draft outside the bound was valid and submittable
- ADR 0067: A track spans what the field holds — a slider with no declared bound drew a track ending at 100 and put the thumb there for a value of 150, in both renderers, each having invented the default separately
- ADR 0068: A draft does not go backwards — a tab that had been open a while replaced a newer draft and stamped it with an earlier time, so the one field a later reader could use said the opposite
- ADR 0069: An unreadable condition does not open — a misspelled operator evaluated to
trueand showed a section to everyone, and a condition’s pattern carried no cost gate - ADR 0070: A server is asked about a value the field accepts — a tax id typed group by group sent four requests for values
minLength(11)already rejects, andwhenloses because it restates a bound the field declared - ADR 0071: A document is answered the same in both its shapes — the tree parser dropped fields in silence where the flat list reported them, and
strictapproved a document whose only field it had discarded - ADR 0072: A positional change set carries its whole list — a compacted list of changed rows said nothing about where they were, so a server applying it by position wrote the wrong row
- ADR 0073: A verdict a person can see is one the form counts — a picker showed an unreadable entry as an error and the form called itself submittable, sending the field empty
- ADR 0074: A file the field turned away is something the page says — the transition reported what it refused, no renderer showed it, and no message table had a word for it
- ADR 0075: A popup that opens says so — the lifecycle policy answered
announcefor every open and close, and two of the three renderers read neither - ADR 0076: A state belongs to something that can be in it — the one opener the contract gave no role wore
aria-invalidandaria-requiredwhere nothing could read them - ADR 0077: The opener a contract names is the one a keyboard reaches — two date inputs claimed the popup’s state while the part that opens it was not a tab stop, and lit’s pickers had no keyboard door at all
- ADR 0078: A widget announces the refusal it makes — twelve kinds refused every change while read-only and said nothing, because the rule that made silence right had since been implemented
- ADR 0079: A rule a document writes is a rule the form keeps — the parser guarded
rulesas behaviour and nothing applied one, so a document saying to disable a field produced a form that sent it - ADR 0080: A path is an instruction a row’s shape can refuse — one extra segment in a draft key built a row and a member no document declared, and the form called itself ready
- ADR 0081: A secret is excluded by the name a person writes —
excludematched one exact path, so the guide’s own example — a card number in a list — was the case it could not answer - ADR 0082: A commit word answers for the control a person types in — a range is neither
livenorconfirm, and a colours field has two controls that commit differently - ADR 0083: A form reports what it could not do — the diagnostics vocabulary was published and nothing took a sink, so a check that stopped running was invisible on every surface
- ADR 0084: A contract version names the anatomy — parts were removed and an element changed while the number that exists to say so stayed at 1
- ADR 0085: A name a package exports belongs to one function — two hooks shipped under
useMdyField, andexport *yielded to the local one in silence - ADR 0086: A derived form starts at an empty its schema accepts — a bridge seeded a sentinel its own schema refused, so one emptiness got two answers and
requiredcontradictedvalid - ADR 0087: A target answers with everything it knows about the project — three code targets called a project compatible that the contract compiler reports errors on, and said nothing about a kind they cannot draw
- ADR 0088: A draft is not replaced by one belonging to another form — two live forms on one key meant the last save deleted the other’s work, and nothing said so
- ADR 0089: A field that says it is a secret is treated as one — the contract’s
sensitiveflag reached no protection, so a secret was autosaved in clear and printed in the panel - ADR 0090: A rule with a bug in it is a verdict, not an outage — a validator that threw made every later read of the form throw, and a predicate that threw stopped the form being built
- ADR 0091: A form that has ended answers with one voice — a destroyed form called itself submittable with nothing to submit, and a late write reached one surface of two
- ADR 0093: A field that leaves play takes its overlay with it — a rule took a field out of play and left its calendar open, expanded, and answering nothing
- ADR 0094: One question about emptiness, one answer —
requiredrefused a valueisEmptycalled filled, so a rule opened a section the form was still refusing to submit - ADR 0095: A flat pair carries the shape of a row — a collection with no rows had no template, so a form rebuilt from the flat pair accepted a row and held nothing
- ADR 0096: A row the form was not built with is a change —
reset()threw a new row away whilegetChanges()reported nothing, so a patch never carried the rows a user made - ADR 0097: A member nobody declared is reported — the published schema closes every object and the parser closed none, so the one check a stored document meets said nothing
- ADR 0098: A fixture’s context lives beside it — v4’s conditions read what the host supplies, and the shared corpus had no way to say what that is
- ADR 0099: A password is said to be one — the published description of a password was the description of a text field, so masking was private knowledge in every adapter
- ADR 0100: A position is an identity — a fully disabled row left the payload, so every row after it was sent at a position it does not have
- ADR 0101: A collection asks for its own rows — a question scoped to a path was answered by a scan of the whole form, so declaring rows cost more the more rows there were
- ADR 0102: A change set is a payload too — a positional list was carried whole out of the form value, so a disabled cell left through the change set that a submit withheld
- ADR 0103: A patch names cells, in a list too — a partial positional row rebuilt the cells it did not name from the schema, so a form could not read the change set its own door produced
- ADR 0104: Change is decided by
Object.is— the conformance gate accepted a runtime whose default equality was===, so a field written-0over0re-rendered nothing - ADR 0105: One handle registry per realm — a module-level registry is per copy, so two copies of the engine in one tree turned the cross-runtime guard off
- ADR 0106: A door named for a contract reads it — the Angular component was named for the dynamic contract and took only the parsed half, so a document’s rules vanished and each host wrote the parse step again
- ADR 0107: A draft is read by the form that wrote it — the write side compared the recorded form shape and the read side did not, so the draft the writer refused to replace was the one the reader restored
- ADR 0108: A seed a theme can be read on — the default primary sat in the 13% of sRGB where the rule picks light text that cannot reach AA, so the filled button shipped at 4.09:1
- ADR 0109: An element nobody bound says so — the element surface was the one published door that failed in silence: an empty custom element, no control, and nothing said
- ADR 0110: A promise is declared where the popup is —
aria-haspopupwas a literal at fourteen openers with no common source, so two kinds promised a popup neither renderer opened - ADR 0111: A selector cannot close the sheet it is written into — the theme compiler guarded a selector against escaping its CSS rule and not against escaping the
<style>block around it - ADR 0112: A radio group has nowhere to jump —
HomeandEndwere declared for every kind that navigates options, and three renderers independently declined to implement them on a radio group - ADR 0113: A field name cannot break the value it is in — a field named
toStringshadowed the method every string conversion goes through, so${value}threw in the consumer’s own code - ADR 0114: A dev-time advisory is pinned, not waived — twenty-three alerts across eight packages that no published tarball carries, pinned inside their own majors because an unbounded override took
fast-uriacross one - ADR 0115: An hour carries the half of the day it is in — a 24-hour picker could not be moved off the half of the day it opened on, because the one seam that writes took 1–12 while every other surface spoke 0–23
- ADR 0116: One clock in every renderer — three renderers each wrote down their own default format, so one document could render a different clock in each adapter
- ADR 0117: A row is not a target — a boolean’s wrapper was a
<label>, so the empty remainder of the row toggled the field, and removing it left the drawn box unclickable - ADR 0118: One cell says whether a popup is open — the contract closes a popup whose field leaves play by writing the controller’s
open, and Angular painted a cell of its own - ADR 0119: An overlay’s boundary is the contract’s, not the renderer’s — four renderers each answered “is this press inside the popup” and three could get the portalled part wrong in silence
- ADR 0120: A picker that offers only some of the times — a granularity as data, refused by name where it is declared, obeyed by the face, the arrows, the typing and the drag alike
- ADR 0121: A legitimate value must not be indistinguishable from its own absence — four defects in one evening shared a guard that asked whether a value was usable when it needed to ask whether it was there
- ADR 0122: Tab moves inside a popup that has controls of its own — Tab dismissed every overlay, so a timepicker’s confirm button was unreachable and its only commit path was a pointer
- ADR 0123: A default is published, or it is copied — four renderers each spelled the clock default out and two had already drifted, while the view a picker opens in was a capability no document could reach
- ADR 0124: The public pitch is a single claim set — three surfaces answered “what is Modyra” three ways, and the positioning that could have stopped it lived in an untracked directory
- ADR 0125: A chip strip is one thing to a keyboard — twelve choices cost twenty-six tab stops, and reordering was being built on the strip that the fix for it has to rebuild
- ADR 0126: Focus is placed, not dropped — removing a chip returned a keyboard user to the top of the page, and the pin that found it asserted a threshold rather than a rule
- ADR 0127: A strip that scrolls, against the published practice — an outside review ranked the scrolling row last but one and called it a deliberate 1.4.10 departure, and the one-row-per-control rule is why it was taken anyway
- ADR 0128: A chip is one thing, not a cell — the APG’s composite pattern is grid, and the design had already removed the children grid exists to reach
- ADR 0129: One way back, not three — a clear-all with no undo is worse than no clear-all, and an undo that covers the loudest action and not the quiet ones is worse than none
- ADR 0130: A popup outlives the box it opens from — superseded: the clipping it was built on was a rectangle compared against a rectangle, and every popup is
position: fixed - ADR 0131: A rectangle outside a box is not a clipped one — the defect did not exist; the battle written to prove it now pins the property instead
- ADR 0132: A part’s name says what it is for, its role says what it is — one kind took its role as its part name, and a consumer asking the other kind for it got
undefined - ADR 0133: A chip’s mark is drawn, never written — the argument that decided it turned out to be false, and the decision stands on the two reasons that were not why it was taken
- ADR 0134: The projection decides an id, and every renderer applies it — an
aria-labelledbythe contract emits resolved in one renderer and pointed at nothing in another - ADR 0135: An id is a function of the document, not of mount order — two renderers minted ids from a counter, so the same declaration got a different id depending on what was mounted first
- ADR 0136: A version one runtime accepts is a version all of them accept — TypeScript accepted a version Rust and Java refuse, so a document built a form in one runtime and did not exist for the other two
- ADR 0137: A row that wraps where one line stops being a layout rule — the scroll departure was paid for with an announcement no role could carry, and reflow was owed to a sighted person all along
- ADR 0138: A chip is an item in a list, not a number in a range — the quantity chip gives up the spinbutton published practice prescribes, because a role that carries a value cannot carry a position
- ADR 0139: A select has two shapes, and one renderer only ever draws one of them —
searchableswitches between a native control and a combobox, the contract describes only the combobox, and plain never leaves it - ADR 0140: A popup that holds a draft is a dialog, and the catalogue says so — the kind declares the popup’s role, modality is a separate property from it, and a draft’s dialog is named by the field’s label
- ADR 0141: An id built from a value escapes it — a published id is a handle a consumer may select with, so caller data is hex-escaped into it rather than embedded raw
- ADR 0142: A field that holds controls is not a control — the box becomes a container, the chip strip and the opener become siblings, and a press stops being decided by the length of a word
- ADR 0143: A widget box inside the shell every kind sits in — one part name means one element, so a kind that draws two boxes declares two parts and its parts hang off the inner one
- ADR 0144: A slot that is always there — the way back reserves its line whether or not it is offered, because a cost that moves is one nobody can learn
- ADR 0145: A dial that repeats what a box already says — a redundant graphic is hidden from assistive technology, and what it announced moves to the control a person can reach
- ADR 0146: A form carries its own scope — every form has an id scope whether or not anybody asked, because the person who pays for a collision is not the one who reads the warning
- ADR 0147: The cluster at the end of a field — the undo joins the controls it is the opposite of, in a slot reserved so nothing slides under a thumb
- ADR 0148: A strip a browsing reader can reach — a listitem does not switch a screen reader’s mode, so the strip’s whole keyboard model never reached somebody who arrived by browsing
- ADR 0149: A form answers its own reset by returning to the initial values
- ADR 0150: What is submitted is what was on screen — the browser writes into a person’s form and tells nobody, so the field showed one value while the form held another
- ADR 0151: The floor is Baseline widely available, and everything below it is declared — the repository never said which browsers it works in, and arrived here one rule at a time
- ADR 0152: A form built with these controls submits, and the payload carries the field’s own names — no control wrote a
name, so a native submit sent nothing at all - ADR 0153: A control’s accessible name is what it is for, never what it holds — the check asked a chooser to answer to the grey word inside it, so it failed the renderer that was right
- ADR 0154: A part’s classes read the same from the record and from the accessor — two published surfaces gave opposite answers about the same part, and neither reader was misreading
- ADR 0155: A press completes on the release — one renderer opened a list while the button was still down, so the gesture a person uses to take a tap back did nothing
- ADR 0156: A panel answers both doors — a list opened with the mouse answered no key, because a press leaves focus where it landed and it landed on nothing
- ADR 0157: A key that waits for something to have happened — a key that does nothing because the moment is wrong is indistinguishable from one nobody implemented
- ADR 0158: A thirteenth that is a door — a field that takes every colour offered twelve, and neither of its two routes could see the disagreement
- ADR 0159: The filled square is what opens the panel — the most recognisable shape on the field did one thing in two renderers and another in the third, and a caret beside it was a second command for one act
- ADR 0160: A form nests six levels deep, and the limit is about people — nesting costs the machine nothing measurable, which is why the reason had to be written down, and one of the two doors was not enforcing it
- ADR 0161: The nesting limit was on the wrong axis — the argument was about conditionality and the constant limited arrangement, which was unguarded on the axis it defended
- ADR 0162: A renderer writes no class of its own — five undeclared classes outlived the stylesheet that used them, exempted from the conformance gate by an option the gate’s green result never showed
- ADR 0163: One live region for the page — eight adapters each named a region of their own; two regions speaking at once are read in an order nothing specifies, and a queue can only stand in front of one
- ADR 0164: When an optional part is there — 193 optional nodes and not one said when; the error container is reserved under fields that can fail, and the reservation stays after the correction
- ADR 0165: Empty is not wrong —
aria-invalidis a verdict on an act, not a state; a required field nobody has reached contains nothing, and a value that arrived already wrong speaks at once - ADR 0166: A key is data, not syntax — a key derived from a value is compared, never interpolated into a selector, and the order the chips are in is asked of the contract by name
- ADR 0167: Reading a form is not declining it — a form speaks when the value has been touched, never when only focus has; a panel is inside its field’s focus scope wherever it is rendered
- ADR 0168: A gesture that adds, and a gesture that removes — a gesture that adds is refused under a held accelerator, one that removes is honoured whatever is held, and the rule is declared rather than coded
- ADR 0169: Which presence conditions owe a resolver — a condition owes one when two renderers could disagree from the same state; where the answer restates an input the renderer holds, a resolver is ceremony
- ADR 0170: A caption nobody wrote — the caption element always exists so every reference resolves, carries the field’s key where a document wrote none, and is taken out of sight rather than out of the tree
- ADR 0171: Absent for configuration, disabled for state — a control that is part of a field is always present and announces when it cannot act; absence is for what a configuration left out
- ADR 0172: The trailing column carries one thing — the field’s trailing edge holds only the affordance every kind has; commands about the value stand with the value, and a glyph-only button is named by its action
- ADR 0173: A position may depend on the field, never on the value — a control that stands beside a variable-length value moves under the hand that is reaching for it; the commands return to the trailing edge with the opener outermost and a full target of space between them
- ADR 0174: A key means what the part under it means — the space bar is a character in a text box, “this one” on a highlighted day or option, and a press on a button; the meaning is the part’s, not the kind’s
- ADR 0175: The caption names the group, each control names itself — one caption over two boxes named both boxes the same thing; the caption names the pair and each box says its own role
- ADR 0176: A select is two shapes, and the contract says which — the native chooser and the combobox are variants; read as one anatomy the contract owed every select the combobox’s parts
- ADR 0177: The second door, and what the contract declines to say — a pointer’s second way into a panel is declared; an optional part with no condition is the renderer’s choice, and a control named by a reference or by the words is one answer
- ADR 0178: A date a person can read aloud — the display names its month in the reader’s language, the value stays ISO, input accepts widely and echoes back what it understood
- ADR 0179: Where the keyboard stands when a list opens — focus goes where the next input will arrive, and how the panel was opened says what kind that is; the cursor is primed on the first value already chosen
- ADR 0180: A container held open under a field that can fail — the message’s container is present from the first paint, so the page does not move under a person and one reference stands; a named empty container is not a dangling one — amended: the complement, when an empty container may go, and why an empty flex child is still charged a gap
- ADR 0181: A field that holds a paragraph is three lines tall, and says so in lines — two lines read as a one-line field that came out wrong; and a length stops meaning “three lines” the moment a person enlarges their text, so the size is stated in lines
- ADR 0182: Where the text a person types begins is declared once — the inset is declared on the inliner and on the control and both apply, so one renderer pays it twice; declared once, applied once, at the number two renderers already draw — amended: a colour field hides a native picker ahead of its hex box, so a rule about “the control inside the wrapper” reaches an element nobody writes in; that and two ways a correct cascade rule loses
- ADR 0183: A number that is reserved, and a debt that is stated — the diff says major and the release ships as a minor, because the number 3 is reserved for a narrower surface than this one; the warning the version withholds is paid in a BREAKING section, and if that section ever thins the number has to move instead
- ADR 0184:
okis false as soon as something was lost — a parse that graded a diagnostic an error and reported success in the same breath let a dropped condition mount as a field with no condition;oknow means what its name says, and lenient still returns what it read - ADR 0185: A word the language gains takes a number —
integercould be written by hand and not declared, and the schema gate refused the obvious repair: all three versions are published, so the word takes v5, and a member’s arrival is declared once where both the parser and the audit read it - ADR 0186: The corpus declares what it expects, and who must have read it — three documents sat unread by both SDK readers and neither suite went red, because a fixture nobody reads cannot fail; a document now declares its verdict beside itself and every reader must be able to name it
- ADR 0187: A baseline is moved by what it photographs — a per-widget image is hostage to fractional layout above it, so the plain suite photographs the bench and the page written to be read keeps no baseline
- ADR 0188: A panel that cannot say nothing — an inspection panel whose every datum carries whether it was read, because a blank cell claims a value is absent when it may mean nobody looked; and what it inherits from 0048, 0089 and 0099 rather than deciding again
- ADR 0189: A second reader of the same question — a question the contract can answer has one reader; two that are never run together are two beliefs, and putting the panel’s and the bench’s on six documents found a precedence inversion latent under the assertion it sat beneath
- ADR 0190: A verdict that has not happened — an asynchronous rule that has not answered is
unknown, not valid and not invalid; a restore re-derives rather than installs; and a runtime that has not declared the capability is refused by name - ADR 0191: A gate nobody runs — the conformance kit’s browser half was in no workflow; its eleven findings against plain were all its own, and a probe that does not assert its focus landed cannot tell a press that went nowhere from a widget that ignored it
- ADR 0192: A debt that cannot be paid where it is incurred — linux baselines are recorded from a pushed head, so the commit-time check announces the debt and names the run instead of refusing the commit it cannot let through
- ADR 0193: A rewrite is not a deletion — the filter matches by shape because a censor that names what it censors has published it, excluded names live in the ignore file that is not itself versioned, and a force-push removes an object from reachable history without deleting it from the remote
- ADR 0194: A class that belongs to no kind — the class contract has three doors; a part costs a semantic and a presentation class costs none, and a survey that asks two doors of three invents work
- ADR 0195: A list that arrives after the control is on screen — the option list is configuration in React and a call everywhere else, so a React select closes when its list is replaced and the others do not
- ADR 0196: A filter says what may be added, never what is already held — the widening ran on both sides of the host’s filter, so a held value it refuses vanished on one route and survived on the other; the held value is always offered
- ADR 0197: A panel opens on what it is for — the primary operable unit takes focus; five kinds already did it privately and the sixth had three answers
- ADR 0198: An action in a panel is one a key can reach — the predicate was narrower than the sentence beside it, and one panel’s action was pointer-only for years
- ADR 0199: The view a calendar is showing is one a key can change — the arrows move within a view and nothing declared a door between them; the accelerator opens it, chosen by measurement
- ADR 0200: A lint rule is loosened only to keep an intention already stated — a red check nobody runs and a green one bought by loosening protect equally; each exemption names the sentence it keeps
- ADR 0201: The custom properties nothing reads are removed, and seven that look alike are kept — a var() is not the only way a property is surface: seven are set by a theme rather than read by the library
- ADR 0202: What repeats is derived from the anatomy, not listed by name
- ADR 0203: An audit that could not ask is not an audit that found nothing — a transport error and a security finding left through the same exit code; the asymmetry is announce on a push, refuse at the release boundary
- ADR 0204: A dependency the barrel cannot load without is not optional — a single entry point resolves
react-domfor everyone who imports it, so the peer is required; the separate headless entry point waits for a consumer who wants it - ADR 0205: A document declares in two channels, and only one of them validates — rules judge a value,
configdraws the control;stepis an affordance that gives way to the value, so it is not a rule - ADR 0206: A field that leaves the scene closes what it holds open — the third species of closing, stated so it stops depending on where a renderer draws its panel; ADR 0131’s freedom is untouched
- ADR 0207: A part that shows something is told what, in a closed vocabulary — a slider’s number, a file field’s prompt, a swatch’s colour: projected in two members and no more, because a free channel is a surface no check can defend
- ADR 0208: A value of the wrong shape is a verdict, not a crash
- ADR 0209: A literal cannot carry a position — a door that depends on an observation made before it is called takes the observation, not a boolean asserting someone made it; correct prose, followed internally and required of nobody, is what let this through
- ADR 0210: A promise points at what keeps it —
aria-haspopupclaims a role andaria-controlssays which element has it: the reference names the sub-region carrying it, not the panel around it - ADR 0211: An argument of the wrong shape is a defect — a value is a verdict and an argument is a code defect: two audiences, two policies, and a wrong shape that never passes in silence
- ADR 0212: What a calendar is called — the grid is named by the month it shows, through the visible heading rather than a second copy of it; the panel is named by the field, and no element carries two naming channels
- ADR 0213: A time picker is complete without a clock face — the face stays optional and redundant to the typed boxes; a renderer without one conforms, owes parity as debt, and a spec of the face judges only hosts that draw it
- ADR 0214: A captioned control is named by its caption — no aria-label copied from a visible caption; aria-label only when declared; the required marker is outside the name
- ADR 0215: A caption beside a box does not say whether it is ticked — a boolean caption never carries –filled; the floating-label signal keeps one meaning
- ADR 0216: A restored value is the option it copies — a widget recognises an option by sameChoice and reports the option’s own value; the validator and draft restore share one strict, key-order-independent rule
- ADR 0217: A project id cannot leave its quotes — Studio refuses a project whose ids, references or implementation keys fall outside a safe character set, so no id can break out of the quoted HTML and selectors the editor writes — amended: the guarantee belongs to
loadProject, sonormalizeandcompileToContracttake only a project that has already passed it; a node’s name is author text and stays escaped where it is written - ADR 0218: An expression past the depth limit is a document question — the evaluator keeps answering true past the depth cap, which the parser enforces on documents; questions with a polarity-free answer, such as a non-text matches pattern, decide nothing
- ADR 0219: A development server answers only its own page and build — the Studio and plain-preview dev servers serve only the page and dist, follow links to their real place before serving, and refuse a foreign Host
- ADR 0220: The Vue adapter runs on the reactivity Vue itself runs on — every reactive primitive is imported from
vueand@vue/reactivityis no longer a peer, so a consumer’scomputedandwatchEffecttrack form state in every install layout - ADR 0221: A server validator carries no message of its own —
StudioServerValidatorhas noerrorMessageand the inspector no message input: a server refusal’s text comes from the server, or from the preview mock’s configuration - ADR 0222: A form a component owns starts when the component mounts —
useVueFormanduseSolidFormbuild inactive and activate on mount, so a server render starts no draft, history or async validator; the factories keep the active default, and Svelte documents the pattern instead of adding a hook - ADR 0223: The Rust reader judges a document by the TypeScript reader’s rules —
modyra-contractrefuses what@modyra/corerefuses with the same codes, judging patterns by ECMAScript’s grammar through theregresscrate and a port of ADR 0050’s shape test; the Java reader’s corpus test is knowingly red on the 18 rules it lacks - ADR 0224: A number stepper steps by the HTML algorithm —
numberSteppedin@modyra/widgetsis the one stepping rule every renderer’s steppers call: grid frommin, off-grid values snap in the direction pressed, bounds land on the grid, decimals stay exact; an empty box out of range enters at the near end - ADR 0225: Escape leaves the calendar view it is in — in a datepicker or date range, Escape from the month or year view shows the days and focuses the grid, and only Escape from the days closes the popup; every renderer answers the view before the panel
- ADR 0226: The focused day follows the month a calendar shows — paging, choosing a month and choosing a year move
focusedDateinto the shown month (the selected day if there, else the same day clamped), so the day grid always has one tab stop; a year chosen from the keyboard lands on the months in every renderer - ADR 0227: A date travels in a form’s payload as the value the field holds, not as its spelling — a range sends
path.start/path.endas ISO dates from hidden inputs via an optionalkeyson thehiddenshape, and no date box carries a name - ADR 0228: The Java reader runs on every push, against a ledger of where it disagrees with the corpus — CI runs the Java suite; its corpus disagreements are a ledger held exact both ways, so a new one fails and a repaired one must leave the list
- ADR 0229: A diagnostic path is a JSON Pointer into the document as written — key segments are escaped per RFC 6901, and a field’s finding points at its list entry by index or its tree declaration, once per collection template
- ADR 0230: A malformed child is that child’s problem — a group’s child that is not a node is refused alone and counted once; its siblings survive in lenient, and a rejection counts declarations, not findings
- ADR 0231: A document costs what it writes, not what its rows expand to — a flatten may visit at most 10,000 nodes beyond those the schema declares; past it the schema is refused whole with
MDY_DYNAMIC_SCHEMA_LIMITat/schema - ADR 0232: A collection writes at most a hundred rows — every reader refuses a collection whose own
initialValuewrites more than 100 rows, withMDY_DYNAMIC_SCHEMA_LIMITat itsinitialValue, as a finding about the tree’s shape - ADR 0092: A condition travels with the form —
whenandasyncWhenwere closures, so a schema carrying one was not data and the document half could not say what the typed half said
Security-relevant records
Section titled “Security-relevant records”Start here for a security review: 0007 (untrusted documents are never executed), 0009 (the trust boundary and the draft storage exposure), 0010 (which of those properties are held by a test rather than by prose), 0024 (the same refusal to execute a document, at the editor boundary), 0025 (the registry credential, and why no publish token is stored), 0111 (the one place a caller’s string reaches a stylesheet, and which container it is held inside), 0216 (what a draft read back from storage may restore), 0217 (a Studio project file is untrusted, and its ids are refused at the door), 0218 (where expression depth is enforced, and what an application owes when it evaluates untrusted expressions itself), 0219 (what the development servers expose to a page the developer has open), 0222 (which form builders read storage and call validators during a server render).
Changing a record
Section titled “Changing a record”A decision that no longer holds is superseded, not edited into agreement with the present. Write
the new record, and set the old one’s status to Superseded by [ADR NNNN]. The reasoning that was
correct under the old constraints is what makes the new decision legible.
A decision that still holds but has grown may be amended in place, with the amendment marked as such — 0005 carries one.