@markuplint/html-spec 5.0.0-rc.2 → 5.0.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,478 +0,0 @@
1
- # Maintenance Guide
2
-
3
- This is a practical operations and maintenance guide for contributors working on this package.
4
-
5
- ## Commands
6
-
7
- | Command | Description |
8
- | ------------------------------------------------------- | ------------------------------------------------- |
9
- | `yarn workspace @markuplint/html-spec run gen` | Full generation: build + Prettier formatting |
10
- | `yarn workspace @markuplint/html-spec run gen:build` | Run `node build.mjs` to generate index.json |
11
- | `yarn workspace @markuplint/html-spec run gen:prettier` | Format index.json with Prettier |
12
- | `yarn up:gen` | Regenerate all spec packages from repository root |
13
-
14
- ## Build Pipeline Overview
15
-
16
- The generation process (`gen`) performs two steps in sequence via `npm-run-all`:
17
-
18
- 1. **`gen:build`** -- Executes `build.ts`, which calls the `main()` function from
19
- `generator/`. This reads all `src/spec.*.jsonc` files, merges them
20
- with scraped MDN data and the common attribute/content files, appends obsolete
21
- element stubs, and writes the consolidated output to `index.json`.
22
- 2. **`gen:prettier`** -- Runs Prettier on `index.json` to ensure consistent formatting
23
- across regenerations.
24
-
25
- The build is network-dependent because `generator/` fetches live data
26
- from MDN for each element (descriptions, compatibility flags, attribute metadata).
27
- Expect the build to take several minutes on a clean run.
28
-
29
- ## Element Name Resolution
30
-
31
- The build script derives element names from file names using a regex replacement:
32
-
33
- - `spec.div.jsonc` becomes element name `div`
34
- - `spec.svg_circle.jsonc` becomes element name `svg_circle`, which is later resolved
35
- to namespace `svg:circle` by `resolveNamespace()`
36
- - `spec.mml_math.jsonc` becomes element name `mml_math`, which is later resolved
37
- to namespace `mml:math` by `resolveNamespace()`
38
- - Heading elements (`h1` through `h6`) are mapped to the MDN URL path `Heading_Elements`
39
-
40
- This naming convention is critical. Any deviation from the `spec.<name>.jsonc` pattern
41
- will cause the element to be silently excluded from the build output.
42
-
43
- ## Common Recipes
44
-
45
- ### 1. Adding a New HTML Element
46
-
47
- 1. Create `src/spec.<element>.jsonc` (e.g., `src/spec.dialog.jsonc`)
48
- 2. Define the specification with at minimum:
49
- - `contentModel` with `contents`
50
- - `globalAttrs` (typically `#HTMLGlobalAttrs`, `#GlobalEventAttrs`, `#ARIAAttrs` all set to `true`)
51
- - `attributes` (element-specific attributes, can be empty `{}`)
52
- - `aria` with `implicitRole` and `permittedRoles`
53
- 3. Add comments at the top referencing the relevant spec URLs:
54
- ```
55
- // https://html.spec.whatwg.org/multipage/...
56
- // https://www.w3.org/TR/html-aria/#el-<element>
57
- // https://w3c.github.io/html-aria/#el-<element>
58
- ```
59
- 4. If the element belongs to any content categories (flow, phrasing, etc.), add it
60
- to the appropriate categories in `src/spec-common.contents.jsonc` -- otherwise
61
- `@markuplint/rules`' `permitted-contents` rule will not recognize it as valid
62
- content in parent elements that allow those categories
63
- 5. Run `yarn workspace @markuplint/html-spec run gen`
64
- 6. Verify the element appears correctly in `index.json`
65
-
66
- ### 2. Modifying an Existing Element's Attributes
67
-
68
- 1. Open the relevant `src/spec.<element>.jsonc`
69
- 2. Add or modify entries in the `attributes` object
70
- 3. For conditional attributes, add a `condition` field with a CSS selector:
71
- ```json
72
- "accept": {
73
- "type": { "token": "Accept", "separator": "comma" },
74
- "condition": "[type='file' i]"
75
- }
76
- ```
77
- 4. Run `yarn workspace @markuplint/html-spec run gen`
78
- 5. Check `index.json` to confirm the attribute appears with correct metadata
79
-
80
- ### 2b. Using Conditional Value Types (`ConditionalAttributeType[]`)
81
-
82
- When an attribute's expected value type depends on **another attribute's value** (not just its existence), use `ConditionalAttributeType[]` as the `type` field instead of a single `AttributeType`.
83
-
84
- This is different from the attribute-level `condition` field (Recipe 2), which controls **whether the attribute is valid at all**. Conditional value types control **what values are valid** depending on element state.
85
-
86
- **Example** -- `input[value]` type depends on the `type` attribute:
87
-
88
- ```jsonc
89
- "value": {
90
- "type": [
91
- { "condition": "[type='color' i]", "type": "SimpleColor" },
92
- { "condition": "[type='url' i]", "type": "URL" },
93
- { "condition": "[type='email' i]", "type": "Email" },
94
- { "condition": ["[type='number' i]", "[type='range' i]"], "type": { "type": "float" } },
95
- { "condition": "[type='date' i]", "type": "DateString" }
96
- ]
97
- }
98
- ```
99
-
100
- **How it works at runtime:**
101
-
102
- 1. `isValidAttr()` in `@markuplint/rules` detects `ConditionalAttributeType[]`
103
- 2. Each entry's `condition` (a CSS selector or array of selectors) is matched against the element
104
- 3. The first matching entry's `type` is used for validation
105
- 4. If no condition matches, the value falls back to `Any` (no constraint)
106
-
107
- **When to use:** When the HTML spec says "the value must be X when attribute Y is Z" — e.g., `<input value>` must be a valid simple color when `type=color`.
108
-
109
- **Example 2** -- `link[as]` valid values depend on `rel`:
110
-
111
- ```jsonc
112
- "as": {
113
- "type": [
114
- {
115
- "condition": "[rel~='preload' i]",
116
- "type": { "enum": ["fetch", "font", "image", "script", "style", "track"] }
117
- },
118
- {
119
- "condition": "[rel~='modulepreload' i]",
120
- "type": { "enum": ["audioworklet", "json", "paintworklet", "script", "serviceworker", "sharedworker", "style", "worker"] }
121
- }
122
- ],
123
- "condition": ["[rel~='preload' i]", "[rel~='modulepreload' i]"]
124
- }
125
- ```
126
-
127
- Note: The attribute-level `condition` controls whether the `as` attribute is valid at all (only with `rel=preload` or `rel=modulepreload`), while the type-level `ConditionalAttributeType[]` controls which enum values are valid for each `rel` value.
128
-
129
- **See also:** `ConditionalAttributeType` in `@markuplint/ml-spec` docs (`docs/type-definitions.md`).
130
-
131
- ### 3. Adding an SVG Element
132
-
133
- 1. Create `src/spec.svg_<localname>.jsonc` (e.g., `src/spec.svg_circle.jsonc`)
134
- 2. The element name will be inferred as `svg:<localname>` (e.g., `svg:circle`)
135
- 3. Use SVG-specific global attribute categories:
136
- ```json
137
- "globalAttrs": {
138
- "#HTMLGlobalAttrs": true,
139
- "#GlobalEventAttrs": true,
140
- "#ARIAAttrs": true,
141
- "#SVGCoreAttrs": ["id", "tabindex", "autofocus", "lang", "xml:space", "class", "style"],
142
- "#SVGPresentationAttrs": [...]
143
- }
144
- ```
145
- 4. For ARIA, SVG elements typically use AAM references:
146
- ```json
147
- "aria": {
148
- "implicitRole": "group",
149
- "permittedRoles": { "core-aam": true, "graphics-aam": true }
150
- }
151
- ```
152
- 5. Run `yarn workspace @markuplint/html-spec run gen`
153
-
154
- ### 4. Adding a MathML Element
155
-
156
- 1. Create `src/spec.mml_<localname>.jsonc` (e.g., `src/spec.mml_mfrac.jsonc`)
157
- 2. The element name will be inferred as `mml:<localname>` (e.g., `mml:mfrac`)
158
- 3. Use MathML-specific global attribute categories:
159
- ```json
160
- "globalAttrs": {
161
- "#HTMLGlobalAttrs": true,
162
- "#GlobalEventAttrs": true,
163
- "#ARIAAttrs": true,
164
- "#MathMLGlobalAttrs": true
165
- }
166
- ```
167
- 4. For ARIA, MathML elements typically use the MathML-AAM reference:
168
- ```json
169
- "aria": {
170
- "implicitRole": false,
171
- "permittedRoles": { "mathml-aam": true }
172
- }
173
- ```
174
- 5. For elements with a fixed number of children (e.g., `mfrac` has exactly 2),
175
- use the `max` field:
176
- ```json
177
- "contentModel": {
178
- "contents": [{ "oneOrMore": ":model(MathMLPresentation)", "max": 2 }]
179
- }
180
- ```
181
- 6. If the element belongs to a MathML content category, add it to the appropriate
182
- category in `src/spec-common.contents.jsonc` (e.g., `#MathMLPresentation`)
183
- 7. Run `yarn workspace @markuplint/html-spec run gen`
184
-
185
- ### 5. Updating Global Attribute Categories
186
-
187
- 1. Edit `src/spec-common.attributes.jsonc`
188
- 2. Each top-level key is a category (e.g., `#HTMLGlobalAttrs`)
189
- 3. Add, remove, or modify attribute definitions within the category
190
- 4. Run `yarn workspace @markuplint/html-spec run gen`
191
- 5. All elements referencing that category will pick up the changes
192
-
193
- ### 6. Adding or Updating Content Model Categories
194
-
195
- 1. Edit `src/spec-common.contents.jsonc`
196
- 2. Add a new entry to the `models` object or add elements to existing categories:
197
- ```json
198
- "#newCategory": ["element1", "element2", "svg|element3"]
199
- ```
200
- 3. Use `svg|<name>` prefix for SVG elements and `mml|<name>` prefix for MathML elements in category lists
201
- 4. Reference the new category in element specs: `":model(newCategory)"`
202
- 5. Run `yarn workspace @markuplint/html-spec run gen`
203
-
204
- **Important:** Content model categories directly affect downstream behavior:
205
-
206
- - `@markuplint/ml-spec` resolves category names to element lists at runtime via
207
- `contentModelCategoryToTagNames()`. A missing element in a category means it
208
- won't be recognized as valid content where that category is permitted.
209
- - `@markuplint/rules`' `permitted-contents` rule validates child elements against
210
- these patterns. If a new element is not added to its categories, it will be
211
- flagged as an unexpected child.
212
- - The `#palpable` category is used by the `no-empty-palpable-content` rule.
213
- - New category names must also conform to the `Category` enum in
214
- `@markuplint/ml-spec/schemas/content-models.schema.json`.
215
-
216
- ### 7. Updating ARIA Mappings
217
-
218
- 1. Open the relevant `src/spec.<element>.jsonc`
219
- 2. Modify the `aria` object:
220
- - Change `implicitRole` for the default role
221
- - Update `permittedRoles` array
222
- - Add/modify `conditions` for context-dependent ARIA
223
- - Add version-specific overrides under `"1.1"` or `"1.2"` keys
224
- 3. Reference:
225
- - HTML-ARIA: https://w3c.github.io/html-aria/
226
- - WAI-ARIA 1.3: https://w3c.github.io/aria/
227
- 4. Run `yarn workspace @markuplint/html-spec run gen`
228
-
229
- **Note on ARIA versions:** WAI-ARIA 1.1 and 1.2 are finalized Recommendations --
230
- their role/property definitions are stable and will not change. Version-specific
231
- overrides in manual spec files (e.g., `"1.1": { "permittedRoles": [...] }`) exist
232
- to preserve backward-compatible behavior for these fixed versions. WAI-ARIA 1.3 is
233
- still a Working Draft and is the primary source of ongoing ARIA changes in
234
- `yarn up:gen`.
235
-
236
- ### 8. Periodic Specification Update
237
-
238
- The specification data in this package is kept up-to-date by regenerating `index.json`,
239
- which fetches the latest data from MDN and W3C. This is the standard workflow for
240
- incorporating upstream specification changes.
241
-
242
- **Step 1: Regenerate and review the diff**
243
-
244
- ```bash
245
- yarn up:gen
246
- git diff packages/@markuplint/html-spec/index.json
247
- ```
248
-
249
- `index.json` will reflect the latest MDN-scraped data. Review the diff to identify
250
- what has changed. Typical changes include:
251
-
252
- - **Minor description rewording** -- MDN frequently refines element/attribute/role
253
- descriptions. These are cosmetic and can be committed as-is.
254
- - **New attributes added** -- MDN may surface newly standardized or experimental
255
- attributes (e.g., `interestfor`, `switch`). These come from MDN scraping and
256
- require no manual spec file changes.
257
- - **Flag changes** -- Attributes may transition between `experimental`, `deprecated`,
258
- and `nonStandard` status as standards evolve.
259
- - **Significant specification changes** -- For example, an ARIA property changing
260
- from `required` to `inherited`, or a content model restructuring.
261
- - **ARIA changes** -- The ARIA role and property definitions in `index.json` are
262
- scraped from W3C specifications. WAI-ARIA 1.1 and 1.2 are finalized
263
- Recommendations and will not change. WAI-ARIA 1.3, however, is still a Working
264
- Draft, so `yarn up:gen` will regularly pull in new or revised role definitions,
265
- property requirements, and description updates from the evolving 1.3 spec.
266
-
267
- **Step 2: Handle minor changes**
268
-
269
- For description rewording and other cosmetic changes, no action is needed beyond
270
- committing the updated `index.json`. These changes reflect upstream improvements
271
- and should be accepted as-is.
272
-
273
- > **Caution -- ARIA version duplication:** `index.json` contains role definitions for
274
- > WAI-ARIA 1.1, 1.2, and 1.3, so many strings appear three times. When editing
275
- > descriptions or properties, **do not use `replace_all`** -- it will modify all three
276
- > versions simultaneously. Always target the specific version block you intend to change.
277
-
278
- **Step 3: Handle significant specification changes**
279
-
280
- If the diff reveals a substantive change to element behavior, ARIA mappings, or
281
- content models, the manual spec files may need updating:
282
-
283
- 1. Identify which elements are affected
284
- 2. Update the relevant `src/spec.*.jsonc` or `src/spec-common.*.jsonc` files to
285
- reflect the new specification. In rare cases, `@markuplint/ml-spec` schemas
286
- or types may also need updating.
287
- 3. Regenerate to incorporate the manual spec changes:
288
- ```bash
289
- yarn up:gen
290
- ```
291
- 4. **Idempotency verification** -- Confirm your spec file changes produce stable
292
- output before committing:
293
- ```bash
294
- # Stage spec files and index.json
295
- git add packages/@markuplint/html-spec/src/spec.*.jsonc packages/@markuplint/html-spec/index.json
296
- # Regenerate
297
- yarn up:gen
298
- # Check that the attributes you changed are NOT in the diff (= stable output)
299
- git diff packages/@markuplint/html-spec/index.json | grep '"your-attr"'
300
- # If stable, discard the regenerated file and use the staged version
301
- git checkout packages/@markuplint/html-spec/index.json
302
- ```
303
- If the diff shows unexpected changes for your attribute, it means the spec file
304
- and the generator produce different values -- investigate before committing.
305
-
306
- **Step 4: Commit and PR**
307
-
308
- Stage and commit `index.json` (and any modified `src/` files if applicable).
309
-
310
- Use conventional commit prefixes based on the nature of the change:
311
-
312
- | Change type | Prefix | Example |
313
- | ------------------------ | ------- | ------------------------------------------------- |
314
- | Description updates only | `chore` | `chore(html-spec): update role descriptions` |
315
- | Attribute/spec additions | `feat` | `feat(html-spec): add input switch attribute` |
316
- | Spec data corrections | `fix` | `fix(html-spec): correct ARIA mapping for button` |
317
-
318
- **PR separation:** Each specification change (new attribute, ARIA mapping fix, etc.)
319
- should be on its own branch and PR. Description-only updates can be batched into a
320
- single PR.
321
-
322
- This process may seem involved, but reviewing the diff is essential for understanding
323
- what has changed in web standards and ensuring the spec data remains accurate.
324
-
325
- ### 9. Marking an Element as Obsolete
326
-
327
- Elements can be marked obsolete in two ways:
328
-
329
- - **Via the hardcoded list**: Add the element name to the `obsoleteList` array in `packages/@markuplint/html-spec/generator/html-elements.ts`
330
- - **Via manual spec**: Set `"obsolete": true` in the element's spec file
331
-
332
- Obsolete elements automatically get:
333
-
334
- - `cite` pointing to the HTML spec obsolete features section
335
- - `contents: true` (any content allowed)
336
- - `permittedRoles: true`, `implicitRole: false`
337
-
338
- ## File Classification
339
-
340
- ### Editable Files (modify these)
341
-
342
- | File | Description |
343
- | ---------------------------------- | -------------------------------------- |
344
- | `src/spec.*.jsonc` | Per-element specifications (208 files) |
345
- | `src/spec-common.attributes.jsonc` | Global attribute category definitions |
346
- | `src/spec-common.contents.jsonc` | Content model category macros |
347
- | `build.mjs` | Build script configuration |
348
-
349
- ### Generated Files (DO NOT EDIT)
350
-
351
- | File | Description |
352
- | ------------ | ---------------------------------------------- |
353
- | `index.json` | Consolidated specification output (48K+ lines) |
354
-
355
- ### Static Files
356
-
357
- | File | Description |
358
- | ------------ | ---------------------------- |
359
- | `index.js` | CommonJS entry point |
360
- | `index.d.ts` | TypeScript type declarations |
361
-
362
- ## Testing
363
-
364
- ### Schema Validation Tests
365
-
366
- The test file `test/structure.spec.mjs` validates:
367
-
368
- 1. **Structure test**: Ensures all elements can be resolved via `resolveNamespace()` and `getAttrSpecsByNames()`
369
- 2. **Schema tests**: Validates source JSON files against JSON schemas from `@markuplint/ml-spec`:
370
- - `spec.*.jsonc` files against `element.schema.json` (with aria, content-models, global-attributes, attributes, and types schemas)
371
- - `spec-common.attributes.jsonc` against `global-attributes.schema.json`
372
-
373
- The schema validation uses `ajv` (Another JSON Schema Validator) with multiple
374
- interrelated schemas loaded together. The element schema references the aria,
375
- content-models, global-attributes, attributes, and types schemas, so all must
376
- be registered in the same `Ajv` instance.
377
-
378
- Run tests:
379
-
380
- ```bash
381
- yarn workspace @markuplint/html-spec run test
382
- ```
383
-
384
- Or from the repository root:
385
-
386
- ```bash
387
- yarn test --scope @markuplint/html-spec
388
- ```
389
-
390
- ### Manual Verification
391
-
392
- After regeneration, it is good practice to spot-check `index.json` for the
393
- elements you changed. Because the file exceeds 48,000 lines, use targeted
394
- searches rather than manual scrolling:
395
-
396
- ```bash
397
- # Find a specific element's entry
398
- grep -n '"name": "dialog"' index.json
399
-
400
- # Check an attribute was added
401
- grep -A5 '"accept"' index.json
402
- ```
403
-
404
- ## Dependency Management
405
-
406
- ### Production Dependency
407
-
408
- - **`@markuplint/ml-spec`**: Provides type definitions (`Cites`, `ElementSpec`, `SpecDefs`) and JSON schemas used for validation. When `ml-spec` types change, element spec files may need updating to match.
409
-
410
- ### Dev Dependencies
411
-
412
- - **`@markuplint/test-tools`**: Test utilities. Updates are generally safe.
413
-
414
- ### When Updating Dependencies
415
-
416
- 1. Always regenerate after updating `generator/`: `yarn up:gen`
417
- 2. Review `index.json` diff carefully for unexpected changes
418
- 3. Run tests to ensure schema validation passes
419
-
420
- ## Troubleshooting
421
-
422
- ### Build Failure: Network Error During Generation
423
-
424
- **Symptom**: `gen:build` fails with fetch errors.
425
- **Cause**: MDN or W3C servers are unreachable or have changed their page structure.
426
- **Resolution**:
427
-
428
- - Check network connectivity
429
- - Failed fetches are cached as empty strings; the build will continue but affected elements will have missing metadata
430
- - If MDN page structure changed, `generator/` scraping selectors may need updating
431
-
432
- ### Schema Validation Error
433
-
434
- **Symptom**: Tests fail with "X is invalid" errors.
435
- **Cause**: A spec file doesn't conform to the JSON schema.
436
- **Resolution**:
437
-
438
- - Check the reported file against the referenced schema
439
- - Common issues: missing required fields, wrong type for a field, invalid enum values
440
- - Schemas are in `packages/@markuplint/ml-spec/schemas/`
441
-
442
- ### Unexpected Changes in index.json
443
-
444
- **Symptom**: `index.json` diff shows unexpected additions or removals after regeneration.
445
- **Cause**: External data sources (MDN, W3C) have been updated.
446
- **Resolution**:
447
-
448
- - Review changes carefully; MDN updates are generally improvements
449
- - If a change is incorrect, override it in the manual spec file (manual data takes precedence)
450
- - Check if MDN page URLs have changed (element pages may have been restructured)
451
-
452
- ### Missing Element After Regeneration
453
-
454
- **Symptom**: An element disappears from `index.json`.
455
- **Cause**: The source spec file may have been deleted or renamed incorrectly.
456
- **Resolution**:
457
-
458
- - Verify the file exists: `src/spec.<element>.json`
459
- - Check file naming: must match `spec.*.jsonc` glob pattern
460
- - For SVG: must be `spec.svg_<name>.jsonc`
461
- - For MathML: must be `spec.mml_<name>.jsonc`
462
-
463
- ### JSON Comment Syntax Errors
464
-
465
- **Symptom**: Build fails with a JSON parse error.
466
- **Cause**: Spec files support JavaScript-style comments (`//` and `/* */`) via
467
- `jsonc-parser`, but other non-standard JSON syntax is not supported.
468
- **Resolution**:
469
-
470
- - Ensure trailing commas are not present
471
- - Verify comment syntax uses `//` or `/* */` only
472
- - Check for unbalanced braces or brackets
473
-
474
- ## Related Documentation
475
-
476
- - [Element Specification Format](./element-spec-format.md) -- Comprehensive
477
- reference for the JSON element spec file format, content model patterns,
478
- attribute definitions, and ARIA integration