cursedbelt 1.0.0 โ 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.js +1 -1
- package/dist/core/category/index.d.ts.map +1 -1
- package/dist/core/category/index.js +2 -1
- package/dist/core/category/index.js.map +1 -1
- package/dist/core/category/types.js +1 -1
- package/dist/core/category/types.js.map +1 -1
- package/dist/core/file-tree/fileTreeModel.d.ts +49 -0
- package/dist/core/file-tree/fileTreeModel.d.ts.map +1 -1
- package/dist/core/file-tree/fileTreeModel.js +84 -0
- package/dist/core/file-tree/fileTreeModel.js.map +1 -1
- package/dist/core/layout/index.js +1 -1
- package/dist/core/layout/index.js.map +1 -1
- package/dist/core/media/index.js +1 -1
- package/dist/core/media/index.js.map +1 -1
- package/dist/core/media/types.js +1 -1
- package/dist/core/media/types.js.map +1 -1
- package/dist/core/media/uploadLimits.js +1 -1
- package/dist/core/media/uploadLimits.js.map +1 -1
- package/dist/core/sharing/model.d.ts +1 -1
- package/dist/ctgr/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react/activity-bar/ActivityBar.d.ts +17 -0
- package/dist/react/activity-bar/ActivityBar.d.ts.map +1 -1
- package/dist/react/activity-bar/ActivityBar.js.map +1 -1
- package/dist/react/analytics/index.d.ts +1 -1
- package/dist/react/analytics/index.js +1 -1
- package/dist/react/chat/MessageComposer.d.ts.map +1 -1
- package/dist/react/chat/MessageComposer.js +5 -4
- package/dist/react/chat/MessageComposer.js.map +1 -1
- package/dist/react/components/Button.d.ts.map +1 -1
- package/dist/react/components/Button.js +3 -2
- package/dist/react/components/Button.js.map +1 -1
- package/dist/react/components/Checkbox.d.ts +19 -3
- package/dist/react/components/Checkbox.d.ts.map +1 -1
- package/dist/react/components/Checkbox.js +29 -12
- package/dist/react/components/Checkbox.js.map +1 -1
- package/dist/react/components/CodeArea.d.ts.map +1 -1
- package/dist/react/components/CodeArea.js +2 -1
- package/dist/react/components/CodeArea.js.map +1 -1
- package/dist/react/components/Collapsible.d.ts.map +1 -1
- package/dist/react/components/Collapsible.js +2 -1
- package/dist/react/components/Collapsible.js.map +1 -1
- package/dist/react/components/ComboboxField.d.ts.map +1 -1
- package/dist/react/components/ComboboxField.js +3 -2
- package/dist/react/components/ComboboxField.js.map +1 -1
- package/dist/react/components/CopyButton.d.ts.map +1 -1
- package/dist/react/components/CopyButton.js +2 -1
- package/dist/react/components/CopyButton.js.map +1 -1
- package/dist/react/components/DataTable/ColumnChecklist.d.ts.map +1 -1
- package/dist/react/components/DataTable/ColumnChecklist.js +2 -1
- package/dist/react/components/DataTable/ColumnChecklist.js.map +1 -1
- package/dist/react/components/DataTable/Pagination.d.ts.map +1 -1
- package/dist/react/components/DataTable/Pagination.js +2 -1
- package/dist/react/components/DataTable/Pagination.js.map +1 -1
- package/dist/react/components/DataTable/SortHeader.d.ts.map +1 -1
- package/dist/react/components/DataTable/SortHeader.js +2 -1
- package/dist/react/components/DataTable/SortHeader.js.map +1 -1
- package/dist/react/components/DataTable/TableFilterBuilder.d.ts +61 -0
- package/dist/react/components/DataTable/TableFilterBuilder.d.ts.map +1 -0
- package/dist/react/components/DataTable/{FilterBuilder.js โ TableFilterBuilder.js} +26 -3
- package/dist/react/components/DataTable/TableFilterBuilder.js.map +1 -0
- package/dist/react/components/DataTable/Toolbar.d.ts +1 -1
- package/dist/react/components/DataTable/Toolbar.js +3 -3
- package/dist/react/components/DataTable/Toolbar.js.map +1 -1
- package/dist/react/components/DataTable/cells/LinkCell.d.ts.map +1 -1
- package/dist/react/components/DataTable/cells/LinkCell.js +2 -1
- package/dist/react/components/DataTable/cells/LinkCell.js.map +1 -1
- package/dist/react/components/DataTable/index.d.ts +1 -1
- package/dist/react/components/DataTable/index.d.ts.map +1 -1
- package/dist/react/components/DataTable/index.js +4 -1
- package/dist/react/components/DataTable/index.js.map +1 -1
- package/dist/react/components/DragHandle.d.ts.map +1 -1
- package/dist/react/components/DragHandle.js +2 -1
- package/dist/react/components/DragHandle.js.map +1 -1
- package/dist/react/components/EmojiField.d.ts.map +1 -1
- package/dist/react/components/EmojiField.js +2 -1
- package/dist/react/components/EmojiField.js.map +1 -1
- package/dist/react/components/EventTimeline.d.ts +3 -2
- package/dist/react/components/EventTimeline.d.ts.map +1 -1
- package/dist/react/components/EventTimeline.js +3 -2
- package/dist/react/components/EventTimeline.js.map +1 -1
- package/dist/react/components/IconButton.d.ts +1 -1
- package/dist/react/components/IconButton.js +1 -1
- package/dist/react/components/InfoHint.d.ts.map +1 -1
- package/dist/react/components/InfoHint.js +5 -4
- package/dist/react/components/InfoHint.js.map +1 -1
- package/dist/react/components/InheritBadge.js +1 -1
- package/dist/react/components/InheritBadge.js.map +1 -1
- package/dist/react/components/InlineEditableText.d.ts.map +1 -1
- package/dist/react/components/InlineEditableText.js +2 -1
- package/dist/react/components/InlineEditableText.js.map +1 -1
- package/dist/react/components/MeterList.d.ts.map +1 -1
- package/dist/react/components/MeterList.js +2 -1
- package/dist/react/components/MeterList.js.map +1 -1
- package/dist/react/components/PathText.d.ts +2 -2
- package/dist/react/components/PathText.d.ts.map +1 -1
- package/dist/react/components/PathText.js +2 -1
- package/dist/react/components/PathText.js.map +1 -1
- package/dist/react/components/RatingField.d.ts.map +1 -1
- package/dist/react/components/RatingField.js +2 -1
- package/dist/react/components/RatingField.js.map +1 -1
- package/dist/react/components/SecretInput.js +3 -2
- package/dist/react/components/SecretInput.js.map +1 -1
- package/dist/react/components/SegmentedControl.d.ts.map +1 -1
- package/dist/react/components/SegmentedControl.js +2 -1
- package/dist/react/components/SegmentedControl.js.map +1 -1
- package/dist/react/components/StarRating.d.ts.map +1 -1
- package/dist/react/components/StarRating.js +9 -5
- package/dist/react/components/StarRating.js.map +1 -1
- package/dist/react/components/StatStrip.d.ts.map +1 -1
- package/dist/react/components/StatStrip.js +2 -1
- package/dist/react/components/StatStrip.js.map +1 -1
- package/dist/react/components/StatTile.d.ts.map +1 -1
- package/dist/react/components/StatTile.js +3 -2
- package/dist/react/components/StatTile.js.map +1 -1
- package/dist/react/components/Tabs.d.ts.map +1 -1
- package/dist/react/components/Tabs.js +2 -1
- package/dist/react/components/Tabs.js.map +1 -1
- package/dist/react/components/TruncatedText.d.ts +2 -1
- package/dist/react/components/TruncatedText.d.ts.map +1 -1
- package/dist/react/components/TruncatedText.js +2 -1
- package/dist/react/components/TruncatedText.js.map +1 -1
- package/dist/react/components/calendar/MonthGrid.d.ts.map +1 -1
- package/dist/react/components/calendar/MonthGrid.js +2 -1
- package/dist/react/components/calendar/MonthGrid.js.map +1 -1
- package/dist/react/components/calendar/WeekView.d.ts.map +1 -1
- package/dist/react/components/calendar/WeekView.js +2 -1
- package/dist/react/components/calendar/WeekView.js.map +1 -1
- package/dist/react/components/revealOnHover.d.ts.map +1 -1
- package/dist/react/components/revealOnHover.js +2 -1
- package/dist/react/components/revealOnHover.js.map +1 -1
- package/dist/react/file-tree/FileTree.d.ts +50 -1
- package/dist/react/file-tree/FileTree.d.ts.map +1 -1
- package/dist/react/file-tree/FileTree.js +234 -6
- package/dist/react/file-tree/FileTree.js.map +1 -1
- package/dist/react/file-tree/fileTree.css +52 -0
- package/dist/react/filter-rail/FilterRail.d.ts.map +1 -1
- package/dist/react/filter-rail/FilterRail.js +5 -4
- package/dist/react/filter-rail/FilterRail.js.map +1 -1
- package/dist/react/folder-tree/FolderTreeRail.d.ts.map +1 -1
- package/dist/react/folder-tree/FolderTreeRail.js +2 -1
- package/dist/react/folder-tree/FolderTreeRail.js.map +1 -1
- package/dist/react/index.d.ts +5 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/layoutEngine/index.js +1 -1
- package/dist/react/layoutEngine/index.js.map +1 -1
- package/dist/react/lib/field.d.ts.map +1 -1
- package/dist/react/lib/field.js +3 -2
- package/dist/react/lib/field.js.map +1 -1
- package/dist/react/lib/floatingStyles.d.ts.map +1 -1
- package/dist/react/lib/floatingStyles.js +2 -1
- package/dist/react/lib/floatingStyles.js.map +1 -1
- package/dist/react/lib/tapTarget.d.ts +96 -0
- package/dist/react/lib/tapTarget.d.ts.map +1 -0
- package/dist/react/lib/tapTarget.js +96 -0
- package/dist/react/lib/tapTarget.js.map +1 -0
- package/dist/react/media/VideoTrimmer.d.ts +1 -1
- package/dist/react/media/VideoTrimmer.js +1 -1
- package/dist/react/media/denseGallery.css +3 -3
- package/dist/react/media/ffmpegWasm.js +2 -2
- package/dist/react/media/ffmpegWasm.js.map +1 -1
- package/dist/react/media/videoPlayer.css +42 -0
- package/dist/react/namespace/NamespaceTargetCapsule.d.ts +2 -2
- package/dist/react/namespace/NamespaceTargetCapsule.d.ts.map +1 -1
- package/dist/react/nav/BreadcrumbTrail.d.ts.map +1 -1
- package/dist/react/nav/BreadcrumbTrail.js +4 -3
- package/dist/react/nav/BreadcrumbTrail.js.map +1 -1
- package/dist/react/nav/HubTileGrid.d.ts.map +1 -1
- package/dist/react/nav/HubTileGrid.js +2 -1
- package/dist/react/nav/HubTileGrid.js.map +1 -1
- package/dist/react/nav/SideNav.d.ts.map +1 -1
- package/dist/react/nav/SideNav.js +4 -3
- package/dist/react/nav/SideNav.js.map +1 -1
- package/dist/react/nav/SideNavCustomize.d.ts.map +1 -1
- package/dist/react/nav/SideNavCustomize.js +2 -1
- package/dist/react/nav/SideNavCustomize.js.map +1 -1
- package/dist/react/palette/CommandPalette.d.ts.map +1 -1
- package/dist/react/palette/CommandPalette.js +3 -2
- package/dist/react/palette/CommandPalette.js.map +1 -1
- package/dist/server/drivers/pooledControlStores.pg.test.js +1 -0
- package/dist/server/drivers/pooledControlStores.pg.test.js.map +1 -1
- package/dist/server/drivers/pooledIdentityStore.pg.test.js +1 -0
- package/dist/server/drivers/pooledIdentityStore.pg.test.js.map +1 -1
- package/dist/server/drivers/retentionPolicy.pg.test.js +1 -0
- package/dist/server/drivers/retentionPolicy.pg.test.js.map +1 -1
- package/dist/server/guard/argon2Binding.d.ts +1 -1
- package/dist/server/guard/argon2Binding.js +1 -1
- package/dist/server/incidents/store.d.ts +3 -3
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/master-lock/masterLock.d.ts +1 -1
- package/dist/server/media-bun/index.js +1 -1
- package/dist/server/media-bun/index.js.map +1 -1
- package/dist/server/paths/appDataHome.d.ts +20 -0
- package/dist/server/paths/appDataHome.d.ts.map +1 -0
- package/dist/server/paths/appDataHome.js +69 -0
- package/dist/server/paths/appDataHome.js.map +1 -0
- package/dist/server/retention/archive.d.ts +3 -4
- package/dist/server/retention/archive.d.ts.map +1 -1
- package/dist/server/retention/archive.js +20 -15
- package/dist/server/retention/archive.js.map +1 -1
- package/dist/server/retention/index.d.ts +1 -1
- package/dist/server/retention/index.js +1 -1
- package/dist/server/retention/policy.d.ts +2 -2
- package/dist/server/retention/policy.d.ts.map +1 -1
- package/dist/server/retention/policy.js +1 -1
- package/dist/server/retention/store.d.ts.map +1 -1
- package/dist/server/retention/store.js +27 -20
- package/dist/server/retention/store.js.map +1 -1
- package/dist/server/runtime/localHost.d.ts +10 -5
- package/dist/server/runtime/localHost.d.ts.map +1 -1
- package/dist/server/runtime/localHost.js +12 -8
- package/dist/server/runtime/localHost.js.map +1 -1
- package/dist/server/runtime/meterRollup.pg.test.js +1 -0
- package/dist/server/runtime/meterRollup.pg.test.js.map +1 -1
- package/dist/server/runtime/recipe.d.ts +4 -4
- package/dist/server/runtime/retentionJob.pg.test.js +1 -0
- package/dist/server/runtime/retentionJob.pg.test.js.map +1 -1
- package/dist/server/search/pgSearchProvider.pg.test.js +1 -0
- package/dist/server/search/pgSearchProvider.pg.test.js.map +1 -1
- package/dist/shared/errors.d.ts +1 -1
- package/dist/shared/errors.js +1 -1
- package/dist/styles-static.css +413 -0
- package/dist/styles-utilities.css +5074 -0
- package/dist/styles.css +49 -0
- package/package.json +30 -168
- package/scripts/generateStaticStyles.ts +122 -0
- package/scripts/generateUtilityStyles.ts +215 -0
- package/scripts/publicSurface.ts +455 -0
- package/scripts/testTmpRoot.ts +34 -5
- package/src/client/index.ts +1 -1
- package/src/core/category/index.ts +2 -1
- package/src/core/category/types.ts +1 -1
- package/src/core/file-tree/fileTreeModel.spec.ts +134 -0
- package/src/core/file-tree/fileTreeModel.ts +104 -0
- package/src/core/layout/index.ts +1 -1
- package/src/core/media/index.ts +1 -1
- package/src/core/media/types.ts +1 -1
- package/src/core/media/uploadLimits.ts +1 -1
- package/src/core/sharing/model.ts +1 -1
- package/src/ctgr/index.ts +1 -1
- package/src/declaredDepsAreImported.spec.ts +87 -0
- package/src/demoFixture.spec.ts +133 -0
- package/src/index.ts +1 -1
- package/src/lockfileIsTracked.spec.ts +50 -0
- package/src/namedSubpathsResolve.spec.ts +119 -0
- package/src/publicSurface.spec.ts +72 -0
- package/src/react/activity-bar/ActivityBar.tsx +17 -0
- package/src/react/analytics/index.ts +1 -1
- package/src/react/chat/MessageComposer.tsx +5 -4
- package/src/react/components/Button.tsx +3 -2
- package/src/react/components/Checkbox.spec.tsx +51 -0
- package/src/react/components/Checkbox.tsx +68 -26
- package/src/react/components/CodeArea.tsx +2 -1
- package/src/react/components/CodeChip.spec.tsx +2 -2
- package/src/react/components/Collapsible.tsx +2 -1
- package/src/react/components/ComboboxField.tsx +3 -2
- package/src/react/components/CopyButton.tsx +2 -1
- package/src/react/components/CopyableText.spec.tsx +1 -1
- package/src/react/components/DataTable/ColumnChecklist.tsx +2 -1
- package/src/react/components/DataTable/Pagination.tsx +2 -1
- package/src/react/components/DataTable/SortHeader.tsx +2 -1
- package/src/react/components/DataTable/{FilterBuilder.tsx โ TableFilterBuilder.tsx} +33 -5
- package/src/react/components/DataTable/Toolbar.tsx +3 -3
- package/src/react/components/DataTable/cells/LinkCell.tsx +2 -1
- package/src/react/components/DataTable/index.ts +8 -1
- package/src/react/components/DragHandle.tsx +2 -1
- package/src/react/components/EmojiField.tsx +2 -1
- package/src/react/components/EventTimeline.tsx +3 -2
- package/src/react/components/FileLocation.spec.tsx +1 -1
- package/src/react/components/IconButton.tsx +1 -1
- package/src/react/components/InfoHint.tsx +5 -4
- package/src/react/components/InheritBadge.tsx +1 -1
- package/src/react/components/InlineEditableText.tsx +2 -1
- package/src/react/components/MeterList.tsx +2 -1
- package/src/react/components/PathText.tsx +4 -3
- package/src/react/components/RatingField.tsx +2 -1
- package/src/react/components/SecretInput.tsx +3 -3
- package/src/react/components/SegmentedControl.tsx +2 -2
- package/src/react/components/StarRating.spec.tsx +2 -2
- package/src/react/components/StarRating.tsx +9 -5
- package/src/react/components/StatStrip.tsx +2 -1
- package/src/react/components/StatTile.tsx +3 -3
- package/src/react/components/Tabs.spec.tsx +3 -2
- package/src/react/components/Tabs.tsx +2 -1
- package/src/react/components/TruncatedText.tsx +2 -1
- package/src/react/components/calendar/MonthGrid.spec.tsx +2 -1
- package/src/react/components/calendar/MonthGrid.tsx +2 -1
- package/src/react/components/calendar/WeekView.tsx +2 -1
- package/src/react/components/revealOnHover.ts +3 -2
- package/src/react/file-tree/FileTree.spec.tsx +248 -0
- package/src/react/file-tree/FileTree.tsx +304 -4
- package/src/react/file-tree/fileTree.css +52 -0
- package/src/react/file-tree/fileTreeGeometry.fixture.json +151 -0
- package/src/react/file-tree/fileTreeGeometry.spec.ts +33 -52
- package/src/react/filter-rail/FilterRail.spec.tsx +2 -1
- package/src/react/filter-rail/FilterRail.tsx +5 -4
- package/src/react/folder-tree/FolderTreeRail.tsx +2 -1
- package/src/react/index.ts +13 -3
- package/src/react/layoutEngine/index.ts +1 -1
- package/src/react/layoutEngine/types.ts +1 -1
- package/src/react/lib/field.ts +4 -2
- package/src/react/lib/floatingStyles.spec.ts +2 -1
- package/src/react/lib/floatingStyles.ts +3 -1
- package/src/react/lib/tapTarget.ts +100 -0
- package/src/react/media/MaskPainter.tsx +2 -2
- package/src/react/media/VideoTrimmer.tsx +1 -1
- package/src/react/media/denseGallery.css +3 -3
- package/src/react/media/ffmpegWasm.ts +2 -2
- package/src/react/media/videoPlayer.css +42 -0
- package/src/react/namespace/NamespaceTargetCapsule.tsx +2 -2
- package/src/react/nav/BreadcrumbTrail.tsx +4 -3
- package/src/react/nav/HubTileGrid.tsx +2 -1
- package/src/react/nav/SideNav.tsx +4 -3
- package/src/react/nav/SideNavCustomize.tsx +2 -1
- package/src/react/palette/CommandPalette.tsx +3 -2
- package/src/server/ctgr.ts +1 -1
- package/src/server/drivers/pooledControlStores.pg.test.ts +1 -0
- package/src/server/drivers/pooledIdentityStore.pg.test.ts +1 -0
- package/src/server/drivers/retentionPolicy.pg.test.ts +1 -0
- package/src/server/guard/argon2Binding.ts +1 -1
- package/src/server/incidents/store.ts +3 -3
- package/src/server/index.ts +1 -1
- package/src/server/master-lock/masterLock.spec.ts +18 -2
- package/src/server/master-lock/masterLock.ts +1 -1
- package/src/server/media-bun/image.spec.ts +1 -0
- package/src/server/media-bun/index.ts +1 -1
- package/src/server/media-bun/video.spec.ts +1 -0
- package/src/server/paths/appDataHome.spec.ts +87 -0
- package/src/server/paths/appDataHome.ts +84 -0
- package/src/server/retention/archive.spec.ts +26 -4
- package/src/server/retention/archive.ts +20 -15
- package/src/server/retention/index.ts +1 -1
- package/src/server/retention/policy.ts +2 -2
- package/src/server/retention/store.spec.ts +38 -10
- package/src/server/retention/store.ts +26 -19
- package/src/server/runtime/localHost.spec.ts +22 -3
- package/src/server/runtime/localHost.ts +12 -9
- package/src/server/runtime/meterRollup.pg.test.ts +1 -0
- package/src/server/runtime/recipe.ts +4 -4
- package/src/server/runtime/retentionJob.pg.test.ts +1 -0
- package/src/server/search/pgSearchProvider.pg.test.ts +1 -0
- package/src/server/storage/binaryServerProcessing.integration.spec.ts +6 -2
- package/src/server/storage/chunked.integration.spec.ts +5 -2
- package/src/server/storage/fileUploadClient-chunked.integration.spec.ts +5 -2
- package/src/server/storage/files/adapter.photo.display.spec.ts +1 -0
- package/src/server/storage/imageThumbnail.spec.ts +1 -0
- package/src/server/storage/transcode.integration.spec.ts +5 -2
- package/src/server/storage/videoProcessor.posterOnly.spec.ts +1 -0
- package/src/shared/errors.ts +1 -1
- package/src/skippedTestsAreAnswered.spec.ts +215 -0
- package/src/styles-static.css +413 -0
- package/src/styles-utilities.css +5074 -0
- package/src/styles.css +49 -0
- package/src/stylesStaticMatches.spec.ts +134 -0
- package/src/stylesUtilitiesMatches.spec.ts +314 -0
- package/src/tapTargetStylesheet.spec.ts +150 -0
- package/src/testTmpRoot.spec.ts +102 -0
- package/dist/core/duplicates/index.d.ts +0 -116
- package/dist/core/duplicates/index.d.ts.map +0 -1
- package/dist/core/duplicates/index.js +0 -178
- package/dist/core/duplicates/index.js.map +0 -1
- package/dist/core/namespace/helpers.d.ts +0 -14
- package/dist/core/namespace/helpers.d.ts.map +0 -1
- package/dist/core/namespace/helpers.js +0 -29
- package/dist/core/namespace/helpers.js.map +0 -1
- package/dist/core/namespace/index.d.ts +0 -4
- package/dist/core/namespace/index.d.ts.map +0 -1
- package/dist/core/namespace/index.js +0 -6
- package/dist/core/namespace/index.js.map +0 -1
- package/dist/core/namespace/memoryStore.d.ts +0 -19
- package/dist/core/namespace/memoryStore.d.ts.map +0 -1
- package/dist/core/namespace/memoryStore.js +0 -70
- package/dist/core/namespace/memoryStore.js.map +0 -1
- package/dist/core/namespace/types.d.ts +0 -44
- package/dist/core/namespace/types.d.ts.map +0 -1
- package/dist/core/namespace/types.js +0 -8
- package/dist/core/namespace/types.js.map +0 -1
- package/dist/game/core/DisposeRegistry.d.ts +0 -42
- package/dist/game/core/DisposeRegistry.d.ts.map +0 -1
- package/dist/game/core/DisposeRegistry.js +0 -67
- package/dist/game/core/DisposeRegistry.js.map +0 -1
- package/dist/game/core/drawBudget.d.ts +0 -26
- package/dist/game/core/drawBudget.d.ts.map +0 -1
- package/dist/game/core/drawBudget.js +0 -55
- package/dist/game/core/drawBudget.js.map +0 -1
- package/dist/game/core/fixedTimestep.d.ts +0 -38
- package/dist/game/core/fixedTimestep.d.ts.map +0 -1
- package/dist/game/core/fixedTimestep.js +0 -47
- package/dist/game/core/fixedTimestep.js.map +0 -1
- package/dist/game/core/index.d.ts +0 -9
- package/dist/game/core/index.d.ts.map +0 -1
- package/dist/game/core/index.js +0 -8
- package/dist/game/core/index.js.map +0 -1
- package/dist/game/core/inputCollector.d.ts +0 -82
- package/dist/game/core/inputCollector.d.ts.map +0 -1
- package/dist/game/core/inputCollector.js +0 -176
- package/dist/game/core/inputCollector.js.map +0 -1
- package/dist/game/core/resizeCanvas.d.ts +0 -19
- package/dist/game/core/resizeCanvas.d.ts.map +0 -1
- package/dist/game/core/resizeCanvas.js +0 -29
- package/dist/game/core/resizeCanvas.js.map +0 -1
- package/dist/game/react/index.d.ts +0 -3
- package/dist/game/react/index.d.ts.map +0 -1
- package/dist/game/react/index.js +0 -5
- package/dist/game/react/index.js.map +0 -1
- package/dist/game/react/useGameEngine.d.ts +0 -64
- package/dist/game/react/useGameEngine.d.ts.map +0 -1
- package/dist/game/react/useGameEngine.js +0 -78
- package/dist/game/react/useGameEngine.js.map +0 -1
- package/dist/react/canvas/CanvasNavHUD.d.ts +0 -64
- package/dist/react/canvas/CanvasNavHUD.d.ts.map +0 -1
- package/dist/react/canvas/CanvasNavHUD.js +0 -58
- package/dist/react/canvas/CanvasNavHUD.js.map +0 -1
- package/dist/react/canvas/fitLabel.d.ts +0 -53
- package/dist/react/canvas/fitLabel.d.ts.map +0 -1
- package/dist/react/canvas/fitLabel.js +0 -104
- package/dist/react/canvas/fitLabel.js.map +0 -1
- package/dist/react/canvas/index.d.ts +0 -4
- package/dist/react/canvas/index.d.ts.map +0 -1
- package/dist/react/canvas/index.js +0 -4
- package/dist/react/canvas/index.js.map +0 -1
- package/dist/react/canvas/lod.d.ts +0 -40
- package/dist/react/canvas/lod.d.ts.map +0 -1
- package/dist/react/canvas/lod.js +0 -37
- package/dist/react/canvas/lod.js.map +0 -1
- package/dist/react/components/DataTable/FilterBuilder.d.ts +0 -38
- package/dist/react/components/DataTable/FilterBuilder.d.ts.map +0 -1
- package/dist/react/components/DataTable/FilterBuilder.js.map +0 -1
- package/dist/react/seo/HeadProvider.d.ts +0 -14
- package/dist/react/seo/HeadProvider.d.ts.map +0 -1
- package/dist/react/seo/HeadProvider.js +0 -44
- package/dist/react/seo/HeadProvider.js.map +0 -1
- package/dist/react/seo/applyHeadToDocument.d.ts +0 -3
- package/dist/react/seo/applyHeadToDocument.d.ts.map +0 -1
- package/dist/react/seo/applyHeadToDocument.js +0 -66
- package/dist/react/seo/applyHeadToDocument.js.map +0 -1
- package/dist/react/seo/index.d.ts +0 -3
- package/dist/react/seo/index.d.ts.map +0 -1
- package/dist/react/seo/index.js +0 -7
- package/dist/react/seo/index.js.map +0 -1
- package/dist/react/timeline/ClipView.d.ts +0 -29
- package/dist/react/timeline/ClipView.d.ts.map +0 -1
- package/dist/react/timeline/ClipView.js +0 -53
- package/dist/react/timeline/ClipView.js.map +0 -1
- package/dist/react/timeline/Playhead.d.ts +0 -16
- package/dist/react/timeline/Playhead.d.ts.map +0 -1
- package/dist/react/timeline/Playhead.js +0 -26
- package/dist/react/timeline/Playhead.js.map +0 -1
- package/dist/react/timeline/TimeRuler.d.ts +0 -21
- package/dist/react/timeline/TimeRuler.d.ts.map +0 -1
- package/dist/react/timeline/TimeRuler.js +0 -31
- package/dist/react/timeline/TimeRuler.js.map +0 -1
- package/dist/react/timeline/TimelineSurface.d.ts +0 -29
- package/dist/react/timeline/TimelineSurface.d.ts.map +0 -1
- package/dist/react/timeline/TimelineSurface.js +0 -56
- package/dist/react/timeline/TimelineSurface.js.map +0 -1
- package/dist/react/timeline/TrackLane.d.ts +0 -31
- package/dist/react/timeline/TrackLane.d.ts.map +0 -1
- package/dist/react/timeline/TrackLane.js +0 -32
- package/dist/react/timeline/TrackLane.js.map +0 -1
- package/dist/react/timeline/index.d.ts +0 -6
- package/dist/react/timeline/index.d.ts.map +0 -1
- package/dist/react/timeline/index.js +0 -12
- package/dist/react/timeline/index.js.map +0 -1
- package/dist/server/private-media/index.d.ts +0 -7
- package/dist/server/private-media/index.d.ts.map +0 -1
- package/dist/server/private-media/index.js +0 -13
- package/dist/server/private-media/index.js.map +0 -1
- package/dist/server/seo/index.d.ts +0 -3
- package/dist/server/seo/index.d.ts.map +0 -1
- package/dist/server/seo/index.js +0 -6
- package/dist/server/seo/index.js.map +0 -1
- package/dist/server/seo/mountSeoRoutes.d.ts +0 -9
- package/dist/server/seo/mountSeoRoutes.d.ts.map +0 -1
- package/dist/server/seo/mountSeoRoutes.js +0 -19
- package/dist/server/seo/mountSeoRoutes.js.map +0 -1
- package/dist/server/seo/serveHeadInjectedIndex.d.ts +0 -9
- package/dist/server/seo/serveHeadInjectedIndex.d.ts.map +0 -1
- package/dist/server/seo/serveHeadInjectedIndex.js +0 -20
- package/dist/server/seo/serveHeadInjectedIndex.js.map +0 -1
- package/dist/upload.d.ts +0 -148
- package/dist/upload.d.ts.map +0 -1
- package/dist/upload.js +0 -159
- package/dist/upload.js.map +0 -1
- package/dist/verify/runtime/index.d.ts +0 -16
- package/dist/verify/runtime/index.d.ts.map +0 -1
- package/dist/verify/runtime/index.js +0 -16
- package/dist/verify/runtime/index.js.map +0 -1
- package/scripts/demoCheck.ts +0 -42
- package/src/core/duplicates/index.spec.ts +0 -116
- package/src/core/duplicates/index.ts +0 -203
- package/src/core/namespace/helpers.ts +0 -31
- package/src/core/namespace/index.ts +0 -12
- package/src/core/namespace/memoryStore.ts +0 -77
- package/src/core/namespace/namespace.spec.ts +0 -117
- package/src/core/namespace/types.ts +0 -53
- package/src/game/core/DisposeRegistry.spec.ts +0 -53
- package/src/game/core/DisposeRegistry.ts +0 -70
- package/src/game/core/drawBudget.spec.ts +0 -51
- package/src/game/core/drawBudget.ts +0 -75
- package/src/game/core/fixedTimestep.spec.ts +0 -91
- package/src/game/core/fixedTimestep.ts +0 -79
- package/src/game/core/index.ts +0 -16
- package/src/game/core/inputCollector.spec.ts +0 -29
- package/src/game/core/inputCollector.ts +0 -245
- package/src/game/core/resizeCanvas.ts +0 -31
- package/src/game/react/index.ts +0 -10
- package/src/game/react/useGameEngine.ts +0 -125
- package/src/react/canvas/CanvasNavHUD.spec.tsx +0 -62
- package/src/react/canvas/CanvasNavHUD.tsx +0 -210
- package/src/react/canvas/fitLabel.spec.ts +0 -82
- package/src/react/canvas/fitLabel.ts +0 -150
- package/src/react/canvas/index.ts +0 -15
- package/src/react/canvas/lod.ts +0 -53
- package/src/react/seo/HeadProvider.tsx +0 -67
- package/src/react/seo/applyHeadToDocument.ts +0 -73
- package/src/react/seo/index.ts +0 -12
- package/src/react/seo/seo.spec.tsx +0 -101
- package/src/react/timeline/ClipView.tsx +0 -146
- package/src/react/timeline/Playhead.tsx +0 -63
- package/src/react/timeline/TimeRuler.tsx +0 -85
- package/src/react/timeline/TimelineSurface.tsx +0 -157
- package/src/react/timeline/TrackLane.tsx +0 -87
- package/src/react/timeline/index.ts +0 -12
- package/src/react/timeline/timeline.spec.tsx +0 -175
- package/src/server/private-media/index.ts +0 -45
- package/src/server/seo/index.ts +0 -8
- package/src/server/seo/mountSeoRoutes.ts +0 -37
- package/src/server/seo/seo.spec.ts +0 -128
- package/src/server/seo/serveHeadInjectedIndex.ts +0 -36
- package/src/upload.spec.ts +0 -300
- package/src/upload.ts +0 -288
- package/src/verify/runtime/index.ts +0 -64
package/src/styles.css
CHANGED
|
@@ -5,6 +5,55 @@
|
|
|
5
5
|
* @import "cursedbelt/theme.css";
|
|
6
6
|
* This `@source` makes Tailwind v4 scan cursedbelt's source files so the utility
|
|
7
7
|
* classes used by its components are generated in the app's stylesheet.
|
|
8
|
+
*
|
|
9
|
+
* โโ Two ways in, and which one an app wants โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
10
|
+
* `@source "."` is the expensive half: it points Tailwind's JIT at cursedbelt's
|
|
11
|
+
* ENTIRE source tree, so the app emits the union of the whole design system
|
|
12
|
+
* whether it renders three components or eighty, and its own build walks 827
|
|
13
|
+
* files it does not own. Everything else in this file is CSS the components
|
|
14
|
+
* genuinely need and no app can do without.
|
|
15
|
+
*
|
|
16
|
+
* A CSS `@import` takes the whole file, so the split is two more subpaths:
|
|
17
|
+
*
|
|
18
|
+
* cursedbelt/styles.css this file โ the real CSS + the `@source` scan
|
|
19
|
+
* cursedbelt/styles-static.css the same file MINUS the `@source` line
|
|
20
|
+
* cursedbelt/styles-utilities.css the utilities that `@source` line would have
|
|
21
|
+
* produced, precompiled at cursedbelt's build
|
|
22
|
+
*
|
|
23
|
+
* The second two are a PAIR and neither is usable alone โ `styles-static.css` on
|
|
24
|
+
* its own gets the tooltip pointer-events fix, the Progress keyframes, the
|
|
25
|
+
* lightbox bridge, the ambient/glass layer and `tw-animate-css`, and emits none
|
|
26
|
+
* of cursedbelt's utility classes, i.e. an unstyled app:
|
|
27
|
+
*
|
|
28
|
+
* @import "tailwindcss";
|
|
29
|
+
* @import "cursedbelt/styles-static.css";
|
|
30
|
+
* @import "cursedbelt/styles-utilities.css";
|
|
31
|
+
* @import "cursedbelt/theme.css";
|
|
32
|
+
*
|
|
33
|
+
* ๐ด Do NOT hand-list cursedbelt's component files to get the utilities back.
|
|
34
|
+
* Tailwind v4 resolves every `@source` to a DIRECTORY and scans it recursively โ
|
|
35
|
+
* a file path and a glob are both silently widened to their parent โ and
|
|
36
|
+
* `VirtualList`/`VirtualCardGrid` live at the root of `src/react/`, so the finest
|
|
37
|
+
* grain an app can write is the whole design system. A relative `@source` reaching
|
|
38
|
+
* into this package's source tree is also a `file:` dependency wearing a
|
|
39
|
+
* stylesheet's clothes, and it buys nothing: it is the thing the previous
|
|
40
|
+
* generation's 29-line-per-app arrangement is remembered for, and those 29 lines
|
|
41
|
+
* were never file-level scoping either.
|
|
42
|
+
*
|
|
43
|
+
* The pair is not SMALLER than this file โ measured, both routes emit the same
|
|
44
|
+
* rules within 54 bytes. What it buys is a build that scans nothing of another
|
|
45
|
+
* package's source tree, and a `styles-static.css` that finally has a correct
|
|
46
|
+
* use. The measurements, and what would actually shrink a consumer, are in the
|
|
47
|
+
* header of `scripts/generateUtilityStyles.ts`.
|
|
48
|
+
*
|
|
49
|
+
* ๐ด This header is in BOTH of the first two files, because one is generated from
|
|
50
|
+
* the other and a test holds them byte-identical apart from the `@source` line.
|
|
51
|
+
* Always edit `src/styles.css`; never `src/styles-static.css`, which
|
|
52
|
+
* `scripts/generateStaticStyles.ts` rewrites on every `bun run build` and
|
|
53
|
+
* `src/stylesStaticMatches.spec.ts` fails on when it is stale. Editing this file
|
|
54
|
+
* invalidates `src/styles-utilities.css` too โ it is compiled against the static
|
|
55
|
+
* variant, `scripts/generateUtilityStyles.ts` rewrites it in the same build step,
|
|
56
|
+
* and `src/stylesUtilitiesMatches.spec.ts` is what fails when it is stale.
|
|
8
57
|
*/
|
|
9
58
|
|
|
10
59
|
/* Animation utilities (animate-in/out, fade/zoom/slide-in-from-*) consumed by the
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/styles-static.css` is `src/styles.css` minus its `@source` line โ and
|
|
3
|
+
* nothing else (2026-09-14).
|
|
4
|
+
*
|
|
5
|
+
* ## What this exists to prevent
|
|
6
|
+
*
|
|
7
|
+
* The scoped entry point has to be a SECOND FILE, because a CSS `@import` takes
|
|
8
|
+
* the whole of the one it is given: an app cannot ask for `styles.css` without
|
|
9
|
+
* `@source "."`, and `@source "."` is what makes every consumer emit the union
|
|
10
|
+
* of the entire design system.
|
|
11
|
+
*
|
|
12
|
+
* The previous generation's second file was `satellite-kit/styles/belt-static.css`
|
|
13
|
+
* โ a hand-kept copy with the one line deleted and nothing comparing the two.
|
|
14
|
+
* That is a file that drifts, and drifts SILENTLY: the day someone adds a rule to
|
|
15
|
+
* `styles.css` and not to the copy, every scoped app quietly loses it. The loss is
|
|
16
|
+
* invisible in review (the diff looks complete), invisible to `tsc` (it is CSS),
|
|
17
|
+
* and invisible to Biome (nothing is wrong with either file). It shows up as a
|
|
18
|
+
* tooltip eating a click, or a Progress bar that never animates, in one app, weeks
|
|
19
|
+
* later.
|
|
20
|
+
*
|
|
21
|
+
* So the copy is OUTPUT โ `scripts/generateStaticStyles.ts`, which `bun run build`
|
|
22
|
+
* runs before it compiles โ and this spec is the gate that makes that true rather
|
|
23
|
+
* than merely intended. `bun run verify` builds before it tests, so a forgotten
|
|
24
|
+
* regeneration self-heals there; editing `styles.css` and running `bun test src`
|
|
25
|
+
* alone is where this goes red.
|
|
26
|
+
*
|
|
27
|
+
* The assertions deliberately come at it from both sides: one compares the file on
|
|
28
|
+
* disk to what the generator produces (is the checked-in output current?), and the
|
|
29
|
+
* rest compare it to `styles.css` DIRECTLY (is the generator still doing only what
|
|
30
|
+
* it claims?). A spec that only asked the generator to agree with itself would pass
|
|
31
|
+
* a generator that had started dropping real rules.
|
|
32
|
+
*
|
|
33
|
+
* ๐ด `Bun.file`, NOT node:fs โ this package's test preload virtualizes node:fs, so
|
|
34
|
+
* a `readFileSync` here would read MOCK data and pass against files it never
|
|
35
|
+
* opened. Same trap as `src/publishShape.spec.ts`; see its header.
|
|
36
|
+
*/
|
|
37
|
+
import { describe, expect, it } from 'bun:test';
|
|
38
|
+
import pkg from '../package.json';
|
|
39
|
+
import {
|
|
40
|
+
REPO_ROOT,
|
|
41
|
+
SOURCE_DIRECTIVE,
|
|
42
|
+
STATIC_STYLESHEET,
|
|
43
|
+
readStylesheets,
|
|
44
|
+
staticStylesFrom,
|
|
45
|
+
} from '../scripts/generateStaticStyles';
|
|
46
|
+
|
|
47
|
+
const { source, expected, onDisk } = await readStylesheets();
|
|
48
|
+
|
|
49
|
+
/** The command every failure below is fixed by, as a line the owner can paste. */
|
|
50
|
+
const REGENERATE = `cd ${REPO_ROOT} && bun run styles:static`;
|
|
51
|
+
|
|
52
|
+
const lines = (css: string): string[] => css.split('\n');
|
|
53
|
+
const directives = (css: string): string[] => lines(css).filter((l) => SOURCE_DIRECTIVE.test(l));
|
|
54
|
+
|
|
55
|
+
describe('styles-static.css is a generated, gated copy of styles.css', () => {
|
|
56
|
+
it('is checked in and byte-identical to what the generator produces', () => {
|
|
57
|
+
expect(onDisk, `${STATIC_STYLESHEET} is missing. It is generated:\n ${REGENERATE}`).not.toBeNull();
|
|
58
|
+
expect(
|
|
59
|
+
onDisk,
|
|
60
|
+
`${STATIC_STYLESHEET} is stale โ src/styles.css changed and the static variant was not\n` +
|
|
61
|
+
'regenerated, so every app importing `cursedbelt/styles-static.css` is missing the\n' +
|
|
62
|
+
`difference. Run:\n ${REGENERATE}`,
|
|
63
|
+
).toBe(expected);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('drops the `@source` line and keeps every other line, in order', () => {
|
|
67
|
+
// The independent half: compared against styles.css itself, not against the
|
|
68
|
+
// generator's output. Blank lines are excluded from the sequence check only
|
|
69
|
+
// because the dropped directive takes its trailing blank line with it; the
|
|
70
|
+
// exact line-count assertion below is what pins that down.
|
|
71
|
+
const kept = lines(source).filter((l) => l.trim() !== '' && !SOURCE_DIRECTIVE.test(l));
|
|
72
|
+
expect(lines(expected).filter((l) => l.trim() !== '')).toEqual(kept);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('removes exactly the directive and the blank line it leaves behind', () => {
|
|
76
|
+
expect(
|
|
77
|
+
directives(source),
|
|
78
|
+
'src/styles.css must carry exactly the one `@source "."` this whole split is about',
|
|
79
|
+
).toEqual(['@source ".";']);
|
|
80
|
+
expect(directives(expected), 'the static variant must carry no `@source` at all').toEqual([]);
|
|
81
|
+
// 2 = the directive + the now-orphaned blank line after it. A third line
|
|
82
|
+
// going missing means the transform grew a second job.
|
|
83
|
+
expect(lines(source).length - lines(expected).length).toBe(2);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('is not a pointless duplicate', () => {
|
|
87
|
+
// If `@source "."` ever leaves styles.css, the two files become identical and
|
|
88
|
+
// the static subpath is dead weight โ but worse, the FULL entry point has
|
|
89
|
+
// stopped scanning and every consuming app emits no cursedbelt utilities.
|
|
90
|
+
expect(expected).not.toBe(source);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('is exported as a subpath that resolves into a directory `files` ships', () => {
|
|
94
|
+
const exports = (pkg as { exports: Record<string, unknown> }).exports;
|
|
95
|
+
expect(exports['./styles-static.css']).toBe(`./${STATIC_STYLESHEET}`);
|
|
96
|
+
// It must resolve out of `src/`, exactly like its sibling: `./styles.css`
|
|
97
|
+
// points at the SOURCE tree (not dist) because `@source "."` has to resolve
|
|
98
|
+
// relative to the files it is meant to scan, and a pair of app-facing
|
|
99
|
+
// stylesheets served from two different directories is a difference nobody
|
|
100
|
+
// can see in CSS.
|
|
101
|
+
expect(exports['./styles.css']).toBe('./src/styles.css');
|
|
102
|
+
// `files` ships whole directories, so `src` is what actually puts both of
|
|
103
|
+
// them in the tarball. publishShape.spec.ts proves the general rule; this is
|
|
104
|
+
// the one line that makes THIS subpath's failure legible.
|
|
105
|
+
expect((pkg as { files: string[] }).files).toContain('src');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('resolves as `cursedbelt/styles-static.css` through the real resolver', () => {
|
|
109
|
+
// The assertions above read the manifest; this one RESOLVES the specifier the
|
|
110
|
+
// way a consumer's bundler does, through the package's own `exports` map (bun
|
|
111
|
+
// honours a self-reference by package name). An export entry pointing at a
|
|
112
|
+
// path that does not exist satisfies every string comparison in this file and
|
|
113
|
+
// fails here โ which is the failure `belt-static.css` would have been.
|
|
114
|
+
expect(Bun.resolveSync(`${pkg.name}/styles-static.css`, REPO_ROOT)).toBe(
|
|
115
|
+
`${REPO_ROOT}/${STATIC_STYLESHEET}`,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('is regenerated by the build, so a stale copy cannot be published', () => {
|
|
120
|
+
// `prepublishOnly` โ `verify` โ `build`, so the tarball's copy is always the
|
|
121
|
+
// one this repo's current styles.css produces. Without this step in `build`,
|
|
122
|
+
// the only thing standing between a drifted copy and a publish is a person
|
|
123
|
+
// remembering to run a script.
|
|
124
|
+
expect((pkg as { scripts: Record<string, string> }).scripts.build).toContain(
|
|
125
|
+
'scripts/generateStaticStyles.ts',
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('exposes the transform as a pure function', () => {
|
|
130
|
+
// Cheap guard on the one behaviour every assertion above leans on.
|
|
131
|
+
expect(staticStylesFrom('a\n@source ".";\n\nb\n')).toBe('a\nb\n');
|
|
132
|
+
expect(staticStylesFrom('a\nb\n')).toBe('a\nb\n');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/styles-utilities.css` is a current, COMPLETE precompile of `src/react/**`
|
|
3
|
+
* (2026-09-14).
|
|
4
|
+
*
|
|
5
|
+
* ## What this exists to prevent
|
|
6
|
+
*
|
|
7
|
+
* The artifact replaces a `@source` scan the consuming app used to run. The scan
|
|
8
|
+
* was self-healing โ it read whatever was in `src/react` at the app's build time โ
|
|
9
|
+
* and the artifact is not: it is a file, generated once here, and every app that
|
|
10
|
+
* imports it gets exactly what was checked in.
|
|
11
|
+
*
|
|
12
|
+
* Its failure mode is SILENT and it is the worst one available. A stale artifact
|
|
13
|
+
* is missing the utilities a new component uses, so that component renders
|
|
14
|
+
* unstyled in every app; a collapsed scan writes an empty utility layer, so the
|
|
15
|
+
* WHOLE app renders unstyled. Neither is visible to `tsc` (it is CSS), to Biome
|
|
16
|
+
* (nothing is wrong with the file), or to a byte count read in one direction (a
|
|
17
|
+
* smaller stylesheet is the thing the split was asked for).
|
|
18
|
+
*
|
|
19
|
+
* So the assertions below do not compare size. They compile a consuming app BOTH
|
|
20
|
+
* ways โ `@import "cursedbelt/styles.css"` (today, scanning) against
|
|
21
|
+
* `styles-static.css` + `styles-utilities.css` with `source(none)` (after
|
|
22
|
+
* adoption, scanning nothing) โ and compare the rules that come out: every
|
|
23
|
+
* selector, every declaration, every custom property the declarations reference.
|
|
24
|
+
* `scripts/generateUtilityStyles.ts` owns the WHY and the measurements; this file
|
|
25
|
+
* is the WHETHER.
|
|
26
|
+
*
|
|
27
|
+
* Two differences between the two routes are real, deliberate and normalised for
|
|
28
|
+
* below rather than papered over โ each is asserted to STILL BE the only one of
|
|
29
|
+
* its kind, so the normalisation cannot quietly widen into an amnesty:
|
|
30
|
+
*
|
|
31
|
+
* ยท `var(--x)` on the scanning route vs `var(--x, <literal>)` on the
|
|
32
|
+
* precompiled one, for tokens that reach the compiler through `@reference`.
|
|
33
|
+
* Same variable, same computed value, plus a fallback.
|
|
34
|
+
* ยท `@theme inline` alias declarations (`--color-card: var(--card)`) that the
|
|
35
|
+
* scanning route emits into `:root` and nothing ever reads โ `inline` means
|
|
36
|
+
* the utility itself carries `var(--card)`, so the alias is bookkeeping. Only
|
|
37
|
+
* custom properties something actually `var()`s are compared.
|
|
38
|
+
*
|
|
39
|
+
* `Bun.file` for every read here, not node:fs โ the convention the sibling specs
|
|
40
|
+
* (`src/publishShape.spec.ts`, `src/declaredDepsAreImported.spec.ts`) state and
|
|
41
|
+
* the reason they give: a spec that reads through a mockable builtin can pass
|
|
42
|
+
* against files it never opened. The Tailwind compiler this file drives does use
|
|
43
|
+
* node:fs internally, which is unavoidable and is the point โ it is resolving the
|
|
44
|
+
* same `@import`s a consumer's bundler will.
|
|
45
|
+
*/
|
|
46
|
+
import { describe, expect, it } from 'bun:test';
|
|
47
|
+
import pkg from '../package.json';
|
|
48
|
+
import {
|
|
49
|
+
CONSUMER_PRECOMPILED,
|
|
50
|
+
CONSUMER_SCANNING,
|
|
51
|
+
MIN_CANDIDATES,
|
|
52
|
+
REPO_ROOT,
|
|
53
|
+
UTILITY_STYLESHEET,
|
|
54
|
+
compileStylesheet,
|
|
55
|
+
readUtilityStylesheet,
|
|
56
|
+
} from '../scripts/generateUtilityStyles';
|
|
57
|
+
|
|
58
|
+
const { expected, onDisk, candidates } = await readUtilityStylesheet();
|
|
59
|
+
|
|
60
|
+
/** The command every staleness failure below is fixed by, as a line the owner can paste. */
|
|
61
|
+
const REGENERATE = `cd ${REPO_ROOT} && bun run styles:utilities`;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Flatten a stylesheet into `at-rule โ โฆ โ selector` โ the set of declarations in
|
|
65
|
+
* that block, merging blocks that repeat (both routes emit `@keyframes spin`
|
|
66
|
+
* twice; a consumer's browser does not care, and neither should a comparison).
|
|
67
|
+
*
|
|
68
|
+
* A hand-rolled walk rather than a CSS parser because the only input is Tailwind's
|
|
69
|
+
* own output: well-formed, and the one construct that could confuse brace counting
|
|
70
|
+
* โ a `{` inside a string or a comment โ is skipped explicitly.
|
|
71
|
+
*/
|
|
72
|
+
const flatten = (css: string): Map<string, Set<string>> => {
|
|
73
|
+
const out = new Map<string, Set<string>>();
|
|
74
|
+
const stack: string[] = [];
|
|
75
|
+
const open: string[][] = [[]];
|
|
76
|
+
let buf = '';
|
|
77
|
+
const push = (raw: string): void => {
|
|
78
|
+
const decl = raw.trim();
|
|
79
|
+
if (decl) open[open.length - 1]?.push(decl);
|
|
80
|
+
};
|
|
81
|
+
for (let i = 0; i < css.length; i++) {
|
|
82
|
+
const ch = css[i];
|
|
83
|
+
if (ch === '/' && css[i + 1] === '*') {
|
|
84
|
+
i = css.indexOf('*/', i) + 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === '"' || ch === "'") {
|
|
88
|
+
const end = css.indexOf(ch, i + 1);
|
|
89
|
+
buf += css.slice(i, end + 1);
|
|
90
|
+
i = end;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (ch === '{') {
|
|
94
|
+
stack.push(buf.trim().replace(/\s+/g, ' '));
|
|
95
|
+
buf = '';
|
|
96
|
+
open.push([]);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (ch === '}') {
|
|
100
|
+
push(buf);
|
|
101
|
+
buf = '';
|
|
102
|
+
const key = stack.join(' โ ');
|
|
103
|
+
const set = out.get(key) ?? new Set<string>();
|
|
104
|
+
for (const decl of open.pop() ?? []) set.add(decl);
|
|
105
|
+
out.set(key, set);
|
|
106
|
+
stack.pop();
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// At depth 0 a `;` ends a statement at-rule (`@layer a, b;`, `@import โฆ`),
|
|
110
|
+
// which carries no declarations and must not stick to the next selector.
|
|
111
|
+
if (ch === ';') {
|
|
112
|
+
if (stack.length) push(buf);
|
|
113
|
+
buf = '';
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
buf += ch;
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** `var(--x, <anything>)` โ `var(--x)`, so a fallback reads as the same declaration. */
|
|
122
|
+
const dropFallbacks = (decl: string): string => decl.replace(/var\((\s*--[\w-]+)[^)]*\)/g, 'var($1)');
|
|
123
|
+
|
|
124
|
+
const CUSTOM_PROPERTY = /^(--[\w-]+)\s*:/;
|
|
125
|
+
|
|
126
|
+
/** Every custom property a stylesheet actually reads through `var()`. */
|
|
127
|
+
const referenced = (css: string): Set<string> => {
|
|
128
|
+
const out = new Set<string>();
|
|
129
|
+
for (const m of css.matchAll(/var\(\s*(--[\w-]+)/g)) out.add(m[1] as string);
|
|
130
|
+
return out;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** Every custom property a stylesheet declares, including `@property` registrations. */
|
|
134
|
+
const declared = (css: string): Set<string> => {
|
|
135
|
+
const out = new Set<string>();
|
|
136
|
+
for (const m of css.matchAll(/(?:^|[;{\s])(--[\w-]+)\s*:/g)) out.add(m[1] as string);
|
|
137
|
+
for (const m of css.matchAll(/@property\s+(--[\w-]+)/g)) out.add(m[1] as string);
|
|
138
|
+
return out;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// The two routes, compiled from the SAME candidate list (the classes cursedbelt's
|
|
142
|
+
// components use), so neither side is credited with utilities the other was never
|
|
143
|
+
// asked for. The precompiled route is built with no candidates at all: everything
|
|
144
|
+
// it emits came out of the artifact on disk.
|
|
145
|
+
const scanning = (await compileStylesheet(CONSUMER_SCANNING)).build(candidates);
|
|
146
|
+
const precompiled = (await compileStylesheet(CONSUMER_PRECOMPILED)).build([]);
|
|
147
|
+
const before = flatten(scanning);
|
|
148
|
+
const after = flatten(precompiled);
|
|
149
|
+
/** Hoisted: both loops below ask this per declaration, over 160 KB of CSS. */
|
|
150
|
+
const readByScanning = referenced(scanning);
|
|
151
|
+
|
|
152
|
+
describe('styles-utilities.css is a generated, gated precompile of src/react', () => {
|
|
153
|
+
it('is checked in and byte-identical to what the generator produces', () => {
|
|
154
|
+
expect(onDisk, `${UTILITY_STYLESHEET} is missing. It is generated:\n ${REGENERATE}`).not.toBeNull();
|
|
155
|
+
expect(
|
|
156
|
+
onDisk,
|
|
157
|
+
`${UTILITY_STYLESHEET} is stale โ src/react (or src/styles.css, which it is compiled\n` +
|
|
158
|
+
'against) changed and the artifact was not regenerated, so every app importing\n' +
|
|
159
|
+
`\`cursedbelt/styles-utilities.css\` is missing the difference. Run:\n ${REGENERATE}`,
|
|
160
|
+
).toBe(expected);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('was compiled from a scan that did not collapse', () => {
|
|
164
|
+
// The generator throws below its own floor; this is the assertion that says
|
|
165
|
+
// what the floor is FOR, and it fails here rather than at publish time.
|
|
166
|
+
expect(candidates.length).toBeGreaterThanOrEqual(MIN_CANDIDATES);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('has compiled two real consumer stylesheets to compare', () => {
|
|
170
|
+
// Guards every assertion below: two empty strings agree about everything.
|
|
171
|
+
expect(before.size).toBeGreaterThan(500);
|
|
172
|
+
expect(after.size).toBeGreaterThan(500);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('emits every rule the `@source` scan emits, with the same declarations', () => {
|
|
176
|
+
// The core proof, and the reason this spec does not read a byte count: an
|
|
177
|
+
// app that adopts the pair must RENDER the same. A missing key is a selector
|
|
178
|
+
// that vanished; a missing declaration is a property that stopped applying.
|
|
179
|
+
const lostRules: string[] = [];
|
|
180
|
+
const lostDeclarations: string[] = [];
|
|
181
|
+
for (const [key, decls] of before) {
|
|
182
|
+
const mine = after.get(key);
|
|
183
|
+
if (!mine) {
|
|
184
|
+
lostRules.push(key);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const theirs = new Set([...mine].map(dropFallbacks));
|
|
188
|
+
for (const decl of decls) {
|
|
189
|
+
// `@theme inline` aliases nothing reads โ see the header.
|
|
190
|
+
const prop = CUSTOM_PROPERTY.exec(decl)?.[1];
|
|
191
|
+
if (prop && !readByScanning.has(prop)) continue;
|
|
192
|
+
if (!theirs.has(dropFallbacks(decl))) lostDeclarations.push(`${key} โ ${decl}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
expect(
|
|
196
|
+
lostRules,
|
|
197
|
+
'`styles-static.css` + `styles-utilities.css` emits FEWER rules than\n' +
|
|
198
|
+
'`styles.css` does. Every selector listed here renders unstyled in any app that\n' +
|
|
199
|
+
`adopted the pair. Usually staleness:\n ${REGENERATE}`,
|
|
200
|
+
).toEqual([]);
|
|
201
|
+
expect(
|
|
202
|
+
lostDeclarations,
|
|
203
|
+
'the precompiled pair emits these selectors but not these declarations, so the\n' +
|
|
204
|
+
`affected components render PARTLY styled. Usually staleness:\n ${REGENERATE}`,
|
|
205
|
+
).toEqual([]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('reads no custom property the files beside it do not declare', async () => {
|
|
209
|
+
// The trap the split creates and the scan never could: Tailwind emits only
|
|
210
|
+
// the theme variables its own build finds a use for, so a utility shipped in
|
|
211
|
+
// a prebuilt file can reference a `--text-sm` the consuming app never had a
|
|
212
|
+
// reason to emit โ and `font-size: var(--text-sm)` with no `--text-sm` is an
|
|
213
|
+
// invisible nothing.
|
|
214
|
+
//
|
|
215
|
+
// ๐ด Asked of the FILES, not of a compile. Tailwind v4 back-fills: it tracks
|
|
216
|
+
// the `var()`s in whatever CSS it is handed, so the artifact's whole theme
|
|
217
|
+
// layer can be deleted and a compiled consumer still declares everything
|
|
218
|
+
// (verified by deleting it). That back-fill is real and it is why the rule
|
|
219
|
+
// comparison above passes โ but it is Tailwind's promise, not this file's,
|
|
220
|
+
// and a `<link>` tag gets none of it. This asks what the artifact and the
|
|
221
|
+
// stylesheets a consumer imports beside it actually WRITE DOWN.
|
|
222
|
+
const artifact = onDisk ?? '';
|
|
223
|
+
// styles-static.css carries exactly one bare specifier, and its `@utility`
|
|
224
|
+
// definitions and `@property` registrations are where the `--tw-enter-*` /
|
|
225
|
+
// `--tw-animation-*` the artifact reads are declared. Pinned so the coupling
|
|
226
|
+
// cannot rot into a silent pass.
|
|
227
|
+
const animateDir = `${REPO_ROOT}/node_modules/tw-animate-css`;
|
|
228
|
+
expect(
|
|
229
|
+
(pkg as { dependencies: Record<string, string> }).dependencies['tw-animate-css'],
|
|
230
|
+
'tw-animate-css is what src/styles.css @imports',
|
|
231
|
+
).toBeDefined();
|
|
232
|
+
// Its own `exports` map, not a guessed filename โ a CSS-only package resolves
|
|
233
|
+
// through the `style` condition, which Bun.resolveSync does not follow.
|
|
234
|
+
const animateManifest = (await Bun.file(`${animateDir}/package.json`).json()) as {
|
|
235
|
+
exports: Record<string, { style?: string }>;
|
|
236
|
+
};
|
|
237
|
+
const animateEntry = animateManifest.exports['.']?.style;
|
|
238
|
+
expect(animateEntry, 'tw-animate-css no longer exports a `style` entry').toBeDefined();
|
|
239
|
+
const animateCss = await Bun.file(`${animateDir}/${animateEntry}`).text();
|
|
240
|
+
expect(animateCss.length, 'tw-animate-css moved its stylesheet').toBeGreaterThan(1_000);
|
|
241
|
+
const beside = await Promise.all(
|
|
242
|
+
['src/theme.css', 'src/styles-static.css'].map((f) => Bun.file(`${REPO_ROOT}/${f}`).text()),
|
|
243
|
+
);
|
|
244
|
+
const provided = new Set([artifact, animateCss, ...beside].flatMap((css) => [...declared(css)]));
|
|
245
|
+
// Differential, not an allowlist: whatever the scanning route leaves to the
|
|
246
|
+
// runtime (`--radix-*`/`--reka-*` from the positioning libraries, `--cb-*`
|
|
247
|
+
// set inline by components) is fine here too, and nothing NEW is.
|
|
248
|
+
const declaredByScanning = declared(scanning);
|
|
249
|
+
const known = [...readByScanning].filter((v) => !declaredByScanning.has(v));
|
|
250
|
+
expect(
|
|
251
|
+
[...referenced(artifact)].filter((v) => !provided.has(v) && !known.includes(v)).sort(),
|
|
252
|
+
`${UTILITY_STYLESHEET} reads these custom properties and neither it, src/theme.css,\n` +
|
|
253
|
+
'src/styles-static.css nor tw-animate-css declares them. Under the `@source` scan the\n' +
|
|
254
|
+
"consuming app's own build emitted them; a prebuilt file cannot count on that.\n" +
|
|
255
|
+
`Run:\n ${REGENERATE}`,
|
|
256
|
+
).toEqual([]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('normalised exactly the two differences its header documents, and no more', () => {
|
|
260
|
+
// The two allowances above are the difference between a proof and a rubber
|
|
261
|
+
// stamp, so they are measured rather than trusted: if either grows, this
|
|
262
|
+
// fails and the header has to be rewritten before the pair ships again.
|
|
263
|
+
const fallbackOnly: string[] = [];
|
|
264
|
+
const aliasOnly: string[] = [];
|
|
265
|
+
const read = referenced(scanning);
|
|
266
|
+
for (const [key, decls] of before) {
|
|
267
|
+
const mine = after.get(key);
|
|
268
|
+
if (!mine) continue;
|
|
269
|
+
for (const decl of decls) {
|
|
270
|
+
if (mine.has(decl)) continue;
|
|
271
|
+
const prop = CUSTOM_PROPERTY.exec(decl)?.[1];
|
|
272
|
+
if (prop && !read.has(prop)) aliasOnly.push(`${key} โ ${decl}`);
|
|
273
|
+
else fallbackOnly.push(`${key} โ ${decl}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// One: `.animate-progress-indeterminate`, whose `@theme` token reaches the
|
|
277
|
+
// artifact's compile through `@reference "./styles-static.css"`.
|
|
278
|
+
expect(fallbackOnly.map((d) => d.split(' โ ')[0])).toEqual([
|
|
279
|
+
'@layer utilities โ .animate-progress-indeterminate',
|
|
280
|
+
]);
|
|
281
|
+
// Four: the `@theme inline` aliases in src/theme.css that no utility reads.
|
|
282
|
+
// A number, not a list, because which four is an implementation detail of
|
|
283
|
+
// Tailwind's theme tree-shaking; a JUMP here means the allowance is covering
|
|
284
|
+
// something it was not written for.
|
|
285
|
+
expect(aliasOnly.length).toBeLessThanOrEqual(8);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('is exported as a subpath that resolves into a directory `files` ships', () => {
|
|
289
|
+
const exports = (pkg as { exports: Record<string, unknown> }).exports;
|
|
290
|
+
expect(exports['./styles-utilities.css']).toBe(`./${UTILITY_STYLESHEET}`);
|
|
291
|
+
// Out of `src/`, beside the two stylesheets it completes โ a trio of
|
|
292
|
+
// app-facing stylesheets served from two different directories is a
|
|
293
|
+
// difference nobody can see in CSS.
|
|
294
|
+
expect(exports['./styles-static.css']).toBe('./src/styles-static.css');
|
|
295
|
+
expect((pkg as { files: string[] }).files).toContain('src');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('resolves as `cursedbelt/styles-utilities.css` through the real resolver', () => {
|
|
299
|
+
// The assertions above read the manifest; this one RESOLVES the specifier the
|
|
300
|
+
// way a consumer's bundler does. An export entry pointing at a path that does
|
|
301
|
+
// not exist satisfies every string comparison in this file and fails here.
|
|
302
|
+
expect(Bun.resolveSync(`${pkg.name}/styles-utilities.css`, REPO_ROOT)).toBe(
|
|
303
|
+
`${REPO_ROOT}/${UTILITY_STYLESHEET}`,
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('is regenerated by the build, so a stale artifact cannot be published', () => {
|
|
308
|
+
// `prepublishOnly` โ `verify` โ `build`, so the tarball's copy is always the
|
|
309
|
+
// one this repo's current src/react produces.
|
|
310
|
+
expect((pkg as { scripts: Record<string, string> }).scripts.build).toContain(
|
|
311
|
+
'scripts/generateUtilityStyles.ts',
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { Scanner } from '@tailwindcss/oxide';
|
|
3
|
+
import { compile } from 'tailwindcss';
|
|
4
|
+
import {
|
|
5
|
+
TAP_TARGET,
|
|
6
|
+
TAP_TARGET_ALWAYS,
|
|
7
|
+
TAP_TARGET_HALO,
|
|
8
|
+
TAP_TARGET_HEIGHT,
|
|
9
|
+
TAP_TARGET_SQUARE,
|
|
10
|
+
} from './react/lib/tapTarget';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ๐ด THE TAP FLOOR SURVIVES THE JIT.
|
|
14
|
+
*
|
|
15
|
+
* `src/react/lib/tapTarget.ts` collapsed 48 hand-written copies of `DESIGN.md`
|
|
16
|
+
* ยง5's 44px floor โ and 12 of the hover-reveal fix that goes with it โ into five
|
|
17
|
+
* constants. That refactor has one failure mode, and it is silent: **Tailwind's
|
|
18
|
+
* JIT scans source TEXT, it does not evaluate code.** A
|
|
19
|
+
* class that stops being a literal inside this package's own `@source "."` glob โ
|
|
20
|
+
* moved to a file the glob does not cover, assembled from a variable, split
|
|
21
|
+
* across a template it cannot read โ is a class Tailwind never emits. Nothing
|
|
22
|
+
* reds. The types are fine, the build is fine, every component still carries the
|
|
23
|
+
* class name in its `className`, and the rule it names simply does not exist in
|
|
24
|
+
* the stylesheet any more. Every tap target on every phone in the fleet quietly
|
|
25
|
+
* loses its floor.
|
|
26
|
+
*
|
|
27
|
+
* So this compiles the REAL `src/styles.css` โ its own `@source` directive, its
|
|
28
|
+
* own scan of this package's tree, Tailwind's own candidate extractor โ and
|
|
29
|
+
* asserts the emitted CSS still carries a rule for every utility the constants
|
|
30
|
+
* name. It is the one check that can tell "the constant is exported" apart from
|
|
31
|
+
* "the constant does anything".
|
|
32
|
+
*
|
|
33
|
+
* ๐ด NOTHING IN THIS FILE MAY SPELL A TAP UTILITY OUT. The `@source` glob covers
|
|
34
|
+
* `src/`, and a spec is source: the first draft of this header quoted the height
|
|
35
|
+
* class in its own prose, and that comment alone kept the class in the candidate
|
|
36
|
+
* set after the constant had been broken. The check passed against a stylesheet
|
|
37
|
+
* held up by a sentence about the check. Utilities come from the imports above
|
|
38
|
+
* and from nowhere else in here.
|
|
39
|
+
*
|
|
40
|
+
* Verified failing, 2026-09-14, before it was trusted โ each probe applied,
|
|
41
|
+
* observed and removed again:
|
|
42
|
+
* ยท `TAP_TARGET_HEIGHT` rewritten as a template that interpolates its `11`
|
|
43
|
+
* โ both the extraction test and "utilities the emitted stylesheet has no
|
|
44
|
+
* rule for: <the height class>". `TAP_TARGET` composes the same constant,
|
|
45
|
+
* so every button in the package loses the floor from this one edit.
|
|
46
|
+
* ยท `TAP_TARGET_HEIGHT` truncated to its scale-less prefix (the trailing `11`
|
|
47
|
+
* dropped) โ same failure, which is the anchored-selector half. A plain
|
|
48
|
+
* `includes()` would have PASSED this one: the truncated name is a substring
|
|
49
|
+
* of the rule the untruncated class emits elsewhere in the same stylesheet.
|
|
50
|
+
* ๐ด Truncating by one DIGIT instead does not fail, and should not โ the
|
|
51
|
+
* result is another real Tailwind utility with a real rule; what would be
|
|
52
|
+
* broken there is the design, not the pipeline this file measures.
|
|
53
|
+
* ยท the `@source` line removed from `src/styles.css` โ "scanned no candidates",
|
|
54
|
+
* the guard below that stops every other assertion passing vacuously.
|
|
55
|
+
*
|
|
56
|
+
* ~1.5s: one Tailwind compile of ~118 KB over ~25k candidates.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
const REPO_ROOT = import.meta.dir.replace(/\/src$/, '');
|
|
60
|
+
|
|
61
|
+
/** Directory half of an absolute path โ `node:path` is virtualized by the test preload. */
|
|
62
|
+
const dirOf = (path: string): string => path.slice(0, path.lastIndexOf('/'));
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a CSS `@import` the way a bundler would.
|
|
66
|
+
*
|
|
67
|
+
* ๐ด `Bun.file`, never `node:fs`: this package's test preload virtualizes node
|
|
68
|
+
* builtins, so a `readFileSync` here would read MOCK data and the whole
|
|
69
|
+
* measurement would be of nothing (same trap as `publishShape.spec.ts`). A CSS
|
|
70
|
+
* package announces its entry through the `style` condition, not `main`.
|
|
71
|
+
*/
|
|
72
|
+
const loadStylesheet = async (
|
|
73
|
+
id: string,
|
|
74
|
+
base: string,
|
|
75
|
+
): Promise<{ path: string; base: string; content: string }> => {
|
|
76
|
+
const read = async (path: string) => ({
|
|
77
|
+
path,
|
|
78
|
+
base: dirOf(path),
|
|
79
|
+
content: await Bun.file(path).text(),
|
|
80
|
+
});
|
|
81
|
+
if (id.startsWith('.')) return read(`${base}/${id}`);
|
|
82
|
+
const packageDir = `${REPO_ROOT}/node_modules/${id}`;
|
|
83
|
+
const manifest = (await Bun.file(`${packageDir}/package.json`).json()) as {
|
|
84
|
+
exports?: { '.'?: { style?: string } };
|
|
85
|
+
style?: string;
|
|
86
|
+
main?: string;
|
|
87
|
+
};
|
|
88
|
+
const entry = manifest.exports?.['.']?.style ?? manifest.style ?? manifest.main;
|
|
89
|
+
if (!entry) throw new Error(`no stylesheet entry for "${id}"`);
|
|
90
|
+
return read(`${packageDir}/${entry.replace(/^\.\//, '')}`);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Every utility the five constants name, deduped โ the thing that must survive. */
|
|
94
|
+
const UTILITIES = [
|
|
95
|
+
...new Set(
|
|
96
|
+
[TAP_TARGET_HEIGHT, TAP_TARGET, TAP_TARGET_SQUARE, TAP_TARGET_HALO, TAP_TARGET_ALWAYS]
|
|
97
|
+
.join(' ')
|
|
98
|
+
.split(/\s+/)
|
|
99
|
+
.filter(Boolean),
|
|
100
|
+
),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const emitted = await (async () => {
|
|
104
|
+
// An app imports Tailwind first, then this package's stylesheet โ exactly the
|
|
105
|
+
// two lines `src/styles.css`'s own header tells a consumer to write.
|
|
106
|
+
const source = await Bun.file(`${REPO_ROOT}/src/styles.css`).text();
|
|
107
|
+
const compiler = await compile(`@import "tailwindcss";\n${source}`, {
|
|
108
|
+
base: `${REPO_ROOT}/src`,
|
|
109
|
+
loadStylesheet,
|
|
110
|
+
});
|
|
111
|
+
const candidates = new Scanner({ sources: compiler.sources }).scan();
|
|
112
|
+
return { candidates, css: compiler.build(candidates) };
|
|
113
|
+
})();
|
|
114
|
+
|
|
115
|
+
describe('the tap floor reaches the stylesheet', () => {
|
|
116
|
+
it('scanned this package for candidates at all', () => {
|
|
117
|
+
// Guards every assertion below: with no `@source` directive the scan returns
|
|
118
|
+
// nothing, `build([])` emits no utilities, and "the rule is missing" would be
|
|
119
|
+
// indistinguishable from "the refactor broke it".
|
|
120
|
+
expect(emitted.candidates.length).toBeGreaterThan(1000);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('names utilities Tailwind actually extracted from the source text', () => {
|
|
124
|
+
// The JIT half: each class is a literal in a file the `@source` glob covers.
|
|
125
|
+
expect(UTILITIES.filter((u) => !emitted.candidates.includes(u))).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('emits a CSS rule for every utility the tap-target constants name', () => {
|
|
129
|
+
// A `:` is backslash-escaped inside a selector, so the class `a:b` is written
|
|
130
|
+
// `.a\:b` in the output โ and a candidate that survived extraction but
|
|
131
|
+
// generated no rule (a utility Tailwind does not know) still fails here.
|
|
132
|
+
//
|
|
133
|
+
// ๐ด Anchored on the END of the class name, not a substring: a class truncated
|
|
134
|
+
// by one character is a prefix of the rule its untruncated self emits, so a
|
|
135
|
+
// plain `includes()` would have found it in a stylesheet that never had it.
|
|
136
|
+
const missing = UTILITIES.filter((u) => {
|
|
137
|
+
// The utility โ its escaped selector text โ a regex matching that text, with
|
|
138
|
+
// the CSS backslash itself escaped a second time for the regex.
|
|
139
|
+
const selectorText = `.${u.replace(/:/g, '\\:')}`;
|
|
140
|
+
const anchored = new RegExp(
|
|
141
|
+
`${selectorText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w\\\\-])`,
|
|
142
|
+
);
|
|
143
|
+
return !anchored.test(emitted.css);
|
|
144
|
+
});
|
|
145
|
+
expect(
|
|
146
|
+
missing,
|
|
147
|
+
`utilities the emitted stylesheet has no rule for: ${missing.join(', ')}`,
|
|
148
|
+
).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
});
|