@hirobius/design-system 0.21.0 → 0.22.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.
Files changed (90) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +969 -0
  3. package/CONSUMING.md +23 -9
  4. package/MIGRATIONS.md +603 -0
  5. package/README.md +2 -0
  6. package/UPGRADING.md +222 -0
  7. package/codemods/hds-prefix.mjs +28 -13
  8. package/codemods/jsx-fold.mjs +28 -13
  9. package/codemods/lib/atomic-write.mjs +28 -0
  10. package/codemods/lib/installed-version.mjs +354 -0
  11. package/codemods/lib/project.mjs +395 -0
  12. package/codemods/lib/record.mjs +110 -0
  13. package/codemods/lib/scan.mjs +323 -0
  14. package/codemods/lib/semver.mjs +57 -0
  15. package/codemods/not-found-pattern.mjs +3 -3
  16. package/codemods/patterns-subpath.mjs +28 -12
  17. package/codemods/registry.mjs +67 -0
  18. package/codemods/tile-grid.mjs +3 -3
  19. package/codemods/upgrade.mjs +909 -0
  20. package/dist/hirobius-ui.js +1 -1
  21. package/dist/manifest.js +3 -3
  22. package/dist/styles.css +1 -1
  23. package/dist/tokens.css +1 -1
  24. package/dist/types/src/app/components/alert-dialog.d.ts +1 -1
  25. package/dist/types/src/app/components/alert.d.ts +1 -1
  26. package/dist/types/src/app/components/asset-img.d.ts +1 -1
  27. package/dist/types/src/app/components/avatar-group.d.ts +1 -1
  28. package/dist/types/src/app/components/avatar.d.ts +1 -1
  29. package/dist/types/src/app/components/badge.d.ts +1 -1
  30. package/dist/types/src/app/components/blockquote.d.ts +1 -1
  31. package/dist/types/src/app/components/breadcrumb.d.ts +1 -1
  32. package/dist/types/src/app/components/button.d.ts +1 -1
  33. package/dist/types/src/app/components/callout.d.ts +1 -1
  34. package/dist/types/src/app/components/card.d.ts +1 -1
  35. package/dist/types/src/app/components/checkbox.d.ts +1 -1
  36. package/dist/types/src/app/components/code-block.d.ts +1 -1
  37. package/dist/types/src/app/components/combobox.d.ts +1 -1
  38. package/dist/types/src/app/components/data-table-section.d.ts +1 -1
  39. package/dist/types/src/app/components/destructive-section.d.ts +1 -1
  40. package/dist/types/src/app/components/dialog.d.ts +1 -1
  41. package/dist/types/src/app/components/disclosure.d.ts +1 -1
  42. package/dist/types/src/app/components/divider.d.ts +1 -1
  43. package/dist/types/src/app/components/empty-state.d.ts +1 -1
  44. package/dist/types/src/app/components/field.d.ts +1 -1
  45. package/dist/types/src/app/components/form-actions.d.ts +1 -1
  46. package/dist/types/src/app/components/form.d.ts +1 -1
  47. package/dist/types/src/app/components/hds-tooltip.d.ts +1 -1
  48. package/dist/types/src/app/components/icon.d.ts +1 -1
  49. package/dist/types/src/app/components/inline-link.d.ts +1 -1
  50. package/dist/types/src/app/components/input.d.ts +1 -1
  51. package/dist/types/src/app/components/kbd.d.ts +1 -1
  52. package/dist/types/src/app/components/menu.d.ts +1 -1
  53. package/dist/types/src/app/components/metadata-list.d.ts +1 -1
  54. package/dist/types/src/app/components/metric-tiles.d.ts +1 -1
  55. package/dist/types/src/app/components/pagination.d.ts +1 -1
  56. package/dist/types/src/app/components/popover.d.ts +1 -1
  57. package/dist/types/src/app/components/progress.d.ts +1 -1
  58. package/dist/types/src/app/components/radio.d.ts +1 -1
  59. package/dist/types/src/app/components/select.d.ts +1 -1
  60. package/dist/types/src/app/components/skeleton.d.ts +1 -1
  61. package/dist/types/src/app/components/slider.d.ts +1 -1
  62. package/dist/types/src/app/components/spinner.d.ts +1 -1
  63. package/dist/types/src/app/components/stat.d.ts +1 -1
  64. package/dist/types/src/app/components/status-list-item.d.ts +1 -1
  65. package/dist/types/src/app/components/status-tile.d.ts +1 -1
  66. package/dist/types/src/app/components/table.d.ts +1 -1
  67. package/dist/types/src/app/components/tabs.d.ts +1 -1
  68. package/dist/types/src/app/components/tag.d.ts +1 -1
  69. package/dist/types/src/app/components/text.d.ts +1 -1
  70. package/dist/types/src/app/components/textarea.d.ts +1 -1
  71. package/dist/types/src/app/components/timestamp.d.ts +1 -1
  72. package/dist/types/src/app/components/toast.d.ts +1 -1
  73. package/dist/types/src/app/components/toggle.d.ts +1 -1
  74. package/dist/types/src/index.d.ts +79 -32
  75. package/hirobius.tokens.json +86 -39
  76. package/mcp/catalog.mjs +10 -7
  77. package/mcp/core-set.mjs +66 -0
  78. package/mcp/guide.mjs +9 -52
  79. package/package.json +23 -7
  80. package/public/hds-manifest.json +109 -109
  81. package/src/app/data/component-api.json +54 -54
  82. package/upgrade/index.json +208 -0
  83. package/upgrade/releases/0.17.0.json +962 -0
  84. package/upgrade/releases/0.18.0.json +8 -0
  85. package/upgrade/releases/0.19.0.json +233 -0
  86. package/upgrade/releases/0.19.1.json +8 -0
  87. package/upgrade/releases/0.20.0.json +5108 -0
  88. package/upgrade/releases/0.21.0.json +538 -0
  89. package/upgrade/releases/0.22.0.json +94 -0
  90. package/upgrade/schema.json +989 -0
package/README.md CHANGED
@@ -8,6 +8,8 @@ React + TypeScript component library on a governed design-token pipeline. Live S
8
8
  pnpm add @hirobius/design-system
9
9
  ```
10
10
 
11
+ Upgrading: [UPGRADING.md](UPGRADING.md) lists what each release asks of you. From 0.22.0, `npx @hirobius/design-system@latest upgrade` does it for you.
12
+
11
13
  [![Buttons under the Brand and Theme dials: base and accent-lilac, light and dark](docs/images/storybook-brand-theme-dials.png)](https://hirobius-design-system.vercel.app)
12
14
 
13
15
  <!-- auto:start:front-door-counts -->
package/UPGRADING.md ADDED
@@ -0,0 +1,222 @@
1
+ # Upgrading @hirobius/design-system
2
+
3
+ This file only knows the releases up to the version you have installed, 0.22.0. For newer releases, run `npx @hirobius/design-system@latest upgrade`: it always fetches the newest steps.
4
+
5
+ ## How to upgrade
6
+
7
+ ```sh
8
+ npx @hirobius/design-system@latest upgrade
9
+ ```
10
+
11
+ It finds the version you have, moves you to the newest release, runs each release's codemods (Fixed for you) and lists what is left for you (Do by hand). It works from 0.16.0 on; from an older version, first reach 0.16.0 with the notes in CHANGELOG.md. MIGRATIONS.md has longer guides for the big releases.
12
+
13
+ ## 0.22.0
14
+
15
+ Released 2026-10-08 (minor). 3 changes to make by hand (1 breaking), 1 that looks different and 2 deprecations.
16
+
17
+ ### Looks different
18
+
19
+ - --component-tag-lineHeight is now 1.5 instead of an invalid value the browser ignored, so text that reads it gets that line height.
20
+
21
+ ### Coming next
22
+
23
+ - Divider's strong prop, deprecated since before 0.16.0, still works; use variant="strong" instead. Removed in 1.0.0.
24
+ - InlineCode's compact prop, deprecated since before 0.16.0, still works; use density="compact" instead. Removed in 1.0.0.
25
+
26
+ ### Do by hand
27
+
28
+ - The raw tokens (the tokens export and hirobius.tokens.json) are strict DTCG now, so read semantic.motion.\*.\$value.timingFunction instead of .easing, and the elastic spring and each semantic.elevation level from \$extensions\["com.hirobius.hds"].\$value.
29
+ - The figmaUrl of each component in the ./manifest export (public/hds-manifest.json) now points at file 2VgBbVpKiDnu0aftJEVyBQ, "HDS Tokens & Components"; node ids are unchanged, so a stored link needs only the new file key.
30
+ - The hds-mcp get_component tool now marks core: true only on the 43 ratified core components and recommended: true on the wider set AGENTS.md recommends, and list_core names its set, so read recommended where you relied on core for that wider set.
31
+
32
+ ## 0.21.0
33
+
34
+ Released 2026-10-07 (minor). StatusDot is removed, fonts move to an opt-in fonts.css, the type ramp is cut to 5 roles; AGENTS.md, hds-mcp and the ESLint plugin ship.
35
+
36
+ ### Looks different
37
+
38
+ - AssetImg without a src now shows a fallback that fits its container.
39
+ - A pressed toggle Button now fills with the accent surface, so it reads as on.
40
+ - Card, Surface and StatusTile no longer fill their parent's height, so give the parent a stretching layout (Grid align="stretch") where one must fill it.
41
+ - CodeBlock pads its code 16px instead of 80px.
42
+ - Dialog and AlertDialog now keep a 16px margin from the screen edge and scroll inside when taller than the screen.
43
+ - Disclosure variant="card" no longer clips its content or leaves empty space when closed.
44
+ - A vertical Divider now stretches across its row without needing a parent height.
45
+ - ErrorPattern now fits its container instead of filling the viewport height, so pass fullPage where it is a whole error page.
46
+ - A Button with no variant in FormActions primary now renders as primary, so pass variant where you want another style there.
47
+ - Grid now aligns its items to the start and Grid.Item no longer sets height: 100%, so pass align="stretch" to a Grid whose Cards, Surfaces or StatusTiles must share one height.
48
+ - A horizontal MetadataList now sizes its term column to its content.
49
+ - Progress now shows its neutral fill on a visible track, and its sm bar is 6px tall.
50
+ - SegmentedControl md segments now hug their labels in a 40px rail (sm 32px), so every segment shows instead of one filling the rail.
51
+ - Select now matches Input (40px tall, 44px on small screens, the same fill and an up-down chevron), and the built-in labels of Input, Textarea and Select are 14px like a FormField label.
52
+ - A rectangular Skeleton is now 5rem tall by default and uses the sunken surface fill, so it shows on a Card.
53
+ - Slider's track is now an 8px bar with a visible fill instead of a padded 48px slab.
54
+ - Stat now aligns its content to the start.
55
+ - Table no longer pads its scroll region by default, so pass flush={false} where you want the old inset back.
56
+ - Checkbox, Radio and Toggle rows now share one inset and height.
57
+ - Input, Textarea, Select, Combobox and Slider are now at most 40rem wide, so set --hds-form-control-max-width or add max-w-none to the control's className where one must be wider.
58
+ - On touch screens, Checkbox, Radio and Toggle rows, the Pagination page buttons and the CodeBlock copy button are now at least 44px tall.
59
+ - Headings (h1 to h3, heading1 to heading3) now render at 24px, display at 48px, body at 16px with 1.6 leading and mono at 13px, and eyebrow, badge and micro text is no longer uppercase.
60
+
61
+ ### Coming next
62
+
63
+ - The Text variants heading1, heading2, heading3, technical, eyebrow, badge, docLede, docBody, docSmall and docCode still work but are deprecated; use title, mono, caption, body or ui instead. Removed in 1.0.0.
64
+ - The hds.typeStyles keys from before the ramp (h1 to h3, heading1 to heading3, eyebrow, badge, micro, technical, small, label and the rest) and hds.semantic.typography label, labelDescriptive and labelTechnical still work but are deprecated; use display, title, body, ui, caption or mono instead. Removed in 1.0.0.
65
+
66
+ ### Do by hand
67
+
68
+ - StatusDot and StatusDotProps are removed, so replace each \<StatusDot tone size label> with \<Badge dot tone size label> (BadgeProps for the type), moving any style to a wrapper element or a className first.
69
+ - The HDS ESLint plugin's recommended config now fails raw \<button>, \<input>, \<select>, \<textarea> and \<form> (hds/no-raw-controls), so use Button, Input, Select, Textarea and Form instead or set the rule to 'off'.
70
+ - An info or success Alert now has role="status" instead of role="alert", so tests that find it by role must look for status.
71
+ - A labelled dot Badge now has role="img" instead of role="status", so tests that find it by role must look for img.
72
+ - A className on Button or Card can now override the colours a tone sets, so check any toned Button or Card that also passes colour classes.
73
+ - Combobox now returns focus to its trigger after Escape or a pick.
74
+ - Toggle's input now has role="switch", so tests that find it by role checkbox must look for switch.
75
+ - Checkbox, Radio and Toggle now take one tab stop each instead of two, so keyboard tests that press Tab twice per control must press it once.
76
+ - The CSS variable --semantic-typography-eyebrow-text-transform is gone, so set text-transform: uppercase yourself where a label must stay uppercase.
77
+ - Add import '@hirobius/design-system/fonts.css' next to your tokens.css or styles.css import, or text renders in the system font instead of Satoshi and Geist Mono.
78
+ - The ESLint plugin now ships in the package, so import hds from '@hirobius/design-system/eslint-plugin' and drop the separate @hirobius/eslint-plugin-hds dependency.
79
+ - A Badge with label but no dot now warns once in development, because label only names a dot badge, so put the text in children instead.
80
+
81
+ ## 0.20.0
82
+
83
+ Released 2026-10-01 (minor). Pattern components move to /patterns; deprecated, lab and folded components, Hds\* aliases and 7 runtime deps are removed.
84
+
85
+ ### Fixed for you
86
+
87
+ The upgrade command runs these codemods for you.
88
+
89
+ - Each of these is no longer exported from the package root; import it from @hirobius/design-system/patterns instead: `AssetImg`, `AssetImgProps`, `CodeBlock`, `CodeBlockProps`, `ErrorPattern`, `ErrorPatternProps`, `FieldWiring`, `FieldWiringInput`, `Form`, `FormField`, `FormFieldProps`, `FormFieldShell`, `FormFieldShellProps`, `FormProps`, `Page`, `PageProps`, `Reveal`, `RevealAnimation`, `RevealProps`, `StatusTile`, `StatusTileProps`, `StatusTileTone`, `blockCodeTextVariants`, `blockContainerVariants`, `blockHeaderVariants`, `chevronVariants`, `collapsibleToggleVariants`, `copyButtonVariants`, `inlineCodeTextVariants`, `inlineWrapperVariants`, `prePanelVariants` and `useFieldWiring`. Codemod: `hds-patterns-subpath`.
90
+ - HdsCheckbox is removed from the package root; use Checkbox, the same component under its bare name. Codemod: `hds-prefix`.
91
+ - HdsRadio is removed from the package root; use Radio, the same component under its bare name. Codemod: `hds-prefix`.
92
+ - HdsSelect is removed from the package root; use Select, the same component under its bare name. Codemod: `hds-prefix`.
93
+ - HdsSlider is removed from the package root; use Slider, the same component under its bare name. Codemod: `hds-prefix`.
94
+ - HdsToggle is removed from the package root; use Toggle, the same component under its bare name. Codemod: `hds-prefix`.
95
+ - HdsTooltip is removed from the package root; use Tooltip, the same component under its bare name. Codemod: `hds-prefix`.
96
+ - NotFoundPattern is removed; use ErrorPattern displayText="404" message="Page not found" from @hirobius/design-system/patterns (hds-not-found-pattern rewrites it) instead.
97
+ - TileGrid is removed; use Grid layout="auto-fill" minItemWidth="…" gap="medium" (hds-tile-grid rewrites it) instead.
98
+
99
+ ### Looks different
100
+
101
+ - AssetImg's expand label now uses the on-accent text color, so it stays readable in dark mode.
102
+ - Box sx spacing given as a t-shirt name such as 'md' now applies that spacing, where before it applied none.
103
+ - A pressed Button now tints only its fill with a 5% overlay, white in dark mode, instead of dimming the whole control, and the press no longer animates.
104
+ - Input keeps its inner padding beside icons, the clear button and the spinner, so long text no longer runs under them.
105
+ - A string passed to Input's prefix prop now shows as text inside the field, where before it went to the input element as an attribute.
106
+ - A sortable Table header now fades its label to the muted color on hover.
107
+ - Tooltip now casts the floating shadow that popovers and menus use instead of the dialog shadow.
108
+
109
+ ### Coming next
110
+
111
+ - Box sx spacing names 'tight', 'normal', 'inset' and 'spacious' still work but are deprecated, and a development build warns once for each; use 'sm' to 'xl' instead. Removed in 1.0.0.
112
+ - Stack gap names 'tight', 'normal', 'inset' and 'spacious' still work and do not warn yet, but are removed in 1.0.0, and Stack names no replacement until then.
113
+ - StatusDot still works but is removed in 0.21.0; use Badge dot with the same tone, size and label, and move any style prop to a wrapper or a className first.
114
+ - StatusDotProps is removed in 0.21.0; use BadgeProps instead.
115
+ - hds.density still works but is deprecated; use hds.semantic.space.scale one step down, so density.sm becomes scale.xs. Removed in 1.0.0.
116
+ - Sixteen Tailwind utilities that only HDS's deleted internal files used, such as pt-1 and max-w-2xl, still ship in styles.css and tokens.css until 1.0.0, so a page that relies on HDS's compiled CSS for them should generate them with its own Tailwind. Removed in 1.0.0.
117
+
118
+ ### Do by hand
119
+
120
+ - Each of these is removed with no drop-in replacement, so rewrite or delete the code that imports it: `ActivityEvent`, `ActivityFeed`, `ActivityFeedProps`, `ActivityStatus`, `ActivityTone`, `AppShell`, `AppShellProps`, `ButtonGroup`, `ButtonGroupProps`, `Calendar`, `CalendarProps`, `Carousel`, `CarouselProps`, `CaseStudyLayout`, `CaseStudyLayoutProps`, `CinematicLink`, `CinematicLinkProps`, `CommandPalette`, `CommandPaletteProps`, `ComponentInstanceMatrix`, `ContextMenu`, `DateInput`, `DateInputProps`, `DateRangeInput`, `DateRangeInputProps`, `DateTimeInput`, `DateTimeInputProps`, `DocLinkCard`, `DocLinkCardProps`, `ErrorBoundary`, `ErrorBoundaryProps`, `FileInput`, `FileInputProps`, `FoundationSwatch`, `FoundationSwatchProps`, `HdsDocsShell`, `HdsDocsShellProps`, `HdsSystemDocLayout`, `HeadingStack`, `HeadingStackProps`, `HistoryCard`, `HistoryCardCommit`, `HistoryCardProps`, `HoverCard`, `Lightbox`, `LightboxProps`, `NavGroup`, `NavGroupProps`, `NavItem`, `NavProps`, `OverflowList`, `OverflowListProps`, `SideNav`, `SideNavLevel`, `SideNavProps`, `Sketch`, `SketchProps`, `StackedCardRail`, `StackedCardRailCard`, `StackedCardRailProps`, `Step`, `Stepper`, `StepperField`, `StepperFieldProps`, `StepperProps`, `TextLockup`, `TextLockupProps`, `Token`, `TokenProps`, `Tokenizer`, `TokenizerProps`, `Toolbar`, `ToolbarComponent`, `TopNav`, `TopNavProps`, `TreeList`, `TreeListProps`, `TreeNode`, `activityAvatarVariants`, `activityToneVariants`, `appShellVariants`, `buttonGroupVariants`, `carouselControlVariants`, `circularProgressVariants`, `cmdkDescriptionVariants`, `cmdkKindBadgeVariants`, `cmdkRowVariants`, `defaultActivityEvents`, `docLinkCardVariants`, `fileInputVariants`, `hdsTimeInputVariants`, `hdsToggleButtonVariants`, `headingStackLevelVariants`, `headingStackVariants`, `navGroupLabelVariants`, `navIndicatorVariants`, `navItemVariants`, `selectableCardVariants`, `sideNavVariants`, `stepMarkerVariants`, `tokenLabelVariants`, `tokenNodeInlineVariants` and `tokenShellVariants`.
121
+ - Each of these is no longer exported, so style its component through the component's props instead: `badgeVariants`, `blockquoteVariants`, `buttonVariants`, `cardVariants`, `crumbLabelVariants`, `disclosureTriggerVariants`, `fieldValueVariants`, `inlineCodeVariants`, `inputVariants`, `kbdVariants`, `metadataListVariants`, `overflowBubbleVariants`, `progressTrackVariants`, `segmentedControlDescriptionVariants`, `segmentedControlFocusRingVariants`, `segmentedControlIndicatorVariants`, `segmentedControlItemVariants`, `segmentedControlLabelVariants`, `segmentedControlRailVariants`, `segmentedControlWrapperVariants`, `skeletonVariants`, `spinnerVariants`, `statVariants`, `statusDotVariants`, `statusListItemDotVariants`, `surfaceVariants`, `tableDataCellVariants`, `tableHeaderCellVariants`, `tableSortButtonVariants`, `tagButtonVariants`, `tagPillVariants`, `textVariants`, `textareaVariants` and `toastIconVariants`.
122
+ - AspectRatio is removed; use Box style={{ aspectRatio: '16 / 9' }} instead.
123
+ - Each of these is removed; use BoxProps instead: `AspectRatioProps`, `BleedProps`, `CoverProps` and `FrameProps`.
124
+ - Bleed is removed; use Box style={{ marginInline: 'calc(-1 \* var(--semantic-space-scale-md))' }} instead.
125
+ - Center is removed; use Container maxWidth="content", with a Box style={{ paddingInline }} inside for the gutter instead.
126
+ - CenterProps is removed; use ContainerProps instead.
127
+ - CircularProgress is removed; use Progress variant="circular" instead.
128
+ - CircularProgressProps is removed; use ProgressProps instead.
129
+ - Cluster is removed; use Stack direction="row" wrap="wrap" align="center" instead.
130
+ - ClusterProps is removed; use StackProps instead.
131
+ - Cover is removed; use Box style={{ display: 'flex', flexDirection: 'column', minHeight: '100svh' }} around a Box style={{ marginBlock: 'auto' }} instead.
132
+ - Frame is removed; use Box style={{ aspectRatio: '16 / 9', overflow: 'hidden', borderRadius: hds.borderRadius.md }} instead.
133
+ - IconButton is removed; use Button iconOnly label="…" iconLeft={\<Icon icon={…} />} instead.
134
+ - Each of these is removed; use ButtonProps instead: `IconButtonProps` and `ToggleButtonProps`.
135
+ - InputGroup is removed; use Input prefix / suffix instead.
136
+ - Each of these is removed; use InputProps instead: `InputGroupProps` and `TimeInputProps`.
137
+ - MultiSelector is removed; use Combobox multiple instead.
138
+ - MultiSelectorOption is removed; use ComboboxOption instead.
139
+ - MultiSelectorProps is removed; use ComboboxMultipleProps instead.
140
+ - SelectableCard is removed; use Card selectable selected onSelectedChange instead.
141
+ - SelectableCardProps is removed; use CardProps instead.
142
+ - TileGridProps is removed; use GridProps (minTileWidth is minItemWidth, gap 'sm' is 'medium') instead.
143
+ - TimeInput is removed; use Input type="time" instead.
144
+ - ToggleButton is removed; use Button pressed onPressedChange (variant="ghost" is variant="tertiary") instead.
145
+ - HDS no longer installs each of these, so add it to your own dependencies if your code imports it: `@radix-ui/react-aspect-ratio`, `@radix-ui/react-context-menu`, `@radix-ui/react-hover-card`, `@radix-ui/react-toggle`, `@radix-ui/react-toolbar`, `date-fns` and `react-day-picker`.
146
+ - An icon-only Button now takes its accessible name from label, unless it has an aria-label.
147
+ - Card selectable, which replaces SelectableCard, toggles on click or Space only, so code or tests that press Enter to toggle it must press Space instead.
148
+ - Combobox options now report their real position, and a Select with showLabel={false} is named by its label and value, so tests that find them by role and name may need updating.
149
+ - The open Select list and the Combobox popup now carry an accessible name, so tests that find them by role and name may need the new name.
150
+ - The manifest no longer has a health field, so manifest.health is now undefined.
151
+ - The rows inside PageHeader, FormActions, DataTableSection and DestructiveSection now carry data-hds-component="Stack" instead of "Cluster", so selectors and tests that look for Cluster there must change.
152
+
153
+ ## 0.19.1
154
+
155
+ Released 2026-09-30 (patch). Smaller Button-only bundles for webpack and esbuild, and Storybook accessibility fixes; nothing for a consumer to change.
156
+
157
+ Nothing in this release asks anything of you.
158
+
159
+ ## 0.19.0
160
+
161
+ Released 2026-09-30 (minor). Overlays portal into the nearest data-hds scope, compact density tightens spacing, containers share one radius; adds /icons.
162
+
163
+ ### Looks different
164
+
165
+ - A Table with no density prop now follows the nearest data-density attribute, so a Table inside a compact region renders compact unless you pass density.
166
+ - Under compact density (data-density="compact", which HdsThemeProvider density="compact" and ThemeProvider's setDensity also set) the spacing scale, surface padding and region gutter that components read now tighten, so compact screens render visibly tighter than before.
167
+ - Surface, StatusTile, DocLinkCard, static.css's .hds-card and the .hds-soft-nav-card and .hds-sketchbook-canvas-stage classes now round to the tenant's container radius, 12px by default, instead of a fixed 8px.
168
+ - The dark theme (data-theme="dark" or the dark class, which HdsThemeProvider theme="dark" and ThemeProvider set) now declares color-scheme: dark, so native scrollbars and form controls inside it render dark.
169
+ - The scrim behind an open Dialog, AlertDialog or CommandPalette is now the theme-aware scrim color, black in dark mode where it was a white wash.
170
+
171
+ ### Do by hand
172
+
173
+ - In development, a Button with iconOnly and no iconLeft now logs one warning, so a test that fails on console warnings will flag it.
174
+ - Overlays mount in the nearest data-hds element, not document.body, and take its theme, so check CSS or tests that query body: Dialog, AlertDialog, Menu, ContextMenu, Popover, Select, HoverCard, Tooltip, Lightbox, AssetImg, and the overlays of Combobox, MultiSelector, CommandPalette and date inputs.
175
+
176
+ ## 0.18.0
177
+
178
+ Released 2026-09-30 (minor). Adds the hds-patterns-subpath codemod, which moves root imports of the deprecated pattern components to /patterns.
179
+
180
+ Nothing in this release asks anything of you.
181
+
182
+ ## 0.17.0
183
+
184
+ Released 2026-09-30 (minor). Adds /patterns and the standard type ramp; root pattern imports, Hds\* names and seven spacing tokens are deprecated.
185
+
186
+ ### Looks different
187
+
188
+ - Badge text grows from 11px to 12px, CommandPalette's kind badges, shortcuts and footer from 10px to 12px, and SegmentedControl labels shrink from 15px to 14px on a 20px line, now that they use the ramp's text-xs and text-sm.
189
+ - Calendar's days outside the shown month are no longer drawn at half opacity, so they read darker.
190
+ - A disabled Tokenizer shows muted text instead of fading the whole field to 70% opacity.
191
+ - Second-level headings (Text and HeadingStack heading2, and any h2 inside HdsThemeProvider or another data-hds element) keep their 30px size, but their line height drops from 42px to 40px.
192
+ - Elements with the hds-focus class, which many HDS components use (Dialog, Select, Radio, Toggle and Slider among them), now show their focus ring on every keyboard focus, where before it showed only if the page set data-input-modality="keyboard" on html, which no HDS code did.
193
+ - The highlighted option in Menu, Select, Combobox and MultiSelector now also shows an inset ring in the focus color.
194
+ - Text follows Tailwind's default type ramp, so body text renders at 16px instead of 17px, UI and button text 1px smaller, captions and eyebrows at 12px instead of 13px, mono at 14px instead of 13px, display at 60px instead of 72px, and text-xs to text-6xl shrink to match.
195
+
196
+ ### Coming next
197
+
198
+ - Importing each of these from the package root still works but is deprecated; import it from @hirobius/design-system/patterns instead: `ActivityFeed`, `AppShell`, `AssetImg`, `Calendar`, `Carousel`, `CodeBlock`, `CommandPalette`, `DocLinkCard`, `ErrorPattern`, `FileInput`, `Form`, `Lightbox`, `NavItem`, `OverflowList`, `Page`, `Reveal`, `SideNav`, `Stepper`, `Toolbar`, `TopNav` and `TreeList`. Removed early, in [0.20.0](#0200) (planned for 1.0.0).
199
+ - HdsCheckbox still works but is deprecated; use Checkbox, the same component under its bare name. Removed early, in [0.20.0](#0200) (planned for 1.0.0).
200
+ - HdsRadio still works but is deprecated; use Radio, the same component under its bare name. Removed early, in [0.20.0](#0200) (planned for 1.0.0).
201
+ - HdsSelect still works but is deprecated; use Select, the same component under its bare name. Removed early, in [0.20.0](#0200) (planned for 1.0.0).
202
+ - HdsSlider still works but is deprecated; use Slider, the same component under its bare name. Removed early, in [0.20.0](#0200) (planned for 1.0.0).
203
+ - HdsToggle still works but is deprecated; use Toggle, the same component under its bare name. Removed early, in [0.20.0](#0200) (planned for 1.0.0).
204
+ - HdsTooltip still works but is deprecated; use Tooltip, the same component under its bare name. Removed early, in [0.20.0](#0200) (planned for 1.0.0).
205
+ - The spacing token semantic.space.component.gap (--semantic-space-component-gap) still works but is deprecated; use semantic.space.scale.xs, the same 8px at the default density. Removed in 1.0.0.
206
+ - The spacing token semantic.space.component.padding (--semantic-space-component-padding) still works but is deprecated; use semantic.space.surface.padding, which it now aliases. Removed in 1.0.0.
207
+ - The spacing token semantic.space.layout.gutter (--semantic-space-layout-gutter) still works but is deprecated; use semantic.space.region.gutter, which it now aliases. Removed in 1.0.0.
208
+ - The spacing token semantic.space.layout.inset (--semantic-space-layout-inset) still works but is deprecated; use semantic.space.scale.lg, the same 32px at the default density. Removed in 1.0.0.
209
+ - The spacing token semantic.space.layout.normal (--semantic-space-layout-normal) still works but is deprecated; use semantic.space.scale.md, the same 24px at the default density. Removed in 1.0.0.
210
+ - The spacing token semantic.space.layout.spacious (--semantic-space-layout-spacious) still works but is deprecated; use semantic.space.scale.xl, the same 48px at the default density. Removed in 1.0.0.
211
+ - The spacing token semantic.space.layout.tight (--semantic-space-layout-tight) still works but is deprecated; use semantic.space.scale.sm, the same 16px at the default density. Removed in 1.0.0.
212
+
213
+ ### Do by hand
214
+
215
+ - AssetImg with alt text is now role img named by it, and one with neither alt nor onClick is hidden from assistive technology, so tests that find it by role or name may need updating.
216
+ - Card.Progress now carries an aria-label, its string label or "Progress", so tests that find it by role and name may need the new name.
217
+ - StepperField's label is now tied to its input, so the input is announced and found by its label text.
218
+ - Table now renders role table, row, columnheader and cell, wrapping each row in an element with display: contents, so selectors and tests that query its cells by role or by position in the DOM may need updating.
219
+ - OverflowList puts its +N item in a list item and names it with hidden text instead of aria-label, and MetadataList wraps its footer in a dt and dd pair, so tests that query that markup or the "+N more" label may need updating.
220
+ - Carousel's slide strip and StackedCardRail's scroll areas are now named regions that take keyboard focus, so each adds a Tab stop and tests that count Tab presses past them need one more.
221
+
222
+ <!-- Generated by scripts/upgrade/compile.mjs from upgrade/releases/*.json; do not edit. Run node scripts/upgrade/compile.mjs to regenerate. -->
@@ -30,7 +30,8 @@
30
30
  * end (codemods/unrewritable.mjs), needs a manual look
31
31
  * --dry-run write nothing; print each changed line before (-) and after (+), exit 0
32
32
  */
33
- import { readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
33
+ import { readFileSync, readdirSync, realpathSync } from 'node:fs';
34
+ import { writeFileAtomic } from './lib/atomic-write.mjs';
34
35
  import { relative, resolve, join } from 'node:path';
35
36
  import { fileURLToPath } from 'node:url';
36
37
  import { findUnrewritable as findHidden, maskSource } from './unrewritable.mjs';
@@ -135,24 +136,38 @@ export function findUnrewritable(source, renames = RENAMES) {
135
136
  return findHidden(source, ROOT_PKG, Object.keys(renames));
136
137
  }
137
138
 
138
- function* walk(dir) {
139
- for (const entry of readdirSync(dir)) {
140
- if (SKIP_DIRS.has(entry)) continue;
141
- const full = join(dir, entry);
142
- const st = statSync(full);
143
- if (st.isDirectory()) yield* walk(full);
144
- else if (EXTS.has(full.slice(full.lastIndexOf('.')))) yield full;
139
+ /** `skip`: absolute directories not to enter (the upgrade command's nested importers). */
140
+ function* walk(dir, skip = new Set()) {
141
+ let entries;
142
+ try {
143
+ entries = readdirSync(dir, { withFileTypes: true });
144
+ } catch {
145
+ return; // an unreadable directory has nothing to rewrite
146
+ }
147
+ // Symlinks are skipped, never followed: a dangling one cannot be read and a
148
+ // looping one never ends.
149
+ for (const entry of entries) {
150
+ if (SKIP_DIRS.has(entry.name)) continue;
151
+ const full = join(dir, entry.name);
152
+ if (entry.isDirectory()) {
153
+ if (!skip.has(full)) yield* walk(full, skip);
154
+ } else if (entry.isFile() && EXTS.has(full.slice(full.lastIndexOf('.')))) yield full;
145
155
  }
146
156
  }
147
157
 
148
- /** Scan a directory. Writes only when `write` is true. */
149
- export function runCodemod({ root, write = false, renames = RENAMES }) {
158
+ /** Scan a directory. Writes only when `write` is true; `skip` lists directories not to enter. */
159
+ export function runCodemod({ root, write = false, renames = RENAMES, skip = [] }) {
150
160
  const files = [];
151
161
  const manual = [];
152
162
  const renamed = new Set();
153
163
  let sites = 0;
154
- for (const file of walk(root)) {
155
- const src = readFileSync(file, 'utf8');
164
+ for (const file of walk(root, new Set(skip.map((d) => resolve(d))))) {
165
+ let src;
166
+ try {
167
+ src = readFileSync(file, 'utf8');
168
+ } catch {
169
+ continue; // unreadable: nothing to rewrite
170
+ }
156
171
  if (!src.includes(ROOT_PKG)) continue;
157
172
  const r = transformSource(src, renames);
158
173
  for (const stmt of findUnrewritable(r.source, renames))
@@ -161,7 +176,7 @@ export function runCodemod({ root, write = false, renames = RENAMES }) {
161
176
  files.push({ file: relative(root, file), sites: r.sites, renamed: r.renamed, edits: r.edits });
162
177
  r.renamed.forEach((n) => renamed.add(n));
163
178
  sites += r.sites;
164
- if (write) writeFileSync(file, r.source);
179
+ if (write) writeFileAtomic(file, r.source);
165
180
  }
166
181
  return { files, sites, manual, names: [...renamed].sort() };
167
182
  }
@@ -22,7 +22,8 @@
22
22
  * removed props type, and a namespace or dynamic import that reads the name are
23
23
  * listed too. Running a fold twice changes nothing.
24
24
  */
25
- import { readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
25
+ import { readFileSync, readdirSync, realpathSync } from 'node:fs';
26
+ import { writeFileAtomic } from './lib/atomic-write.mjs';
26
27
  import { join, relative, resolve } from 'node:path';
27
28
  import { fileURLToPath } from 'node:url';
28
29
  import { findUnrewritable, maskSource } from './unrewritable.mjs';
@@ -421,30 +422,44 @@ export function insertionPoint(source, tag, attr) {
421
422
  return attr ? { at: attr.start, sep } : { at: tag.nameEnd, sep };
422
423
  }
423
424
 
424
- function* walk(dir) {
425
- for (const entry of readdirSync(dir)) {
426
- if (SKIP_DIRS.has(entry)) continue;
427
- const full = join(dir, entry);
428
- const st = statSync(full);
429
- if (st.isDirectory()) yield* walk(full);
430
- else if (EXTS.has(full.slice(full.lastIndexOf('.')))) yield full;
425
+ /** `skip`: absolute directories not to enter (the upgrade command's nested importers). */
426
+ function* walk(dir, skip = new Set()) {
427
+ let entries;
428
+ try {
429
+ entries = readdirSync(dir, { withFileTypes: true });
430
+ } catch {
431
+ return; // an unreadable directory has nothing to rewrite
432
+ }
433
+ // Symlinks are skipped, never followed: a dangling one cannot be read and a
434
+ // looping one never ends.
435
+ for (const entry of entries) {
436
+ if (SKIP_DIRS.has(entry.name)) continue;
437
+ const full = join(dir, entry.name);
438
+ if (entry.isDirectory()) {
439
+ if (!skip.has(full)) yield* walk(full, skip);
440
+ } else if (entry.isFile() && EXTS.has(full.slice(full.lastIndexOf('.')))) yield full;
431
441
  }
432
442
  }
433
443
 
434
- /** Scan a directory with one rule. Writes only when `write` is true. */
435
- export function runFold({ root, write = false, rule }) {
444
+ /** Scan a directory with one rule. Writes only when `write` is true; `skip` lists directories not to enter. */
445
+ export function runFold({ root, write = false, rule, skip = [] }) {
436
446
  const files = [];
437
447
  const manual = [];
438
448
  let sites = 0;
439
- for (const file of walk(root)) {
440
- const src = readFileSync(file, 'utf8');
449
+ for (const file of walk(root, new Set(skip.map((d) => resolve(d))))) {
450
+ let src;
451
+ try {
452
+ src = readFileSync(file, 'utf8');
453
+ } catch {
454
+ continue; // unreadable: nothing to rewrite
455
+ }
441
456
  if (!src.includes(ROOT_PKG)) continue;
442
457
  const r = foldComponent(src, rule);
443
458
  for (const stmt of r.manual) manual.push({ file: relative(root, file), stmt });
444
459
  if (!r.changed) continue;
445
460
  files.push({ file: relative(root, file), sites: r.sites, edits: r.edits });
446
461
  sites += r.sites;
447
- if (write) writeFileSync(file, r.source);
462
+ if (write) writeFileAtomic(file, r.source);
448
463
  }
449
464
  return { files, sites, manual };
450
465
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * atomic-write — replace a file without ever leaving half of it (hds#452).
3
+ * The text goes to a temp file next to it, which is then renamed over it, so
4
+ * a full disk or a kill partway leaves the old file or the new one, never a
5
+ * truncated one. The file keeps its mode. Node builtins only.
6
+ */
7
+ import { chmodSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
8
+
9
+ /**
10
+ * @param {string} file
11
+ * @param {string} text
12
+ */
13
+ export function writeFileAtomic(file, text) {
14
+ let mode;
15
+ try {
16
+ mode = statSync(file).mode & 0o7777;
17
+ } catch {
18
+ mode = undefined; // a new file: the default mode
19
+ }
20
+ const tmp = `${file}.${process.pid}.tmp`;
21
+ try {
22
+ writeFileSync(tmp, text);
23
+ if (mode !== undefined) chmodSync(tmp, mode);
24
+ renameSync(tmp, file);
25
+ } finally {
26
+ rmSync(tmp, { force: true });
27
+ }
28
+ }