Design decisions
Architecture
- One crate. The core and the CLI live in one crate (
crates/annealer) for now. It will be split into a workspace only when a real need appears, to avoid premature abstraction. The CLI'sclapdependency is behind the defaultclifeature, so the library builds for WebAssembly with--no-default-features. - Profiles are YAML, not code. HTML, Vue and future profiles are data. Each profile has
schemaVersion, because the format is as much a public API as the Rust function signature. - Strict validation. Unknown keys are rejected when the profile is parsed, so typos surface immediately. A JSON Schema (draft-07) is provided for editor support. Two rules that draft-07 can't express (exactly one fallback group, unique group names) are checked only by the loader.
- malva is pinned loosely (
0.16), in line with the library dependency policy used in other projects. - Only the order changes. annealer asks malva how property names sort and moves declarations as written; malva's printed output is not used. Whitespace, quotes and other formatting are left to a formatter such as Prettier or Oxfmt.
- The npm package embeds the WebAssembly binary as base64 instead of shipping a separate
.wasmfile. The cost is about 33% more bytes before compression. In return,format()is synchronous and needs no bundler or loader setup.
Decisions made during implementation
- HTML profile order. The original table had no place for unmatched attributes or for
alt/title. The agreed order is id → class → supplementary (alt,title,placeholder) → semantic (type,href,src,srcset) → key-value (name,value,content) → dimensions (width,height) → other (state:disabled,checked, …) → ARIA → data → events. The Vue profile uses the same order inside its other-attributes tier. - Static before bound applies to
classonly. For other names, the cluster keeps the source order, because Vue mergesstylewith the later declaration winning and keeps only one value for other duplicates. spreadSafegroups. Compile-time directives never merge with av-bindspread object, so they may move across spreads. Everything else stays inside its segment.- One attribute per line in multiline tags. Otherwise reordering could put two attributes on one line. This matches the multiline default of
vue/max-attributes-per-lineand can be turned off withlayout.oneAttributePerLine. - Vue tier 8 is split into other directives, then other attributes, as in
vue/attributes-order(OTHER_DIRECTIVES→OTHER_ATTR). void: alwaysdiffers fromvue/html-self-closing's default (html.void: never). Projects that keep that ESLint rule need to align one side.contentNewlineis on in the HTML profile too. Whitespace at the start and end of block-level content isn't rendered, and theignorelist covers the elements where it could be.- Vendor-prefixed declarations go first (
vendorPrefix: start), so the standard property is declared last and wins. See Stylesheets.
Resolved questions
v-modeland a plainvalue: never clustered. The keyv-modeldiffers fromvalue.- Profile strictness: unknown keys are rejected at parse time.
- malva version: loose range
0.16.
Open items
- Upgrade raffia to 0.13 once malva does (malva 0.16 still uses raffia 0.12).
- Svelte profile.
- Value ordering for static
classattributes (Tailwind approximation, Bootstrap proposal). - Library profiles, profile composition (
extends) and per-tag or per-component overrides. - Fixtures taken from real projects, in addition to the hand-written ones.