@markuplint/html-spec 4.16.1 → 4.17.0

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