@qijenchen/design-system 0.1.0-beta.145 → 0.1.0-beta.147

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 (576) hide show
  1. package/dist/components/Accordion/accordion.d.ts +1 -1
  2. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  3. package/dist/components/Accordion/accordion.js +8 -3
  4. package/dist/components/Accordion/accordion.js.map +1 -1
  5. package/dist/components/AccountMenu/account-menu.d.ts.map +1 -1
  6. package/dist/components/AccountMenu/account-menu.js.map +1 -1
  7. package/dist/components/AgentPanel/agent-panel-fab.d.ts +7 -1
  8. package/dist/components/AgentPanel/agent-panel-fab.d.ts.map +1 -1
  9. package/dist/components/AgentPanel/agent-panel-fab.js +36 -14
  10. package/dist/components/AgentPanel/agent-panel-fab.js.map +1 -1
  11. package/dist/components/AgentPanel/agent-panel-logo.d.ts +3 -3
  12. package/dist/components/AgentPanel/agent-panel-logo.js.map +1 -1
  13. package/dist/components/AgentPanel/agent-panel.d.ts +1 -1
  14. package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -1
  15. package/dist/components/AgentPanel/agent-panel.js +28 -22
  16. package/dist/components/AgentPanel/agent-panel.js.map +1 -1
  17. package/dist/components/AppShell/app-shell.d.ts +1 -1
  18. package/dist/components/AppShell/app-shell.js +1 -1
  19. package/dist/components/AppShell/app-shell.js.map +1 -1
  20. package/dist/components/Avatar/avatar.d.ts +95 -1
  21. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  22. package/dist/components/Avatar/avatar.js +106 -15
  23. package/dist/components/Avatar/avatar.js.map +1 -1
  24. package/dist/components/Avatar/index.js +14 -2
  25. package/dist/components/Breadcrumb/breadcrumb.d.ts +4 -2
  26. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  27. package/dist/components/Breadcrumb/breadcrumb.js +2 -2
  28. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  29. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  30. package/dist/components/BulkActionBar/bulk-action-bar.js +27 -2
  31. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  32. package/dist/components/Button/button.d.ts +2 -2
  33. package/dist/components/Button/button.d.ts.map +1 -1
  34. package/dist/components/Button/button.js +118 -22
  35. package/dist/components/Button/button.js.map +1 -1
  36. package/dist/components/Calendar/calendar.d.ts +34 -10
  37. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  38. package/dist/components/Calendar/calendar.js +210 -36
  39. package/dist/components/Calendar/calendar.js.map +1 -1
  40. package/dist/components/Carousel/carousel.d.ts +1 -1
  41. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  42. package/dist/components/Carousel/carousel.js +3 -2
  43. package/dist/components/Carousel/carousel.js.map +1 -1
  44. package/dist/components/Chart/chart.d.ts +1 -0
  45. package/dist/components/Chart/chart.d.ts.map +1 -1
  46. package/dist/components/Chart/chart.js +8 -0
  47. package/dist/components/Chart/chart.js.map +1 -1
  48. package/dist/components/Checkbox/checkbox.d.ts +1 -1
  49. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  50. package/dist/components/Checkbox/checkbox.js +10 -2
  51. package/dist/components/Checkbox/checkbox.js.map +1 -1
  52. package/dist/components/Chip/chip.d.ts +1 -1
  53. package/dist/components/Chip/chip.d.ts.map +1 -1
  54. package/dist/components/Chip/chip.js +4 -2
  55. package/dist/components/Chip/chip.js.map +1 -1
  56. package/dist/components/Combobox/combobox.d.ts +1 -1
  57. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  58. package/dist/components/Combobox/combobox.js +37 -11
  59. package/dist/components/Combobox/combobox.js.map +1 -1
  60. package/dist/components/Command/command.d.ts +1 -1
  61. package/dist/components/Command/command.d.ts.map +1 -1
  62. package/dist/components/Command/command.js +106 -8
  63. package/dist/components/Command/command.js.map +1 -1
  64. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  65. package/dist/components/DataTable/cell-registry.js +3 -2
  66. package/dist/components/DataTable/cell-registry.js.map +1 -1
  67. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  68. package/dist/components/DataTable/data-table-column-visibility-panel.js +24 -24
  69. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  70. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -1
  71. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  72. package/dist/components/DataTable/data-table-interaction-layer.js +3 -4
  73. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  74. package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
  75. package/dist/components/DataTable/data-table-sort-manager.js +20 -20
  76. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  77. package/dist/components/DataTable/data-table.d.ts +8 -5
  78. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  79. package/dist/components/DataTable/data-table.js +197 -71
  80. package/dist/components/DataTable/data-table.js.map +1 -1
  81. package/dist/components/DateGrid/date-grid.d.ts +1 -1
  82. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  83. package/dist/components/DateGrid/date-grid.js +358 -225
  84. package/dist/components/DateGrid/date-grid.js.map +1 -1
  85. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  86. package/dist/components/DatePicker/date-picker.js +16 -1
  87. package/dist/components/DatePicker/date-picker.js.map +1 -1
  88. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  89. package/dist/components/Dialog/dialog.js +55 -43
  90. package/dist/components/Dialog/dialog.js.map +1 -1
  91. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts +86 -0
  92. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts.map +1 -0
  93. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js +172 -0
  94. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js.map +1 -0
  95. package/dist/components/DropdownMenu/dropdown-menu.d.ts +6 -1
  96. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  97. package/dist/components/DropdownMenu/dropdown-menu.js +50 -39
  98. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  99. package/dist/components/Field/field-context.d.ts +4 -0
  100. package/dist/components/Field/field-context.d.ts.map +1 -1
  101. package/dist/components/Field/field-context.js +1 -2
  102. package/dist/components/Field/field-context.js.map +1 -1
  103. package/dist/components/Field/field-edit-keys.d.ts +8 -0
  104. package/dist/components/Field/field-edit-keys.d.ts.map +1 -1
  105. package/dist/components/Field/field-edit-keys.js +3 -0
  106. package/dist/components/Field/field-edit-keys.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +18 -1
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +39 -10
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +3 -2
  112. package/dist/components/Field/field.d.ts.map +1 -1
  113. package/dist/components/Field/field.js +2 -1
  114. package/dist/components/Field/field.js.map +1 -1
  115. package/dist/components/FileItem/file-item.d.ts +26 -2
  116. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  117. package/dist/components/FileItem/file-item.js +82 -25
  118. package/dist/components/FileItem/file-item.js.map +1 -1
  119. package/dist/components/FileItem/index.js +4 -2
  120. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  121. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  122. package/dist/components/FileUpload/file-upload.js +132 -55
  123. package/dist/components/FileUpload/file-upload.js.map +1 -1
  124. package/dist/components/FileViewer/file-viewer.d.ts +1 -1
  125. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  126. package/dist/components/FileViewer/file-viewer.js +9 -4
  127. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  128. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  129. package/dist/components/InlineEdit/inline-edit.js +1 -1
  130. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  131. package/dist/components/Input/input.d.ts.map +1 -1
  132. package/dist/components/Input/input.js +6 -1
  133. package/dist/components/Input/input.js.map +1 -1
  134. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  135. package/dist/components/LinkInput/link-input.js +20 -4
  136. package/dist/components/LinkInput/link-input.js.map +1 -1
  137. package/dist/components/Notice/notice.js +1 -1
  138. package/dist/components/Notice/notice.js.map +1 -1
  139. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  140. package/dist/components/NumberInput/number-input.js +6 -1
  141. package/dist/components/NumberInput/number-input.js.map +1 -1
  142. package/dist/components/OverflowIndicator/index.js +2 -1
  143. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +19 -1
  144. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  145. package/dist/components/OverflowIndicator/overflow-indicator.js +47 -27
  146. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  147. package/dist/components/Pagination/pagination.d.ts +1 -1
  148. package/dist/components/Pagination/pagination.js +1 -1
  149. package/dist/components/Pagination/pagination.js.map +1 -1
  150. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +7 -6
  151. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -1
  152. package/dist/components/PeoplePicker/avatar-stack-overflow.js +6 -7
  153. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  154. package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
  155. package/dist/components/PeoplePicker/people-picker-helpers.js +1 -1
  156. package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
  157. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  158. package/dist/components/PeoplePicker/people-picker.js +2 -2
  159. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  160. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  161. package/dist/components/PeoplePicker/person-display.js +26 -28
  162. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  163. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  164. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  165. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  166. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  167. package/dist/components/RadioGroup/radio-group.js +10 -4
  168. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  169. package/dist/components/Rating/rating.d.ts +28 -6
  170. package/dist/components/Rating/rating.d.ts.map +1 -1
  171. package/dist/components/Rating/rating.js +97 -80
  172. package/dist/components/Rating/rating.js.map +1 -1
  173. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  174. package/dist/components/ScrollArea/scroll-area.js +1 -1
  175. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  176. package/dist/components/SegmentedControl/segmented-control.d.ts +1 -1
  177. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  178. package/dist/components/SegmentedControl/segmented-control.js +3 -2
  179. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  180. package/dist/components/Select/select.d.ts.map +1 -1
  181. package/dist/components/Select/select.js +15 -8
  182. package/dist/components/Select/select.js.map +1 -1
  183. package/dist/components/SelectMenu/index.js +2 -1
  184. package/dist/components/SelectMenu/index.js.map +1 -1
  185. package/dist/components/SelectMenu/select-menu-keyboard.d.ts +59 -0
  186. package/dist/components/SelectMenu/select-menu-keyboard.d.ts.map +1 -0
  187. package/dist/components/SelectMenu/select-menu-keyboard.js +107 -0
  188. package/dist/components/SelectMenu/select-menu-keyboard.js.map +1 -0
  189. package/dist/components/SelectMenu/select-menu.d.ts +1 -22
  190. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  191. package/dist/components/SelectMenu/select-menu.js +18 -34
  192. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  193. package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
  194. package/dist/components/SelectionControl/selection-item.js +1 -0
  195. package/dist/components/SelectionControl/selection-item.js.map +1 -1
  196. package/dist/components/Sidebar/sidebar.d.ts +7 -9
  197. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  198. package/dist/components/Sidebar/sidebar.js +171 -50
  199. package/dist/components/Sidebar/sidebar.js.map +1 -1
  200. package/dist/components/Slider/slider.d.ts +1 -1
  201. package/dist/components/Slider/slider.d.ts.map +1 -1
  202. package/dist/components/Slider/slider.js +9 -4
  203. package/dist/components/Slider/slider.js.map +1 -1
  204. package/dist/components/Steps/steps.d.ts.map +1 -1
  205. package/dist/components/Steps/steps.js +54 -26
  206. package/dist/components/Steps/steps.js.map +1 -1
  207. package/dist/components/Switch/switch.d.ts +1 -1
  208. package/dist/components/Switch/switch.d.ts.map +1 -1
  209. package/dist/components/Switch/switch.js +7 -2
  210. package/dist/components/Switch/switch.js.map +1 -1
  211. package/dist/components/Tabs/tabs.d.ts +1 -1
  212. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  213. package/dist/components/Tabs/tabs.js +39 -4
  214. package/dist/components/Tabs/tabs.js.map +1 -1
  215. package/dist/components/Tag/tag.d.ts +1 -1
  216. package/dist/components/Tag/tag.d.ts.map +1 -1
  217. package/dist/components/Tag/tag.js +3 -2
  218. package/dist/components/Tag/tag.js.map +1 -1
  219. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  220. package/dist/components/Textarea/textarea.js +8 -2
  221. package/dist/components/Textarea/textarea.js.map +1 -1
  222. package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
  223. package/dist/components/TimePicker/time-columns.js +8 -4
  224. package/dist/components/TimePicker/time-columns.js.map +1 -1
  225. package/dist/components/TimePicker/time-picker.d.ts +1 -1
  226. package/dist/components/TimePicker/time-picker.js +1 -1
  227. package/dist/components/TimePicker/time-picker.js.map +1 -1
  228. package/dist/components/TreeView/tree-view.d.ts +9 -5
  229. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  230. package/dist/components/TreeView/tree-view.js +266 -214
  231. package/dist/components/TreeView/tree-view.js.map +1 -1
  232. package/dist/hooks/use-input-modality.d.ts +12 -17
  233. package/dist/hooks/use-input-modality.d.ts.map +1 -1
  234. package/dist/hooks/use-input-modality.js +4 -34
  235. package/dist/hooks/use-input-modality.js.map +1 -1
  236. package/dist/hooks/use-overflow-items.d.ts.map +1 -1
  237. package/dist/hooks/use-overflow-items.js +2 -0
  238. package/dist/hooks/use-overflow-items.js.map +1 -1
  239. package/dist/hooks/use-truncated.d.ts.map +1 -1
  240. package/dist/hooks/use-truncated.js.map +1 -1
  241. package/dist/index.d.ts +3 -0
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +23 -3
  244. package/dist/index.js.map +1 -1
  245. package/dist/lib/drag-announcements.d.ts +32 -0
  246. package/dist/lib/drag-announcements.d.ts.map +1 -1
  247. package/dist/lib/drag-announcements.js +39 -1
  248. package/dist/lib/drag-announcements.js.map +1 -1
  249. package/dist/lib/focus-after-trigger.d.ts +45 -0
  250. package/dist/lib/focus-after-trigger.d.ts.map +1 -0
  251. package/dist/lib/focus-after-trigger.js +66 -0
  252. package/dist/lib/focus-after-trigger.js.map +1 -0
  253. package/dist/lib/overlay-coexistence.js +1 -1
  254. package/dist/lib/roving-list-keyboard.d.ts +208 -0
  255. package/dist/lib/roving-list-keyboard.d.ts.map +1 -0
  256. package/dist/lib/roving-list-keyboard.js +174 -0
  257. package/dist/lib/roving-list-keyboard.js.map +1 -0
  258. package/dist/lib/utils.d.ts.map +1 -1
  259. package/dist/lib/utils.js +6 -1
  260. package/dist/lib/utils.js.map +1 -1
  261. package/dist/patterns/element-anatomy/item-anatomy.d.ts +4 -4
  262. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  263. package/dist/patterns/element-anatomy/item-anatomy.js +14 -4
  264. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  265. package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -1
  266. package/dist/patterns/horizontal-overflow/horizontal-overflow.js +25 -0
  267. package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
  268. package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
  269. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  270. package/dist/tokens/categorical-color.d.ts +6 -1
  271. package/dist/tokens/categorical-color.d.ts.map +1 -1
  272. package/dist/tokens/categorical-color.js.map +1 -1
  273. package/dist/tokens/uiSize/icon-size.d.ts +1 -1
  274. package/dist/tokens/uiSize/stack-gap.d.ts +18 -0
  275. package/dist/tokens/uiSize/stack-gap.d.ts.map +1 -0
  276. package/dist/tokens/uiSize/stack-gap.js +5 -0
  277. package/dist/tokens/uiSize/stack-gap.js.map +1 -0
  278. package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +1 -1
  279. package/ds-canonical/fork/codex/hooks.json +1 -1
  280. package/ds-canonical/fork/consumer/lock.json +17 -14
  281. package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +9 -0
  282. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +47 -9
  283. package/ds-canonical/fork/consumer/managed-files/5a0d134a2645114c8caac20458070b00a7f0fd55b3e8805b7325cb4cf52fa15e.blob +110 -0
  284. package/ds-canonical/fork/consumer/managed-files/5af0a46760af338d51fd0f7950675f8e97f7d2f09386b8818fcd7a58e6654e75.blob +790 -0
  285. package/ds-canonical/fork/consumer/managed-files/68bfcddff9c4a16388c248cd34fb66163e9e64b3d809a48ef3984f6ae4ff6d12.blob +211 -0
  286. package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +45 -6
  287. package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +42 -2
  288. package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +27 -1
  289. package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +62 -14
  290. package/ds-canonical/fork/governance.lock +41 -17
  291. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +16 -4
  292. package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +3 -1
  293. package/ds-canonical/fork/hooks/check_pattern_invariants.sh +13 -9
  294. package/ds-canonical/fork/launchers/settings-hooks.json +1 -1
  295. package/ds-canonical/fork/manifest.json +17 -11
  296. package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +1 -1
  297. package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
  298. package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +1 -1
  299. package/ds-canonical/fork/skills/performance-audit/SKILL.md +1 -1
  300. package/ds-canonical/hooks/check_consumer_app_invariants.sh +16 -4
  301. package/ds-canonical/hooks/check_consumer_code_quality.sh +3 -1
  302. package/ds-canonical/hooks/check_datatable_invariants.sh +4 -1
  303. package/ds-canonical/hooks/check_pattern_invariants.sh +13 -9
  304. package/ds-canonical/hooks/check_plugin_fork_health.sh +8 -1
  305. package/ds-canonical/hooks/check_propose_discipline.sh +8 -1
  306. package/ds-canonical/hooks/check_storybook_addon_packaging.sh +8 -1
  307. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +64 -2
  308. package/ds-canonical/hooks/lib/_overlay_handcraft.sh +5 -2
  309. package/ds-canonical/hooks/registrations.json +12 -7
  310. package/ds-canonical/hooks/session_start_governance_check.sh +16 -0
  311. package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +25 -0
  312. package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +4 -0
  313. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +14 -0
  314. package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +24 -0
  315. package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +32 -0
  316. package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +59 -0
  317. package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +32 -1
  318. package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +21 -0
  319. package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +21 -0
  320. package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +97 -0
  321. package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +49 -0
  322. package/ds-canonical/references/README.md +12 -6
  323. package/ds-canonical/references/drag-canonical.md +3 -1
  324. package/ds-canonical/references/focus-canonical.md +26 -13
  325. package/ds-canonical/references/governance-audit-coverage.md +1 -1
  326. package/ds-canonical/references/hit-area-canonical.md +361 -0
  327. package/ds-canonical/references/item-anatomy-recipe.md +4 -2
  328. package/ds-canonical/references/keyboard-model-canonical.md +559 -0
  329. package/ds-canonical/rules/meta-patterns.md +4 -4
  330. package/ds-canonical/rules/self-verify.md +4 -4
  331. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
  332. package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +96 -0
  333. package/ds-canonical/skills/knowledge-prune/SKILL.md +4 -1
  334. package/ds-canonical/skills/performance-audit/SKILL.md +1 -1
  335. package/ds-story-manifest.json +19 -7
  336. package/llms-full.txt +2 -2
  337. package/llms.txt +2 -2
  338. package/package.json +3 -1
  339. package/src/components/Accordion/accordion.anatomy.stories.tsx +3 -3
  340. package/src/components/Accordion/accordion.spec.md +1 -1
  341. package/src/components/Accordion/accordion.tsx +8 -3
  342. package/src/components/AccountMenu/account-menu.spec.md +2 -3
  343. package/src/components/AccountMenu/account-menu.tsx +0 -1
  344. package/src/components/AgentPanel/agent-decision-card.spec.md +88 -0
  345. package/src/components/AgentPanel/agent-panel-fab.spec.md +151 -0
  346. package/src/components/AgentPanel/agent-panel-fab.tsx +67 -24
  347. package/src/components/AgentPanel/agent-panel-logo.spec.md +87 -0
  348. package/src/components/AgentPanel/agent-panel-logo.tsx +3 -3
  349. package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +1 -1
  350. package/src/components/AgentPanel/agent-panel.principles.stories.tsx +1 -1
  351. package/src/components/AgentPanel/agent-panel.spec.md +30 -275
  352. package/src/components/AgentPanel/agent-panel.stories.tsx +113 -8
  353. package/src/components/AgentPanel/agent-panel.tsx +59 -22
  354. package/src/components/Alert/alert.anatomy.stories.tsx +1 -1
  355. package/src/components/AppShell/app-shell.tsx +1 -1
  356. package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +10 -15
  357. package/src/components/Avatar/avatar.anatomy.stories.tsx +23 -26
  358. package/src/components/Avatar/avatar.principles.stories.tsx +10 -10
  359. package/src/components/Avatar/avatar.spec.md +45 -6
  360. package/src/components/Avatar/avatar.stories.tsx +8 -8
  361. package/src/components/Avatar/avatar.tsx +227 -21
  362. package/src/components/Badge/badge.anatomy.stories.tsx +16 -21
  363. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +3 -3
  364. package/src/components/Breadcrumb/breadcrumb.spec.md +3 -3
  365. package/src/components/Breadcrumb/breadcrumb.tsx +5 -3
  366. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
  367. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +1 -1
  368. package/src/components/BulkActionBar/bulk-action-bar.spec.md +1 -0
  369. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +57 -2
  370. package/src/components/BulkActionBar/bulk-action-bar.tsx +32 -2
  371. package/src/components/Button/button.anatomy.stories.tsx +174 -33
  372. package/src/components/Button/button.spec.md +23 -8
  373. package/src/components/Button/button.tsx +116 -21
  374. package/src/components/Calendar/calendar.anatomy.stories.tsx +34 -11
  375. package/src/components/Calendar/calendar.principles.stories.tsx +44 -1
  376. package/src/components/Calendar/calendar.spec.md +97 -20
  377. package/src/components/Calendar/calendar.stories.tsx +101 -4
  378. package/src/components/Calendar/calendar.tsx +379 -74
  379. package/src/components/Carousel/carousel.anatomy.stories.tsx +16 -17
  380. package/src/components/Carousel/carousel.spec.md +48 -2
  381. package/src/components/Carousel/carousel.tsx +9 -3
  382. package/src/components/Chart/chart.anatomy.stories.tsx +7 -5
  383. package/src/components/Chart/chart.spec.md +32 -7
  384. package/src/components/Chart/chart.tsx +9 -0
  385. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +21 -28
  386. package/src/components/Checkbox/checkbox.spec.md +4 -2
  387. package/src/components/Checkbox/checkbox.tsx +10 -2
  388. package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
  389. package/src/components/Chip/chip.spec.md +2 -1
  390. package/src/components/Chip/chip.tsx +4 -2
  391. package/src/components/Combobox/combobox.anatomy.stories.tsx +30 -37
  392. package/src/components/Combobox/combobox.spec.md +11 -3
  393. package/src/components/Combobox/combobox.tsx +43 -12
  394. package/src/components/Command/command.spec.md +11 -0
  395. package/src/components/Command/command.tsx +143 -10
  396. package/src/components/DataTable/cell-registry.tsx +5 -1
  397. package/src/components/DataTable/data-table-column-visibility-panel.tsx +5 -6
  398. package/src/components/DataTable/data-table-interaction-layer.tsx +27 -14
  399. package/src/components/DataTable/data-table-known-defects.spec.md +72 -0
  400. package/src/components/DataTable/data-table-scroll-performance.spec.md +295 -0
  401. package/src/components/DataTable/data-table-sort-manager.tsx +5 -6
  402. package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
  403. package/src/components/DataTable/data-table.css +64 -0
  404. package/src/components/DataTable/data-table.spec.md +70 -346
  405. package/src/components/DataTable/data-table.stories.tsx +56 -9
  406. package/src/components/DataTable/data-table.tsx +392 -103
  407. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +2 -2
  408. package/src/components/DateGrid/date-grid.spec.md +163 -1
  409. package/src/components/DateGrid/date-grid.tsx +270 -7
  410. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +18 -27
  411. package/src/components/DatePicker/date-picker.spec.md +6 -2
  412. package/src/components/DatePicker/date-picker.tsx +20 -1
  413. package/src/components/Dialog/dialog.spec.md +6 -2
  414. package/src/components/Dialog/dialog.stories.tsx +18 -9
  415. package/src/components/Dialog/dialog.tsx +83 -48
  416. package/src/components/DropdownMenu/dropdown-menu-keyboard.ts +255 -0
  417. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +32 -33
  418. package/src/components/DropdownMenu/dropdown-menu.spec.md +18 -5
  419. package/src/components/DropdownMenu/dropdown-menu.stories.tsx +58 -1
  420. package/src/components/DropdownMenu/dropdown-menu.tsx +70 -33
  421. package/src/components/Empty/empty.spec.md +1 -2
  422. package/src/components/Field/field-context.ts +9 -4
  423. package/src/components/Field/field-controls.spec.md +5 -2
  424. package/src/components/Field/field-edit-keys.ts +12 -0
  425. package/src/components/Field/field-wrapper.tsx +76 -10
  426. package/src/components/Field/field.anatomy.stories.tsx +1 -1
  427. package/src/components/Field/field.spec.md +11 -6
  428. package/src/components/Field/field.stories.tsx +24 -1
  429. package/src/components/Field/field.tsx +10 -4
  430. package/src/components/FileItem/file-item.anatomy.stories.tsx +20 -10
  431. package/src/components/FileItem/file-item.principles.stories.tsx +14 -7
  432. package/src/components/FileItem/file-item.spec.md +50 -48
  433. package/src/components/FileItem/file-item.stories.tsx +41 -28
  434. package/src/components/FileItem/file-item.tsx +184 -50
  435. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +11 -15
  436. package/src/components/FileUpload/file-upload.spec.md +27 -1
  437. package/src/components/FileUpload/file-upload.stories.tsx +33 -0
  438. package/src/components/FileUpload/file-upload.tsx +173 -43
  439. package/src/components/FileViewer/file-viewer.tsx +9 -4
  440. package/src/components/HoverCard/hover-card.stories.tsx +2 -2
  441. package/src/components/InlineEdit/inline-edit.spec.md +2 -0
  442. package/src/components/InlineEdit/inline-edit.tsx +8 -3
  443. package/src/components/Input/input.anatomy.stories.tsx +23 -31
  444. package/src/components/Input/input.spec.md +11 -0
  445. package/src/components/Input/input.tsx +6 -1
  446. package/src/components/LinkInput/link-input.anatomy.stories.tsx +18 -21
  447. package/src/components/LinkInput/link-input.principles.stories.tsx +4 -4
  448. package/src/components/LinkInput/link-input.spec.md +12 -1
  449. package/src/components/LinkInput/link-input.tsx +37 -4
  450. package/src/components/Menu/menu-item.anatomy.stories.tsx +50 -56
  451. package/src/components/Menu/menu-item.spec.md +1 -1
  452. package/src/components/Menu/menu-item.stories.tsx +48 -42
  453. package/src/components/Notice/notice.anatomy.stories.tsx +8 -16
  454. package/src/components/Notice/notice.spec.md +1 -1
  455. package/src/components/Notice/notice.tsx +1 -1
  456. package/src/components/NumberInput/number-input.anatomy.stories.tsx +21 -28
  457. package/src/components/NumberInput/number-input.spec.md +2 -0
  458. package/src/components/NumberInput/number-input.tsx +8 -1
  459. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +28 -48
  460. package/src/components/OverflowIndicator/overflow-indicator.spec.md +13 -1
  461. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +12 -9
  462. package/src/components/OverflowIndicator/overflow-indicator.tsx +32 -12
  463. package/src/components/Pagination/pagination.spec.md +1 -0
  464. package/src/components/Pagination/pagination.tsx +1 -1
  465. package/src/components/PeoplePicker/avatar-stack-overflow.ts +14 -13
  466. package/src/components/PeoplePicker/people-picker-helpers.ts +5 -2
  467. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  468. package/src/components/PeoplePicker/people-picker.spec.md +4 -2
  469. package/src/components/PeoplePicker/people-picker.tsx +1 -4
  470. package/src/components/PeoplePicker/person-display.tsx +45 -28
  471. package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
  472. package/src/components/Popover/popover.principles.stories.tsx +3 -12
  473. package/src/components/Popover/popover.spec.md +7 -3
  474. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  475. package/src/components/ProfileCard/profile-card.spec.md +1 -1
  476. package/src/components/ProfileCard/profile-card.tsx +0 -3
  477. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +11 -21
  478. package/src/components/ProgressBar/progress-bar.spec.md +1 -1
  479. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +17 -24
  480. package/src/components/RadioGroup/radio-group.spec.md +1 -1
  481. package/src/components/RadioGroup/radio-group.tsx +22 -4
  482. package/src/components/Rating/rating.anatomy.stories.tsx +131 -124
  483. package/src/components/Rating/rating.principles.stories.tsx +69 -43
  484. package/src/components/Rating/rating.spec.md +142 -63
  485. package/src/components/Rating/rating.stories.tsx +11 -15
  486. package/src/components/Rating/rating.tsx +131 -117
  487. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  488. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +6 -3
  489. package/src/components/ScrollArea/scroll-area.spec.md +3 -1
  490. package/src/components/ScrollArea/scroll-area.stories.tsx +5 -2
  491. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  492. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
  493. package/src/components/SegmentedControl/segmented-control.spec.md +2 -2
  494. package/src/components/SegmentedControl/segmented-control.tsx +3 -2
  495. package/src/components/Select/select.anatomy.stories.tsx +26 -35
  496. package/src/components/Select/select.spec.md +4 -3
  497. package/src/components/Select/select.tsx +11 -4
  498. package/src/components/SelectMenu/select-menu-keyboard.ts +218 -0
  499. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +6 -17
  500. package/src/components/SelectMenu/select-menu.spec.md +12 -4
  501. package/src/components/SelectMenu/select-menu.tsx +21 -56
  502. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +9 -33
  503. package/src/components/SelectionControl/selection-item.spec.md +2 -2
  504. package/src/components/SelectionControl/selection-item.tsx +3 -1
  505. package/src/components/Separator/separator.stories.tsx +4 -4
  506. package/src/components/Sheet/sheet.principles.stories.tsx +31 -13
  507. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  508. package/src/components/Sidebar/sidebar.spec.md +73 -6
  509. package/src/components/Sidebar/sidebar.stories.tsx +82 -4
  510. package/src/components/Sidebar/sidebar.tsx +281 -64
  511. package/src/components/Skeleton/skeleton.spec.md +1 -1
  512. package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
  513. package/src/components/Slider/slider.spec.md +6 -5
  514. package/src/components/Slider/slider.tsx +13 -8
  515. package/src/components/Steps/steps-state-visuals.spec.md +157 -0
  516. package/src/components/Steps/steps.anatomy.stories.tsx +1 -0
  517. package/src/components/Steps/steps.spec.md +59 -142
  518. package/src/components/Steps/steps.stories.tsx +1 -1
  519. package/src/components/Steps/steps.tsx +125 -33
  520. package/src/components/Switch/switch.spec.md +3 -3
  521. package/src/components/Switch/switch.tsx +6 -1
  522. package/src/components/Tabs/tabs.spec.md +5 -1
  523. package/src/components/Tabs/tabs.tsx +42 -3
  524. package/src/components/Tag/tag.anatomy.stories.tsx +24 -28
  525. package/src/components/Tag/tag.spec.md +10 -11
  526. package/src/components/Tag/tag.tsx +3 -3
  527. package/src/components/Textarea/textarea.tsx +7 -1
  528. package/src/components/TimePicker/time-columns.tsx +9 -4
  529. package/src/components/TimePicker/time-picker.spec.md +4 -1
  530. package/src/components/TimePicker/time-picker.tsx +1 -1
  531. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +6 -15
  532. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -16
  533. package/src/components/Tooltip/tooltip.spec.md +2 -1
  534. package/src/components/TreeView/tree-view.anatomy.stories.tsx +35 -13
  535. package/src/components/TreeView/tree-view.spec.md +67 -38
  536. package/src/components/TreeView/tree-view.stories.tsx +221 -12
  537. package/src/components/TreeView/tree-view.tsx +463 -356
  538. package/src/hooks/use-input-modality.ts +17 -46
  539. package/src/hooks/use-overflow-items.ts +4 -0
  540. package/src/hooks/use-truncated.ts +3 -2
  541. package/src/index.ts +17 -0
  542. package/src/lib/README.md +2 -0
  543. package/src/lib/drag-announcements.ts +78 -0
  544. package/src/lib/focus-after-trigger.ts +102 -0
  545. package/src/lib/roving-list-keyboard.ts +400 -0
  546. package/src/lib/utils.ts +5 -0
  547. package/src/patterns/action-bar/action-bar.spec.md +2 -0
  548. package/src/patterns/element-anatomy/inline-action.spec.md +16 -12
  549. package/src/patterns/element-anatomy/item-anatomy.spec.md +98 -6
  550. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +75 -70
  551. package/src/patterns/element-anatomy/item-anatomy.tsx +17 -6
  552. package/src/patterns/element-anatomy/truncated-text.spec.md +1 -0
  553. package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
  554. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +2 -0
  555. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +36 -0
  556. package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +28 -0
  557. package/src/patterns/overlay-surface/overlay-chrome-sizing.spec.md +180 -0
  558. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -162
  559. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -3
  560. package/src/patterns/resize-handle/resize-handle.spec.md +3 -3
  561. package/src/styles/base.css +1 -1
  562. package/src/tokens/categorical-color.ts +6 -1
  563. package/src/tokens/color/color.spec.md +91 -24
  564. package/src/tokens/color/color.stories.tsx +50 -2
  565. package/src/tokens/color/primitives.css +41 -38
  566. package/src/tokens/color/semantic.css +105 -36
  567. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -0
  568. package/src/tokens/motion/motion.spec.md +31 -8
  569. package/src/tokens/orphan-tokens.spec.md +1 -1
  570. package/src/tokens/uiSize/icon-size.ts +1 -1
  571. package/src/tokens/uiSize/stack-gap.ts +17 -0
  572. package/src/tokens/uiSize/uiSize.css +12 -0
  573. package/src/tokens/uiSize/uiSize.spec.md +17 -16
  574. package/src/tokens/uiSize/uiSize.stories.tsx +1 -1
  575. package/dist/node_modules/aria-hidden/dist/es2015/index.js +0 -147
  576. package/dist/node_modules/aria-hidden/dist/es2015/index.js.map +0 -1
@@ -0,0 +1,211 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // 凍結受測建置:把 storybook-static 複製成本次執行獨佔的快照
3
+ // ═══════════════════════════════════════════════════════════════════════════
4
+ //
5
+ // **為什麼有這個**(2026-09-24,本機誤紅一次):
6
+ // `data-table-invariants.mjs` 在 :156 `waitForSelector` 逾時 30 秒,重跑就綠。實際經過:
7
+ // 另一個 agent 在**同一份工作樹**跑 `npm run build-storybook`(10:24:00 起,10:26:13 建完),
8
+ // Storybook 8 的 build 第一步就是把輸出目錄整個刪掉再重寫
9
+ // (node_modules/@storybook/core/dist/core-server/index.js「Cleaning outputDir」→ rm recursive),
10
+ // 前 ~70 秒 iframe.html 與 assets/ 都不存在。閘的第一次導覽用的是舊檔、成功;第二次導覽時目錄
11
+ // 已被清空 → 資源 404 → story 永遠不渲染 → 30 秒後被當成「表格沒有列」。重跑之所以綠,只是因為
12
+ // 那時對方剛好建完了。
13
+ //
14
+ // 這是 M37:閘開頭的 stale-build 守衛驗的是「那一刻的那份建置」,之後每次導覽讀的卻是
15
+ // 「路徑 storybook-static 底下當下剛好有的東西」—— 把「路徑」當成「建置身分」。
16
+ //
17
+ // 正解:守衛通過之後,把那份建置複製到只有本次執行讀得到的暫存目錄,後續一律從快照供檔。
18
+ // 身分標記用既有的 build-info.json(scripts/gen-build-info.mjs):它是 `npm run build-storybook`
19
+ // **最後**寫的檔、內含 builtAt 毫秒時間戳,而重建的第一步會把它刪掉 —— 所以
20
+ // 「複製前後都存在且逐位元組相同」⇔「複製期間沒有任何重建開始或結束」。
21
+ // 不存在 = 目前沒有完整的建置(正在重建或從沒建完),以**儀器失效**的名義紅,不指控被測物。
22
+ //
23
+ // **第二道:建置完整性(2026-09-25)**。上面那道只證明「複製期間沒人重建」,證明不了「建完之後檔還是
24
+ // 當時那些檔」—— 同一天本機的 storybook-static 被雲端同步弄壞:一批 chunk 變成 0 位元組、另外冒出
25
+ // `<name> 2.js` 衝突副本,而 build-info.json 完好如初。標記「存在且沒變」被當成「建置完整」(M37 同一種代理)。
26
+ // 所以 gen-build-info.mjs 在建置最後把**每個檔的相對路徑與位元組數**寫進 build-info.json 的 `manifest`
27
+ // (再加整份清單的 sha256),這裡對**複製出來的快照**逐檔核對:
28
+ // 清單裡的檔不見了 / 大小不同 → 以儀器失效拒絕,點名前幾個檔(「這不是被測元件的失敗」);
29
+ // 清單外多出來的檔(衝突副本、部署時另寫的 deployment.json)→ 不影響量測,只記一筆說明;
30
+ // 舊建置沒有 manifest → 印一行警告照常放行(CI 每次都重建,這個分支只會出現在本機舊產物上)。
31
+ // 注意:本檔會交付給 consumer(scripts/build-fork-governance.mjs),不得 import 本 repo 限定的模組;
32
+ // 「儀器失效」的標記字 INSTRUMENT-FAIL 與 scripts/lib/launch-browser.mjs 相同(test-storybook-static-snapshot 斷言)。
33
+
34
+ import { createHash } from 'node:crypto'
35
+ import { cpSync, existsSync, lstatSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
36
+ import { tmpdir } from 'node:os'
37
+ import { join } from 'node:path'
38
+
39
+ export class StorybookBuildNotStableError extends Error {}
40
+
41
+ const MARKER = 'build-info.json'
42
+ const SNAPSHOT_PREFIX = 'storybook-static-snapshot-'
43
+ const INSTRUMENT_FAIL = 'INSTRUMENT-FAIL'
44
+ // 建置不完整時訊息裡最多點名幾個檔(其餘只給數量)
45
+ const MAX_NAMED_FILES = 8
46
+ // 正常結束、丟例外都會在 exit 時刪快照;只有被 SIGKILL / 逾時強制中止的那一次會留下(每份約 16MB)。
47
+ // 下一次建快照時順手清掉「超過這個年紀」的殘留 —— 年紀取得夠長,不會刪到別人正在用的那份
48
+ // (最慢的全 story 掃描約 6 分鐘,這裡給 6 小時)。清不掉就算了,不影響本次量測。
49
+ const STALE_SNAPSHOT_AGE_MS = 6 * 60 * 60 * 1000
50
+
51
+ export function sweepStaleSnapshots({ root = tmpdir(), now = Date.now(), maxAgeMs = STALE_SNAPSHOT_AGE_MS } = {}) {
52
+ const removed = []
53
+ let entries = []
54
+ try { entries = readdirSync(root) } catch { return removed }
55
+ for (const name of entries) {
56
+ if (!name.startsWith(SNAPSHOT_PREFIX)) continue
57
+ const dir = join(root, name)
58
+ try {
59
+ if (now - statSync(dir).mtimeMs <= maxAgeMs) continue
60
+ rmSync(dir, { recursive: true, force: true })
61
+ removed.push(dir)
62
+ } catch { /* 別人剛好在刪,或沒權限 —— 不影響本次 */ }
63
+ }
64
+ return removed
65
+ }
66
+
67
+ // ─── 檔案清單(寫:gen-build-info.mjs;讀:snapshotStorybookStatic)──────────────
68
+
69
+ /** 建置目錄裡每個一般檔的 [相對路徑('/' 分隔), 位元組數],依路徑排序;不含根目錄的 build-info.json 本身。 */
70
+ function listBuildFiles(dir) {
71
+ const out = []
72
+ const walk = (rel) => {
73
+ for (const name of readdirSync(join(dir, rel))) {
74
+ const relPath = rel ? `${rel}/${name}` : name
75
+ if (relPath === MARKER) continue
76
+ const info = lstatSync(join(dir, relPath))
77
+ if (info.isDirectory()) walk(relPath)
78
+ else if (info.isFile()) out.push([relPath, info.size])
79
+ }
80
+ }
81
+ walk('')
82
+ return out.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
83
+ }
84
+
85
+ const manifestDigest = (files) => createHash('sha256').update(JSON.stringify(files)).digest('hex')
86
+
87
+ /**
88
+ * 算出建置目錄的檔案清單:`{ version, algorithm, fileCount, totalBytes, sha256, files: { 相對路徑: 位元組數 } }`。
89
+ * sha256 = 整份 files(依路徑排序後的 JSON)的雜湊 —— 同一份建置恆得同一個值,可直接比較兩份建置是否逐檔同大小。
90
+ */
91
+ export function buildFileManifest(dir) {
92
+ const entries = listBuildFiles(dir)
93
+ const files = Object.fromEntries(entries)
94
+ return {
95
+ version: 1,
96
+ algorithm: 'sha256',
97
+ fileCount: entries.length,
98
+ totalBytes: entries.reduce((sum, [, size]) => sum + size, 0),
99
+ sha256: manifestDigest(files),
100
+ files,
101
+ }
102
+ }
103
+
104
+ /**
105
+ * **刻意**改動建置之後(例:meta-test 往建好的 CSS 注入違規)重簽 build-info.json 的檔案清單,
106
+ * 否則快照的完整性核對會先以儀器失效拒絕 —— 那個紅不是「偵測到違規」。回傳原本的 build-info.json 原文,
107
+ * 呼叫端還原建置時一併寫回。
108
+ */
109
+ export function resignBuildManifest(dir) {
110
+ const markerPath = join(dir, MARKER)
111
+ const original = readFileSync(markerPath, 'utf8')
112
+ writeFileSync(markerPath, JSON.stringify({ ...JSON.parse(original), manifest: buildFileManifest(dir) }, null, 2))
113
+ return original
114
+ }
115
+
116
+ /**
117
+ * 用 build-info.json 的 manifest 核對一個目錄(snapshotStorybookStatic 對快照呼叫;也可單獨用)。
118
+ * @returns {{ status: 'verified', fileCount: number, extras: string[] } | { status: 'no-manifest' }}
119
+ * @throws {StorybookBuildNotStableError} 清單本身不一致、或有檔缺少 / 大小不符
120
+ */
121
+ export function verifyBuildManifest(dir, manifest) {
122
+ if (manifest == null) return { status: 'no-manifest' }
123
+ const files = manifest.files
124
+ if (!files || typeof files !== 'object' || Array.isArray(files) || manifestDigest(files) !== manifest.sha256) {
125
+ throw new StorybookBuildNotStableError(
126
+ `${INSTRUMENT_FAIL}: build-info.json 的檔案清單本身不一致(sha256 對不上或格式錯)—— 無法證明這是一份完整的建置。` +
127
+ '這不是被測元件的失敗;重新跑 `npm run build-storybook`。',
128
+ )
129
+ }
130
+ const bad = []
131
+ for (const [relPath, size] of Object.entries(files)) {
132
+ let actual = null
133
+ try { const info = lstatSync(join(dir, relPath)); if (info.isFile()) actual = info.size } catch { /* 不存在 */ }
134
+ if (actual === null) bad.push(`${relPath}(不見了)`)
135
+ else if (actual !== size) bad.push(`${relPath}(${actual} 位元組,建置時 ${size})`)
136
+ }
137
+ if (bad.length) {
138
+ const named = bad.slice(0, MAX_NAMED_FILES).join('、')
139
+ throw new StorybookBuildNotStableError(
140
+ `${INSTRUMENT_FAIL}: 建置不完整:${bad.length} / ${Object.keys(files).length} 個檔缺少或大小與建置時不同 —— ${named}` +
141
+ `${bad.length > MAX_NAMED_FILES ? ` …另 ${bad.length - MAX_NAMED_FILES} 個` : ''}。` +
142
+ '建完之後有東西截斷或刪了檔(雲端同步衝突、別人清目錄)。這不是被測元件的失敗;重新跑 `npm run build-storybook`。',
143
+ )
144
+ }
145
+ const listed = new Set(Object.keys(files))
146
+ const extras = listBuildFiles(dir).map(([relPath]) => relPath).filter((relPath) => !listed.has(relPath))
147
+ return { status: 'verified', fileCount: listed.size, extras }
148
+ }
149
+
150
+ // ─── 快照 ───────────────────────────────────────────────────────────────────
151
+
152
+ // `copy` 只給測試注入(在複製途中改動標記,確定性地走到「複製期間被重建」那一枝);正式呼叫一律用預設。
153
+ // `log` 只給測試注入(收集警告 / 說明);正式呼叫印到 stderr。
154
+ export function snapshotStorybookStatic(staticDir, {
155
+ copy = (from, to) => cpSync(from, to, { recursive: true }),
156
+ log = (line) => console.error(line),
157
+ } = {}) {
158
+ const markerPath = join(staticDir, MARKER)
159
+ const readMarker = () => (existsSync(markerPath) ? readFileSync(markerPath, 'utf8') : null)
160
+ const before = readMarker()
161
+ if (before === null) {
162
+ throw new StorybookBuildNotStableError(
163
+ `${INSTRUMENT_FAIL}: ${markerPath} 不存在 —— storybook-static 不是一份完整的建置(正在重建,或上次沒建完)。` +
164
+ '這不是被測元件的失敗;等建置完成(或跑 `npm run build-storybook`)再執行。',
165
+ )
166
+ }
167
+ sweepStaleSnapshots()
168
+ const dir = mkdtempSync(join(tmpdir(), SNAPSHOT_PREFIX))
169
+ try {
170
+ copy(staticDir, dir)
171
+ } catch (error) {
172
+ rmSync(dir, { recursive: true, force: true })
173
+ throw new StorybookBuildNotStableError(`${INSTRUMENT_FAIL}: 複製 storybook-static 失敗(複製期間目錄被改動?):${error.message}`)
174
+ }
175
+ const after = readMarker()
176
+ const copied = existsSync(join(dir, MARKER)) ? readFileSync(join(dir, MARKER), 'utf8') : null
177
+ if (after !== before || copied !== before) {
178
+ rmSync(dir, { recursive: true, force: true })
179
+ throw new StorybookBuildNotStableError(
180
+ `${INSTRUMENT_FAIL}: 複製期間 storybook-static 被重建(build-info.json 前後不一致)—— 快照不是單一建置。這不是被測元件的失敗。`,
181
+ )
182
+ }
183
+ let buildInfo
184
+ let integrity
185
+ try {
186
+ buildInfo = JSON.parse(before)
187
+ // 核對的是**快照**(之後供檔的就是它),不是活目錄
188
+ integrity = verifyBuildManifest(dir, buildInfo.manifest)
189
+ } catch (error) {
190
+ rmSync(dir, { recursive: true, force: true })
191
+ if (error instanceof StorybookBuildNotStableError) throw error
192
+ throw new StorybookBuildNotStableError(`${INSTRUMENT_FAIL}: 讀不懂 ${markerPath}(${error.message})—— 無法確認建置身分。這不是被測元件的失敗。`)
193
+ }
194
+ if (integrity.status === 'no-manifest') {
195
+ log(`⚠️ ${markerPath} 沒有檔案清單(舊版建置):這次不核對建置完整性;重新 npm run build-storybook 即會補上。`)
196
+ } else if (integrity.extras.length) {
197
+ log(`· 建置快照:${integrity.fileCount} 個檔逐一核對大小相符;另有 ${integrity.extras.length} 個檔不在清單裡(例:${integrity.extras.slice(0, 3).join('、')})—— 不是這次建置的產物(雲端同步衝突副本等),已忽略。`)
198
+ }
199
+ let disposed = false
200
+ return Object.freeze({
201
+ dir,
202
+ buildInfo,
203
+ /** { status: 'verified', fileCount, extras } 或 { status: 'no-manifest' } */
204
+ integrity: Object.freeze(integrity),
205
+ dispose() {
206
+ if (disposed) return
207
+ disposed = true
208
+ rmSync(dir, { recursive: true, force: true })
209
+ },
210
+ })
211
+ }
@@ -291,20 +291,59 @@ async function downloadCanonicalRegistryTarball(url, {
291
291
  })
292
292
  }
293
293
 
294
- export async function downloadCanonicalNpmTarball(url, { timeoutMs = 30_000 } = {}) {
295
- return downloadCanonicalRegistryTarball(url, {
294
+ // 2026-09-29(ab9a4e1a 那一輪:governance control plane job 在任何閘之前死在「canonical npm download timed out」,
295
+ // PAT 沒有 actions:write 不能重跑;與 09-24 `npm audit` 的 ECONNRESET 同一族)。只對**暫時性傳輸失敗**重試 ——
296
+ // 逾時、連線被重設/拒絕、DNS 暫時失敗、socket hang up、上游 502/503/504 —— 上限 3 次、退避 2s/4s;
297
+ // 契約失敗(非 canonical origin / 重導向 / 內容編碼 / 超出封閉預算 / 長度不符 / 空檔)一律不重試,用盡仍 fail closed、
298
+ // 訊息帶 attempts。下載回來的位元組仍走原本的 SHA-512 / lock digest 驗證,重試不放寬任何完整性檢查。
299
+ // 判定抽成純函式 `isTransientRegistryDownloadFailure`,測試兩面對照(該重試的、不該重試的)。
300
+ export const NPM_RUNTIME_DOWNLOAD_RETRY_LIMIT = 3
301
+ export const NPM_RUNTIME_DOWNLOAD_BACKOFF_MS = 2_000
302
+ const TRANSIENT_DOWNLOAD_REASON = /download timed out|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|EPIPE|socket hang up|download returned HTTP 50[234]\b/i
303
+ const CONTRACT_DOWNLOAD_REASON = /outside the canonical registry contract|unexpected content encoding|closed archive budget|differs from Content-Length|download is empty/i
304
+ export function isTransientRegistryDownloadFailure(error) {
305
+ const text = String(error?.message || '')
306
+ return TRANSIENT_DOWNLOAD_REASON.test(text) && !CONTRACT_DOWNLOAD_REASON.test(text)
307
+ }
308
+
309
+ export async function downloadWithTransientRetry(attempt, {
310
+ label = 'canonical registry',
311
+ retryLimit = NPM_RUNTIME_DOWNLOAD_RETRY_LIMIT,
312
+ backoffMs = NPM_RUNTIME_DOWNLOAD_BACKOFF_MS,
313
+ sleep = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms)),
314
+ report = (line) => console.error(line),
315
+ } = {}) {
316
+ invariant(typeof attempt === 'function', `${label} download attempt must be a function`)
317
+ invariant(Number.isInteger(retryLimit) && retryLimit >= 1, `${label} download retry limit must be a positive integer`)
318
+ for (let attemptNumber = 1; ; attemptNumber += 1) {
319
+ try {
320
+ return await attempt(attemptNumber)
321
+ } catch (error) {
322
+ if (!isTransientRegistryDownloadFailure(error)) throw error
323
+ if (attemptNumber >= retryLimit) {
324
+ throw new Error(`${String(error?.message || error)}(after ${attemptNumber} attempts)`)
325
+ }
326
+ const wait = backoffMs * attemptNumber
327
+ report(`⚠️ GOV-NPM-RUNTIME-001:${label} download transient failure(attempt ${attemptNumber}/${retryLimit}),retrying in ${wait}ms:${String(error?.message || error).slice(0, 240)}`)
328
+ await sleep(wait)
329
+ }
330
+ }
331
+ }
332
+
333
+ export async function downloadCanonicalNpmTarball(url, { timeoutMs = 30_000, retryLimit, backoffMs, sleep, report } = {}) {
334
+ return downloadWithTransientRetry(() => downloadCanonicalRegistryTarball(url, {
296
335
  label: 'canonical npm',
297
336
  pathPattern: /^\/npm\/-\/npm-\d+\.\d+\.\d+\.tgz$/,
298
337
  timeoutMs,
299
- })
338
+ }), { label: 'canonical npm', retryLimit, backoffMs, sleep, report })
300
339
  }
301
340
 
302
- export async function downloadCanonicalNpmSecurityOverlayTarball(url, { timeoutMs = 30_000 } = {}) {
303
- return downloadCanonicalRegistryTarball(url, {
341
+ export async function downloadCanonicalNpmSecurityOverlayTarball(url, { timeoutMs = 30_000, retryLimit, backoffMs, sleep, report } = {}) {
342
+ return downloadWithTransientRetry(() => downloadCanonicalRegistryTarball(url, {
304
343
  label: 'canonical npm security overlay',
305
344
  pathPattern: /^\/(?:brace-expansion\/-\/brace-expansion-5\.0\.9|tar\/-\/tar-7\.5\.22)\.tgz$/,
306
345
  timeoutMs,
307
- })
346
+ }), { label: 'canonical npm security overlay', retryLimit, backoffMs, sleep, report })
308
347
  }
309
348
 
310
349
  function decodeTarField(header, offset, length, label) {
@@ -2,6 +2,7 @@ import fs from 'node:fs'
2
2
  import { createServer } from 'node:http'
3
3
  import path from 'node:path'
4
4
  import { isContainedPath } from './canonical-path-containment.mjs'
5
+ import { snapshotStorybookStatic } from './storybook-static-snapshot.mjs'
5
6
 
6
7
  const LOOPBACK_HOST = '127.0.0.1'
7
8
  const CONTENT_TYPES = new Map([
@@ -64,9 +65,26 @@ export function resolveA11yStaticFile(rootDirectory, requestUrl, { defaultFile =
64
65
  }
65
66
 
66
67
  /** Start one owned ephemeral loopback server and return its unforgeable origin + awaited stop. */
67
- export async function startA11yStaticServer({ rootDirectory, defaultFile = 'index.html' } = {}) {
68
- const root = canonicalStaticRoot(rootDirectory)
68
+ // **Storybook 建置一律從本次獨佔的快照供檔**(2026-09-24):根目錄裡有 build-info.json(= 一份完整的
69
+ // `npm run build-storybook` 產物)時,先凍結成快照再服務,不再每次請求讀活目錄。錨例:
70
+ // data-table-invariants.mjs 本機誤紅 —— 另一個 agent 同時 build-storybook,第一步就清空輸出目錄,
71
+ // 後續導覽全部 404,被判成「表格沒有列」(M37:把「路徑」當成「建置身分」)。沒有 build-info.json 的
72
+ // 根目錄(consumer dist 等)照舊直接服務;建置不完整或複製期間被重建 → 以「儀器失效」的名義丟出。
73
+ // `notFound` 是同源 404 帳本:快照不會再變,任何 404 都是「建置缺檔」或「story 要了不存在的檔」,
74
+ // 呼叫端失敗時應一併印出,不讓「儀器沒拿到檔」被讀成「元件沒渲染」。
75
+ export async function startA11yStaticServer({ rootDirectory, defaultFile = 'index.html', snapshot = 'auto' } = {}) {
76
+ const liveRoot = canonicalStaticRoot(rootDirectory)
69
77
  safeDefaultFile(defaultFile)
78
+ // 「這是一份 Storybook 建置」的判定:有完成標記,或目錄就叫 storybook-static。後者沒有標記 = 別人正在重建
79
+ // (重建第一步就刪標記、最後才寫回)或從沒建完 —— 這時 snapshotStorybookStatic 會以「儀器失效」丟出,
80
+ // 不再退回讀活目錄(那正是 2026-09-24 誤紅的路徑)。其他根目錄(consumer dist、暫存頁)照舊直接服務。
81
+ const isStorybookBuild = fs.existsSync(path.join(liveRoot, 'build-info.json')) || path.basename(liveRoot) === 'storybook-static'
82
+ const frozen = (snapshot === 'auto' && isStorybookBuild) || snapshot === true
83
+ ? snapshotStorybookStatic(liveRoot)
84
+ : null
85
+ const root = frozen ? canonicalStaticRoot(frozen.dir) : liveRoot
86
+ const notFound = []
87
+ if (frozen) process.once('exit', () => frozen.dispose())
70
88
  const server = createServer((request, response) => {
71
89
  if (!['GET', 'HEAD'].includes(request.method || '')) {
72
90
  response.statusCode = 405
@@ -76,6 +94,7 @@ export async function startA11yStaticServer({ rootDirectory, defaultFile = 'inde
76
94
  }
77
95
  const file = resolveA11yStaticFile(root, request.url || '/', { defaultFile })
78
96
  if (!file) {
97
+ notFound.push((request.url || '/').split(/[?#]/, 1)[0])
79
98
  response.statusCode = 404
80
99
  response.end()
81
100
  return
@@ -121,6 +140,7 @@ export async function startA11yStaticServer({ rootDirectory, defaultFile = 'inde
121
140
  await new Promise((resolveClosed, rejectClosed) => {
122
141
  server.close(error => error ? rejectClosed(error) : resolveClosed())
123
142
  })
143
+ frozen?.dispose()
124
144
  }
125
145
  return Object.freeze({
126
146
  host: LOOPBACK_HOST,
@@ -130,5 +150,25 @@ export async function startA11yStaticServer({ rootDirectory, defaultFile = 'inde
130
150
  // `close` 是 Node server 的習慣名字,呼叫端很自然會伸手去拿;沒有它的時候
131
151
  // `server.close?.()` 會靜靜地什麼都不做(2026-09-18 實測四支閘都是這樣寫的)。給同一個實作,別再有人踩。
132
152
  close: stop,
153
+ /** 同源 404 帳本(路徑,不含 query);呼叫端失敗時印出 `[...new Set(server.notFound)]`。 */
154
+ notFound,
155
+ /** 本次服務的建置快照(沒有 build-info.json 的根目錄為 null)。 */
156
+ snapshot: frozen ? Object.freeze({ dir: frozen.dir, buildInfo: frozen.buildInfo }) : null,
157
+ /** 正在服務的根目錄(有快照 = 快照目錄;沒有 = 原根目錄)。讀清單一律從這裡讀,見 readServedStorybookIndex。 */
158
+ servedRoot: root,
133
159
  })
134
160
  }
161
+
162
+ /**
163
+ * 讀**正在服務的那一份**建置的 Storybook index.json(2026-09-25,待辦總帳 C5)。
164
+ * story 清單必須跟頁面出自同一份建置:伺服器從快照供檔、清單卻讀活目錄的閘,在別人同時 build-storybook 時
165
+ * 會拿到「清單有、快照沒有」(或反過來)的 story,把「清單與頁面不是同一份」讀成「story 載入失敗」(假的儀器紅),
166
+ * 或把新增的 story 漏量。有快照 → 讀快照;沒有快照(非 Storybook 根目錄)→ 讀服務中的根目錄 —— 兩者都是「正在服務的那一份」。
167
+ * 缺檔 / 壞檔照常丟例外(呼叫端先用 requireStorybookBuild 擋「根本沒有建置」)。
168
+ * @param {{ servedRoot: string }} server startA11yStaticServer 的回傳
169
+ * @returns {any} 解析後的 index.json
170
+ */
171
+ export function readServedStorybookIndex(server) {
172
+ if (!server || typeof server.servedRoot !== 'string') throw new TypeError('readServedStorybookIndex:要傳 startA11yStaticServer 的回傳(需有 servedRoot)')
173
+ return JSON.parse(fs.readFileSync(path.join(server.servedRoot, 'index.json'), 'utf8'))
174
+ }
@@ -2558,10 +2558,31 @@ try {
2558
2558
  'live upgraded transaction authority',
2559
2559
  )
2560
2560
  assertClosedGitLocalConfiguration(repoRoot)
2561
+ // 2026-09-27(M37:「未暫存的 diff ≡ 這次改了什麼」只在沒有新檔的版本成立):重建端的 patch 是
2562
+ // `git add -A` 之後的 --cached diff(新增檔以 new file 出現),而這裡原本拿「未暫存的 git diff」比
2563
+ // bytes —— untracked 的新增受管檔案根本不會出現在未暫存 diff。這道比對 2026-07-28 上線後,
2564
+ // beta.146 是第一個新增受管檔案的 release(scripts/lib/launch-browser / storybook-render-health /
2565
+ // storybook-static-snapshot),WM run 36303338183 第 6 步因此少三個檔而被判「與重建不符」。
2566
+ // 修法:live 端用**同一條**指令暫存(add -A -- .)、比同樣的 --cached diff,比完用 `git reset -- .`
2567
+ // 把 index 還原(只動 index、不動工作樹),下游契約不變:index 乾淨、HEAD 不動、新檔留在工作樹
2568
+ // 當 untracked 交給 PR 步驟的 `git add --all` 收。對照組:scripts/test-sync-all-transaction.mjs
2569
+ // 「新增受管檔案」情境(修法前紅、修法後綠)。
2570
+ const liveStage = runClosedGit(['add', '-A', '--', '.'], { cwd: repoRoot, output: 'ignore' })
2571
+ if (liveStage.error || liveStage.signal !== null || liveStage.status !== 0) {
2572
+ throw new Error('live upgrade result could not be staged for the protected-base comparison')
2573
+ }
2561
2574
  const livePatch = runClosedGit(
2562
- ['diff', '--binary', '--full-index', '--no-ext-diff', '--no-textconv', '--no-renames'],
2575
+ ['diff', '--cached', '--binary', '--full-index', '--no-ext-diff', '--no-textconv', '--no-renames'],
2563
2576
  { cwd: repoRoot, output: 'buffer', maxOutputBytes: 64 * 1024 * 1024 },
2564
2577
  )
2578
+ const unstagedDiff = runClosedGit(
2579
+ ['diff', '--name-only', '-z', '--no-ext-diff', '--no-textconv'],
2580
+ { cwd: repoRoot, output: 'buffer' },
2581
+ )
2582
+ const liveUnstage = runClosedGit(['reset', '-q', '--', '.'], { cwd: repoRoot, output: 'ignore' })
2583
+ if (liveUnstage.error || liveUnstage.signal !== null || liveUnstage.status !== 0) {
2584
+ throw new Error('live upgrade index could not be restored after the protected-base comparison')
2585
+ }
2565
2586
  const stagedDiff = runClosedGit(
2566
2587
  ['diff', '--cached', '--name-only', '-z', '--no-ext-diff', '--no-textconv'],
2567
2588
  { cwd: repoRoot, output: 'buffer' },
@@ -2574,6 +2595,11 @@ try {
2574
2595
  || livePatch.status !== 0
2575
2596
  || !Buffer.isBuffer(livePatch.stdout)
2576
2597
  || !livePatch.stdout.equals(reconstructedUpgrade.patchBytes)
2598
+ || unstagedDiff.error
2599
+ || unstagedDiff.signal !== null
2600
+ || unstagedDiff.status !== 0
2601
+ || !Buffer.isBuffer(unstagedDiff.stdout)
2602
+ || unstagedDiff.stdout.length !== 0
2577
2603
  || stagedDiff.error
2578
2604
  || stagedDiff.signal !== null
2579
2605
  || stagedDiff.status !== 0
@@ -8,21 +8,30 @@
8
8
  // 4. fail = block PR
9
9
  //
10
10
  // 對齊 DS repo `npm run a11y:check`(scripts/audit-a11y.mjs)的 consumer-side equivalent。
11
+ //
12
+ // **「頁面真的渲染出來了」由共用的 openStory(document 模式)證明,不再用固定睡眠代替**(2026-09-25,M37)。
13
+ // 錨例:app 的 bundle 404(dist/assets 缺檔)或 bundle 載入後什麼都沒掛上時,`#root` 是空的,axe 對一個
14
+ // 空白頁當然找不到任何違規 —— 舊版(`networkidle` + 固定睡 1000ms、沒有任何渲染檢查)於是印出
15
+ // 「✅ All apps pass WCAG 2 A + AA」、exit 0。要保證的性質是「axe 量的是 app 真正渲染出來的畫面」,
16
+ // 量的卻是「網路靜了、又過了一秒」。現在:根節點沒有內容 / 關鍵資源(document/script/stylesheet)缺檔 /
17
+ // 頁面例外 → 以 INSTRUMENT-FAIL(儀器失效:沒量到,不是產品裁決)的名義紅,並點名 app 與缺的檔。
18
+ //
19
+ // 交付面:這支腳本隨 fork 治理套件與 published template 交付給 consumer,所以它 import 的
20
+ // `lib/launch-browser.mjs` 與其依賴 `lib/storybook-render-health.mjs` 一併登記進四個發送清單:
21
+ // build-fork-governance.mjs 的 MANAGED_FILE_SOURCES / infra/governance/inventory/managed-repos.json /
22
+ // published-template-mirror-policy.json / build-published-template-mirror.mjs 的 ALLOWLIST;另外
23
+ // consumer-source-harness.mjs 的 TRUSTED_PRODUCT_CHECK_FILES(staged 的受保護檢查器)也必須是完整閉包。
24
+ // fork 產生器、mirror 產生器、test-consumer-source-harness 各有相依閉包檢查,test-setup-governance 由本檔的
25
+ // import 閉包推導清單並比對四個發送清單 —— 漏登記的相對 import 會當場擋下(2026-09-25 兩面實跑)。
26
+ // 先前為了不擴張交付面而就地抄一份 SANDBOX_ARGS;現在 launch-browser 本來就要交付,單一住所改回直接 import(M17)。
11
27
 
12
28
  import { existsSync, lstatSync, readdirSync, realpathSync } from 'node:fs'
13
29
  import { join, resolve } from 'node:path'
14
30
  import { chromium } from 'playwright'
15
-
16
- // 受限沙箱起 Chromium 需要的兩個參數。**刻意就地寫,不 import 共用 helper**:
17
- // 這支腳本會被交付到 consumer repo,而 import `lib/launch-browser.mjs` 會讓那支也必須進
18
- // 四個交付/白名單清單(2026-09-21 實測:fork delivery / 檔案所有權 / published-template 鏡像 /
19
- // 鏡像路徑白名單),為兩個旗標擴張消費端交付面不值得 —— M17 的門檻是同值 **3+** 個消費者才抽共用,
20
- // 這裡是第 2 個。DS 內部閘的單一住所仍是 `scripts/lib/launch-browser.mjs` 的 `SANDBOX_ARGS`,
21
- // 兩邊若要改必須一起改(值相同:單行程 + 關沙箱)。
22
- const SANDBOX_ARGS = ['--single-process', '--no-sandbox']
23
31
  import { createRequire } from 'node:module'
24
32
  import { fileURLToPath } from 'node:url'
25
33
  import { resolveA11yStaticFile, startA11yStaticServer } from './lib/a11y-static-server.mjs'
34
+ import { INSTRUMENT_FAIL_MARKER, SANDBOX_ARGS, StoryRenderInstrumentError, openStory } from './lib/launch-browser.mjs'
26
35
 
27
36
  const require = createRequire(import.meta.url)
28
37
  const axeBundle = require.resolve('axe-core/axe.min.js')
@@ -89,11 +98,32 @@ export async function startStaticServer({ distPath } = {}) {
89
98
  return startA11yStaticServer({ rootDirectory: distPath, defaultFile: 'index.html' })
90
99
  }
91
100
 
101
+ /**
102
+ * 「這個 app 的頁面沒有渲染出來」的儀器失效(沒量到,不是產品裁決)。訊息以 INSTRUMENT-FAIL 開頭、點名 app,
103
+ * 附缺的檔(同源 404)與失敗原因 —— 絕不被讀成「0 個違規」。
104
+ */
105
+ export class ConsumerAppRenderError extends Error {
106
+ constructor({ app, cause }) {
107
+ const detail = cause instanceof StoryRenderInstrumentError ? cause.detail : String(cause?.message ?? cause).split('\n')[0]
108
+ super(`${INSTRUMENT_FAIL_MARKER} app「${app}」的頁面沒有渲染出來 —— ${detail}。`
109
+ + '這是儀器失效(沒量到),不是產品裁決:axe 沒有對一個空白 / 半載入的頁面下結論,這次不能算通過', { cause })
110
+ this.name = 'ConsumerAppRenderError'
111
+ this.app = app
112
+ this.kind = cause instanceof StoryRenderInstrumentError ? cause.kind : 'unknown'
113
+ this.failedRequests = cause instanceof StoryRenderInstrumentError ? cause.failedRequests : []
114
+ }
115
+ }
116
+
92
117
  export async function auditUrlWithAxe({
93
118
  url,
119
+ app = url,
120
+ notFound = null,
94
121
  browserType = chromium,
95
122
  axeBundlePath = axeBundle,
96
- settleMs = 1000,
123
+ // 版面連續靜止幾個影格才開始量(影格不是毫秒:慢的機器只會等久一點,不會提早取樣)。0 = 不等靜止。
124
+ settleFrames = 10,
125
+ // 「根節點出現內容」的等待上限(不是「已渲染」的代理 —— 成功一律由根節點真的有內容決定)。
126
+ healthTimeoutMs = 15_000,
97
127
  } = {}) {
98
128
  let browser
99
129
  try {
@@ -102,9 +132,20 @@ export async function auditUrlWithAxe({
102
132
  // 差別就只在這組參數。同一份程式在不同環境給不同答案,本身就是量具問題。
103
133
  browser = await browserType.launch({ headless: true, args: [...SANDBOX_ARGS] })
104
134
  const page = await (await browser.newContext()).newPage()
105
- const response = await page.goto(url, { waitUntil: 'networkidle' })
106
- if (!response || !response.ok()) throw new Error(`owned app server returned ${response?.status() ?? 'no response'}`)
107
- if (settleMs > 0) await page.waitForTimeout(settleMs)
135
+ // 共用的「真的渲染完成了」:document 模式 = 根節點(#root / main / body)有內容、document/script/stylesheet
136
+ // 沒有缺檔、沒有頁面例外、渲染期間發出的請求全部結束、版面靜止 —— 任何一項不成立就丟,不量。
137
+ try {
138
+ await openStory(page, url, {
139
+ storybook: false,
140
+ label: app,
141
+ notFound,
142
+ requestsSettled: true,
143
+ settleFrames,
144
+ healthTimeoutMs,
145
+ })
146
+ } catch (error) {
147
+ throw new ConsumerAppRenderError({ app, cause: error })
148
+ }
108
149
 
109
150
  // Exact lockfile dependency: no mutable CDN code enters the protected audit.
110
151
  await page.addScriptTag({ path: axeBundlePath })
@@ -153,6 +194,9 @@ export async function runConsumerA11yAudit({
153
194
  const violations = validateAxeViolations(await auditUrl({
154
195
  ...auditOptions,
155
196
  url: server.origin,
197
+ app,
198
+ // 伺服器端的同源 404 帳本:缺檔時點名是哪個檔(瀏覽器事件可能晚到,伺服器記的不會)
199
+ notFound: Array.isArray(server.notFound) ? server.notFound : null,
156
200
  }))
157
201
 
158
202
  if (violations.length > 0) {
@@ -165,7 +209,11 @@ export async function runConsumerA11yAudit({
165
209
  logger.log(' ✅ 0 WCAG violations')
166
210
  }
167
211
  } catch (error) {
168
- logger.error(` ❌ a11y audit infrastructure error:${error.message}`)
212
+ if (error instanceof ConsumerAppRenderError) {
213
+ logger.error(` ❌ ${error.message}`)
214
+ } else {
215
+ logger.error(` ❌ ${INSTRUMENT_FAIL_MARKER} a11y audit infrastructure error:${error.message}`)
216
+ }
169
217
  errors.push({ app, auditError: error.message })
170
218
  } finally {
171
219
  await server?.stop()
@@ -183,7 +231,7 @@ export async function runConsumerA11yAudit({
183
231
  const product = errors.filter((item) => !item.auditError)
184
232
  if (product.length > 0) logger.error(`❌ ${product.length} app(s) have a11y issues`)
185
233
  if (infrastructure.length > 0) {
186
- logger.error(`❌ ${infrastructure.length} app(s) 量不到(儀器失效,不是產品有問題):`)
234
+ logger.error(`❌ ${INSTRUMENT_FAIL_MARKER}:${infrastructure.length} app(s) 量不到(儀器失效,不是產品有問題;沒量到不等於 0 個違規):`)
187
235
  for (const item of infrastructure) logger.error(` - ${item.app}:${String(item.auditError).split('\n')[0]}`)
188
236
  }
189
237
  return 1