Skip to content

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.

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

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).

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.