baseline-foundry 0.1.3 → 0.1.4

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.
Files changed (43) hide show
  1. package/README.md +568 -552
  2. package/config/experiments/ibm-plex-engine-smoke.json +115 -115
  3. package/config/experiments/ubuntu-engine-smoke.json +115 -115
  4. package/config/foundation-theme.json +110 -110
  5. package/config/tiers/app.json +116 -116
  6. package/config/tiers/documentation.json +116 -116
  7. package/config/tiers/editorial.json +116 -116
  8. package/config/tiers/os.json +116 -116
  9. package/dist/baseline-grid-overlay.js +43 -43
  10. package/dist/css-app-tier.js +73 -73
  11. package/dist/css-components/article-pagination.js +140 -140
  12. package/dist/css-components/button-actions.js +161 -161
  13. package/dist/css-components/cards-options.js +176 -176
  14. package/dist/css-components/chip-badge-status.js +158 -158
  15. package/dist/css-components/content-card.js +490 -490
  16. package/dist/css-components/control-row.js +14 -14
  17. package/dist/css-components/cta-figure-aspect.js +120 -120
  18. package/dist/css-components/document-navigation.js +390 -390
  19. package/dist/css-components/editorial-content.js +42 -42
  20. package/dist/css-components/icon.js +72 -72
  21. package/dist/css-components/interactive-feedback.js +217 -217
  22. package/dist/css-components/interactive-tables.js +155 -155
  23. package/dist/css-components/legacy-navigation.d.ts +9 -0
  24. package/dist/css-components/legacy-navigation.js +763 -0
  25. package/dist/css-components/linked-logo-site-layout.js +177 -177
  26. package/dist/css-components/list-tree.js +94 -94
  27. package/dist/css-components/logo-media.js +201 -201
  28. package/dist/css-components/navigation-layout.js +144 -144
  29. package/dist/css-components/panel.js +144 -144
  30. package/dist/css-components/search-box-and-filter.js +263 -263
  31. package/dist/css-components/sites-editorial-ports.js +243 -243
  32. package/dist/css-components/sites-foundation.js +146 -146
  33. package/dist/css-components/sites-rich-lists.js +344 -344
  34. package/dist/css-components/static-content-ports.js +182 -182
  35. package/dist/css-components/tab-section.js +82 -82
  36. package/dist/css-components/table.js +75 -75
  37. package/dist/css-components/tabs-choice-breadcrumbs.js +303 -303
  38. package/dist/css-components/tiered-list-equal-height-row.js +271 -271
  39. package/dist/css-components.js +1791 -2542
  40. package/dist/css-grid.js +172 -172
  41. package/dist/css.js +240 -240
  42. package/docs/publishing.md +30 -13
  43. package/package.json +119 -108
package/dist/css.js CHANGED
@@ -164,49 +164,49 @@ export function generateFoundryCss(tokens, options = {}) {
164
164
  if (!body) {
165
165
  throw new Error("Theme tokens require a body role.");
166
166
  }
167
- return `${fontFaces}${fontFaces ? "\n" : ""}${generateBaselineGridOverlayCss({ baselineUnit: tokens.baselineUnit })}
168
-
169
- ${themeSurfaceRule(":where(.bf-theme)", tokens)}
170
- ${scopedThemeCss}
171
-
172
- :where(.bf-theme) {
173
- ${vanillaThemeColorVars("light")}${foundryThemeRootColorVars("light")}
174
- background: var(--bf-color-bg);
175
- color: var(--bf-color-text);
176
- font-family: var(--bf-body-font-family, ${body.fontStack});
177
- font-size: var(--bf-body-font-size, ${body.fontSize});
178
- font-style: var(--bf-body-font-style, ${body.fontStyle ?? "normal"});
179
- font-weight: var(--bf-body-font-weight, ${body.fontWeight ?? 400});
180
- line-height: var(--bf-body-line-height, ${body.lineHeight});
181
- }
182
-
183
- :where(.bf-theme.is-dark) {
184
- ${vanillaThemeColorVars("dark")}${foundryThemeRootColorVars("dark")}
185
- color-scheme: dark;
186
- }
187
-
188
- :where(.bf-theme.is-light) {
189
- color-scheme: light;
190
- }
191
-
192
- :where(.bf-theme),
193
- :where(.bf-theme) * {
194
- box-sizing: border-box;
195
- }
196
-
197
- ${generateBaselineGridThemeOverrideCss()}
198
-
199
- :where(.bf-theme) :where(h1, h2, h3, h4, h5, h6, p, blockquote, figure, ul, ol, dl, pre) {
200
- margin: 0;
201
- }
202
-
203
- :where(.bf-theme) :where(img, picture, svg, video) {
204
- block-size: auto;
205
- display: block;
206
- inline-size: auto;
207
- max-inline-size: 100%;
208
- }
209
-
167
+ return `${fontFaces}${fontFaces ? "\n" : ""}${generateBaselineGridOverlayCss({ baselineUnit: tokens.baselineUnit })}
168
+
169
+ ${themeSurfaceRule(":where(.bf-theme)", tokens)}
170
+ ${scopedThemeCss}
171
+
172
+ :where(.bf-theme) {
173
+ ${vanillaThemeColorVars("light")}${foundryThemeRootColorVars("light")}
174
+ background: var(--bf-color-bg);
175
+ color: var(--bf-color-text);
176
+ font-family: var(--bf-body-font-family, ${body.fontStack});
177
+ font-size: var(--bf-body-font-size, ${body.fontSize});
178
+ font-style: var(--bf-body-font-style, ${body.fontStyle ?? "normal"});
179
+ font-weight: var(--bf-body-font-weight, ${body.fontWeight ?? 400});
180
+ line-height: var(--bf-body-line-height, ${body.lineHeight});
181
+ }
182
+
183
+ :where(.bf-theme.is-dark) {
184
+ ${vanillaThemeColorVars("dark")}${foundryThemeRootColorVars("dark")}
185
+ color-scheme: dark;
186
+ }
187
+
188
+ :where(.bf-theme.is-light) {
189
+ color-scheme: light;
190
+ }
191
+
192
+ :where(.bf-theme),
193
+ :where(.bf-theme) * {
194
+ box-sizing: border-box;
195
+ }
196
+
197
+ ${generateBaselineGridThemeOverrideCss()}
198
+
199
+ :where(.bf-theme) :where(h1, h2, h3, h4, h5, h6, p, blockquote, figure, ul, ol, dl, pre) {
200
+ margin: 0;
201
+ }
202
+
203
+ :where(.bf-theme) :where(img, picture, svg, video) {
204
+ block-size: auto;
205
+ display: block;
206
+ inline-size: auto;
207
+ max-inline-size: 100%;
208
+ }
209
+
210
210
  :where(.bf-theme) :where(a) {
211
211
  color: var(--bf-color-link);
212
212
  text-decoration: none;
@@ -221,211 +221,211 @@ ${generateBaselineGridThemeOverrideCss()}
221
221
  :where(.bf-theme) :where(a:visited) {
222
222
  color: var(--bf-color-link-visited);
223
223
  }
224
-
225
- :where(.bf-theme) :where(a:focus-visible) {
226
- outline: 2px solid var(--bf-color-focus);
227
- outline-offset: 2px;
228
- }
229
-
230
- :where(.bf-theme) :where(code) {
231
- font-family: "Ubuntu Sans Mono", ui-monospace, SFMono-Regular, Consolas, monospace;
232
- font-size: 0.95em;
233
- }
234
-
235
- :where(.bf-theme) :where(.bf-page) {
236
- margin-inline: auto;
237
- max-inline-size: var(--bf-content-max-width);
238
- padding-inline: max(var(--bf-page-margin), var(--bf-content-padding-inline));
239
- }
240
-
241
- :where(.bf-theme) :where(.bf-page.is-fill) {
242
- min-block-size: 100vh;
243
- padding-block-end: var(--bf-section-space);
244
- }
245
-
246
- :where(.bf-theme) :where(.bf-section) {
247
- margin-block-end: 0;
248
- }
249
-
250
- :where(.bf-theme) :where(.bf-section.is-shallow) {
251
- margin-block-end: 0;
252
- }
253
-
254
- :where(.bf-theme) :where(.bf-section.is-deep) {
255
- margin-block-end: 0;
256
- }
257
-
258
- :where(.bf-theme) :where(.bf-strip) {
259
- padding-block-end: var(--bf-strip-space);
260
- }
261
-
262
- :where(.bf-theme) :where(.bf-fixed-width, .bf-measure) {
263
- inline-size: min(100%, var(--bf-measure));
264
- }
265
-
266
- :where(.bf-theme) :where(.bf-inline-size) {
267
- --bf-inline-size: 18rem;
268
- flex: 0 1 var(--bf-inline-size);
269
- inline-size: min(100%, var(--bf-inline-size));
270
- min-inline-size: min(100%, var(--bf-inline-size));
271
- }
272
-
273
- :where(.bf-theme) :where(.bf-inline-size.is-compact) {
274
- --bf-inline-size: 12rem;
275
- }
276
-
277
- :where(.bf-theme) :where(.bf-inline-size.is-regular) {
278
- --bf-inline-size: 18rem;
279
- }
280
-
281
- :where(.bf-theme) :where(.bf-inline-size.is-medium) {
282
- --bf-inline-size: 20rem;
283
- }
284
-
285
- :where(.bf-theme) :where(.bf-inline-size.is-wide) {
286
- --bf-inline-size: 24rem;
287
- }
288
-
289
- :where(.bf-theme) :where(.bf-inline-size.is-x-wide) {
290
- --bf-inline-size: 28rem;
291
- }
292
-
293
- :where(.bf-theme) :where(.bf-stage-shell) {
294
- --bf-stage-shell-gap: var(--bf-space-3);
295
- align-content: center;
296
- align-items: center;
297
- display: grid;
298
- gap: var(--bf-stage-shell-gap);
299
- justify-items: center;
300
- min-block-size: 100%;
301
- min-inline-size: 0;
302
- padding-block: var(--bf-space-4);
303
- }
304
-
305
- :where(.bf-theme) :where(.bf-stage-shell.is-tight) {
306
- --bf-stage-shell-gap: var(--bf-space-2);
307
- }
308
-
309
- :where(.bf-theme) :where(.bf-stage-shell.is-loose) {
310
- --bf-stage-shell-gap: var(--bf-space-4);
311
- }
312
-
224
+
225
+ :where(.bf-theme) :where(a:focus-visible) {
226
+ outline: 2px solid var(--bf-color-focus);
227
+ outline-offset: 2px;
228
+ }
229
+
230
+ :where(.bf-theme) :where(code) {
231
+ font-family: "Ubuntu Sans Mono", ui-monospace, SFMono-Regular, Consolas, monospace;
232
+ font-size: 0.95em;
233
+ }
234
+
235
+ :where(.bf-theme) :where(.bf-page) {
236
+ margin-inline: auto;
237
+ max-inline-size: var(--bf-content-max-width);
238
+ padding-inline: max(var(--bf-page-margin), var(--bf-content-padding-inline));
239
+ }
240
+
241
+ :where(.bf-theme) :where(.bf-page.is-fill) {
242
+ min-block-size: 100vh;
243
+ padding-block-end: var(--bf-section-space);
244
+ }
245
+
246
+ :where(.bf-theme) :where(.bf-section) {
247
+ margin-block-end: 0;
248
+ }
249
+
250
+ :where(.bf-theme) :where(.bf-section.is-shallow) {
251
+ margin-block-end: 0;
252
+ }
253
+
254
+ :where(.bf-theme) :where(.bf-section.is-deep) {
255
+ margin-block-end: 0;
256
+ }
257
+
258
+ :where(.bf-theme) :where(.bf-strip) {
259
+ padding-block-end: var(--bf-strip-space);
260
+ }
261
+
262
+ :where(.bf-theme) :where(.bf-fixed-width, .bf-measure) {
263
+ inline-size: min(100%, var(--bf-measure));
264
+ }
265
+
266
+ :where(.bf-theme) :where(.bf-inline-size) {
267
+ --bf-inline-size: 18rem;
268
+ flex: 0 1 var(--bf-inline-size);
269
+ inline-size: min(100%, var(--bf-inline-size));
270
+ min-inline-size: min(100%, var(--bf-inline-size));
271
+ }
272
+
273
+ :where(.bf-theme) :where(.bf-inline-size.is-compact) {
274
+ --bf-inline-size: 12rem;
275
+ }
276
+
277
+ :where(.bf-theme) :where(.bf-inline-size.is-regular) {
278
+ --bf-inline-size: 18rem;
279
+ }
280
+
281
+ :where(.bf-theme) :where(.bf-inline-size.is-medium) {
282
+ --bf-inline-size: 20rem;
283
+ }
284
+
285
+ :where(.bf-theme) :where(.bf-inline-size.is-wide) {
286
+ --bf-inline-size: 24rem;
287
+ }
288
+
289
+ :where(.bf-theme) :where(.bf-inline-size.is-x-wide) {
290
+ --bf-inline-size: 28rem;
291
+ }
292
+
293
+ :where(.bf-theme) :where(.bf-stage-shell) {
294
+ --bf-stage-shell-gap: var(--bf-space-3);
295
+ align-content: center;
296
+ align-items: center;
297
+ display: grid;
298
+ gap: var(--bf-stage-shell-gap);
299
+ justify-items: center;
300
+ min-block-size: 100%;
301
+ min-inline-size: 0;
302
+ padding-block: var(--bf-space-4);
303
+ }
304
+
305
+ :where(.bf-theme) :where(.bf-stage-shell.is-tight) {
306
+ --bf-stage-shell-gap: var(--bf-space-2);
307
+ }
308
+
309
+ :where(.bf-theme) :where(.bf-stage-shell.is-loose) {
310
+ --bf-stage-shell-gap: var(--bf-space-4);
311
+ }
312
+
313
313
  :where(.bf-theme) :where(.bf-stack) {
314
314
  --bf-stack-space: var(--bf-section-space-shallow);
315
315
  align-content: start;
316
316
  display: grid;
317
- gap: var(--bf-stack-space);
318
- }
319
-
320
- :where(.bf-theme) :where(.bf-stack.is-flush) {
321
- --bf-stack-space: 0px;
322
- }
323
-
324
- :where(.bf-theme) :where(.bf-stack.is-extra-dense) {
325
- --bf-stack-space: var(--bf-space-half);
326
- }
327
-
328
- :where(.bf-theme) :where(.bf-stack.is-dense) {
329
- --bf-stack-space: var(--bf-space-1);
330
- }
331
-
332
- :where(.bf-theme) :where(.bf-stack.is-loose) {
333
- --bf-stack-space: var(--bf-space-2);
334
- }
335
-
336
- :where(.bf-theme) :where(.bf-stack.is-section-shallow) {
337
- --bf-stack-space: var(--bf-section-space-shallow);
338
- }
339
-
340
- :where(.bf-theme) :where(.bf-stack.is-section) {
341
- --bf-stack-space: var(--bf-section-space);
342
- }
343
-
317
+ gap: var(--bf-stack-space);
318
+ }
319
+
320
+ :where(.bf-theme) :where(.bf-stack.is-flush) {
321
+ --bf-stack-space: 0px;
322
+ }
323
+
324
+ :where(.bf-theme) :where(.bf-stack.is-extra-dense) {
325
+ --bf-stack-space: var(--bf-space-half);
326
+ }
327
+
328
+ :where(.bf-theme) :where(.bf-stack.is-dense) {
329
+ --bf-stack-space: var(--bf-space-1);
330
+ }
331
+
332
+ :where(.bf-theme) :where(.bf-stack.is-loose) {
333
+ --bf-stack-space: var(--bf-space-2);
334
+ }
335
+
336
+ :where(.bf-theme) :where(.bf-stack.is-section-shallow) {
337
+ --bf-stack-space: var(--bf-section-space-shallow);
338
+ }
339
+
340
+ :where(.bf-theme) :where(.bf-stack.is-section) {
341
+ --bf-stack-space: var(--bf-section-space);
342
+ }
343
+
344
344
  :where(.bf-theme) :where(.bf-stack.is-section-deep) {
345
345
  --bf-stack-space: var(--bf-section-space-deep);
346
346
  }
347
347
 
348
348
  :where(.bf-theme) :where(.bf-cluster) {
349
- align-items: flex-start;
350
- display: flex;
351
- flex-wrap: wrap;
352
- gap: var(--bf-space-2);
353
- }
354
-
355
- :where(.bf-theme) :where(.bf-cluster.is-split) {
356
- justify-content: space-between;
357
- }
358
-
359
- :where(.bf-theme) :where(.bf-prose) {
360
- display: grid;
361
- gap: var(--bf-section-space-shallow);
362
- inline-size: min(100%, var(--bf-measure));
363
- }
364
-
365
- ${roleRules}
366
-
367
- ${capEngineDemo}
368
-
369
- :where(.bf-theme) :where(ul, ol) {
370
- margin-bottom: 0;
371
- }
372
-
373
- :where(.bf-theme) :where(.bf-prose ul, .bf-prose ol) {
374
- padding-inline-start: var(--bf-space-4);
375
- }
376
-
377
- :where(.bf-theme) :where(.bf-prose li) {
378
- margin: 0 0 ${roleMarginBottomVar("body", baselineCompensation(body.nudgeTop, baselineUnit))};
379
- padding-block-end: 0rem;
380
- padding-block-start: ${roleNudgeStartVar("body", body.nudgeTop)};
381
- }
382
-
383
- :where(.bf-theme) :where(.bf-prose blockquote) {
384
- border-inline-start: 1px solid var(--bf-color-rule);
385
- color: var(--bf-color-muted);
386
- font-family: ${roleFontFamilyVar("body", body.fontStack)};
387
- font-size: ${roleFontSizeVar("body", body.fontSize)};
388
- font-style: ${roleFontStyleVar("body", body.fontStyle ?? "normal")};
389
- font-weight: ${roleFontWeightVar("body", body.fontWeight ?? 400)};
390
- line-height: ${roleLineHeightVar("body", body.lineHeight)};
391
- margin-bottom: ${roleMarginBottomVar("body", baselineCompensation(body.nudgeTop, baselineUnit))};
392
- max-inline-size: calc(var(--bf-measure) + var(--bf-space-4));
393
- padding-block-end: 0rem;
394
- padding-block-start: ${roleNudgeStartVar("body", body.nudgeTop)};
395
- padding-inline-start: var(--bf-space-3);
396
- }
397
-
398
- :where(.bf-theme) :where(.bf-rule, .bf-prose hr) {
399
- background: var(--bf-color-rule);
400
- block-size: 1px;
401
- border: 0;
402
- inline-size: 100%;
403
- /* Reserve one half-rem rhythm step after the rule, including its */
404
- /* thickness, so borderless content does not touch the divider. */
405
- margin: 0 0 calc(0.5rem - 1px);
406
- }
407
-
408
- /* Highlight rules share the same scalable emphasis-bar geometry as active
409
- * navigation, tabs, notifications, and document-navigation markers. */
410
- :where(.bf-theme) :where(.bf-rule.is-highlighted, .bf-prose hr.is-highlighted) {
411
- block-size: var(--bf-bar-thickness);
412
- margin-block-end: calc(0.5rem - var(--bf-bar-thickness));
413
- }
414
-
415
- :where(.bf-theme) :where(.bf-token-row) {
416
- border-top: 1px solid var(--bf-color-rule);
417
- display: grid;
418
- gap: var(--bf-space-1);
419
- padding-top: var(--bf-space-2);
420
- }
421
-
422
- :where(.bf-theme) :where(.bf-token-row:first-child) {
423
- border-top: 0;
424
- padding-top: 0;
425
- }
426
-
427
- ${componentsCss(tokens, themeSurfaces)}
428
-
429
- ${gridCss()}${presetCss}
349
+ align-items: flex-start;
350
+ display: flex;
351
+ flex-wrap: wrap;
352
+ gap: var(--bf-space-2);
353
+ }
354
+
355
+ :where(.bf-theme) :where(.bf-cluster.is-split) {
356
+ justify-content: space-between;
357
+ }
358
+
359
+ :where(.bf-theme) :where(.bf-prose) {
360
+ display: grid;
361
+ gap: var(--bf-section-space-shallow);
362
+ inline-size: min(100%, var(--bf-measure));
363
+ }
364
+
365
+ ${roleRules}
366
+
367
+ ${capEngineDemo}
368
+
369
+ :where(.bf-theme) :where(ul, ol) {
370
+ margin-bottom: 0;
371
+ }
372
+
373
+ :where(.bf-theme) :where(.bf-prose ul, .bf-prose ol) {
374
+ padding-inline-start: var(--bf-space-4);
375
+ }
376
+
377
+ :where(.bf-theme) :where(.bf-prose li) {
378
+ margin: 0 0 ${roleMarginBottomVar("body", baselineCompensation(body.nudgeTop, baselineUnit))};
379
+ padding-block-end: 0rem;
380
+ padding-block-start: ${roleNudgeStartVar("body", body.nudgeTop)};
381
+ }
382
+
383
+ :where(.bf-theme) :where(.bf-prose blockquote) {
384
+ border-inline-start: 1px solid var(--bf-color-rule);
385
+ color: var(--bf-color-muted);
386
+ font-family: ${roleFontFamilyVar("body", body.fontStack)};
387
+ font-size: ${roleFontSizeVar("body", body.fontSize)};
388
+ font-style: ${roleFontStyleVar("body", body.fontStyle ?? "normal")};
389
+ font-weight: ${roleFontWeightVar("body", body.fontWeight ?? 400)};
390
+ line-height: ${roleLineHeightVar("body", body.lineHeight)};
391
+ margin-bottom: ${roleMarginBottomVar("body", baselineCompensation(body.nudgeTop, baselineUnit))};
392
+ max-inline-size: calc(var(--bf-measure) + var(--bf-space-4));
393
+ padding-block-end: 0rem;
394
+ padding-block-start: ${roleNudgeStartVar("body", body.nudgeTop)};
395
+ padding-inline-start: var(--bf-space-3);
396
+ }
397
+
398
+ :where(.bf-theme) :where(.bf-rule, .bf-prose hr) {
399
+ background: var(--bf-color-rule);
400
+ block-size: 1px;
401
+ border: 0;
402
+ inline-size: 100%;
403
+ /* Reserve one half-rem rhythm step after the rule, including its */
404
+ /* thickness, so borderless content does not touch the divider. */
405
+ margin: 0 0 calc(0.5rem - 1px);
406
+ }
407
+
408
+ /* Highlight rules share the same scalable emphasis-bar geometry as active
409
+ * navigation, tabs, notifications, and document-navigation markers. */
410
+ :where(.bf-theme) :where(.bf-rule.is-highlighted, .bf-prose hr.is-highlighted) {
411
+ block-size: var(--bf-bar-thickness);
412
+ margin-block-end: calc(0.5rem - var(--bf-bar-thickness));
413
+ }
414
+
415
+ :where(.bf-theme) :where(.bf-token-row) {
416
+ border-top: 1px solid var(--bf-color-rule);
417
+ display: grid;
418
+ gap: var(--bf-space-1);
419
+ padding-top: var(--bf-space-2);
420
+ }
421
+
422
+ :where(.bf-theme) :where(.bf-token-row:first-child) {
423
+ border-top: 0;
424
+ padding-top: 0;
425
+ }
426
+
427
+ ${componentsCss(tokens, themeSurfaces)}
428
+
429
+ ${gridCss()}${presetCss}
430
430
  `;
431
431
  }
@@ -30,22 +30,39 @@ CSS, JSON, JavaScript, and type declarations.
30
30
 
31
31
  ## Release checklist
32
32
 
33
- The initial `0.1.3` publication is bootstrapped interactively because npm can
33
+ The initial `0.1.3` publication was bootstrapped interactively because npm can
34
34
  only attach a trusted publisher after a package exists. Routine releases use
35
- the OIDC workflow below.
35
+ the OIDC workflow below. `npm publish` invokes the package's `prepublishOnly`
36
+ release gate, so the workflow does not duplicate that command. The workflow
37
+ pins the supported npm CLI, and no npm write token or recovery code is used.
36
38
 
37
39
  1. Bump BF to the next unused version and regenerate `package-lock.json`.
38
- 2. Run `npm run release:check` and `npm pack --dry-run`; confirm the tarball
39
- contains `dist/`, `config/`, `docs/publishing.md`, `README.md`, `LICENSE`,
40
- and no source or local temporary files.
41
- 3. Commit and push the exact release candidate.
42
- 4. Publish from `.github/workflows/publish.yml`; npm trusted publishing uses a
43
- short-lived GitHub Actions OIDC credential, so no npm token is stored.
44
- 5. Verify `npm view baseline-foundry version`, then install the exact published
45
- version into an empty directory and smoke-test the root, `build`, CSS, token,
46
- surface, and four tier export paths.
47
- 6. Tag the same commit and publish the GitHub release with the npm tarball
48
- attached as a fallback artifact.
40
+ 2. Run `npm run release:preflight:test`, `npm run release:check`, and
41
+ `npm run release:verify -- --pack-current`. The negative fixtures prove used
42
+ versions and ref mismatches fail before publication; the local tarball check
43
+ exercises all runtime and asset entry points in an empty consumer.
44
+ 3. Commit and push the exact release candidate to `main`.
45
+ 4. Dispatch `.github/workflows/publish.yml` with
46
+ `resume_after_publish=false`. The workflow bootstraps the pinned demo font
47
+ and builds the gitignored package outputs before its preflight, which
48
+ requires clean `main`, exact
49
+ `origin/main`, matching lockfile metadata, an unused npm version and tag, and
50
+ the allowlisted package contents.
51
+ 5. `npm publish` obtains a short-lived OIDC credential and owns the sole
52
+ `prepublishOnly` release gate. The workflow then installs the exact registry
53
+ version into an empty directory, checks every public entry point, creates
54
+ SHA-1/SHA-256 records, tags the source commit, and attaches the npm tarball
55
+ and checksums to the matching GitHub release.
56
+ 6. If npm publication succeeded but a later verification or GitHub release step
57
+ failed, rerun the same workflow with `resume_after_publish=true`. Resume mode
58
+ requires the registry version to exist, skips `npm publish`, verifies any
59
+ existing tag targets the same commit, and creates or replaces release assets.
60
+
61
+ The GitHub repository is private. npm trusted publishing still removes the need
62
+ for a long-lived write token, but npm currently does not publish public
63
+ provenance attestations for packages whose source repository is private. The
64
+ tag, checksums, attached npm tarball and clean registry verification are BF's
65
+ durable release evidence; documentation must not claim public provenance.
49
66
 
50
67
  ## Compatibility
51
68