Architecture decision records¶
This directory records the decisions that shape the BlockNote module: what was decided, why, what else was considered, and what the decision costs. Each record is written to be read on its own, by somebody who has never seen the design notes that preceded it. If you want to know why the module looks the way it does, read these before reading the code.
Format¶
Every record is one Markdown file with these sections, in this order.
| Section | What it holds |
|---|---|
| Title | # NNNN. Short imperative statement of the decision |
| Status | Starts with one of Proposed, Accepted, Superseded by NNNN, Rejected, optionally followed by what closed or amended it. A record one part of which a later record replaces reads Accepted; <the part> is superseded by NNNN (date). A proposal that depends on an unfinished experiment says what closes it. |
| Date | The date the status last changed, ISO 8601. |
| Context | The forces: what the module needs, what the upstream library and Drupal core actually do, what was measured. Facts only, with enough detail that the decision can be re-argued. |
| Decision | What was chosen, stated as a rule the code must follow. |
| Alternatives considered | Each option that was genuinely on the table and the reason it lost. |
| Consequences | What follows, including the costs and the work the decision creates. Honest, not promotional. |
| References | Upstream source, core source, issue links, other records. |
A record that waited on an experiment keeps the sections that stated the question and recorded the result, between Decision and Alternatives considered.
Prose is not hard wrapped: one paragraph per line.
A record may cite a file the release tarball leaves out: .gitattributes export-ignores docs/backlog/, CONTRIBUTING.md, recipes/ and every __tests__ directory (js/__tests__/, js/src/__tests__/, modules/blocknote_media/js/__tests__/ and tests/modules/blocknote_test/js/__tests__/), so in a release those paths dangle, and the repository holds the file they name. A record may also cite a draft in docs/drafts/, which only the maintainer's checkout holds: the folder is in .gitignore.
Numbering¶
Files are named NNNN-short-slug.md, numbered from 0001 in the order the decisions were taken, never reused. The number is the record's permanent identity, so it is safe to cite from code comments, issues and commit messages.
Superseding¶
A record is never deleted and never rewritten to say something different. When a decision changes, write a new record that states the new decision and explains what changed, and edit only the Status line of the old one to read Superseded by NNNN, with the date. The old record stays in place so that the reasoning behind code that still exists remains readable, and so that a decision is not quietly reversed twice.
Small corrections of fact inside a record are allowed when the decision itself does not change. A record whose status is Proposed may be moved to Accepted or Rejected by editing the status and date, which is how the records that wait on an experiment are closed. A correction that changes what a paragraph claims is introduced in place with Amended YYYY-MM-DD. before the new text, so that the original reasoning stays readable.
Index¶
| Record | Decision | Status |
|---|---|---|
| 0001 | One document per field item, stored in a versioned envelope | Accepted |
| 0002 | Text formats are the editor profile and the permission set | Accepted |
| 0003 | TypeScript and Vite, a committed IIFE bundle, one guarded runtime | Accepted |
| 0004 | Public output is render arrays and our own semantic HTML | Accepted |
| 0005 | One plugin type describes every block, default blocks included | Accepted |
| 0006 | Media is a submodule, and a media block stores a UUID and a view mode | Accepted; view mode enforcement is superseded by 0015, the media theme variable's registry alter by 0034, alt text not being stored and the preview's trust boundary by 0036 |
| 0007 | An uploaded file is stored as file:<uuid> and resolved when the page is built |
Accepted |
| 0008 | An entity link is custom inline content, not a link mark | Accepted; the placement in blocknote_linkit and the linkit search are superseded in part by 0028, the substitution prop's placement and its permitted list by 0029 |
| 0009 | A document has an identity, a repository, an updater and two post-commit events | Accepted; the change origin's three cases are superseded by 0039 |
| 0010 | The editor enforces the format's link and color policy as the author edits | Accepted; the pasted block and cell color posture is superseded by 0024 |
| 0011 | The editor is named and described by its field, and every function has a keyboard route | Accepted; the media block's own controls are superseded by 0019 |
| 0012 | A read-only widget renders the document instead of disabling the editor | Accepted |
| 0013 | The editor draws the box of a form control on its editable element | Accepted; the disabled state is superseded by 0020 |
| 0014 | A block declares its caption and its toolbar and block menu items at client registration | Accepted |
| 0015 | Permitted media view modes are curation, strict where the author chooses and lenient where the page renders | Accepted |
| 0016 | A setting lives where it is enforced | Accepted |
| 0017 | The filter's settings govern alignment, heading levels and toggles, code block languages, and merged cells | Accepted |
| 0018 | The media block stores an alignment and no width | Accepted; the alignment's values and default are superseded by 0044 |
| 0019 | The media block draws no box, and its functions live in the formatting toolbar and the block menu | Accepted; the toolbar's item set for a selection is superseded by 0025 |
| 0020 | A disabled editor draws the disabled box of the theme's inputs | Accepted |
| 0021 | A caption refuses every block conversion rule and shortcut | Accepted |
| 0022 | Escape leaves the editor, and Tab then continues in the page | Accepted |
| 0023 | Refusal messages speak to the author, name what was refused in their own words, and say what they can do | Accepted |
| 0024 | A forbidden block or cell color is clamped as the author edits, and a stripped document is written back after a refused save | Accepted |
| 0025 | The formatting toolbar is keyed on the selection kind for a caption block | Accepted |
| 0026 | A click into a link places the caret, and an upstream editing behavior changed for Drupal authors is an editor toggle, on by default | Accepted |
| 0027 | One plugin type describes every custom inline content type, on the same terms as a block | Accepted |
| 0028 | The base module links to content by uuid and resolves it by core's link target rule | Accepted; the rejected substitution-prop alternative is superseded by 0029 |
| 0029 | A content link may carry a substitution the format permits, resolved by a decorated resolver | Accepted |
| 0030 | A text format may hold one paragraph, and says which element it renders as | Accepted; the "Summary or trimmed" formatter and the trimmed formatter left for later are superseded by 0045 |
| 0031 | Subscript and superscript join the fixed style vocabulary | Accepted; the server's posture on a run carrying both is superseded by 0037 |
| 0032 | A link's new-tab choice is a boolean style on the link's own runs, permitted per format | Accepted |
| 0033 | Editor icons are BlockNote's own, held as documented icon assets | Accepted |
| 0034 | A block or inline content type declares its template's theme variables on its attribute | Accepted |
| 0035 | Documents that do not render in full are counted by cron in state and reported on the status report | Accepted |
| 0036 | A media block may override an image item's alternative text, stored as the text and a decorative flag | Accepted |
| 0037 | The editor and the server agree on the marks one run may carry: a save refuses what the editor's schema excludes, and the editor loads such a run without code, or without subscript | Accepted |
| 0038 | Formatted text converts to a document through a PHP walker whose rules are tagged services | Accepted |
| 0039 | Stored text converts in place into a document field beside it | Accepted |
| 0040 | Markup the converter cannot convert is kept in a block no plugin provides | Accepted |
| 0041 | Converted references to files and content resolve through two more kinds of rule | Accepted |
| 0042 | Media is found before it is created, and created when its document is written | Accepted |
| 0043 | A heading carries an id made from its words, and a table of contents is a block the author places | Accepted |
| 0044 | A media block floats left or right, and breaks text by default | Accepted |
| 0045 | A trimmed formatter, with a summary field shown in its place | Accepted |
| 0046 | A format lists styles per block type, and a block carries one as classes on its outer element | Accepted |
| 0047 | A media gallery is a block whose children are media blocks, shown in one view mode | Accepted |
| 0048 | Blocks that are needed but not common go in blocknote_extras, and the callout is the first |
Accepted |