From 8c2d88c324f5723b7e0b3044d34ffa4ce9f98e93 Mon Sep 17 00:00:00 2001 From: woksin Date: Sat, 26 Sep 2026 21:58:00 +0200 Subject: [PATCH 1/6] Replace pinned evidence counts with reviewable inventory snapshots --- Source/scripts/export-inventory.json | 56 ++ Source/scripts/kernel-inventory.json | 38 + Source/scripts/lib/package-inventories.mjs | 20 + Source/scripts/parts-inventory.json | 882 ++++++++++++++++++ Source/scripts/renderer-slots-inventory.json | 36 + Source/scripts/verify-exports.mjs | 2 + .../scripts/verify-package-graph-report.mjs | 12 +- Source/scripts/verify-package-graph.mjs | 3 + Source/scripts/verify-parts-manifest.mjs | 29 +- Source/scripts/verify-renderer-contracts.mjs | 17 +- Storybook/README.md | 4 +- Storybook/scripts/renderer-inventory.json | 9 + Storybook/scripts/storybook-inventory.json | 684 ++++++++++++++ .../scripts/verify-adapter-inventory.mjs | 13 +- Storybook/scripts/verify-composed-manager.mjs | 6 +- .../scripts/verify-storybook-indexes.mjs | 43 +- package.json | 3 +- scripts/evidence-inventory.test.mjs | 36 + scripts/generate-inventories.mjs | 37 + scripts/lib/evidence-inventory.mjs | 62 ++ 20 files changed, 1942 insertions(+), 50 deletions(-) create mode 100644 Source/scripts/export-inventory.json create mode 100644 Source/scripts/kernel-inventory.json create mode 100644 Source/scripts/lib/package-inventories.mjs create mode 100644 Source/scripts/parts-inventory.json create mode 100644 Source/scripts/renderer-slots-inventory.json create mode 100644 Storybook/scripts/renderer-inventory.json create mode 100644 Storybook/scripts/storybook-inventory.json create mode 100644 scripts/evidence-inventory.test.mjs create mode 100644 scripts/generate-inventories.mjs create mode 100644 scripts/lib/evidence-inventory.mjs diff --git a/Source/scripts/export-inventory.json b/Source/scripts/export-inventory.json new file mode 100644 index 00000000..04a12299 --- /dev/null +++ b/Source/scripts/export-inventory.json @@ -0,0 +1,56 @@ +{ + "exportSubpaths": [ + ".", + "./Canvas", + "./Canvas/styles", + "./Chat", + "./Chat/styles", + "./CommandDialog", + "./CommandDialog/styles", + "./CommandForm", + "./CommandForm/fields", + "./CommandForm/fields/styles", + "./CommandForm/styles", + "./CommandStepper", + "./CommandStepper/styles", + "./Common", + "./Common/styles", + "./DataPage", + "./DataPage/styles", + "./DataTables", + "./DataTables/styles", + "./Dialogs", + "./Dialogs/styles", + "./Display", + "./Display/styles", + "./Dropdown", + "./Dropdown/styles", + "./Filter", + "./Filter/styles", + "./Notifications", + "./Notifications/styles", + "./ObjectContentEditor", + "./ObjectContentEditor/styles", + "./ObjectNavigationalBar", + "./ObjectNavigationalBar/styles", + "./PivotViewer", + "./PivotViewer/styles", + "./SchemaEditor", + "./SchemaEditor/styles", + "./TimeMachine", + "./TimeMachine/styles", + "./Toolbar", + "./Toolbar/styles", + "./compat-manifest.json", + "./package.json", + "./renderer", + "./renderer/builtin", + "./renderer/builtin/styles", + "./schemas/ui-adapter.schema.json", + "./styles", + "./styles/base", + "./theme", + "./tokens", + "./types" + ] +} diff --git a/Source/scripts/kernel-inventory.json b/Source/scripts/kernel-inventory.json new file mode 100644 index 00000000..d2d34dfd --- /dev/null +++ b/Source/scripts/kernel-inventory.json @@ -0,0 +1,38 @@ +{ + "kernelModules": [ + "Source/Canvas/canvasGesture.ts", + "Source/Canvas/canvasTransformActivity.ts", + "Source/Canvas/panMomentum.ts", + "Source/Canvas/pinchGesture.ts", + "Source/Canvas/shapes/Region/regionContainment.ts", + "Source/Chat/Kit/Mentions/MentionCandidate.ts", + "Source/Chat/Kit/Mentions/MentionQuery.ts", + "Source/Chat/Kit/Mentions/activeMentionQuery.ts", + "Source/Chat/Kit/Mentions/applyMention.ts", + "Source/Chat/Kit/Mentions/extractMentions.ts", + "Source/Chat/Kit/Mentions/findMentionRanges.ts", + "Source/Chat/Kit/Mentions/matchCandidates.ts", + "Source/Chat/Kit/Mentions/mentionSegments.ts", + "Source/Chat/Kit/findOwnReaction.ts", + "Source/Chat/Kit/reactionsExcludingUser.ts", + "Source/Chat/isTopicUnnamed.ts", + "Source/Chat/shouldRequestTopicName.ts", + "Source/Chat/topicsByActivity.ts", + "Source/CommandDialog/applyBeforeExecute.ts", + "Source/CommandForm/commandFormMarkers.ts", + "Source/CommandForm/fields/chipValues.ts", + "Source/CommandForm/fields/fieldValueFromEvent.ts", + "Source/DataTables/DataTableFilterMatcherRegistry.ts", + "Source/DataTables/DataTableFilterMeta.ts", + "Source/DataTables/paginatorRange.ts", + "Source/DataTables/selectionKeys.ts", + "Source/Filter/types.ts", + "Source/Filter/utils.ts", + "Source/PivotViewer/constants.ts", + "Source/PivotViewer/engine/layout.ts", + "Source/PivotViewer/engine/requestCorrelator.ts", + "Source/PivotViewer/engine/store.ts", + "Source/PivotViewer/engine/types.ts", + "Source/SchemaEditor/schemaHelpers.ts" + ] +} diff --git a/Source/scripts/lib/package-inventories.mjs b/Source/scripts/lib/package-inventories.mjs new file mode 100644 index 00000000..8115f901 --- /dev/null +++ b/Source/scripts/lib/package-inventories.mjs @@ -0,0 +1,20 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { kernelSourcePaths } from '../../../ESLint/lib/kernelBoundary.js'; +import { checkInventory } from '../../../scripts/lib/evidence-inventory.mjs'; + +const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const command = 'yarn generate-inventories'; + +export const checkKernelInventory = (update = false) => checkInventory( + path.join(sourceRoot, 'scripts/kernel-inventory.json'), + { kernelModules: kernelSourcePaths }, command, update, +); + +export const checkExportInventory = (exportsMap, update = false) => checkInventory( + path.join(sourceRoot, 'scripts/export-inventory.json'), + { exportSubpaths: Object.keys(exportsMap ?? {}) }, command, update, +); diff --git a/Source/scripts/parts-inventory.json b/Source/scripts/parts-inventory.json new file mode 100644 index 00000000..3d97fe03 --- /dev/null +++ b/Source/scripts/parts-inventory.json @@ -0,0 +1,882 @@ +{ + "componentParts": [ + "ActionMenubar.icon", + "ActionMenubar.label", + "ActionMenubar.root", + "ActionMenubar.spinner", + "Avatar.fallback", + "Avatar.image", + "Avatar.root", + "Badge.root", + "Breadcrumbs.item", + "Breadcrumbs.link", + "Breadcrumbs.root", + "Breadcrumbs.separator", + "Button.icon", + "Button.label", + "Button.root", + "Button.spinner", + "CalendarField.button-bar", + "CalendarField.calendar", + "CalendarField.cell", + "CalendarField.clear", + "CalendarField.dialog", + "CalendarField.grid", + "CalendarField.group", + "CalendarField.header", + "CalendarField.heading", + "CalendarField.input", + "CalendarField.next", + "CalendarField.placeholder", + "CalendarField.popover", + "CalendarField.previous", + "CalendarField.root", + "CalendarField.segment", + "CalendarField.today", + "CalendarField.trigger", + "ChatSidebar.back", + "ChatSidebar.backdrop", + "ChatSidebar.close", + "ChatSidebar.content", + "ChatSidebar.header", + "ChatSidebar.root", + "ChatSidebar.title", + "Checkbox.box", + "Checkbox.indicator", + "Checkbox.input", + "Checkbox.label", + "Checkbox.root", + "CheckboxField.box", + "CheckboxField.indicator", + "CheckboxField.input", + "CheckboxField.root", + "Chip.icon", + "Chip.label", + "Chip.remove", + "Chip.root", + "ChipsField.input", + "ChipsField.item", + "ChipsField.remove", + "ChipsField.root", + "ColorPickerField.input", + "ColorPickerField.root", + "ColorPickerField.value", + "ColumnFilterMenu.filter-actions", + "ColumnFilterMenu.filter-menu", + "ColumnFilterMenu.filter-popover", + "ColumnFilterMenu.filter-trigger", + "ComboBox.action", + "ComboBox.description", + "ComboBox.empty", + "ComboBox.error", + "ComboBox.failure", + "ComboBox.input", + "ComboBox.listbox", + "ComboBox.loading", + "ComboBox.option", + "ComboBox.optionDescription", + "ComboBox.optionLabel", + "ComboBox.popover", + "ComboBox.root", + "ComboBox.trigger", + "CommandDialog.backdrop", + "CommandDialog.busy-scope", + "CommandDialog.cancel", + "CommandDialog.close", + "CommandDialog.confirm", + "CommandDialog.content", + "CommandDialog.footer", + "CommandDialog.header", + "CommandDialog.positioner", + "CommandDialog.root", + "CommandDialog.subtitle", + "CommandDialog.title", + "CommandStepper.header", + "CommandStepper.list", + "CommandStepper.number", + "CommandStepper.panel", + "CommandStepper.panels", + "CommandStepper.root", + "CommandStepper.separator", + "CommandStepper.step", + "CommandStepper.title", + "DataTableCore.body", + "DataTableCore.cell", + "DataTableCore.empty-cell", + "DataTableCore.empty-row", + "DataTableCore.failure-cell", + "DataTableCore.failure-row", + "DataTableCore.head", + "DataTableCore.header-cell", + "DataTableCore.header-content", + "DataTableCore.header-row", + "DataTableCore.loading-cell", + "DataTableCore.loading-row", + "DataTableCore.root", + "DataTableCore.row", + "DataTableCore.search", + "DataTableCore.search-input", + "DataTableCore.select-all", + "DataTableCore.sort", + "DataTableCore.table", + "DataTableCore.table-container", + "DataTableForObservableQuery.body", + "DataTableForObservableQuery.cell", + "DataTableForObservableQuery.empty-cell", + "DataTableForObservableQuery.empty-row", + "DataTableForObservableQuery.failure-cell", + "DataTableForObservableQuery.failure-row", + "DataTableForObservableQuery.head", + "DataTableForObservableQuery.header-cell", + "DataTableForObservableQuery.header-content", + "DataTableForObservableQuery.header-row", + "DataTableForObservableQuery.loading-cell", + "DataTableForObservableQuery.loading-row", + "DataTableForObservableQuery.root", + "DataTableForObservableQuery.row", + "DataTableForObservableQuery.search", + "DataTableForObservableQuery.search-input", + "DataTableForObservableQuery.select-all", + "DataTableForObservableQuery.sort", + "DataTableForObservableQuery.table", + "DataTableForObservableQuery.table-container", + "DataTableForQuery.body", + "DataTableForQuery.cell", + "DataTableForQuery.empty-cell", + "DataTableForQuery.empty-row", + "DataTableForQuery.failure-cell", + "DataTableForQuery.failure-row", + "DataTableForQuery.head", + "DataTableForQuery.header-cell", + "DataTableForQuery.header-content", + "DataTableForQuery.header-row", + "DataTableForQuery.loading-cell", + "DataTableForQuery.loading-row", + "DataTableForQuery.root", + "DataTableForQuery.row", + "DataTableForQuery.search", + "DataTableForQuery.search-input", + "DataTableForQuery.select-all", + "DataTableForQuery.sort", + "DataTableForQuery.table", + "DataTableForQuery.table-container", + "DatePickerInput.button-bar", + "DatePickerInput.calendar", + "DatePickerInput.cell", + "DatePickerInput.clear", + "DatePickerInput.dialog", + "DatePickerInput.grid", + "DatePickerInput.group", + "DatePickerInput.header", + "DatePickerInput.heading", + "DatePickerInput.input", + "DatePickerInput.next", + "DatePickerInput.placeholder", + "DatePickerInput.popover", + "DatePickerInput.previous", + "DatePickerInput.root", + "DatePickerInput.segment", + "DatePickerInput.today", + "DatePickerInput.trigger", + "Dialog.backdrop", + "Dialog.busy-scope", + "Dialog.cancel", + "Dialog.close", + "Dialog.confirm", + "Dialog.content", + "Dialog.footer", + "Dialog.header", + "Dialog.positioner", + "Dialog.root", + "Dialog.subtitle", + "Dialog.title", + "Dropdown.clear", + "Dropdown.filter", + "Dropdown.indicator", + "Dropdown.listbox", + "Dropdown.multiple", + "Dropdown.option", + "Dropdown.popover", + "Dropdown.root", + "Dropdown.trigger", + "Dropdown.value", + "DropdownField.clear", + "DropdownField.filter", + "DropdownField.indicator", + "DropdownField.listbox", + "DropdownField.multiple", + "DropdownField.option", + "DropdownField.popover", + "DropdownField.root", + "DropdownField.trigger", + "DropdownField.value", + "EventsView.connector", + "EventsView.content", + "EventsView.event", + "EventsView.marker", + "EventsView.separator", + "EventsView.timeline", + "IconButton.icon", + "IconButton.label", + "IconButton.root", + "IconButton.spinner", + "InputTextField.input", + "Message.icon", + "Message.root", + "Message.text", + "MultiSelectField.clear", + "MultiSelectField.filter", + "MultiSelectField.indicator", + "MultiSelectField.listbox", + "MultiSelectField.multiple", + "MultiSelectField.option", + "MultiSelectField.popover", + "MultiSelectField.root", + "MultiSelectField.trigger", + "MultiSelectField.value", + "NumberField.input", + "NumberField.root", + "NumberInput.description", + "NumberInput.error", + "NumberInput.group", + "NumberInput.input", + "NumberInput.prefix", + "NumberInput.root", + "NumberInput.step", + "NumberInput.suffix", + "NumberInputField.description", + "NumberInputField.error", + "NumberInputField.group", + "NumberInputField.input", + "NumberInputField.prefix", + "NumberInputField.root", + "NumberInputField.step", + "NumberInputField.suffix", + "PasswordField.input", + "PasswordField.root", + "PasswordField.toggle", + "ProgressBar.indicator", + "ProgressBar.label", + "ProgressBar.root", + "ProgressSpinner.range", + "ProgressSpinner.root", + "ProgressSpinner.svg", + "ProgressSpinner.track", + "Radio.box", + "Radio.indicator", + "Radio.input", + "Radio.label", + "Radio.root", + "RadioButtonField.box", + "RadioButtonField.indicator", + "RadioButtonField.input", + "RadioButtonField.root", + "RadioGroupField.box", + "RadioGroupField.indicator", + "RadioGroupField.input", + "RadioGroupField.option", + "RadioGroupField.root", + "RatingField.input", + "RatingField.option", + "RatingField.root", + "RatingField.star", + "Skeleton.root", + "SliderField.input", + "SliderField.root", + "SliderField.value", + "Surface.root", + "Switch.control", + "Switch.handle", + "Switch.input", + "Switch.label", + "Switch.root", + "TablePaginator.info", + "TablePaginator.range", + "TablePaginator.root", + "Tabs.list", + "Tabs.panel", + "Tabs.root", + "Tabs.tab", + "Tag.icon", + "Tag.label", + "Tag.root", + "TagGroup.input", + "TagGroup.list", + "TagGroup.remove", + "TagGroup.root", + "TagGroup.tag", + "TextArea.root", + "TextAreaField.textarea", + "TextInput.root", + "Toaster.action", + "Toaster.close", + "Toaster.content", + "Toaster.description", + "Toaster.icon", + "Toaster.region", + "Toaster.title", + "Toaster.toast", + "ToggleGroup.icon", + "ToggleGroup.label", + "ToggleGroup.option", + "ToggleGroup.root", + "ToggleSwitchField.control", + "ToggleSwitchField.handle", + "ToggleSwitchField.input", + "ToggleSwitchField.root", + "Toolbar.root", + "ToolbarButton.button", + "ToolbarButton.icon", + "ToolbarButton.label", + "ToolbarFanOutItem.fanout-panel", + "ToolbarFanOutItem.fanout-root", + "ToolbarFanOutItem.fanout-trigger", + "ToolbarFolder.toolbar-folder", + "ToolbarFolder.toolbar-folder-panel", + "ToolbarFolder.toolbar-folder-trigger", + "ToolbarGroup.toolbar-group", + "ToolbarGroup.toolbar-slot", + "ToolbarGroup.toolbar-slot-incoming", + "ToolbarGroup.toolbar-slot-outgoing", + "ToolbarLayout.toolbar-layout", + "ToolbarLayout.toolbar-slot", + "ToolbarLayout.toolbar-slot-incoming", + "ToolbarLayout.toolbar-slot-outgoing", + "ToolbarSection.toolbar-context", + "ToolbarSection.toolbar-section", + "ToolbarSeparator.toolbar-separator", + "Tooltip.popup", + "Tooltip.trigger" + ], + "componentStates": [ + "ActionMenubar.root.disabled", + "ActionMenubar.root.loading", + "Breadcrumbs.item.selected", + "Breadcrumbs.link.selected", + "Button.root.disabled", + "Button.root.loading", + "CalendarField.calendar.open", + "CalendarField.cell.selected", + "CalendarField.dialog.open", + "CalendarField.group.disabled", + "CalendarField.group.invalid", + "CalendarField.group.open", + "CalendarField.group.readonly", + "CalendarField.group.selected", + "CalendarField.input.disabled", + "CalendarField.input.invalid", + "CalendarField.input.open", + "CalendarField.input.readonly", + "CalendarField.input.selected", + "CalendarField.popover.open", + "CalendarField.root.disabled", + "CalendarField.root.invalid", + "CalendarField.root.open", + "CalendarField.root.readonly", + "CalendarField.root.selected", + "CalendarField.segment.disabled", + "CalendarField.segment.invalid", + "CalendarField.segment.readonly", + "CalendarField.today.disabled", + "CalendarField.trigger.disabled", + "CalendarField.trigger.invalid", + "CalendarField.trigger.open", + "CalendarField.trigger.readonly", + "CalendarField.trigger.selected", + "ChatSidebar.backdrop.open", + "ChatSidebar.backdrop.selected", + "ChatSidebar.root.open", + "ChatSidebar.root.selected", + "Checkbox.box.disabled", + "Checkbox.box.invalid", + "Checkbox.box.readonly", + "Checkbox.box.selected", + "Checkbox.indicator.disabled", + "Checkbox.indicator.invalid", + "Checkbox.indicator.readonly", + "Checkbox.indicator.selected", + "Checkbox.input.disabled", + "Checkbox.input.invalid", + "Checkbox.input.readonly", + "Checkbox.input.selected", + "Checkbox.label.disabled", + "Checkbox.label.invalid", + "Checkbox.label.readonly", + "Checkbox.label.selected", + "Checkbox.root.disabled", + "Checkbox.root.invalid", + "Checkbox.root.readonly", + "Checkbox.root.selected", + "CheckboxField.box.disabled", + "CheckboxField.box.invalid", + "CheckboxField.box.selected", + "CheckboxField.indicator.disabled", + "CheckboxField.indicator.invalid", + "CheckboxField.indicator.selected", + "CheckboxField.input.disabled", + "CheckboxField.input.invalid", + "CheckboxField.input.selected", + "CheckboxField.root.disabled", + "CheckboxField.root.invalid", + "CheckboxField.root.selected", + "ChipsField.input.disabled", + "ChipsField.input.invalid", + "ChipsField.input.readonly", + "ChipsField.item.selected", + "ChipsField.remove.disabled", + "ChipsField.remove.selected", + "ChipsField.root.disabled", + "ChipsField.root.invalid", + "ChipsField.root.readonly", + "ColorPickerField.input.disabled", + "ColorPickerField.input.invalid", + "ColorPickerField.root.disabled", + "ColorPickerField.root.invalid", + "ColorPickerField.value.invalid", + "ColumnFilterMenu.filter-menu.open", + "ColumnFilterMenu.filter-popover.open", + "ColumnFilterMenu.filter-trigger.open", + "ColumnFilterMenu.filter-trigger.pressed", + "ColumnFilterMenu.filter-trigger.selected", + "ComboBox.error.invalid", + "ComboBox.input.disabled", + "ComboBox.input.invalid", + "ComboBox.input.open", + "ComboBox.input.readonly", + "ComboBox.option.disabled", + "ComboBox.option.selected", + "ComboBox.popover.open", + "ComboBox.root.disabled", + "ComboBox.root.invalid", + "ComboBox.root.loading", + "ComboBox.root.readonly", + "ComboBox.trigger.disabled", + "ComboBox.trigger.open", + "CommandDialog.backdrop.busy", + "CommandDialog.backdrop.open", + "CommandDialog.busy-scope.busy", + "CommandDialog.cancel.busy", + "CommandDialog.cancel.disabled", + "CommandDialog.close.busy", + "CommandDialog.close.disabled", + "CommandDialog.confirm.busy", + "CommandDialog.confirm.disabled", + "CommandDialog.content.busy", + "CommandDialog.content.open", + "CommandDialog.positioner.busy", + "CommandDialog.positioner.open", + "CommandDialog.root.busy", + "CommandDialog.root.open", + "CommandStepper.header.busy", + "CommandStepper.header.disabled", + "CommandStepper.header.invalid", + "CommandStepper.header.selected", + "CommandStepper.list.busy", + "CommandStepper.list.invalid", + "CommandStepper.number.busy", + "CommandStepper.number.invalid", + "CommandStepper.number.selected", + "CommandStepper.panel.busy", + "CommandStepper.panel.invalid", + "CommandStepper.panel.selected", + "CommandStepper.panels.busy", + "CommandStepper.panels.invalid", + "CommandStepper.root.busy", + "CommandStepper.root.invalid", + "CommandStepper.separator.busy", + "CommandStepper.separator.invalid", + "CommandStepper.step.busy", + "CommandStepper.step.invalid", + "CommandStepper.step.selected", + "CommandStepper.title.busy", + "CommandStepper.title.invalid", + "CommandStepper.title.selected", + "DataTableCore.cell.selected", + "DataTableCore.header-cell.selected", + "DataTableCore.header-content.selected", + "DataTableCore.root.busy", + "DataTableCore.row.selected", + "DataTableCore.sort.pressed", + "DataTableForObservableQuery.cell.selected", + "DataTableForObservableQuery.header-cell.selected", + "DataTableForObservableQuery.header-content.selected", + "DataTableForObservableQuery.root.busy", + "DataTableForObservableQuery.row.selected", + "DataTableForObservableQuery.sort.pressed", + "DataTableForQuery.cell.selected", + "DataTableForQuery.header-cell.selected", + "DataTableForQuery.header-content.selected", + "DataTableForQuery.root.busy", + "DataTableForQuery.row.selected", + "DataTableForQuery.sort.pressed", + "DatePickerInput.calendar.open", + "DatePickerInput.cell.selected", + "DatePickerInput.dialog.open", + "DatePickerInput.group.disabled", + "DatePickerInput.group.invalid", + "DatePickerInput.group.open", + "DatePickerInput.group.readonly", + "DatePickerInput.group.selected", + "DatePickerInput.input.disabled", + "DatePickerInput.input.invalid", + "DatePickerInput.input.open", + "DatePickerInput.input.readonly", + "DatePickerInput.input.selected", + "DatePickerInput.popover.open", + "DatePickerInput.root.disabled", + "DatePickerInput.root.invalid", + "DatePickerInput.root.open", + "DatePickerInput.root.readonly", + "DatePickerInput.root.selected", + "DatePickerInput.segment.disabled", + "DatePickerInput.segment.invalid", + "DatePickerInput.segment.readonly", + "DatePickerInput.today.disabled", + "DatePickerInput.trigger.disabled", + "DatePickerInput.trigger.invalid", + "DatePickerInput.trigger.open", + "DatePickerInput.trigger.readonly", + "DatePickerInput.trigger.selected", + "Dialog.backdrop.busy", + "Dialog.backdrop.open", + "Dialog.busy-scope.busy", + "Dialog.cancel.busy", + "Dialog.cancel.disabled", + "Dialog.close.busy", + "Dialog.close.disabled", + "Dialog.confirm.busy", + "Dialog.confirm.disabled", + "Dialog.content.busy", + "Dialog.content.open", + "Dialog.positioner.busy", + "Dialog.positioner.open", + "Dialog.root.busy", + "Dialog.root.open", + "Dropdown.clear.disabled", + "Dropdown.filter.disabled", + "Dropdown.filter.invalid", + "Dropdown.filter.open", + "Dropdown.indicator.disabled", + "Dropdown.indicator.invalid", + "Dropdown.indicator.open", + "Dropdown.listbox.open", + "Dropdown.multiple.disabled", + "Dropdown.multiple.invalid", + "Dropdown.multiple.selected", + "Dropdown.option.disabled", + "Dropdown.option.selected", + "Dropdown.popover.open", + "Dropdown.root.disabled", + "Dropdown.root.invalid", + "Dropdown.root.open", + "Dropdown.root.selected", + "Dropdown.trigger.disabled", + "Dropdown.trigger.invalid", + "Dropdown.trigger.open", + "Dropdown.trigger.selected", + "Dropdown.value.disabled", + "Dropdown.value.invalid", + "Dropdown.value.open", + "Dropdown.value.selected", + "DropdownField.clear.disabled", + "DropdownField.filter.disabled", + "DropdownField.filter.invalid", + "DropdownField.filter.open", + "DropdownField.indicator.disabled", + "DropdownField.indicator.invalid", + "DropdownField.indicator.open", + "DropdownField.listbox.open", + "DropdownField.multiple.disabled", + "DropdownField.multiple.invalid", + "DropdownField.multiple.selected", + "DropdownField.option.disabled", + "DropdownField.option.selected", + "DropdownField.popover.open", + "DropdownField.root.disabled", + "DropdownField.root.invalid", + "DropdownField.root.open", + "DropdownField.root.selected", + "DropdownField.trigger.disabled", + "DropdownField.trigger.invalid", + "DropdownField.trigger.open", + "DropdownField.trigger.selected", + "DropdownField.value.disabled", + "DropdownField.value.invalid", + "DropdownField.value.open", + "DropdownField.value.selected", + "EventsView.event.selected", + "EventsView.marker.selected", + "IconButton.root.disabled", + "IconButton.root.loading", + "InputTextField.input.disabled", + "InputTextField.input.invalid", + "InputTextField.input.readonly", + "MultiSelectField.clear.disabled", + "MultiSelectField.filter.disabled", + "MultiSelectField.filter.invalid", + "MultiSelectField.filter.open", + "MultiSelectField.indicator.disabled", + "MultiSelectField.indicator.invalid", + "MultiSelectField.indicator.open", + "MultiSelectField.listbox.open", + "MultiSelectField.multiple.disabled", + "MultiSelectField.multiple.invalid", + "MultiSelectField.multiple.selected", + "MultiSelectField.option.disabled", + "MultiSelectField.option.selected", + "MultiSelectField.popover.open", + "MultiSelectField.root.disabled", + "MultiSelectField.root.invalid", + "MultiSelectField.root.open", + "MultiSelectField.root.selected", + "MultiSelectField.trigger.disabled", + "MultiSelectField.trigger.invalid", + "MultiSelectField.trigger.open", + "MultiSelectField.trigger.selected", + "MultiSelectField.value.disabled", + "MultiSelectField.value.invalid", + "MultiSelectField.value.open", + "MultiSelectField.value.selected", + "NumberField.input.disabled", + "NumberField.input.invalid", + "NumberField.input.readonly", + "NumberField.root.disabled", + "NumberField.root.invalid", + "NumberField.root.readonly", + "NumberInput.error.invalid", + "NumberInput.group.disabled", + "NumberInput.group.invalid", + "NumberInput.group.readonly", + "NumberInput.input.disabled", + "NumberInput.input.focused", + "NumberInput.input.invalid", + "NumberInput.input.readonly", + "NumberInput.prefix.disabled", + "NumberInput.prefix.invalid", + "NumberInput.prefix.readonly", + "NumberInput.root.disabled", + "NumberInput.root.invalid", + "NumberInput.root.readonly", + "NumberInput.step.disabled", + "NumberInput.step.invalid", + "NumberInput.step.readonly", + "NumberInput.suffix.disabled", + "NumberInput.suffix.invalid", + "NumberInput.suffix.readonly", + "NumberInputField.error.invalid", + "NumberInputField.group.disabled", + "NumberInputField.group.invalid", + "NumberInputField.group.readonly", + "NumberInputField.input.disabled", + "NumberInputField.input.focused", + "NumberInputField.input.invalid", + "NumberInputField.input.readonly", + "NumberInputField.prefix.disabled", + "NumberInputField.prefix.invalid", + "NumberInputField.prefix.readonly", + "NumberInputField.root.disabled", + "NumberInputField.root.invalid", + "NumberInputField.root.readonly", + "NumberInputField.step.disabled", + "NumberInputField.step.invalid", + "NumberInputField.step.readonly", + "NumberInputField.suffix.disabled", + "NumberInputField.suffix.invalid", + "NumberInputField.suffix.readonly", + "PasswordField.input.disabled", + "PasswordField.input.invalid", + "PasswordField.input.readonly", + "PasswordField.root.disabled", + "PasswordField.root.invalid", + "PasswordField.root.readonly", + "PasswordField.toggle.disabled", + "ProgressBar.root.busy", + "ProgressBar.root.loading", + "ProgressSpinner.root.busy", + "ProgressSpinner.root.loading", + "Radio.box.disabled", + "Radio.box.invalid", + "Radio.box.readonly", + "Radio.box.selected", + "Radio.indicator.disabled", + "Radio.indicator.invalid", + "Radio.indicator.readonly", + "Radio.indicator.selected", + "Radio.input.disabled", + "Radio.input.invalid", + "Radio.input.readonly", + "Radio.input.selected", + "Radio.label.disabled", + "Radio.label.invalid", + "Radio.label.readonly", + "Radio.label.selected", + "Radio.root.disabled", + "Radio.root.invalid", + "Radio.root.readonly", + "Radio.root.selected", + "RadioButtonField.box.disabled", + "RadioButtonField.box.invalid", + "RadioButtonField.box.selected", + "RadioButtonField.indicator.disabled", + "RadioButtonField.indicator.invalid", + "RadioButtonField.indicator.selected", + "RadioButtonField.input.disabled", + "RadioButtonField.input.invalid", + "RadioButtonField.input.selected", + "RadioButtonField.root.disabled", + "RadioButtonField.root.invalid", + "RadioButtonField.root.selected", + "RadioGroupField.box.disabled", + "RadioGroupField.box.invalid", + "RadioGroupField.box.selected", + "RadioGroupField.indicator.disabled", + "RadioGroupField.indicator.invalid", + "RadioGroupField.indicator.selected", + "RadioGroupField.input.disabled", + "RadioGroupField.input.invalid", + "RadioGroupField.input.selected", + "RadioGroupField.option.disabled", + "RadioGroupField.option.invalid", + "RadioGroupField.option.selected", + "RadioGroupField.root.disabled", + "RadioGroupField.root.invalid", + "RatingField.input.disabled", + "RatingField.input.invalid", + "RatingField.input.selected", + "RatingField.option.disabled", + "RatingField.option.invalid", + "RatingField.option.selected", + "RatingField.root.disabled", + "RatingField.root.invalid", + "RatingField.star.disabled", + "RatingField.star.invalid", + "RatingField.star.selected", + "SliderField.input.disabled", + "SliderField.input.invalid", + "SliderField.root.disabled", + "SliderField.root.invalid", + "SliderField.value.invalid", + "Switch.control.disabled", + "Switch.control.invalid", + "Switch.control.readonly", + "Switch.control.selected", + "Switch.handle.disabled", + "Switch.handle.invalid", + "Switch.handle.readonly", + "Switch.handle.selected", + "Switch.input.disabled", + "Switch.input.invalid", + "Switch.input.readonly", + "Switch.input.selected", + "Switch.label.disabled", + "Switch.label.invalid", + "Switch.label.readonly", + "Switch.label.selected", + "Switch.root.disabled", + "Switch.root.invalid", + "Switch.root.readonly", + "Switch.root.selected", + "Tabs.panel.selected", + "Tabs.root.disabled", + "Tabs.tab.disabled", + "Tabs.tab.selected", + "TagGroup.input.invalid", + "TagGroup.root.disabled", + "TagGroup.root.invalid", + "TagGroup.tag.disabled", + "TextArea.root.disabled", + "TextArea.root.invalid", + "TextArea.root.readonly", + "TextAreaField.textarea.disabled", + "TextAreaField.textarea.invalid", + "TextAreaField.textarea.readonly", + "TextInput.root.disabled", + "TextInput.root.invalid", + "TextInput.root.readonly", + "Toaster.content.busy", + "Toaster.content.loading", + "Toaster.toast.busy", + "Toaster.toast.loading", + "ToggleGroup.option.disabled", + "ToggleGroup.option.invalid", + "ToggleGroup.option.selected", + "ToggleGroup.root.disabled", + "ToggleGroup.root.invalid", + "ToggleSwitchField.control.disabled", + "ToggleSwitchField.control.invalid", + "ToggleSwitchField.control.selected", + "ToggleSwitchField.handle.disabled", + "ToggleSwitchField.handle.invalid", + "ToggleSwitchField.handle.selected", + "ToggleSwitchField.input.disabled", + "ToggleSwitchField.input.invalid", + "ToggleSwitchField.input.selected", + "ToggleSwitchField.root.disabled", + "ToggleSwitchField.root.invalid", + "ToggleSwitchField.root.selected", + "ToolbarButton.button.selected", + "ToolbarFanOutItem.fanout-panel.open", + "ToolbarFanOutItem.fanout-root.open", + "ToolbarFanOutItem.fanout-trigger.open", + "ToolbarFolder.toolbar-folder-panel.open", + "ToolbarFolder.toolbar-folder-trigger.open", + "ToolbarFolder.toolbar-folder.open", + "ToolbarSection.toolbar-context.selected", + "Tooltip.popup.open" + ], + "ptDeclarations": [ + "Chat/ChatSidebar.tsx#ChatSidebarProps", + "CommandDialog/CommandStepper.tsx#StepperCustomizationProps", + "CommandForm/fields/CalendarField.tsx#CalendarFieldComponentProps", + "CommandForm/fields/CheckboxField.tsx#CheckboxFieldComponentProps", + "CommandForm/fields/ChipsField.tsx#ChipsFieldComponentProps", + "CommandForm/fields/ColorPickerField.tsx#ColorPickerFieldComponentProps", + "CommandForm/fields/DropdownField.tsx#DropdownFieldComponentProps", + "CommandForm/fields/InputTextField.tsx#InputTextComponentProps", + "CommandForm/fields/MultiSelectField.tsx#MultiSelectFieldComponentProps", + "CommandForm/fields/NumberField.tsx#NumberFieldComponentProps", + "CommandForm/fields/PasswordField.tsx#PasswordFieldComponentProps", + "CommandForm/fields/RadioButtonField.tsx#RadioButtonFieldComponentProps", + "CommandForm/fields/RadioGroupField.tsx#RadioGroupFieldComponentProps", + "CommandForm/fields/RatingField.tsx#RatingFieldComponentProps", + "CommandForm/fields/SliderField.tsx#SliderFieldComponentProps", + "CommandForm/fields/TextAreaField.tsx#TextAreaFieldComponentProps", + "CommandForm/fields/ToggleSwitchField.tsx#ToggleSwitchFieldComponentProps", + "Common/ActionMenubar.tsx#ActionMenubarProps", + "Common/Breadcrumbs.tsx#BreadcrumbsProps", + "Common/Button.tsx#ButtonProps", + "Common/Checkbox.tsx#CheckboxProps", + "Common/ComboBox.tsx#ComboBoxProps", + "Common/DatePickerInput.tsx#DatePickerInputProps", + "Common/IconButton.tsx#IconButtonProps", + "Common/NumberInput.tsx#NumberInputProps", + "Common/Radio.tsx#RadioProps", + "Common/Surface.tsx#SurfaceProps", + "Common/Switch.tsx#SwitchProps", + "Common/Tabs.tsx#TabsProps", + "Common/TagGroup.tsx#TagGroupProps", + "Common/TextArea.tsx#TextAreaProps", + "Common/TextInput.tsx#TextInputProps", + "Common/ToggleGroup.tsx#ToggleGroupProps", + "DataTables/ColumnFilterMenu.tsx#ColumnFilterMenuProps", + "DataTables/DataTableCore.tsx#DataTableCoreProps", + "DataTables/DataTableForObservableQuery.tsx#DataTableForObservableQueryProps", + "DataTables/DataTableForQuery.tsx#DataTableForQueryProps", + "DataTables/TablePaginator.tsx#TablePaginatorProps", + "Dialogs/Dialog.tsx#DialogProps", + "Dropdown/Dropdown.tsx#DropdownProps", + "Notifications/Toaster.tsx#ToastFrameProps", + "Notifications/Toaster.tsx#ToasterProps", + "TimeMachine/EventsView.tsx#EventsViewProps", + "Toolbar/Toolbar.tsx#ToolbarProps", + "Toolbar/ToolbarButton.tsx#ToolbarButtonProps", + "Toolbar/ToolbarFanOutItem.tsx#ToolbarFanOutItemProps", + "Toolbar/ToolbarFolder.tsx#ToolbarFolderProps", + "Toolbar/ToolbarGroup.tsx#ToolbarGroupProps", + "Toolbar/ToolbarGroup.tsx#anonymous-1", + "Toolbar/ToolbarLayout.tsx#ToolbarLayoutProps", + "Toolbar/ToolbarLayout.tsx#anonymous-1", + "Toolbar/ToolbarSection.tsx#ToolbarSectionProps", + "Toolbar/ToolbarSeparator.tsx#ToolbarSeparatorProps" + ] +} diff --git a/Source/scripts/renderer-slots-inventory.json b/Source/scripts/renderer-slots-inventory.json new file mode 100644 index 00000000..3c9f90bb --- /dev/null +++ b/Source/scripts/renderer-slots-inventory.json @@ -0,0 +1,36 @@ +{ + "atomicSlots": [ + "common.tooltip", + "datatables.paginator", + "dialogs.dialog", + "display.datePicker", + "dropdown.select" + ], + "builtInSlots": [ + "common.button", + "common.checkbox", + "common.iconButton", + "common.progress", + "common.radio", + "common.surface", + "common.switch", + "common.textArea", + "common.textInput", + "common.tooltip", + "datatables.paginator", + "dialogs.dialog", + "display.datePicker", + "dropdown.select" + ], + "presentationSlots": [ + "common.button", + "common.checkbox", + "common.iconButton", + "common.progress", + "common.radio", + "common.surface", + "common.switch", + "common.textArea", + "common.textInput" + ] +} diff --git a/Source/scripts/verify-exports.mjs b/Source/scripts/verify-exports.mjs index 07dc8ae7..f5764eba 100644 --- a/Source/scripts/verify-exports.mjs +++ b/Source/scripts/verify-exports.mjs @@ -33,6 +33,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { checkExportInventory } from './lib/package-inventories.mjs'; import { assertExpectedCascadeLayerOrder, cascadeLayerEstablishmentOrder, @@ -124,6 +125,7 @@ const readPackageManifest = async () => { const pkg = await readPackageManifest(); const exportsMap = pkg.exports ?? {}; const subpaths = Object.keys(exportsMap); +checkExportInventory(exportsMap, process.argv.includes('--update')); if (subpaths.length === 0) { console.error('No `exports` map found in Source/package.json - nothing to verify.'); diff --git a/Source/scripts/verify-package-graph-report.mjs b/Source/scripts/verify-package-graph-report.mjs index 7afdbf5a..26ba81ce 100644 --- a/Source/scripts/verify-package-graph-report.mjs +++ b/Source/scripts/verify-package-graph-report.mjs @@ -14,7 +14,7 @@ const reportPath = path.join(temporary, 'report.json'); try { const result = spawnSync( process.execPath, - ['scripts/verify-package-graph.mjs', '--report', reportPath], + ['scripts/verify-package-graph.mjs', '--report', reportPath, ...(process.argv.includes('--update') ? ['--update'] : [])], { cwd: packageDirectory, encoding: 'utf8' }, ); process.stdout.write(result.stdout); @@ -41,16 +41,16 @@ try { 'Package graph report contains the wrong lean Core-slot subpaths.', ); } - if (!Array.isArray(report.kernelBoundary) || report.kernelBoundary.length !== 34) { - throw new Error( - 'Package graph report must contain all 34 kernel boundary entries.', - ); + const expectedKernelModules = report.kernelSourcePaths; + const reportedKernelModules = report.kernelBoundary?.map(entry => entry.sourcePath); + if (!Array.isArray(reportedKernelModules) || JSON.stringify(reportedKernelModules) !== JSON.stringify(expectedKernelModules)) { + throw new Error('Package graph report kernel boundary entries differ from the declared kernel modules.'); } if (!Array.isArray(report.violations) || report.violations.length !== 0) { throw new Error('Package graph report contains module-boundary violations.'); } - console.log('Package graph report metadata and all 34 kernel entries verified.'); + console.log(`Package graph report metadata and all ${reportedKernelModules.length} kernel entries verified.`); } finally { rmSync(temporary, { recursive: true, force: true }); } diff --git a/Source/scripts/verify-package-graph.mjs b/Source/scripts/verify-package-graph.mjs index f793b4ed..95218250 100644 --- a/Source/scripts/verify-package-graph.mjs +++ b/Source/scripts/verify-package-graph.mjs @@ -51,6 +51,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import ts from 'typescript'; import { kernelEmittedPath, kernelSourcePaths } from '../../ESLint/lib/kernelBoundary.js'; +import { checkExportInventory, checkKernelInventory } from './lib/package-inventories.mjs'; import { analyzeKernelBoundary, browserRuntimeReferences, @@ -86,6 +87,8 @@ function readFileSyncOrFail(file) { } const pkg = readJson(path.join(packageDir, 'package.json')); +checkKernelInventory(process.argv.includes('--update')); +checkExportInventory(pkg.exports, process.argv.includes('--update')); const esmRoot = path.join(packageDir, path.dirname(pkg.module ?? 'dist/esm/index.js')); if (!existsSync(esmRoot)) { diff --git a/Source/scripts/verify-parts-manifest.mjs b/Source/scripts/verify-parts-manifest.mjs index b959f0db..825a8322 100644 --- a/Source/scripts/verify-parts-manifest.mjs +++ b/Source/scripts/verify-parts-manifest.mjs @@ -5,6 +5,7 @@ import { readFileSync, readdirSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import ts from 'typescript'; +import { checkInventory } from '../../scripts/lib/evidence-inventory.mjs'; import { canonicalPartStateNames, dynamicPartExpressions, @@ -21,6 +22,7 @@ import { const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const generatedPath = path.join(sourceRoot, 'types', 'parts.ts'); +const snapshotPath = path.join(sourceRoot, 'scripts/parts-inventory.json'); const expectedCanonicalPartStateNames = [ 'disabled', 'loading', @@ -580,7 +582,7 @@ function verifyPublicPtContracts() { } const discoveredFiles = new Set(); - let declarationCount = 0; + const declarations = []; for (const sourceFile of program.getSourceFiles()) { if ( !sourceFile.fileName.startsWith(sourceRoot) || @@ -598,7 +600,8 @@ function verifyPublicPtContracts() { ((ts.isIdentifier(node.name) && node.name.text === 'pt') || (ts.isStringLiteral(node.name) && node.name.text === 'pt')) ) { - declarationCount++; + const ownerName = node.parent.name?.getText(sourceFile) ?? `anonymous-${declarations.filter(identity => identity.startsWith(`${relative}#anonymous-`)).length + 1}`; + declarations.push(`${relative}#${ownerName}`); discoveredFiles.add(relative); const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1; @@ -638,12 +641,7 @@ function verifyPublicPtContracts() { `Public pt file inventory differs: source=[${actualFiles.join(', ')}], manifest=[${expectedFiles.join(', ')}].`, ); } - if (actualFiles.length !== 50 || declarationCount !== 53) { - problems.push( - `Expected the authoritative 50 pt files / 53 declarations, found ${actualFiles.length} files / ${declarationCount} declarations.`, - ); - } - return problems; + return { problems, declarations }; } function verifyTestSelectors(allParts) { @@ -831,13 +829,22 @@ export function verifyPartsManifest() { partEmissions, stateEmissions, ); - return [ + const publicPtContracts = verifyPublicPtContracts(); + const problems = [ ...emissionProblems, ...definitionProblems, - ...verifyPublicPtContracts(), + ...publicPtContracts.problems, ...verifyTestSelectors(allParts), ...verifyGeneratedFile(), ]; + if (problems.length === 0) { + checkInventory(snapshotPath, { + componentParts: Object.keys(partDefinitions).flatMap(component => resolvedParts(component).map(part => `${component}.${part}`)), + componentStates: Object.keys(partDefinitions).flatMap(component => Object.entries(resolvedPartStates(component)).flatMap(([part, states]) => states.map(state => `${component}.${part}.${state}`))), + ptDeclarations: publicPtContracts.declarations, + }, 'yarn generate-inventories', process.argv.includes('--update')); + } + return problems; } function main() { @@ -874,7 +881,7 @@ function main() { 0, ); console.log( - `Parts manifest verified: ${componentCount} components, ${partCount} component parts, ${stateCount} component/part states, 50 pt files, and 53 pt declarations.`, + `Parts manifest verified: ${componentCount} components, ${partCount} component parts, and ${stateCount} component/part states (public pt declarations checked against scripts/parts-inventory.json).`, ); } diff --git a/Source/scripts/verify-renderer-contracts.mjs b/Source/scripts/verify-renderer-contracts.mjs index a6d5ef9d..c1710a1e 100644 --- a/Source/scripts/verify-renderer-contracts.mjs +++ b/Source/scripts/verify-renderer-contracts.mjs @@ -6,6 +6,7 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { getTypeScriptCompiler } from '../../scripts/lib/typescript-compiler.mjs'; +import { checkInventory } from '../../scripts/lib/evidence-inventory.mjs'; const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const schemaPath = path.join(packageDir, 'schemas/ui-adapter.schema.json'); @@ -200,15 +201,20 @@ if (builtInMetadataProblems.length > 0) { process.exit(1); } if ( - packageJson.cratis.slots.length !== 14 || - Object.keys(packageJson.cratis.modes).length !== 14 || + packageJson.cratis.slots.length === 0 || + JSON.stringify([...packageJson.cratis.slots].sort()) !== JSON.stringify(Object.keys(packageJson.cratis.modes).sort()) || !packageJson.cratis.entry.includes('/renderer/builtin/') ) { console.error( - 'Built-in renderer metadata must describe exactly fourteen slots at the opt-in subpath.', + 'Built-in renderer metadata must describe its complete slot/mode set at the opt-in subpath.', ); process.exit(1); } +checkInventory(path.join(packageDir, 'scripts/renderer-slots-inventory.json'), { + builtInSlots: packageJson.cratis.slots, + atomicSlots: Object.entries(packageJson.cratis.modes).filter(([, mode]) => mode === 'atomic').map(([slot]) => slot), + presentationSlots: Object.entries(packageJson.cratis.modes).filter(([, mode]) => mode === 'presentation').map(([slot]) => slot), +}, 'yarn generate-inventories', process.argv.includes('--update')); console.log( 'Renderer metadata schema accepts the built-in/static fixtures and rejects invalid manifests.', ); @@ -302,8 +308,9 @@ if (Object.keys(builtin).join(',') !== 'unstable_cratisBuiltIn') { throw new Error('The built-in subpath must export only unstable_cratisBuiltIn.'); } const library = builtin.unstable_cratisBuiltIn; -if (!Object.isFrozen(library) || !Object.isFrozen(library.slots) || Object.keys(library.slots).length !== 14) { - throw new Error('The built-in manifest and complete fourteen-slot table must be frozen.'); +if (!Object.isFrozen(library) || !Object.isFrozen(library.slots) || + JSON.stringify(Object.keys(library.slots).sort()) !== ${JSON.stringify(JSON.stringify([...packageJson.cratis.slots].sort()))}) { + throw new Error('The built-in manifest and complete metadata-declared slot table must be frozen.'); } `; const ssrImport = spawnSync( diff --git a/Storybook/README.md b/Storybook/README.md index 93a7a50b..999fb30a 100644 --- a/Storybook/README.md +++ b/Storybook/README.md @@ -27,6 +27,8 @@ yarn workspace @cratis/components.storybook verify-indexes yarn playwright install chromium yarn workspace @cratis/components.storybook test-storybook yarn workspace @cratis/components.storybook dev +# From a clean checkout or after adding a story, renderer, slot, part, kernel module, or export: +yarn generate-inventories ``` -`test-storybook` runs every indexed story with axe in both maintained appearance modes on the built-in preview. The other discovered previews run only the renderer-distinguishing stories: those whose component (or, without a declared component, the story module) transitively imports a production module that calls `unstable_useSlot(...)`, derived from source by `scripts/lib/renderer-matrix-scope.mjs`. The remaining stories render identical DOM on every renderer, so they run once, on the built-in preview. No story is excluded; the run ends by printing the story counts, the matrix scope, and the total number of story/appearance/axe cases. Issue #217 tracks replacing hardcoded evidence counts with generated reviewable inventories. The composed renderer control preserves a stable story id when available, but switching previews remounts the iframe and loses component-local state. +`test-storybook` runs every indexed story with axe in both maintained appearance modes on the built-in preview. The other discovered previews run only the renderer-distinguishing stories: those whose component (or, without a declared component, the story module) transitively imports a production module that calls `unstable_useSlot(...)`, derived from source by `scripts/lib/renderer-matrix-scope.mjs`. The remaining stories render identical DOM on every renderer, so they run once, on the built-in preview. No story is excluded; the run ends by printing the story counts, the matrix scope, and the total number of story/appearance/axe cases. The verifiers compare the built index and source identities against committed snapshots in `Storybook/scripts/` and `Source/scripts/`. After an intentional change, run `yarn generate-inventories` from the root; it builds the published Source artifact, adapters, and Storybook previews, refreshes every snapshot, and fails if any prerequisite verification fails. Review the identity diff before committing. CI runs the Storybook index check after the build and the Source parts, renderer, kernel, and export checks in its existing jobs. The composed renderer control preserves a stable story id when available, but switching previews remounts the iframe and loses component-local state. diff --git a/Storybook/scripts/renderer-inventory.json b/Storybook/scripts/renderer-inventory.json new file mode 100644 index 00000000..a29a084d --- /dev/null +++ b/Storybook/scripts/renderer-inventory.json @@ -0,0 +1,9 @@ +{ + "privateRenderers": [], + "publicRenderers": [ + "cratis-built-in", + "cratis-mui", + "cratis-primereact", + "cratis-primereact10" + ] +} diff --git a/Storybook/scripts/storybook-inventory.json b/Storybook/scripts/storybook-inventory.json new file mode 100644 index 00000000..226a3f23 --- /dev/null +++ b/Storybook/scripts/storybook-inventory.json @@ -0,0 +1,684 @@ +{ + "autodocs": [ + "canvas-canvas--docs", + "canvas-canvascontrols--docs", + "canvas-canvasminimap--docs", + "canvas-canvasoverlay--docs", + "canvas-note--docs", + "canvas-region--docs", + "chat-chatconversation--docs", + "chat-chatmessagebody--docs", + "chat-chatsidebar--docs", + "chat-chatsidebarforobservablequeries--docs", + "chat-chattopiclist--docs", + "chat-kit-chat--docs", + "chat-kit-chatbubble--docs", + "chat-kit-chatkit--docs", + "commanddialog-commanddialog--docs", + "commanddialog-commandstepper--docs", + "commanddialog-steppercommanddialog--docs", + "commandform-auto-command-form--docs", + "commandform-fields--docs", + "commandform-newfields--docs", + "common-actionmenubar--docs", + "common-breadcrumbs--docs", + "common-button--docs", + "common-checkbox--docs", + "common-combobox--docs", + "common-cratiscomponentsprovider--docs", + "common-datepickerinput--docs", + "common-errorboundary--docs", + "common-formelement--docs", + "common-iconbutton--docs", + "common-icondisplay--docs", + "common-numberinput--docs", + "common-page--docs", + "common-radio--docs", + "common-surface--docs", + "common-switch--docs", + "common-tabs--docs", + "common-taggroup--docs", + "common-textarea--docs", + "common-textinput--docs", + "common-togglegroup--docs", + "common-tooltip--docs", + "datapage-datapage--docs", + "datatables-columnfiltermenu--docs", + "datatables-datatablecore--docs", + "datatables-datatableforobservablequery--docs", + "datatables-datatableforquery--docs", + "datatables-tablepaginator--docs", + "dialogs-busyindicatordialog--docs", + "dialogs-confirmationdialog--docs", + "dialogs-dialog--docs", + "display-avatar--docs", + "display-badge--docs", + "display-chip--docs", + "display-message--docs", + "display-overview--docs", + "display-progressbar--docs", + "display-progressspinner--docs", + "display-skeleton--docs", + "display-tag--docs", + "dropdown-dropdown--docs", + "filter-checkboxlistfilter--docs", + "filter-filterpanel--docs", + "filter-rangehistogramfilter--docs", + "notifications-toaster--docs", + "objectcontenteditor-objectcontenteditor--docs", + "objectnavigationalbar-objectnavigationalbar--docs", + "pivotviewer-pivotviewer--docs", + "schemaeditor-schemaeditor--docs", + "timemachine-eventsview--docs", + "timemachine-properties--docs", + "timemachine-readmodelview--docs", + "timemachine-timemachine--docs", + "toolbar-toolbar--docs" + ], + "matrixStories": [ + "chat-kit-chat--basic-thread", + "chat-kit-chat--with-failed-turn", + "chat-kit-chatkit--all-emojis", + "chat-kit-chatkit--anchored", + "chat-kit-chatkit--avatar", + "chat-kit-chatkit--composer", + "chat-kit-chatkit--failed-turn", + "chat-kit-chatkit--mentions", + "chat-kit-chatkit--message-bubble", + "chat-kit-chatkit--quick-reaction-picker", + "chat-kit-chatkit--reactions", + "chat-kit-chatkit--typing", + "commanddialog-commanddialog--before-execute", + "commanddialog-commanddialog--default", + "commanddialog-commanddialog--destructive-command-focuses-dismiss", + "commanddialog-commanddialog--mixed-children", + "commanddialog-commanddialog--multi-column-layout", + "commanddialog-commanddialog--validate-all-fields-on-change", + "commanddialog-commanddialog--validate-on-init", + "commanddialog-commanddialog--validation-on-blur", + "commanddialog-commanddialog--validation-on-change", + "commanddialog-commanddialog--with-busy-state", + "commanddialog-commanddialog--with-custom-validation", + "commanddialog-commanddialog--with-icons", + "commanddialog-commanddialog--with-initial-values", + "commanddialog-commanddialog--with-response-type-and-callbacks", + "commanddialog-commanddialog--with-server-validation", + "commanddialog-commandstepper--conditional-steps", + "commanddialog-commandstepper--default", + "commanddialog-commandstepper--in-dialog-frame", + "commanddialog-commandstepper--in-dialog-frame-with-centered-header", + "commanddialog-commandstepper--vertical", + "commanddialog-commandstepper--with-validation-indicators", + "commanddialog-steppercommanddialog--conditional-steps", + "commanddialog-steppercommanddialog--default", + "commanddialog-steppercommanddialog--three-steps", + "commanddialog-steppercommanddialog--with-busy-state", + "commanddialog-steppercommanddialog--with-footer-cancel", + "commanddialog-steppercommanddialog--with-response-type-and-callbacks", + "commanddialog-steppercommanddialog--with-validation-indicators", + "commandform-auto-command-form--default", + "commandform-fields--all-fields", + "commandform-fields--calendar-field-example", + "commandform-fields--checkbox-field-example", + "commandform-fields--chips-field-example", + "commandform-fields--color-picker-field-example", + "commandform-fields--dropdown-field-example", + "commandform-fields--input-text-field-example", + "commandform-fields--multi-select-field-example", + "commandform-fields--native-and-locale-aware-number-fields", + "commandform-fields--number-field-example", + "commandform-fields--radio-button-field-example", + "commandform-fields--radio-group-field-example", + "commandform-fields--slider-field-example", + "commandform-fields--text-area-field-example", + "common-actionmenubar--custom-parts", + "common-actionmenubar--default", + "common-actionmenubar--disabled-action", + "common-button--ghost", + "common-button--icon-only", + "common-button--label", + "common-button--label-and-icon", + "common-button--link", + "common-button--loading", + "common-button--outline", + "common-button--pill", + "common-button--visual-matrix", + "common-button--with-tooltip", + "common-checkbox--controlled", + "common-checkbox--default", + "common-checkbox--disabled", + "common-checkbox--invalid", + "common-checkbox--read-only", + "common-checkbox--selected", + "common-datepickerinput--narrow-show-time-12-hour", + "common-datepickerinput--open-calendar", + "common-datepickerinput--segment-hit-targets", + "common-datepickerinput--state-matrix", + "common-iconbutton--critical", + "common-iconbutton--default", + "common-iconbutton--disabled", + "common-iconbutton--loading", + "common-radio--controlled", + "common-radio--default", + "common-radio--disabled", + "common-radio--invalid", + "common-radio--read-only", + "common-radio--selected", + "common-surface--article", + "common-surface--div", + "common-surface--section", + "common-switch--controlled", + "common-switch--default", + "common-switch--disabled", + "common-switch--invalid", + "common-switch--read-only", + "common-switch--selected", + "common-textarea--controlled", + "common-textarea--default", + "common-textarea--disabled", + "common-textarea--invalid", + "common-textarea--read-only", + "common-textinput--controlled", + "common-textinput--default", + "common-textinput--disabled", + "common-textinput--invalid", + "common-textinput--read-only", + "common-tooltip--no-content", + "common-tooltip--playground", + "common-tooltip--positions", + "datapage-datapage--default", + "datapage-datapage--multiple-pages", + "datapage-datapage--multiple-pages-with-default-filters", + "datapage-datapage--multiple-pages-with-details", + "datapage-datapage--multiple-pages-without-menu-items", + "datapage-datapage--with-client-filtering", + "datapage-datapage--with-on-refresh", + "datapage-datapage--without-details", + "datatables-columnfiltermenu--boolean", + "datatables-columnfiltermenu--date-value", + "datatables-columnfiltermenu--known-values", + "datatables-columnfiltermenu--localized-and-customized", + "datatables-columnfiltermenu--numeric", + "datatables-columnfiltermenu--text", + "datatables-datatablecore--empty", + "datatables-datatablecore--loaded-array", + "datatables-datatablecore--multiple-selection", + "datatables-datatablecore--query-states", + "datatables-datatableforobservablequery--default", + "datatables-datatableforobservablequery--live-update", + "datatables-datatableforobservablequery--with-selection", + "datatables-datatableforquery--default", + "datatables-datatableforquery--in-bounded-height", + "datatables-datatableforquery--with-selection", + "datatables-tablepaginator--default", + "datatables-tablepaginator--interactive", + "datatables-tablepaginator--localized-and-customized", + "dialogs-busyindicatordialog--default", + "dialogs-confirmationdialog--default", + "dialogs-dialog--custom-buttons", + "dialogs-dialog--destructive-arms-nothing", + "dialogs-dialog--destructive-focuses-dismiss", + "dialogs-dialog--is-busy", + "dialogs-dialog--ok", + "dialogs-dialog--ok-cancel", + "dialogs-dialog--placement-end", + "dialogs-dialog--placement-start", + "dialogs-dialog--with-form", + "dialogs-dialog--with-subtitle-and-close-icon", + "dialogs-dialog--yes-no", + "dialogs-dialog--yes-no-cancel", + "display-overview--overview", + "display-progressbar--clamped-out-of-range", + "display-progressbar--determinate", + "display-progressbar--indeterminate", + "display-progressbar--no-value-label", + "dropdown-dropdown--external-labels", + "dropdown-dropdown--filtered-and-open", + "dropdown-dropdown--filtered-keyboard-commit", + "dropdown-dropdown--filtered-reopened-focus-treatment", + "dropdown-dropdown--selected-option-focus-treatment", + "dropdown-dropdown--state-matrix", + "objectcontenteditor-objectcontenteditor--default", + "objectcontenteditor-objectcontenteditor--edit-mode", + "objectcontenteditor-objectcontenteditor--edit-mode-with-validation", + "objectcontenteditor-objectcontenteditor--simple-object", + "objectcontenteditor-objectcontenteditor--with-array", + "objectcontenteditor-objectcontenteditor--with-timestamp", + "objectnavigationalbar-objectnavigationalbar--at-root", + "objectnavigationalbar-objectnavigationalbar--deep-path", + "objectnavigationalbar-objectnavigationalbar--interactive", + "objectnavigationalbar-objectnavigationalbar--single-level", + "schemaeditor-schemaeditor--edit-mode", + "schemaeditor-schemaeditor--empty-schema", + "schemaeditor-schemaeditor--interactive", + "schemaeditor-schemaeditor--localized-labels", + "schemaeditor-schemaeditor--read-only", + "schemaeditor-schemaeditor--simple-schema", + "schemaeditor-schemaeditor--view-mode" + ], + "slotOwningModules": [ + "Source/Common/Button.tsx", + "Source/Common/Checkbox.tsx", + "Source/Common/DatePickerInput.tsx", + "Source/Common/IconButton.tsx", + "Source/Common/Radio.tsx", + "Source/Common/Surface.tsx", + "Source/Common/Switch.tsx", + "Source/Common/TextArea.tsx", + "Source/Common/TextInput.tsx", + "Source/Common/Tooltip.tsx", + "Source/DataTables/TablePaginator.tsx", + "Source/Dialogs/Dialog.tsx", + "Source/Display/ProgressBar.tsx", + "Source/Dropdown/Dropdown.tsx" + ], + "stories": [ + "canvas-canvas--declarative-children", + "canvas-canvas--localized-controls", + "canvas-canvas--with-controls-and-minimap", + "canvas-canvascontrols--localized-controlled-zoom", + "canvas-canvascontrols--playground", + "canvas-canvasminimap--playground", + "canvas-canvasminimap--synchronized-viewport", + "canvas-canvasoverlay--inspector-panel", + "canvas-note--board", + "canvas-region--board", + "chat-chatconversation--interactive", + "chat-chatconversation--playground", + "chat-chatmessagebody--plain-text", + "chat-chatmessagebody--with-mentions", + "chat-chatsidebar--interactive-host", + "chat-chatsidebar--playground", + "chat-chatsidebarforobservablequeries--playground", + "chat-chattopiclist--empty", + "chat-chattopiclist--playground", + "chat-kit-chat--basic-thread", + "chat-kit-chat--with-failed-turn", + "chat-kit-chatbubble--preview", + "chat-kit-chatbubble--selected-and-thinking", + "chat-kit-chatkit--all-emojis", + "chat-kit-chatkit--anchored", + "chat-kit-chatkit--avatar", + "chat-kit-chatkit--composer", + "chat-kit-chatkit--failed-turn", + "chat-kit-chatkit--mentions", + "chat-kit-chatkit--message-bubble", + "chat-kit-chatkit--quick-reaction-picker", + "chat-kit-chatkit--reactions", + "chat-kit-chatkit--typing", + "commanddialog-commanddialog--before-execute", + "commanddialog-commanddialog--default", + "commanddialog-commanddialog--destructive-command-focuses-dismiss", + "commanddialog-commanddialog--mixed-children", + "commanddialog-commanddialog--multi-column-layout", + "commanddialog-commanddialog--validate-all-fields-on-change", + "commanddialog-commanddialog--validate-on-init", + "commanddialog-commanddialog--validation-on-blur", + "commanddialog-commanddialog--validation-on-change", + "commanddialog-commanddialog--with-busy-state", + "commanddialog-commanddialog--with-custom-validation", + "commanddialog-commanddialog--with-icons", + "commanddialog-commanddialog--with-initial-values", + "commanddialog-commanddialog--with-response-type-and-callbacks", + "commanddialog-commanddialog--with-server-validation", + "commanddialog-commandstepper--conditional-steps", + "commanddialog-commandstepper--default", + "commanddialog-commandstepper--in-dialog-frame", + "commanddialog-commandstepper--in-dialog-frame-with-centered-header", + "commanddialog-commandstepper--vertical", + "commanddialog-commandstepper--with-validation-indicators", + "commanddialog-steppercommanddialog--conditional-steps", + "commanddialog-steppercommanddialog--default", + "commanddialog-steppercommanddialog--three-steps", + "commanddialog-steppercommanddialog--with-busy-state", + "commanddialog-steppercommanddialog--with-footer-cancel", + "commanddialog-steppercommanddialog--with-response-type-and-callbacks", + "commanddialog-steppercommanddialog--with-validation-indicators", + "commandform-auto-command-form--default", + "commandform-fields--all-fields", + "commandform-fields--calendar-field-example", + "commandform-fields--checkbox-field-example", + "commandform-fields--chips-field-example", + "commandform-fields--color-picker-field-example", + "commandform-fields--dropdown-field-example", + "commandform-fields--input-text-field-example", + "commandform-fields--multi-select-field-example", + "commandform-fields--native-and-locale-aware-number-fields", + "commandform-fields--number-field-example", + "commandform-fields--radio-button-field-example", + "commandform-fields--radio-group-field-example", + "commandform-fields--slider-field-example", + "commandform-fields--text-area-field-example", + "commandform-newfields--overview", + "common-actionmenubar--custom-parts", + "common-actionmenubar--default", + "common-actionmenubar--disabled-action", + "common-breadcrumbs--custom-separator", + "common-breadcrumbs--default", + "common-breadcrumbs--marks-the-current-page", + "common-breadcrumbs--router-owned", + "common-button--ghost", + "common-button--icon-only", + "common-button--label", + "common-button--label-and-icon", + "common-button--link", + "common-button--loading", + "common-button--outline", + "common-button--pill", + "common-button--visual-matrix", + "common-button--with-tooltip", + "common-checkbox--controlled", + "common-checkbox--default", + "common-checkbox--disabled", + "common-checkbox--invalid", + "common-checkbox--read-only", + "common-checkbox--selected", + "common-combobox--failure", + "common-combobox--loading", + "common-combobox--open-on-focus-stacked", + "common-combobox--playground", + "common-combobox--reopened-with-a-selection", + "common-combobox--states", + "common-combobox--with-action", + "common-cratiscomponentsprovider--localized-messages", + "common-datepickerinput--narrow-show-time-12-hour", + "common-datepickerinput--open-calendar", + "common-datepickerinput--segment-hit-targets", + "common-datepickerinput--state-matrix", + "common-errorboundary--custom-recovery", + "common-errorboundary--default", + "common-errorboundary--with-error", + "common-formelement--default", + "common-formelement--with-email-icon", + "common-formelement--with-search-icon", + "common-iconbutton--critical", + "common-iconbutton--default", + "common-iconbutton--disabled", + "common-iconbutton--loading", + "common-icondisplay--arbitrary-react-node", + "common-icondisplay--react-icon-node", + "common-icondisplay--string-icon", + "common-icondisplay--string-icon-with-extra-class", + "common-icondisplay--string-vs-react-node", + "common-numberinput--adornments-and-steps", + "common-numberinput--fraction-policies", + "common-numberinput--in-context", + "common-numberinput--interactive", + "common-numberinput--locales", + "common-numberinput--playground", + "common-numberinput--product-owned-box", + "common-numberinput--states", + "common-page--default", + "common-page--with-panel", + "common-page--with-title", + "common-page--without-title", + "common-radio--controlled", + "common-radio--default", + "common-radio--disabled", + "common-radio--invalid", + "common-radio--read-only", + "common-radio--selected", + "common-surface--article", + "common-surface--div", + "common-surface--section", + "common-switch--controlled", + "common-switch--default", + "common-switch--disabled", + "common-switch--invalid", + "common-switch--read-only", + "common-switch--selected", + "common-tabs--default", + "common-tabs--disabled-tab", + "common-tabs--keyboard-navigation", + "common-tabs--two-tabs", + "common-taggroup--adds-and-removes", + "common-taggroup--custom-remove-icon", + "common-taggroup--default", + "common-taggroup--disabled", + "common-taggroup--invalid", + "common-taggroup--not-editable", + "common-textarea--controlled", + "common-textarea--default", + "common-textarea--disabled", + "common-textarea--invalid", + "common-textarea--read-only", + "common-textinput--controlled", + "common-textinput--default", + "common-textinput--disabled", + "common-textinput--invalid", + "common-textinput--read-only", + "common-togglegroup--default", + "common-togglegroup--disabled", + "common-togglegroup--invalid", + "common-togglegroup--keyboard-selection", + "common-togglegroup--with-icons", + "common-tooltip--no-content", + "common-tooltip--playground", + "common-tooltip--positions", + "datapage-datapage--default", + "datapage-datapage--multiple-pages", + "datapage-datapage--multiple-pages-with-default-filters", + "datapage-datapage--multiple-pages-with-details", + "datapage-datapage--multiple-pages-without-menu-items", + "datapage-datapage--with-client-filtering", + "datapage-datapage--with-on-refresh", + "datapage-datapage--without-details", + "datatables-columnfiltermenu--boolean", + "datatables-columnfiltermenu--date-value", + "datatables-columnfiltermenu--known-values", + "datatables-columnfiltermenu--localized-and-customized", + "datatables-columnfiltermenu--numeric", + "datatables-columnfiltermenu--text", + "datatables-datatablecore--empty", + "datatables-datatablecore--loaded-array", + "datatables-datatablecore--multiple-selection", + "datatables-datatablecore--query-states", + "datatables-datatableforobservablequery--default", + "datatables-datatableforobservablequery--live-update", + "datatables-datatableforobservablequery--with-selection", + "datatables-datatableforquery--default", + "datatables-datatableforquery--in-bounded-height", + "datatables-datatableforquery--with-selection", + "datatables-tablepaginator--default", + "datatables-tablepaginator--interactive", + "datatables-tablepaginator--localized-and-customized", + "dialogs-busyindicatordialog--default", + "dialogs-confirmationdialog--default", + "dialogs-dialog--custom-buttons", + "dialogs-dialog--destructive-arms-nothing", + "dialogs-dialog--destructive-focuses-dismiss", + "dialogs-dialog--is-busy", + "dialogs-dialog--ok", + "dialogs-dialog--ok-cancel", + "dialogs-dialog--placement-end", + "dialogs-dialog--placement-start", + "dialogs-dialog--with-form", + "dialogs-dialog--with-subtitle-and-close-icon", + "dialogs-dialog--yes-no", + "dialogs-dialog--yes-no-cancel", + "display-avatar--icon-fallback", + "display-avatar--image", + "display-avatar--initials", + "display-avatar--sizes", + "display-badge--circle", + "display-badge--default", + "display-badge--severities", + "display-badge--sizes", + "display-badge--text-value", + "display-chip--default", + "display-chip--removable", + "display-chip--removable-localized", + "display-chip--with-icon", + "display-message--error", + "display-message--info", + "display-message--no-icon", + "display-message--success", + "display-message--warn", + "display-message--with-children", + "display-overview--overview", + "display-progressbar--clamped-out-of-range", + "display-progressbar--determinate", + "display-progressbar--indeterminate", + "display-progressbar--no-value-label", + "display-progressspinner--custom-label", + "display-progressspinner--default", + "display-progressspinner--large", + "display-skeleton--card-placeholder", + "display-skeleton--circle", + "display-skeleton--custom-border-radius", + "display-skeleton--line", + "display-tag--default", + "display-tag--rounded", + "display-tag--severities", + "display-tag--with-icon", + "dropdown-dropdown--external-labels", + "dropdown-dropdown--filtered-and-open", + "dropdown-dropdown--filtered-keyboard-commit", + "dropdown-dropdown--filtered-reopened-focus-treatment", + "dropdown-dropdown--selected-option-focus-treatment", + "dropdown-dropdown--state-matrix", + "filter-checkboxlistfilter--fits-without-search", + "filter-checkboxlistfilter--overflows-with-sticky-search", + "filter-checkboxlistfilter--search-forced-off", + "filter-checkboxlistfilter--search-forced-on", + "filter-filterpanel--auto-search-when-overflowing", + "filter-filterpanel--custom-editor", + "filter-filterpanel--mixed-filters", + "filter-filterpanel--multi-select-filter", + "filter-filterpanel--no-search-when-it-fits", + "filter-filterpanel--numeric-range-filter", + "filter-filterpanel--single-select-filter", + "filter-rangehistogramfilter--currency", + "filter-rangehistogramfilter--interactive", + "notifications-toaster--playground", + "objectcontenteditor-objectcontenteditor--default", + "objectcontenteditor-objectcontenteditor--edit-mode", + "objectcontenteditor-objectcontenteditor--edit-mode-with-validation", + "objectcontenteditor-objectcontenteditor--simple-object", + "objectcontenteditor-objectcontenteditor--with-array", + "objectcontenteditor-objectcontenteditor--with-timestamp", + "objectnavigationalbar-objectnavigationalbar--at-root", + "objectnavigationalbar-objectnavigationalbar--deep-path", + "objectnavigationalbar-objectnavigationalbar--interactive", + "objectnavigationalbar-objectnavigationalbar--single-level", + "pivotviewer-pivotviewer--default", + "pivotviewer-pivotviewer--large-dataset", + "schemaeditor-schemaeditor--edit-mode", + "schemaeditor-schemaeditor--empty-schema", + "schemaeditor-schemaeditor--interactive", + "schemaeditor-schemaeditor--localized-labels", + "schemaeditor-schemaeditor--read-only", + "schemaeditor-schemaeditor--simple-schema", + "schemaeditor-schemaeditor--view-mode", + "timemachine-eventsview--default", + "timemachine-eventsview--empty-events", + "timemachine-eventsview--single-event", + "timemachine-properties--default", + "timemachine-properties--left-aligned", + "timemachine-properties--right-aligned", + "timemachine-properties--with-null-values", + "timemachine-readmodelview--default", + "timemachine-readmodelview--single-version", + "timemachine-timemachine--default", + "timemachine-timemachine--high-scroll-sensitivity", + "timemachine-timemachine--localized-labels", + "timemachine-timemachine--start-at-latest-version", + "toolbar-toolbar--default", + "toolbar-toolbar--drag-and-drop", + "toolbar-toolbar--folder-grid-vs-list", + "toolbar-toolbar--layout-for-editor-modules", + "toolbar-toolbar--layout-with-global-and-editor-regions", + "toolbar-toolbar--layout-with-smooth-editor-transitions", + "toolbar-toolbar--with-active-button", + "toolbar-toolbar--with-contexts", + "toolbar-toolbar--with-fan-out", + "toolbar-toolbar--with-fan-out-down", + "toolbar-toolbar--with-folder-four-buttons", + "toolbar-toolbar--with-folder-list-mode", + "toolbar-toolbar--with-folder-one-button", + "toolbar-toolbar--with-folder-twenty-buttons", + "toolbar-toolbar--with-groups", + "toolbar-toolbar--with-multiple-slot-contributors", + "toolbar-toolbar--with-react-node-icons", + "toolbar-toolbar--with-separators", + "toolbar-toolbar--with-slot-in-context", + "toolbar-toolbar--with-slot-in-group", + "toolbar-toolbar--zoom-bar" + ], + "storyModules": [ + "Source/Canvas/Canvas.stories.tsx", + "Source/Canvas/CanvasControls.stories.tsx", + "Source/Canvas/CanvasMinimap.stories.tsx", + "Source/Canvas/CanvasOverlay.stories.tsx", + "Source/Canvas/shapes/Note/Note.stories.tsx", + "Source/Canvas/shapes/Region/Region.stories.tsx", + "Source/Chat/ChatConversation.stories.tsx", + "Source/Chat/ChatMessageBody.stories.tsx", + "Source/Chat/ChatSidebar.stories.tsx", + "Source/Chat/ChatSidebarForObservableQueries.stories.tsx", + "Source/Chat/ChatTopicList.stories.tsx", + "Source/Chat/Kit/Chat.stories.tsx", + "Source/Chat/Kit/ChatBubble.stories.tsx", + "Source/Chat/Kit/ChatKit.stories.tsx", + "Source/CommandDialog/CommandDialog.stories.tsx", + "Source/CommandDialog/CommandStepper.stories.tsx", + "Source/CommandDialog/StepperCommandDialog.stories.tsx", + "Source/CommandForm/AutoCommandForm.stories.tsx", + "Source/CommandForm/fields/Fields.stories.tsx", + "Source/CommandForm/fields/NewFields.stories.tsx", + "Source/Common/ActionMenubar.stories.tsx", + "Source/Common/Breadcrumbs.stories.tsx", + "Source/Common/Button.stories.tsx", + "Source/Common/Checkbox.stories.tsx", + "Source/Common/ComboBox.stories.tsx", + "Source/Common/CratisComponentsProvider.stories.tsx", + "Source/Common/DatePickerInput.stories.tsx", + "Source/Common/ErrorBoundary.stories.tsx", + "Source/Common/FormElement.stories.tsx", + "Source/Common/Icon.stories.tsx", + "Source/Common/IconButton.stories.tsx", + "Source/Common/NumberInput.stories.tsx", + "Source/Common/Page.stories.tsx", + "Source/Common/Radio.stories.tsx", + "Source/Common/Surface.stories.tsx", + "Source/Common/Switch.stories.tsx", + "Source/Common/Tabs.stories.tsx", + "Source/Common/TagGroup.stories.tsx", + "Source/Common/TextArea.stories.tsx", + "Source/Common/TextInput.stories.tsx", + "Source/Common/ToggleGroup.stories.tsx", + "Source/Common/Tooltip.stories.tsx", + "Source/DataPage/DataPage.stories.tsx", + "Source/DataTables/ColumnFilterMenu.stories.tsx", + "Source/DataTables/DataTableCore.stories.tsx", + "Source/DataTables/DataTableForObservableQuery.stories.tsx", + "Source/DataTables/DataTableForQuery.stories.tsx", + "Source/DataTables/TablePaginator.stories.tsx", + "Source/Dialogs/BusyIndicatorDialog.stories.tsx", + "Source/Dialogs/ConfirmationDialog.stories.tsx", + "Source/Dialogs/Dialog.stories.tsx", + "Source/Display/Avatar.stories.tsx", + "Source/Display/Badge.stories.tsx", + "Source/Display/Chip.stories.tsx", + "Source/Display/Display.stories.tsx", + "Source/Display/Message.stories.tsx", + "Source/Display/ProgressBar.stories.tsx", + "Source/Display/ProgressSpinner.stories.tsx", + "Source/Display/Skeleton.stories.tsx", + "Source/Display/Tag.stories.tsx", + "Source/Dropdown/Dropdown.stories.tsx", + "Source/Filter/CheckboxListFilter.stories.tsx", + "Source/Filter/FilterPanel.stories.tsx", + "Source/Filter/RangeHistogramFilter.stories.tsx", + "Source/Notifications/Toaster.stories.tsx", + "Source/ObjectContentEditor/ObjectContentEditor.stories.tsx", + "Source/ObjectNavigationalBar/ObjectNavigationalBar.stories.tsx", + "Source/PivotViewer/PivotViewer.stories.tsx", + "Source/SchemaEditor/SchemaEditor.stories.tsx", + "Source/TimeMachine/EventsView.stories.tsx", + "Source/TimeMachine/Properties.stories.tsx", + "Source/TimeMachine/ReadModelView.stories.tsx", + "Source/TimeMachine/TimeMachine.stories.tsx", + "Source/Toolbar/Toolbar.stories.tsx" + ] +} diff --git a/Storybook/scripts/verify-adapter-inventory.mjs b/Storybook/scripts/verify-adapter-inventory.mjs index e5c38294..b48b747c 100644 --- a/Storybook/scripts/verify-adapter-inventory.mjs +++ b/Storybook/scripts/verify-adapter-inventory.mjs @@ -3,19 +3,16 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { checkInventory } from '../../scripts/lib/evidence-inventory.mjs'; import { discoverAdapterPackages } from './lib/adapter-inventory.mjs'; const storybookRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const repositoryRoot = path.resolve(storybookRoot, '..'); const inventory = discoverAdapterPackages(repositoryRoot); -const required = new Set([ - 'cratis-built-in', - 'cratis-mui', - 'cratis-primereact', - 'cratis-primereact10', -]); -const missing = [...required].filter(id => !inventory.adapters.some(adapter => adapter.metadata.id === id)); -if (missing.length > 0) throw new Error(`Missing required renderer previews: ${missing.join(', ')}.`); +checkInventory(path.join(storybookRoot, 'scripts/renderer-inventory.json'), { + publicRenderers: inventory.adapters.map(adapter => adapter.metadata.id), + privateRenderers: inventory.exclusions.map(adapter => adapter.id), +}, 'yarn generate-inventories', process.argv.includes('--update')); const plain = inventory.adapters.filter(adapter => adapter.metadata.id.toLowerCase().includes('plain')); if (plain.length > 0) throw new Error(`Private Plain renderers must not enter the public preview inventory: ${plain.map(adapter => adapter.metadata.id).join(', ')}.`); diff --git a/Storybook/scripts/verify-composed-manager.mjs b/Storybook/scripts/verify-composed-manager.mjs index c7348661..000fbcfc 100644 --- a/Storybook/scripts/verify-composed-manager.mjs +++ b/Storybook/scripts/verify-composed-manager.mjs @@ -6,6 +6,7 @@ import { existsSync, readFileSync, statSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { chromium } from 'playwright'; +import { discoverAdapterPackages } from './lib/adapter-inventory.mjs'; const storybookRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const repositoryRoot = path.resolve(storybookRoot, '..'); @@ -99,8 +100,9 @@ try { const selector = page.locator('select[aria-label="Renderer"]'); await selector.waitFor({ state: 'visible', timeout: 30_000 }); const options = await selector.locator('option').allTextContents(); - if (options.length !== 4 || !options.includes('Cratis PrimeReact 10 renderer')) { - throw new Error(`Expected four renderer options, found: ${options.join(', ')}.`); + const expectedOptions = discoverAdapterPackages(repositoryRoot).adapters.map(adapter => adapter.metadata.displayName).sort(); + if (JSON.stringify([...options].sort()) !== JSON.stringify(expectedOptions)) { + throw new Error(`Renderer options differ from the validated adapter metadata: expected ${expectedOptions.join(', ')}, found ${options.join(', ')}.`); } if ((await selector.inputValue()) !== sourceRendererId) { throw new Error( diff --git a/Storybook/scripts/verify-storybook-indexes.mjs b/Storybook/scripts/verify-storybook-indexes.mjs index 5faafb54..d3950228 100644 --- a/Storybook/scripts/verify-storybook-indexes.mjs +++ b/Storybook/scripts/verify-storybook-indexes.mjs @@ -4,6 +4,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { checkInventory, inventoryDifferences } from '../../scripts/lib/evidence-inventory.mjs'; import { discoverAdapterPackages } from './lib/adapter-inventory.mjs'; import { computeRendererMatrixScope } from './lib/renderer-matrix-scope.mjs'; @@ -25,7 +26,11 @@ const collectTextFiles = directory => readdirSync(directory, { withFileTypes: tr return entry.isFile() && /\.(?:html|js|json)$/u.test(entry.name) ? [entryPath] : []; }); const storyFiles = collectStoryFiles(sourceRoot); -if (storyFiles.length !== 74) throw new Error(`Expected the existing 74 story modules, found ${storyFiles.length}.`); +const relativeSourcePath = file => path.relative(repositoryRoot, file).split(path.sep).join('/'); +const update = process.argv.includes('--update'); +const updateCommand = 'yarn generate-inventories'; +const snapshotPath = path.join(storybookRoot, 'scripts/storybook-inventory.json'); +const committed = !update && existsSync(snapshotPath) ? JSON.parse(readFileSync(snapshotPath, 'utf8')) : undefined; let canonicalStoryIds; let canonicalDocsIds; @@ -41,15 +46,25 @@ for (const adapter of inventory.adapters) { const entries = Object.values(index.entries ?? {}); const storyIds = entries.filter(entry => entry.type === 'story').map(entry => entry.id).sort(); const docsIds = entries.filter(entry => entry.type === 'docs').map(entry => entry.id).sort(); - if (storyIds.length !== 331 || docsIds.length !== 74) { - throw new Error(`${adapter.metadata.id} indexed ${storyIds.length} stories and ${docsIds.length} autodocs pages; expected 331 and 74.`); + if (committed) { + const differences = inventoryDifferences( + { stories: committed.stories, autodocs: committed.autodocs }, + { stories: storyIds, autodocs: docsIds }, + ); + if (differences.length) { + throw new Error(`${adapter.metadata.id} differs from the committed Storybook inventory:\n- ${differences.join('\n- ')}\nRun ${updateCommand} and review the snapshot diff.`); + } } canonicalStoryIds ??= storyIds; canonicalDocsIds ??= docsIds; canonicalStoryEntries ??= entries.filter(entry => entry.type === 'story'); if (JSON.stringify(storyIds) !== JSON.stringify(canonicalStoryIds) || JSON.stringify(docsIds) !== JSON.stringify(canonicalDocsIds)) { - throw new Error(`${adapter.metadata.id} does not expose the same stable story ids as the built-in preview.`); + const differences = inventoryDifferences( + { stories: canonicalStoryIds, autodocs: canonicalDocsIds }, + { stories: storyIds, autodocs: docsIds }, + ); + throw new Error(`${adapter.metadata.id} does not expose the same stable story and autodocs ids as the built-in preview:\n- ${differences.join('\n- ')}`); } const attestation = JSON.parse(readFileSync(attestationFile, 'utf8')); const expectedVersions = adapter.expectedUpstreamVersion ? [adapter.expectedUpstreamVersion] : []; @@ -77,18 +92,14 @@ const { slotOwningModules, matrixStoryIds } = computeRendererMatrixScope({ sourceRoot, }); -// Attested against the derivation, not hand-maintained: a slotted component (or one that composes -// a slotted primitive, such as `CommandDialog` or `DataPage`) is picked up automatically by -// `computeRendererMatrixScope`. These counts are still pinned so that a change in what the -// registry or the derivation reaches — a newly slotted component, a new composite, or a -// regression in the reachability walk itself — fails this check loudly instead of silently -// shrinking (or inflating) what the renderer matrix actually covers. -if (slotOwningModules.size !== 14) { - throw new Error(`Expected 14 slot-owning modules (the stable nine-slot presentation profile plus experimental slots), found ${slotOwningModules.size}.`); -} -if (matrixStoryIds.size !== 179) { - throw new Error(`Expected 179 stories to require the full renderer matrix, found ${matrixStoryIds.size}. If this is an intentional consequence of adding or removing a slotted/composite component, update this pinned count.`); -} + +checkInventory(snapshotPath, { + storyModules: storyFiles.map(relativeSourcePath), + stories: canonicalStoryIds, + autodocs: canonicalDocsIds, + slotOwningModules: [...slotOwningModules].map(relativeSourcePath), + matrixStories: [...matrixStoryIds], +}, updateCommand, update); const appearances = 2; const builtInOnlyStoryCount = canonicalStoryIds.length - matrixStoryIds.size; diff --git a/package.json b/package.json index d3caad81..f2bf718c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "lint": "node ./run-task-on-workspaces.js lint", "test": "node ./run-task-on-workspaces.js test", "generate-compat-manifest": "node ./scripts/generate-compat-manifest.mjs --write", + "generate-inventories": "node ./scripts/generate-inventories.mjs", "verify-compat-manifest": "node ./scripts/generate-compat-manifest.mjs --check", "generate-release-evidence": "node ./scripts/generate-release-evidence.mjs", "test-release-evidence": "node --test ./scripts/generate-release-evidence.test.mjs", @@ -43,7 +44,7 @@ "test-renderer-adapter-matrix": "node --test ./scripts/verify-renderer-adapter-matrix.test.mjs", "verify-renderer-adapters": "node ./scripts/verify-renderer-adapters.mjs", "publish-version": "node ./run-task-on-workspaces.js publish-version", - "test-tooling": "node --test scripts/parse-npm-pack-result.test.mjs scripts/typescript-compiler.test.mjs scripts/typescript-diagnostics.test.mjs scripts/run-task-on-workspaces.test.mjs", + "test-tooling": "node --test scripts/parse-npm-pack-result.test.mjs scripts/typescript-compiler.test.mjs scripts/typescript-diagnostics.test.mjs scripts/run-task-on-workspaces.test.mjs scripts/evidence-inventory.test.mjs", "verify-typescript-consumers": "node scripts/verify-typescript-consumers.mjs", "ci": "yarn test-tooling && yarn verify-compat-manifest && yarn test-release-policy && yarn test-release-evidence && yarn test-renderer-adapter-matrix && node ./run-task-on-workspaces.js ci && yarn verify-typescript-consumers", "up": "node ./run-task-on-workspaces.js up" diff --git a/scripts/evidence-inventory.test.mjs b/scripts/evidence-inventory.test.mjs new file mode 100644 index 00000000..0d546f21 --- /dev/null +++ b/scripts/evidence-inventory.test.mjs @@ -0,0 +1,36 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import assert from 'node:assert/strict'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; +import { checkInventory, inventoryBytes, inventoryDifferences } from './lib/evidence-inventory.mjs'; + +test('when an identity disappears the inventory names the missing identity', () => { + assert.deepEqual(inventoryDifferences({ stories: ['a', 'b'] }, { stories: ['a'] }), ['missing stories: b']); +}); + +test('when an identity appears the inventory names the added identity', () => { + assert.deepEqual(inventoryDifferences({ stories: ['a'] }, { stories: ['a', 'b'] }), ['added stories: b']); +}); + +test('when an inventory changes the verifier reports every missing and added identity with its update command', () => { + const directory = mkdtempSync(path.join(os.tmpdir(), 'cratis-evidence-inventory-')); + try { + const snapshot = path.join(directory, 'inventory.json'); + writeFileSync(snapshot, inventoryBytes({ stories: ['a', 'b'], modules: ['before'] })); + assert.throws(() => checkInventory(snapshot, { stories: ['a', 'c'], modules: ['after'] }, 'yarn generate-inventories'), error => + ['missing stories: b', 'added stories: c', 'missing modules: before', 'added modules: after', 'yarn generate-inventories'].every(value => error.message.includes(value))); + checkInventory(snapshot, { stories: ['c', 'a'], modules: ['after'] }, 'yarn generate-inventories', true); + assert.equal(readFileSync(snapshot, 'utf8'), inventoryBytes({ stories: ['a', 'c'], modules: ['after'] })); + checkInventory(snapshot, { stories: ['a', 'c'], modules: ['after'] }, 'yarn generate-inventories'); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +}); + +test('when duplicate identities are generated the verifier fails instead of dropping them', () => { + assert.throws(() => inventoryBytes({ stories: ['a', 'a'] }), /Duplicate identity/); +}); diff --git a/scripts/generate-inventories.mjs b/scripts/generate-inventories.mjs new file mode 100644 index 00000000..221069fe --- /dev/null +++ b/scripts/generate-inventories.mjs @@ -0,0 +1,37 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Regenerate reviewable tooling evidence from the same built inputs the verifiers read. +import { spawnSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const run = (label, executable, argumentsList) => { + console.log(`\n--- ${label} ---`); + const result = spawnSync(executable, argumentsList, { + cwd: repositoryRoot, + stdio: 'inherit', + env: { ...process.env, NODE_OPTIONS: process.env.NODE_OPTIONS ?? '--max-old-space-size=4096' }, + }); + if (result.error) throw result.error; + if (result.status !== 0) throw new Error(`${label} failed with exit code ${result.status ?? 'unknown'}. Review any updated snapshots before retrying.`); +}; +const node = (label, script) => run(label, process.execPath, [script, '--update']); +const yarn = (label, ...args) => run(label, 'yarn', args); + +// Adapter discovery is checked by Storybook's build, so update its snapshot first. +node('Renderer preview inventory', 'Storybook/scripts/verify-adapter-inventory.mjs'); +yarn('Build Source', 'workspace', '@cratis/components', 'prepare'); +for (const workspace of [ + '@cratis/components.mui', + '@cratis/components.primereact', + '@cratis/components.primereact10', +]) yarn(`Build ${workspace}`, 'workspace', workspace, 'build'); +yarn('Build Storybook indexes', 'workspace', '@cratis/components.storybook', 'build'); +node('Renderer slot inventory', 'Source/scripts/verify-renderer-contracts.mjs'); +node('Parts and states inventory', 'Source/scripts/verify-parts-manifest.mjs'); +node('Kernel and export inventories', 'Source/scripts/verify-package-graph-report.mjs'); +node('Published export inventory', 'Source/scripts/verify-exports.mjs'); +node('Storybook indexes inventory', 'Storybook/scripts/verify-storybook-indexes.mjs'); +console.log('\nReview every generated inventory diff before committing.'); diff --git a/scripts/lib/evidence-inventory.mjs b/scripts/lib/evidence-inventory.mjs new file mode 100644 index 00000000..f1657951 --- /dev/null +++ b/scripts/lib/evidence-inventory.mjs @@ -0,0 +1,62 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import { readFileSync, writeFileSync } from 'node:fs'; + +const sortIdentities = values => [...values].sort(); + +export const normalizeInventory = inventory => Object.fromEntries( + Object.keys(inventory).sort().map(group => { + const values = inventory[group]; + if (!Array.isArray(values) || values.some(value => typeof value !== 'string' || value.length === 0)) { + throw new Error(`Invalid inventory group '${group}': expected nonempty string identities.`); + } + if (new Set(values).size !== values.length) throw new Error(`Duplicate identity in inventory group '${group}'.`); + return [group, sortIdentities(values)]; + }), +); + +export const inventoryBytes = inventory => `${JSON.stringify(normalizeInventory(inventory), null, 4)}\n`; + +export const inventoryDifferences = (committed, generated) => { + const previous = normalizeInventory(committed); + const current = normalizeInventory(generated); + const differences = []; + for (const group of sortIdentities(new Set([...Object.keys(previous), ...Object.keys(current)]))) { + const before = new Set(previous[group] ?? []); + const after = new Set(current[group] ?? []); + for (const identity of sortIdentities([...before].filter(value => !after.has(value)))) { + differences.push(`missing ${group}: ${identity}`); + } + for (const identity of sortIdentities([...after].filter(value => !before.has(value)))) { + differences.push(`added ${group}: ${identity}`); + } + } + return differences; +}; + +export function checkInventory(snapshotPath, generated, updateCommand, update = false) { + const bytes = inventoryBytes(generated); + if (update) { + writeFileSync(snapshotPath, bytes); + console.log(`Updated ${snapshotPath}`); + return; + } + let committedBytes; + try { + committedBytes = readFileSync(snapshotPath, 'utf8'); + } catch (error) { + throw new Error(`Cannot read inventory ${snapshotPath}: ${error instanceof Error ? error.message : String(error)}. Run ${updateCommand}.`); + } + let committed; + try { + committed = JSON.parse(committedBytes); + if (!committed || Array.isArray(committed) || typeof committed !== 'object') throw new Error('Expected an object of identity arrays.'); + } catch (error) { + throw new Error(`Invalid inventory ${snapshotPath}: ${error instanceof Error ? error.message : String(error)}. Run ${updateCommand}.`); + } + const differences = inventoryDifferences(committed, generated); + if (differences.length || committedBytes !== bytes) { + throw new Error(`Inventory ${snapshotPath} differs from the generated evidence:\n- ${differences.join('\n- ') || 'snapshot formatting differs from the deterministic generator'}\nRun ${updateCommand} and review the snapshot diff.`); + } +} From 586fe0f14baf07423d0fc20d82d3b6f7ee379eea Mon Sep 17 00:00:00 2001 From: woksin Date: Sat, 26 Sep 2026 21:59:06 +0200 Subject: [PATCH 2/6] Read renderer expectations from the committed inventory in specs --- .../when_discovering_renderer_previews.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Storybook/for_inventory/when_discovering_renderer_previews.ts b/Storybook/for_inventory/when_discovering_renderer_previews.ts index ba45d90c..8c849488 100644 --- a/Storybook/for_inventory/when_discovering_renderer_previews.ts +++ b/Storybook/for_inventory/when_discovering_renderer_previews.ts @@ -9,6 +9,7 @@ import { expect } from 'chai'; import { describe, it } from 'vitest'; import sourcePackage from '../../Source/package.json'; import adapterSchema from '../../Source/schemas/ui-adapter.schema.json'; +import expectedRenderers from '../scripts/renderer-inventory.json'; import { discoverAdapterPackages } from '../scripts/lib/adapter-inventory.mjs'; import { validateAgainstSchema } from '../scripts/lib/schema-validator.mjs'; @@ -18,12 +19,9 @@ const repositoryRoot = path.resolve(storybookRoot, '..'); describe('when discovering renderer previews', () => { it('should derive the required inventory from schema-valid workspace package metadata', () => { const inventory = discoverAdapterPackages(repositoryRoot); - expect(inventory.adapters.map((adapter) => adapter.metadata.id)).to.deep.equal([ - 'cratis-built-in', - 'cratis-mui', - 'cratis-primereact10', - 'cratis-primereact', - ]); + expect(inventory.adapters.map((adapter) => adapter.metadata.id).sort()).to.deep.equal( + expectedRenderers.publicRenderers, + ); expect( inventory.adapters.every((adapter) => adapter.metadata.kind === 'ui-adapter'), ).to.equal(true); From 11f203b1131ba874c4764c9e355345aecd712ae3 Mon Sep 17 00:00:00 2001 From: woksin Date: Sun, 27 Sep 2026 11:35:50 +0200 Subject: [PATCH 3/6] Pin reviewed renderer option labels in Storybook inventory --- Storybook/scripts/lib/renderer-options.mjs | 22 ++++++++++++++ Storybook/scripts/renderer-inventory.json | 6 ++++ .../scripts/verify-adapter-inventory.mjs | 2 ++ Storybook/scripts/verify-composed-manager.mjs | 12 ++++---- scripts/evidence-inventory.test.mjs | 29 +++++++++++++++++++ 5 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 Storybook/scripts/lib/renderer-options.mjs diff --git a/Storybook/scripts/lib/renderer-options.mjs b/Storybook/scripts/lib/renderer-options.mjs new file mode 100644 index 00000000..e95218f9 --- /dev/null +++ b/Storybook/scripts/lib/renderer-options.mjs @@ -0,0 +1,22 @@ +// Copyright (c) Cratis. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import { inventoryDifferences } from '../../../scripts/lib/evidence-inventory.mjs'; + +export const rendererOptionIdentity = (id, label) => `${id}: ${label}`; + +export const verifyRendererOptions = (options, adapters, snapshot) => { + const actual = options.map(option => rendererOptionIdentity(option.id, option.label)); + const metadata = adapters.map(adapter => rendererOptionIdentity(adapter.metadata.id, adapter.metadata.displayName)); + const metadataDifferences = inventoryDifferences({ publicRendererOptions: metadata }, { publicRendererOptions: actual }); + if (metadataDifferences.length > 0) { + throw new Error(`Renderer options differ from the validated adapter metadata:\n- ${metadataDifferences.join('\n- ')}`); + } + const snapshotDifferences = inventoryDifferences( + { publicRendererOptions: snapshot.publicRendererOptions }, + { publicRendererOptions: actual }, + ); + if (snapshotDifferences.length > 0) { + throw new Error(`Renderer options differ from the committed inventory:\n- ${snapshotDifferences.join('\n- ')}\nRun yarn generate-inventories and review the snapshot diff.`); + } +}; diff --git a/Storybook/scripts/renderer-inventory.json b/Storybook/scripts/renderer-inventory.json index a29a084d..9fa062d2 100644 --- a/Storybook/scripts/renderer-inventory.json +++ b/Storybook/scripts/renderer-inventory.json @@ -1,5 +1,11 @@ { "privateRenderers": [], + "publicRendererOptions": [ + "cratis-built-in: Cratis built-in renderer", + "cratis-mui: Cratis Material UI renderer", + "cratis-primereact10: Cratis PrimeReact 10 renderer", + "cratis-primereact: Cratis PrimeReact renderer" + ], "publicRenderers": [ "cratis-built-in", "cratis-mui", diff --git a/Storybook/scripts/verify-adapter-inventory.mjs b/Storybook/scripts/verify-adapter-inventory.mjs index b48b747c..8e01ee8a 100644 --- a/Storybook/scripts/verify-adapter-inventory.mjs +++ b/Storybook/scripts/verify-adapter-inventory.mjs @@ -5,12 +5,14 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { checkInventory } from '../../scripts/lib/evidence-inventory.mjs'; import { discoverAdapterPackages } from './lib/adapter-inventory.mjs'; +import { rendererOptionIdentity } from './lib/renderer-options.mjs'; const storybookRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const repositoryRoot = path.resolve(storybookRoot, '..'); const inventory = discoverAdapterPackages(repositoryRoot); checkInventory(path.join(storybookRoot, 'scripts/renderer-inventory.json'), { publicRenderers: inventory.adapters.map(adapter => adapter.metadata.id), + publicRendererOptions: inventory.adapters.map(adapter => rendererOptionIdentity(adapter.metadata.id, adapter.metadata.displayName)), privateRenderers: inventory.exclusions.map(adapter => adapter.id), }, 'yarn generate-inventories', process.argv.includes('--update')); const plain = inventory.adapters.filter(adapter => adapter.metadata.id.toLowerCase().includes('plain')); diff --git a/Storybook/scripts/verify-composed-manager.mjs b/Storybook/scripts/verify-composed-manager.mjs index 000fbcfc..a8885723 100644 --- a/Storybook/scripts/verify-composed-manager.mjs +++ b/Storybook/scripts/verify-composed-manager.mjs @@ -7,6 +7,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { chromium } from 'playwright'; import { discoverAdapterPackages } from './lib/adapter-inventory.mjs'; +import { verifyRendererOptions } from './lib/renderer-options.mjs'; const storybookRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const repositoryRoot = path.resolve(storybookRoot, '..'); @@ -99,11 +100,12 @@ try { const selector = page.locator('select[aria-label="Renderer"]'); await selector.waitFor({ state: 'visible', timeout: 30_000 }); - const options = await selector.locator('option').allTextContents(); - const expectedOptions = discoverAdapterPackages(repositoryRoot).adapters.map(adapter => adapter.metadata.displayName).sort(); - if (JSON.stringify([...options].sort()) !== JSON.stringify(expectedOptions)) { - throw new Error(`Renderer options differ from the validated adapter metadata: expected ${expectedOptions.join(', ')}, found ${options.join(', ')}.`); - } + const options = await selector.locator('option').evaluateAll(elements => elements.map(option => ({ + id: option.value, + label: option.textContent ?? '', + }))); + const snapshot = JSON.parse(readFileSync(path.join(storybookRoot, 'scripts/renderer-inventory.json'), 'utf8')); + verifyRendererOptions(options, discoverAdapterPackages(repositoryRoot).adapters, snapshot); if ((await selector.inputValue()) !== sourceRendererId) { throw new Error( 'The renderer selector did not reflect the initial composed ref.', diff --git a/scripts/evidence-inventory.test.mjs b/scripts/evidence-inventory.test.mjs index 0d546f21..5cea2418 100644 --- a/scripts/evidence-inventory.test.mjs +++ b/scripts/evidence-inventory.test.mjs @@ -7,6 +7,8 @@ import os from 'node:os'; import path from 'node:path'; import { test } from 'node:test'; import { checkInventory, inventoryBytes, inventoryDifferences } from './lib/evidence-inventory.mjs'; +import { discoverAdapterPackages } from '../Storybook/scripts/lib/adapter-inventory.mjs'; +import { verifyRendererOptions } from '../Storybook/scripts/lib/renderer-options.mjs'; test('when an identity disappears the inventory names the missing identity', () => { assert.deepEqual(inventoryDifferences({ stories: ['a', 'b'] }, { stories: ['a'] }), ['missing stories: b']); @@ -31,6 +33,33 @@ test('when an inventory changes the verifier reports every missing and added ide } }); +test('when metadata and manager labels drift together the reviewed renderer options still fail', () => { + const repositoryRoot = path.resolve(import.meta.dirname, '..'); + const snapshot = JSON.parse(readFileSync(path.join(repositoryRoot, 'Storybook/scripts/renderer-inventory.json'), 'utf8')); + const currentAdapters = discoverAdapterPackages(repositoryRoot).adapters; + const currentOptions = currentAdapters.map(adapter => ({ + id: adapter.metadata.id, + label: adapter.metadata.displayName, + })); + assert.doesNotThrow(() => verifyRendererOptions(currentOptions, currentAdapters, snapshot)); + const adapters = currentAdapters.map(adapter => ({ + metadata: { + ...adapter.metadata, + displayName: adapter.metadata.id === 'cratis-primereact10' + ? 'Changed PrimeReact 10 renderer' + : adapter.metadata.displayName, + }, + })); + const renderedOptions = adapters.map(adapter => ({ + id: adapter.metadata.id, + label: adapter.metadata.displayName, + })); + assert.throws(() => verifyRendererOptions(renderedOptions, adapters, snapshot), error => + error.message.includes('cratis-primereact10: Cratis PrimeReact 10 renderer') && + error.message.includes('cratis-primereact10: Changed PrimeReact 10 renderer') && + error.message.includes('yarn generate-inventories')); +}); + test('when duplicate identities are generated the verifier fails instead of dropping them', () => { assert.throws(() => inventoryBytes({ stories: ['a', 'a'] }), /Duplicate identity/); }); From 9bee53c4b80369a71d51922a0af1f003fe6bbdce Mon Sep 17 00:00:00 2001 From: woksin Date: Sun, 27 Sep 2026 12:08:03 +0200 Subject: [PATCH 4/6] Record 4.16 parts and Storybook evidence identities --- Source/scripts/parts-inventory.json | 2 ++ Storybook/scripts/storybook-inventory.json | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/Source/scripts/parts-inventory.json b/Source/scripts/parts-inventory.json index 3d97fe03..14642fbf 100644 --- a/Source/scripts/parts-inventory.json +++ b/Source/scripts/parts-inventory.json @@ -357,6 +357,7 @@ "Button.root.loading", "CalendarField.calendar.open", "CalendarField.cell.selected", + "CalendarField.clear.disabled", "CalendarField.dialog.open", "CalendarField.group.disabled", "CalendarField.group.invalid", @@ -511,6 +512,7 @@ "DataTableForQuery.sort.pressed", "DatePickerInput.calendar.open", "DatePickerInput.cell.selected", + "DatePickerInput.clear.disabled", "DatePickerInput.dialog.open", "DatePickerInput.group.disabled", "DatePickerInput.group.invalid", diff --git a/Storybook/scripts/storybook-inventory.json b/Storybook/scripts/storybook-inventory.json index 226a3f23..ab28e889 100644 --- a/Storybook/scripts/storybook-inventory.json +++ b/Storybook/scripts/storybook-inventory.json @@ -63,6 +63,7 @@ "dropdown-dropdown--docs", "filter-checkboxlistfilter--docs", "filter-filterpanel--docs", + "filter-filterpanel-accessibility--docs", "filter-rangehistogramfilter--docs", "notifications-toaster--docs", "objectcontenteditor-objectcontenteditor--docs", @@ -101,6 +102,7 @@ "commanddialog-commanddialog--with-custom-validation", "commanddialog-commanddialog--with-icons", "commanddialog-commanddialog--with-initial-values", + "commanddialog-commanddialog--with-pre-execution-confirmation", "commanddialog-commanddialog--with-response-type-and-callbacks", "commanddialog-commanddialog--with-server-validation", "commanddialog-commandstepper--conditional-steps", @@ -186,6 +188,7 @@ "common-tooltip--no-content", "common-tooltip--playground", "common-tooltip--positions", + "common-tooltip--rich-content", "datapage-datapage--default", "datapage-datapage--multiple-pages", "datapage-datapage--multiple-pages-with-default-filters", @@ -289,6 +292,7 @@ "chat-chatmessagebody--with-mentions", "chat-chatsidebar--interactive-host", "chat-chatsidebar--playground", + "chat-chatsidebar--query-states", "chat-chatsidebarforobservablequeries--playground", "chat-chattopiclist--empty", "chat-chattopiclist--playground", @@ -319,6 +323,7 @@ "commanddialog-commanddialog--with-custom-validation", "commanddialog-commanddialog--with-icons", "commanddialog-commanddialog--with-initial-values", + "commanddialog-commanddialog--with-pre-execution-confirmation", "commanddialog-commanddialog--with-response-type-and-callbacks", "commanddialog-commanddialog--with-server-validation", "commanddialog-commandstepper--conditional-steps", @@ -455,6 +460,7 @@ "common-tooltip--no-content", "common-tooltip--playground", "common-tooltip--positions", + "common-tooltip--rich-content", "datapage-datapage--default", "datapage-datapage--multiple-pages", "datapage-datapage--multiple-pages-with-default-filters", @@ -548,6 +554,7 @@ "filter-filterpanel--no-search-when-it-fits", "filter-filterpanel--numeric-range-filter", "filter-filterpanel--single-select-filter", + "filter-filterpanel-accessibility--focus-search-and-dismiss", "filter-rangehistogramfilter--currency", "filter-rangehistogramfilter--interactive", "notifications-toaster--playground", @@ -563,6 +570,7 @@ "objectnavigationalbar-objectnavigationalbar--single-level", "pivotviewer-pivotviewer--default", "pivotviewer-pivotviewer--large-dataset", + "pivotviewer-pivotviewer--toolbar-labels", "schemaeditor-schemaeditor--edit-mode", "schemaeditor-schemaeditor--empty-schema", "schemaeditor-schemaeditor--interactive", @@ -668,6 +676,7 @@ "Source/Display/Tag.stories.tsx", "Source/Dropdown/Dropdown.stories.tsx", "Source/Filter/CheckboxListFilter.stories.tsx", + "Source/Filter/FilterPanel.accessibility.stories.tsx", "Source/Filter/FilterPanel.stories.tsx", "Source/Filter/RangeHistogramFilter.stories.tsx", "Source/Notifications/Toaster.stories.tsx", From 318c9321ce81e0c89d89bf6148073551ebe7e8e9 Mon Sep 17 00:00:00 2001 From: woksin Date: Sun, 27 Sep 2026 12:40:11 +0200 Subject: [PATCH 5/6] Assert renderer inventory identities and adapter discovery order --- .../when_discovering_renderer_previews.ts | 4 ++ .../when_deriving_renderer_matrix_scope.ts | 42 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Storybook/for_inventory/when_discovering_renderer_previews.ts b/Storybook/for_inventory/when_discovering_renderer_previews.ts index 8c849488..a9901d9d 100644 --- a/Storybook/for_inventory/when_discovering_renderer_previews.ts +++ b/Storybook/for_inventory/when_discovering_renderer_previews.ts @@ -22,6 +22,10 @@ describe('when discovering renderer previews', () => { expect(inventory.adapters.map((adapter) => adapter.metadata.id).sort()).to.deep.equal( expectedRenderers.publicRenderers, ); + expect(inventory.adapters[0].builtIn).to.equal(true); + expect(inventory.adapters.slice(1).every((adapter) => !adapter.builtIn)).to.equal(true); + const displayNames = inventory.adapters.slice(1).map((adapter) => adapter.metadata.displayName); + expect(displayNames).to.deep.equal([...displayNames].sort((left, right) => left.localeCompare(right))); expect( inventory.adapters.every((adapter) => adapter.metadata.kind === 'ui-adapter'), ).to.equal(true); diff --git a/Storybook/for_renderer_matrix_scope/when_deriving_renderer_matrix_scope.ts b/Storybook/for_renderer_matrix_scope/when_deriving_renderer_matrix_scope.ts index 261b82c8..170795b5 100644 --- a/Storybook/for_renderer_matrix_scope/when_deriving_renderer_matrix_scope.ts +++ b/Storybook/for_renderer_matrix_scope/when_deriving_renderer_matrix_scope.ts @@ -7,12 +7,24 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { expect } from 'chai'; import { describe, it } from 'vitest'; +import expectedStorybook from '../scripts/storybook-inventory.json'; import { computeRendererMatrixScope, findSlotOwningModules } from '../scripts/lib/renderer-matrix-scope.mjs'; const storybookRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const repositoryRoot = path.resolve(storybookRoot, '..'); const sourceRoot = path.join(repositoryRoot, 'Source'); +const expectSlotOwningModulesToMatchInventory = ( + slotOwningModules: ReadonlySet, + root: string, + expectedModules: readonly string[], +) => { + expect(slotOwningModules.size).to.be.greaterThan(0); + expect([...slotOwningModules] + .map(file => path.relative(root, file).split(path.sep).join('/')) + .sort()).to.deep.equal([...expectedModules].sort()); +}; + const storyEntry = (id: string, relativeComponentOrStoryFile: string, componentIsKnown = true) => ({ id, componentPath: componentIsKnown ? `./Source/${relativeComponentOrStoryFile}` : undefined, @@ -21,11 +33,11 @@ const storyEntry = (id: string, relativeComponentOrStoryFile: string, componentI describe('when deriving renderer matrix scope', () => { // A regex rotting silently (for example after `unstable_useSlot` is renamed) would make the - // registry empty and quietly send every story down the built-in-only path. Pinning both the - // count and the presentation-profile files guards against that. - it('should derive exactly the nine-slot presentation profile plus the experimental slots from source', () => { + // registry empty and quietly send every story down the built-in-only path. Compare module + // identities with the committed inventory and retain the presentation-profile membership guard. + it('should match the reviewed slot-owning module inventory and nine-slot presentation profile', () => { const slotOwningModules = findSlotOwningModules(sourceRoot); - expect(slotOwningModules.size).to.equal(14); + expectSlotOwningModulesToMatchInventory(slotOwningModules, repositoryRoot, expectedStorybook.slotOwningModules); for (const presentationSlotFile of [ 'Common/Button.tsx', 'Common/IconButton.tsx', @@ -44,6 +56,28 @@ describe('when deriving renderer matrix scope', () => { } }); + it('should detect a new slot owner even when it replaces an existing owner without changing the count', () => { + const root = mkdtempSync(path.join(os.tmpdir(), 'cratis-slot-inventory-')); + try { + const fixtureSourceRoot = path.join(root, 'Source'); + mkdirSync(path.join(fixtureSourceRoot, 'Common'), { recursive: true }); + const original = path.join(fixtureSourceRoot, 'Common/Original.tsx'); + const replacement = path.join(fixtureSourceRoot, 'Common/NewSlot.tsx'); + writeFileSync(original, "unstable_useSlot('common.original');\n"); + const snapshot = ['Source/Common/Original.tsx']; + const before = findSlotOwningModules(fixtureSourceRoot); + expectSlotOwningModulesToMatchInventory(before, root, snapshot); + + rmSync(original); + writeFileSync(replacement, "unstable_useSlot('common.new');\n"); + const after = findSlotOwningModules(fixtureSourceRoot); + expect(after.size).to.equal(before.size); + expect(() => expectSlotOwningModulesToMatchInventory(after, root, snapshot)).to.throw(); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + it('should keep built-in-only components out of the renderer matrix', () => { const storyEntries = [ storyEntry('common-combobox--playground', 'Common/ComboBox.tsx'), From ec60c62e4f5e61bf7a92774585b9ca23f1b1dd3f Mon Sep 17 00:00:00 2001 From: woksin Date: Mon, 28 Sep 2026 06:34:04 +0200 Subject: [PATCH 6/6] Record Canvas control story identities in Storybook inventory --- Storybook/scripts/storybook-inventory.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Storybook/scripts/storybook-inventory.json b/Storybook/scripts/storybook-inventory.json index ab28e889..3196b526 100644 --- a/Storybook/scripts/storybook-inventory.json +++ b/Storybook/scripts/storybook-inventory.json @@ -281,6 +281,8 @@ "canvas-canvas--with-controls-and-minimap", "canvas-canvascontrols--localized-controlled-zoom", "canvas-canvascontrols--playground", + "canvas-canvascontrols--replacement-icons", + "canvas-canvascontrols--without-viewport-insets", "canvas-canvasminimap--playground", "canvas-canvasminimap--synchronized-viewport", "canvas-canvasoverlay--inspector-panel",