cursedbelt 1.0.0 → 2.3.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 +391 -0
- package/dist/styles.css +27 -0
- package/package.json +19 -167
- package/scripts/generateStaticStyles.ts +122 -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 +391 -0
- package/src/styles.css +27 -0
- package/src/stylesStaticMatches.spec.ts +134 -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
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* `bun scripts/publicSurface.ts [--prune]` — refuse a public surface that grows
|
|
4
|
+
* without a recorded reason.
|
|
5
|
+
*
|
|
6
|
+
* ## What this exists for
|
|
7
|
+
*
|
|
8
|
+
* Measured 2026-09-13 across all 845 consumer files on this machine that mention
|
|
9
|
+
* `cursedbelt`: `1.0.0` published **108 export subpaths and 3,359 exported
|
|
10
|
+
* symbols**, of which every consumer together imported **406 — 12.1%**.
|
|
11
|
+
* `cursedbelt/react` alone exposed 1,037 and 178 were used. Nothing objected at
|
|
12
|
+
* any single step, because no single step was large; the owner's own account of
|
|
13
|
+
* how it got there is *"worked on over many projects with tons of features
|
|
14
|
+
* changing"*.
|
|
15
|
+
*
|
|
16
|
+
* 🔴 **This is NOT a dead-code check, and 12.1% is not a bug to delete.** Most of
|
|
17
|
+
* that surface is alive: the `react` barrel re-exports components that live
|
|
18
|
+
* imports keep mounted, and a design system is SUPPOSED to ship more than any one
|
|
19
|
+
* app has adopted. Deleting everything safely deletable removed 4% of the package
|
|
20
|
+
* (tasks 32/33). What was missing was not restraint, it was an INSTRUMENT — the
|
|
21
|
+
* total was never measured anywhere, so growth was invisible at the only moment
|
|
22
|
+
* anyone could have judged it. This file makes growth visible and deliberate; it
|
|
23
|
+
* does not forbid it.
|
|
24
|
+
*
|
|
25
|
+
* Both rows below are a DATED RECORD, not a live claim — the live number is
|
|
26
|
+
* `publicSurface.baseline`, checked against the tree every run, and it can only
|
|
27
|
+
* go down from the second row:
|
|
28
|
+
*
|
|
29
|
+
* subpaths symbols dependencies peers
|
|
30
|
+
* 1.0.0, before tasks 32/33 108 4,167 47 51
|
|
31
|
+
* seeded 2026-09-13 85 3,808 44 47
|
|
32
|
+
*
|
|
33
|
+
* 🔴 Both rows were measured by THIS file, run against `c01b119` and against the
|
|
34
|
+
* tree it was seeded from, so they are commensurable with each other and with
|
|
35
|
+
* every future run. They are NOT the 3,359 in the task that asked for this file:
|
|
36
|
+
* that number came from an independent walk of the same shape, and this one counts
|
|
37
|
+
* 4,167 for the same tree. The gap is method, not surface — a `default` export,
|
|
38
|
+
* a destructured `export const {a, b}`, and a bare `export * from 'cwip/layout'`
|
|
39
|
+
* are each counted here and were not counted there. Which is the point of the
|
|
40
|
+
* "What it counts, exactly" section below: two numbers that do not say what they
|
|
41
|
+
* counted cannot be compared, and only one of them can be a ratchet.
|
|
42
|
+
*
|
|
43
|
+
* `subpaths` excludes the three stylesheet exports, which have no symbols; the
|
|
44
|
+
* `exports` map has three more keys than the count above. The dependency columns
|
|
45
|
+
* are the second half of the same failure and are asserted by
|
|
46
|
+
* `src/declaredDepsAreImported.spec.ts` — see "The dependency half" below.
|
|
47
|
+
*
|
|
48
|
+
* This file is the reason that second row cannot quietly become the first one
|
|
49
|
+
* again: **document the WHY; automate the WHETHER.** A paragraph in a doc asking
|
|
50
|
+
* the next agent to think about whether a new subpath is needed is the failure
|
|
51
|
+
* mode, not the fix.
|
|
52
|
+
*
|
|
53
|
+
* ## The ratchet
|
|
54
|
+
*
|
|
55
|
+
* `publicSurface.baseline` holds a COUNT PER SUBPATH, the same shape as
|
|
56
|
+
* `tools/check-paths.baseline`, which is the mechanism this generation already
|
|
57
|
+
* trusts. Four ways to fail, and the last two are what make it a ratchet rather
|
|
58
|
+
* than a high-water mark:
|
|
59
|
+
*
|
|
60
|
+
* 1. a subpath in `exports` that the baseline does not list → NEW SURFACE
|
|
61
|
+
* 2. a listed subpath whose symbol count went UP → GREW
|
|
62
|
+
* 3. a listed subpath whose count went DOWN, or that is gone → run --prune
|
|
63
|
+
* 4. an entry matching no subpath at all → run --prune
|
|
64
|
+
*
|
|
65
|
+
* (3) and (4) mean a deleted module cannot leave a stale allowance behind for the
|
|
66
|
+
* next module to grow into. Inherited surface can only fall.
|
|
67
|
+
*
|
|
68
|
+
* `--prune` records the burn-down in one deliberate command and REFUSES to raise
|
|
69
|
+
* anything, so a genuine new export is `--prune`-then-review rather than a number
|
|
70
|
+
* edited by hand, and it shows up in the diff as surface.
|
|
71
|
+
*
|
|
72
|
+
* ## 🔴 What it counts, exactly — and what it does not
|
|
73
|
+
*
|
|
74
|
+
* A count is only worth having if it says what it counted; a count that silently
|
|
75
|
+
* measures less than it claims is a guess wearing a measurement's clothes. This
|
|
76
|
+
* walks, per export subpath in `package.json`, the TypeScript AST of the entry
|
|
77
|
+
* module and every `export * from` beneath it, transitively, deduped by name:
|
|
78
|
+
*
|
|
79
|
+
* · `export const/let/var/function/class/interface/type/enum/namespace`
|
|
80
|
+
* · `export { x }`, `export { x as y }`, `export type { x }`
|
|
81
|
+
* · `export * as ns from './m'` (one symbol: `ns`)
|
|
82
|
+
* · `export default` / `export default function Foo` (one symbol: `default`)
|
|
83
|
+
* · `export * from './m'` (transitive; cycles are visited once)
|
|
84
|
+
* · `export * from 'some-package'` — RESOLVED and walked, not skipped. There is
|
|
85
|
+
* one today (`src/core/layout/index.ts` re-exports `cwip/layout`) and a
|
|
86
|
+
* package's symbols re-exported under this package's name are this package's
|
|
87
|
+
* surface. If such a specifier ever resolves to something this cannot parse
|
|
88
|
+
* (a `.js` build with no types), the run FAILS rather than counting zero.
|
|
89
|
+
*
|
|
90
|
+
* Deliberately NOT counted, each because counting it would make the number lie:
|
|
91
|
+
*
|
|
92
|
+
* · non-TS export targets — `./styles.css`, `./theme.css`, `./workspace.css`.
|
|
93
|
+
* A stylesheet has no symbols. They are reported as "asset export(s)" so they
|
|
94
|
+
* cannot vanish from the accounting.
|
|
95
|
+
* · anything not reachable from an `exports` subpath. Internal modules are not
|
|
96
|
+
* public surface. What `src/react/components/**` contains is not the measure;
|
|
97
|
+
* what the `./react` barrel re-exports is.
|
|
98
|
+
* · the two `bin` targets, `cc-verify` and `cc-promote`. 🔴 They ARE public
|
|
99
|
+
* surface — and they are NOT reachable through `exports`, which is exactly why
|
|
100
|
+
* an earlier reachability walk seeded from `exports` alone reported a shipped
|
|
101
|
+
* CLI as removable. A CLI's interface is its argv, not its export list, so
|
|
102
|
+
* counting its exported symbols would measure the wrong thing. Their SHAPE is
|
|
103
|
+
* already asserted by `src/publishShape.spec.ts` ("declares bin paths npm will
|
|
104
|
+
* actually keep"), and nothing here concludes anything is dead — this tool
|
|
105
|
+
* only ever compares `cursedbelt` to its own baseline.
|
|
106
|
+
*
|
|
107
|
+
* ## What this may NOT become
|
|
108
|
+
*
|
|
109
|
+
* · Not a usage check. It must never try to know who imports `cursedbelt`; that
|
|
110
|
+
* is cross-repo, and a repo's gate proves that repo.
|
|
111
|
+
* · Not a ban on growth. New surface is one hand-added line plus a commit
|
|
112
|
+
* message that says why — a reviewable moment, not a wall.
|
|
113
|
+
* · Not a second guard. One check, one baseline, entry criteria in this header.
|
|
114
|
+
*
|
|
115
|
+
* ## The dependency half
|
|
116
|
+
*
|
|
117
|
+
* 47 hard dependencies is the number that decides what a consumer pays, and it is
|
|
118
|
+
* surface by the same argument. It is NOT re-checked here, because it is already
|
|
119
|
+
* checked once: `src/declaredDepsAreImported.spec.ts` fails on any `dependencies`
|
|
120
|
+
* or `peerDependencies` entry that no file under `src/` or `scripts/` imports, and
|
|
121
|
+
* `src/noPathDeps.spec.ts` fails on a `file:`/`link:`/`workspace:`/`portal:` range
|
|
122
|
+
* in any dependency block. Two ratchets that disagree is the guard sprawl this
|
|
123
|
+
* generation is trying to avoid, so this file points at them instead of repeating
|
|
124
|
+
* them.
|
|
125
|
+
*
|
|
126
|
+
* 🔴 If you ever move that check here, keep the property that made it correct: it
|
|
127
|
+
* scans EVERY file type, not just `.ts`. `tw-animate-css` is imported from
|
|
128
|
+
* `src/styles.css` and a `.ts`-only scan calls it dead.
|
|
129
|
+
*
|
|
130
|
+
* Exit 1 on any failure, so it can sit in a verify chain. It sits in the suite
|
|
131
|
+
* instead — `src/publicSurface.spec.ts` — so `bun run verify` needs no new script
|
|
132
|
+
* and cannot be a script somebody forgot to chain.
|
|
133
|
+
*/
|
|
134
|
+
import { dirname, join } from 'node:path';
|
|
135
|
+
import ts from 'typescript';
|
|
136
|
+
|
|
137
|
+
// Resolved from this file, never from `process.cwd()`: the suite imports it and
|
|
138
|
+
// `bun test` can be started from anywhere.
|
|
139
|
+
const ROOT = dirname(import.meta.dir);
|
|
140
|
+
const BASELINE = join(ROOT, 'publicSurface.baseline');
|
|
141
|
+
|
|
142
|
+
const DEFAULT_HEADER = [
|
|
143
|
+
'# Public surface of `cursedbelt`: one line per export subpath, `<exported symbols> <subpath>`.',
|
|
144
|
+
'#',
|
|
145
|
+
'# A CEILING, not an amnesty. The check fails on a subpath NOT listed here, on a listed',
|
|
146
|
+
'# subpath that gained a symbol, and on an entry that no longer matches `package.json`',
|
|
147
|
+
'# exports — so a deleted module cannot leave a stale allowance behind. Surface can only fall.',
|
|
148
|
+
'#',
|
|
149
|
+
'# To record a deliberate change: bun scripts/publicSurface.ts --prune',
|
|
150
|
+
'# It refuses to RAISE anything, so new surface is a reviewed diff, never a silent one.',
|
|
151
|
+
'#',
|
|
152
|
+
'# What a count includes, what it excludes and why, and the measurement this file is a',
|
|
153
|
+
'# ratchet against, are in the header of scripts/publicSurface.ts. Say nothing here that',
|
|
154
|
+
'# the entries below already say.',
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
export type Surface = {
|
|
158
|
+
/** subpath (as written in `exports`) → deduped exported symbol count */
|
|
159
|
+
counts: Map<string, number>;
|
|
160
|
+
/** subpaths skipped because they resolve to a non-TS asset (the three stylesheets) */
|
|
161
|
+
assets: string[];
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const isRelative = (spec: string): boolean => spec.startsWith('./') || spec.startsWith('../');
|
|
165
|
+
const isWalkable = (file: string): boolean => /\.(m|c)?tsx?$/.test(file);
|
|
166
|
+
|
|
167
|
+
/** `./a/b` from `src/x/y.tsx` → the first of `src/a/b.ts(x)`, `src/a/b/index.ts(x)`, … that exists. */
|
|
168
|
+
const resolveRelative = async (fromFile: string, spec: string): Promise<string | null> => {
|
|
169
|
+
const base = join(dirname(fromFile), spec);
|
|
170
|
+
const candidates = [`${base}.ts`, `${base}.tsx`, `${base}/index.ts`, `${base}/index.tsx`, base];
|
|
171
|
+
for (const c of candidates) {
|
|
172
|
+
if (await Bun.file(c).exists()) return c;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* A bare `export * from 'pkg'` — resolved through node resolution so its symbols
|
|
179
|
+
* are counted rather than silently dropped. Throws when the package resolves to
|
|
180
|
+
* something with no parseable types, because a zero there would be a lie.
|
|
181
|
+
*/
|
|
182
|
+
const resolveBare = (fromFile: string, spec: string): string => {
|
|
183
|
+
let resolved: string;
|
|
184
|
+
try {
|
|
185
|
+
resolved = Bun.resolveSync(spec, dirname(fromFile));
|
|
186
|
+
} catch (cause) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`${fromFile}: \`export * from '${spec}'\` re-exports a package that does not resolve, so its ` +
|
|
189
|
+
'symbols cannot be counted. Install it, or name the symbols — do not let the count lie.',
|
|
190
|
+
{ cause },
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (!isWalkable(resolved)) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`${fromFile}: \`export * from '${spec}'\` resolves to ${resolved}, which this measurement cannot ` +
|
|
196
|
+
'parse. Point it at TypeScript, name the symbols, or teach scripts/publicSurface.ts to read ' +
|
|
197
|
+
'a declaration bundle — do not let the count lie.',
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return resolved;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// One parse per file, shared across all 88 subpaths: the barrels overlap heavily
|
|
204
|
+
// (every `./react/*` subpath re-parses a slice of what `./react` already walked).
|
|
205
|
+
const parsed = new Map<string, ts.SourceFile>();
|
|
206
|
+
const sourceOf = async (file: string): Promise<ts.SourceFile> => {
|
|
207
|
+
const hit = parsed.get(file);
|
|
208
|
+
if (hit !== undefined) return hit;
|
|
209
|
+
// 🔴 Bun.file, NOT node:fs — this package's test preload shares one process with
|
|
210
|
+
// specs that virtualize node:fs, so a readFileSync scan reads MOCK data and the
|
|
211
|
+
// measurement passes against files it never opened. Same trap as
|
|
212
|
+
// publishShape.spec.ts; see its header.
|
|
213
|
+
const source = ts.createSourceFile(file, await Bun.file(file).text(), ts.ScriptTarget.ES2022, true);
|
|
214
|
+
parsed.set(file, source);
|
|
215
|
+
return source;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/** Every name a binding pattern introduces — `export const { a, b: [c] } = …` is three. */
|
|
219
|
+
const bindingNames = (name: ts.BindingName, out: Set<string>): void => {
|
|
220
|
+
if (ts.isIdentifier(name)) {
|
|
221
|
+
out.add(name.text);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
for (const el of name.elements) {
|
|
225
|
+
if (ts.isBindingElement(el)) bindingNames(el.name, out);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/** Deduped exported names of `file`, following `export * from` chains. */
|
|
230
|
+
const exportedNames = async (file: string, seen = new Set<string>()): Promise<Set<string>> => {
|
|
231
|
+
const names = new Set<string>();
|
|
232
|
+
if (seen.has(file)) return names;
|
|
233
|
+
seen.add(file);
|
|
234
|
+
|
|
235
|
+
const source = await sourceOf(file);
|
|
236
|
+
const modifiersOf = (node: ts.Node): readonly ts.Modifier[] =>
|
|
237
|
+
ts.canHaveModifiers(node) ? (ts.getModifiers(node) ?? []) : [];
|
|
238
|
+
|
|
239
|
+
for (const st of source.statements) {
|
|
240
|
+
if (ts.isExportDeclaration(st)) {
|
|
241
|
+
if (st.exportClause && ts.isNamedExports(st.exportClause)) {
|
|
242
|
+
for (const el of st.exportClause.elements) names.add(el.name.text);
|
|
243
|
+
} else if (st.exportClause && ts.isNamespaceExport(st.exportClause)) {
|
|
244
|
+
names.add(st.exportClause.name.text);
|
|
245
|
+
} else {
|
|
246
|
+
const spec =
|
|
247
|
+
st.moduleSpecifier && ts.isStringLiteral(st.moduleSpecifier) ? st.moduleSpecifier.text : null;
|
|
248
|
+
if (spec === null) continue;
|
|
249
|
+
let target: string | null;
|
|
250
|
+
if (isRelative(spec)) {
|
|
251
|
+
target = await resolveRelative(file, spec);
|
|
252
|
+
if (target === null) throw new Error(`${file}: \`export * from '${spec}'\` resolves to nothing.`);
|
|
253
|
+
} else {
|
|
254
|
+
target = resolveBare(file, spec);
|
|
255
|
+
}
|
|
256
|
+
for (const n of await exportedNames(target, seen)) names.add(n);
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (ts.isExportAssignment(st)) {
|
|
261
|
+
names.add('default');
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const modifiers = modifiersOf(st);
|
|
265
|
+
if (!modifiers.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) continue;
|
|
266
|
+
// `export default function Foo()` is imported as `default`, not as `Foo`.
|
|
267
|
+
if (modifiers.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) {
|
|
268
|
+
names.add('default');
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (ts.isVariableStatement(st)) {
|
|
272
|
+
for (const d of st.declarationList.declarations) bindingNames(d.name, names);
|
|
273
|
+
} else if (
|
|
274
|
+
(ts.isFunctionDeclaration(st) ||
|
|
275
|
+
ts.isClassDeclaration(st) ||
|
|
276
|
+
ts.isInterfaceDeclaration(st) ||
|
|
277
|
+
ts.isTypeAliasDeclaration(st) ||
|
|
278
|
+
ts.isEnumDeclaration(st) ||
|
|
279
|
+
ts.isModuleDeclaration(st)) &&
|
|
280
|
+
st.name !== undefined
|
|
281
|
+
) {
|
|
282
|
+
if (ts.isIdentifier(st.name) || ts.isStringLiteral(st.name)) names.add(st.name.text);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return names;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/** The `./src/**` or `./scripts/**` TypeScript file a subpath resolves to, or null when it is an asset. */
|
|
289
|
+
const entryOf = (value: unknown): string | null => {
|
|
290
|
+
if (typeof value === 'string')
|
|
291
|
+
return (value.startsWith('./src/') || value.startsWith('./scripts/')) && isWalkable(value)
|
|
292
|
+
? join(ROOT, value.slice(2))
|
|
293
|
+
: null;
|
|
294
|
+
if (typeof value !== 'object' || value === null) return null;
|
|
295
|
+
// `bun` and `source` are the conditions that point at TypeScript; `types` and
|
|
296
|
+
// `import` point into `dist`, which is build output and may not exist. Reading
|
|
297
|
+
// the conditions in this order is what makes the measurement work on a clean
|
|
298
|
+
// checkout, before `bun run build` has ever run.
|
|
299
|
+
for (const key of ['bun', 'source', 'types', 'import', 'default']) {
|
|
300
|
+
if (key in (value as Record<string, unknown>)) {
|
|
301
|
+
const hit = entryOf((value as Record<string, unknown>)[key]);
|
|
302
|
+
if (hit !== null) return hit;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
for (const v of Object.values(value as Record<string, unknown>)) {
|
|
306
|
+
const hit = entryOf(v);
|
|
307
|
+
if (hit !== null) return hit;
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export const measureSurface = async (pkg: { exports: Record<string, unknown> }): Promise<Surface> => {
|
|
313
|
+
const counts = new Map<string, number>();
|
|
314
|
+
const assets: string[] = [];
|
|
315
|
+
for (const [subpath, value] of Object.entries(pkg.exports)) {
|
|
316
|
+
const entry = entryOf(value);
|
|
317
|
+
if (entry === null) {
|
|
318
|
+
assets.push(subpath);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
counts.set(subpath, (await exportedNames(entry)).size);
|
|
322
|
+
}
|
|
323
|
+
return { counts, assets };
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// ── the baseline ────────────────────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
export type Baseline = { header: string[]; counts: Map<string, number> };
|
|
329
|
+
|
|
330
|
+
export const parseBaseline = (text: string): Baseline => {
|
|
331
|
+
const header = text.split('\n').filter((l) => l.startsWith('#'));
|
|
332
|
+
const counts = new Map<string, number>();
|
|
333
|
+
for (const line of text.split('\n')) {
|
|
334
|
+
if (!line.trim() || line.startsWith('#')) continue;
|
|
335
|
+
const m = line.match(/^\s*(\d+)\s+(\S.*)$/);
|
|
336
|
+
if (m?.[2] !== undefined) counts.set(m[2].trim(), Number(m[1]));
|
|
337
|
+
}
|
|
338
|
+
return { header: header.length > 0 ? header : DEFAULT_HEADER, counts };
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export const readBaseline = async (): Promise<Baseline> => {
|
|
342
|
+
const f = Bun.file(BASELINE);
|
|
343
|
+
return parseBaseline((await f.exists()) ? await f.text() : '');
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export const formatBaseline = (header: string[], counts: Map<string, number>): string =>
|
|
347
|
+
`${[...header, ...[...counts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([k, n]) => `${n} ${k}`)].join(
|
|
348
|
+
'\n',
|
|
349
|
+
)}\n`;
|
|
350
|
+
|
|
351
|
+
export type Failures = { grown: string[]; fresh: string[]; stale: string[] };
|
|
352
|
+
|
|
353
|
+
export const compare = (surface: Surface, baseline: Baseline): Failures => {
|
|
354
|
+
const fresh: string[] = [];
|
|
355
|
+
const grown: string[] = [];
|
|
356
|
+
for (const [subpath, n] of surface.counts) {
|
|
357
|
+
const allowed = baseline.counts.get(subpath);
|
|
358
|
+
if (allowed === undefined) fresh.push(`${subpath} — NEW subpath, ${n} exported symbol(s)`);
|
|
359
|
+
else if (n > allowed) grown.push(`${subpath} — baseline allows ${allowed}, exports ${n} now`);
|
|
360
|
+
}
|
|
361
|
+
const stale = [...baseline.counts.entries()]
|
|
362
|
+
.filter(([subpath, n]) => (surface.counts.get(subpath) ?? 0) < n)
|
|
363
|
+
.map(([subpath, n]) =>
|
|
364
|
+
surface.counts.has(subpath)
|
|
365
|
+
? `${subpath} — baseline says ${n}, exports ${surface.counts.get(subpath)}`
|
|
366
|
+
: `${subpath} — baseline says ${n}, the subpath is gone`,
|
|
367
|
+
);
|
|
368
|
+
return { fresh, grown, stale };
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export const run = async (): Promise<Failures & { surface: Surface; baseline: Baseline }> => {
|
|
372
|
+
const pkg = (await Bun.file(join(ROOT, 'package.json')).json()) as { exports: Record<string, unknown> };
|
|
373
|
+
const surface = await measureSurface(pkg);
|
|
374
|
+
const baseline = await readBaseline();
|
|
375
|
+
return { ...compare(surface, baseline), surface, baseline };
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// ── CLI ─────────────────────────────────────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
if (import.meta.main) {
|
|
381
|
+
const argv = process.argv.slice(2);
|
|
382
|
+
const prune = argv.includes('--prune');
|
|
383
|
+
const { fresh, grown, stale, surface, baseline } = await run();
|
|
384
|
+
const total = [...surface.counts.values()].reduce((a, b) => a + b, 0);
|
|
385
|
+
|
|
386
|
+
// `--seed` exists once, to record the surface the ratchet starts from. It REFUSES
|
|
387
|
+
// over an existing baseline, so re-seeding means deleting the file first — two
|
|
388
|
+
// visible steps and a whole-file diff, rather than one command that quietly
|
|
389
|
+
// re-baselines whatever grew.
|
|
390
|
+
if (argv.includes('--seed')) {
|
|
391
|
+
if (baseline.counts.size > 0) {
|
|
392
|
+
console.error(`publicSurface --seed refuses: ${BASELINE} already records ${baseline.counts.size} subpath(s).`);
|
|
393
|
+
console.error('Lower it with --prune. Raising it is a hand edit in a commit that says why.');
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
await Bun.write(BASELINE, formatBaseline(baseline.header, surface.counts));
|
|
397
|
+
console.log(`publicSurface: seeded ${surface.counts.size} subpath(s), ${total} exported symbol(s).`);
|
|
398
|
+
process.exit(0);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (prune) {
|
|
402
|
+
// 🔴 It may only LOWER. Growth is the thing this file exists to make visible;
|
|
403
|
+
// a prune that could absorb it would be the disable switch.
|
|
404
|
+
if (fresh.length > 0 || grown.length > 0) {
|
|
405
|
+
console.error('publicSurface --prune refuses while the surface has GROWN — it may only lower the baseline.\n');
|
|
406
|
+
for (const v of [...fresh, ...grown]) console.error(` ${v}`);
|
|
407
|
+
console.error(
|
|
408
|
+
'\nTo record deliberate new surface, add the line by hand and say in the commit message why the\npackage needs it — that is the reviewable moment this check exists to create.\n',
|
|
409
|
+
);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const lowered = new Map<string, number>();
|
|
413
|
+
for (const [subpath, n] of baseline.counts) {
|
|
414
|
+
const now = surface.counts.get(subpath);
|
|
415
|
+
if (now !== undefined) lowered.set(subpath, Math.min(n, now));
|
|
416
|
+
}
|
|
417
|
+
await Bun.write(BASELINE, formatBaseline(baseline.header, lowered));
|
|
418
|
+
console.log(
|
|
419
|
+
`publicSurface: baseline pruned to ${lowered.size} subpath(s), ` +
|
|
420
|
+
`${baseline.counts.size - lowered.size} removed, ${[...lowered.values()].reduce((a, b) => a + b, 0)} symbols.`,
|
|
421
|
+
);
|
|
422
|
+
process.exit(0);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
let bad = false;
|
|
426
|
+
const report = (label: string, lines: string[], advice: string): void => {
|
|
427
|
+
if (lines.length === 0) return;
|
|
428
|
+
bad = true;
|
|
429
|
+
console.error(`\n${lines.length} ${label}:\n`);
|
|
430
|
+
for (const l of lines) console.error(` ${l}`);
|
|
431
|
+
console.error(`\n${advice}\n`);
|
|
432
|
+
};
|
|
433
|
+
report(
|
|
434
|
+
'export subpath(s) the baseline does not know',
|
|
435
|
+
fresh,
|
|
436
|
+
'A new subpath is a permanent public promise. If it is genuinely wanted, add its line to\n' +
|
|
437
|
+
`${BASELINE} and say why in the commit message.`,
|
|
438
|
+
);
|
|
439
|
+
report(
|
|
440
|
+
'subpath(s) that gained exported symbols',
|
|
441
|
+
grown,
|
|
442
|
+
'A baseline entry is a ceiling. Raise it by hand, in a commit that says why.',
|
|
443
|
+
);
|
|
444
|
+
report(
|
|
445
|
+
'baseline entr(y|ies) that no longer match `exports`',
|
|
446
|
+
stale,
|
|
447
|
+
`Surface came off and the ratchet was not tightened. Run:\n cd ${ROOT} && bun scripts/publicSurface.ts --prune`,
|
|
448
|
+
);
|
|
449
|
+
if (bad) process.exit(1);
|
|
450
|
+
|
|
451
|
+
console.log(
|
|
452
|
+
`publicSurface: ${surface.counts.size} subpath(s), ${total} exported symbol(s), ` +
|
|
453
|
+
`${surface.assets.length} asset export(s) — none grown.`,
|
|
454
|
+
);
|
|
455
|
+
}
|
package/scripts/testTmpRoot.ts
CHANGED
|
@@ -41,19 +41,48 @@
|
|
|
41
41
|
* no behaviour here for an app to get wrong — only a path.
|
|
42
42
|
*/
|
|
43
43
|
import { mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
44
|
+
import { join, resolve } from "node:path";
|
|
44
45
|
|
|
45
46
|
/** Older than this and no live run can still want it. */
|
|
46
47
|
const STALE_MS = 6 * 60 * 60 * 1000;
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* ## 🔴 Where the scratch root is, and the bug that moved it (2026-09-13)
|
|
51
|
+
*
|
|
52
|
+
* This used to assemble the PREVIOUS generation's state root out of the ambient
|
|
53
|
+
* home directory. It is a preload, so it ran before the first test in this repo on
|
|
54
|
+
* every `bun test`, which meant every `verify` in this generation was writing into
|
|
55
|
+
* the last one. Measured the day it was found: 77 MB and 259 process directories
|
|
56
|
+
* under it, and a single spec run while the task was being written made number 260.
|
|
57
|
+
* This generation's first law is that nothing may depend on an older one.
|
|
58
|
+
*
|
|
59
|
+
* The checker could not see it: an interpolated `${VAR}/…` matches none of the
|
|
60
|
+
* shapes it looks for, and the line that actually built the path carried no banned
|
|
61
|
+
* string at all — it was assembled from a variable. So `testTmpRoot.spec.ts` is
|
|
62
|
+
* what holds this now, and it asserts the RESOLVED root rather than the source.
|
|
63
|
+
*
|
|
64
|
+
* `$FORGE_STATE` is this generation's state root and the runner hands it to every
|
|
65
|
+
* worker, so under autopilot it is always set. A bare checkout with no generation
|
|
66
|
+
* around it gets a repo-local `.agent.noindex/tmp`, which is already gitignored
|
|
67
|
+
* here — never a path built from `$HOME`.
|
|
68
|
+
*
|
|
69
|
+
* 🔴 `.noindex` stays in the directory name either way: it is the only marker that
|
|
70
|
+
* keeps Spotlight out of files written LATER into a tree, and a test root is
|
|
71
|
+
* written to constantly (measured 43× on this machine). `.agent.noindex` already
|
|
72
|
+
* carries it.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveTestTmpRoot(env: Record<string, string | undefined> = process.env): string {
|
|
75
|
+
const forgeState = env.FORGE_STATE?.trim();
|
|
76
|
+
if (forgeState) return join(forgeState, "test-scratch.noindex", "tmp");
|
|
77
|
+
// This file is `<repo>/scripts/testTmpRoot.ts`; the root is one hop up.
|
|
78
|
+
return resolve(import.meta.dir, "..", ".agent.noindex", "tmp");
|
|
79
|
+
}
|
|
80
|
+
|
|
48
81
|
export function redirectTestTmpRoot(): void {
|
|
49
82
|
// A suite that genuinely wants the real `$TMPDIR` sets this and wins.
|
|
50
83
|
if (process.env.TMPDIR_TEST_PASSTHROUGH) return;
|
|
51
84
|
|
|
52
|
-
|
|
53
|
-
// out of files written LATER into a tree, and a test root is written to
|
|
54
|
-
// constantly (measured 43× on this machine).
|
|
55
|
-
const appManaged = process.env.APP_MANAGED_HOME ?? `${process.env.HOME}/.app-managed`;
|
|
56
|
-
const tmpRoot = `${appManaged}/test-scratch.noindex/tmp`;
|
|
85
|
+
const tmpRoot = resolveTestTmpRoot();
|
|
57
86
|
|
|
58
87
|
// Sweep BEFORE claiming ours. A SIGKILLed run cannot clean up after itself —
|
|
59
88
|
// that is precisely how the 310k pile was built — so the next run is the only
|
package/src/client/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* cursedbelt/client — the browser half of the cursedbelt server toolkit:
|
|
3
3
|
* • the ctgr GET-tunnel (localStorage mode flag, v1 request converter, hard-block guard),
|
|
4
|
-
* pairing with `cursedbelt/server` (`unGETify`) + `
|
|
4
|
+
* pairing with `cursedbelt/server` (`unGETify`) + the ctgr codec (`src/ctgr`, re-exported from `cursedbelt/server`);
|
|
5
5
|
* • the WS hub's auto-reconnecting channel socket (partysocket), pairing with
|
|
6
6
|
* `cursedbelt/server`'s `createWsHub`.
|
|
7
7
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/core/category — reusable hierarchical category/folder model, reaching consumers
|
|
2
|
+
// through `cursedbelt/react`'s categoryTree. Pure and
|
|
2
3
|
// React-free (the `TreeView` adapter lives in `cursedbelt/react`), so servers
|
|
3
4
|
// can import the contract + store without pulling any UI runtime.
|
|
4
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/core/category — a reusable, hierarchical category/folder model shared
|
|
2
2
|
// by content plugins (notes today; snippets/aliases/… later). React-free and
|
|
3
3
|
// storage-agnostic: the pure `Category` data + a host-injected `CategoryStore`
|
|
4
4
|
// contract, mirroring how `cwip`'s `AuditSink` and cursedshell's `NotesStore`
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
matchTypeahead,
|
|
11
11
|
parentIdOf,
|
|
12
12
|
resolveDrop,
|
|
13
|
+
resolveKeyboardMove,
|
|
14
|
+
describeDrop,
|
|
13
15
|
} from './fileTreeModel';
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -326,3 +328,135 @@ describe('filterFileTree', () => {
|
|
|
326
328
|
expect(filterFileTree(TREE, ' ')).toBe(TREE);
|
|
327
329
|
});
|
|
328
330
|
});
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* ⌥→ / ⌥← — the keyboard's half of the drag, and the only half touch has.
|
|
334
|
+
*
|
|
335
|
+
* 🔴 The assertions that matter are the REFUSALS. A keyboard route that can reach a
|
|
336
|
+
* destination the pointer refuses is a second, weaker opinion about what is legal, and it is
|
|
337
|
+
* the one nobody looks at because the pointer is what gets tested by hand.
|
|
338
|
+
*/
|
|
339
|
+
describe('resolveKeyboardMove', () => {
|
|
340
|
+
const rows = flattenFileTree(TREE, []);
|
|
341
|
+
|
|
342
|
+
test('→ demotes into the nearest FOLDER above it among its own siblings', () => {
|
|
343
|
+
// `posters` sits under `simpsons` after `screencaps`; the only unambiguous "in" is the
|
|
344
|
+
// folder immediately above it at the same level.
|
|
345
|
+
expect(
|
|
346
|
+
resolveKeyboardMove({
|
|
347
|
+
nodes: TREE,
|
|
348
|
+
rows,
|
|
349
|
+
row: rowFor(rows, 'simpsons/posters'),
|
|
350
|
+
direction: 'in',
|
|
351
|
+
}).target,
|
|
352
|
+
).toEqual({ kind: 'into', parentId: 'simpsons/screencaps' });
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('→ with nothing above it says so rather than doing nothing', () => {
|
|
356
|
+
const offer = resolveKeyboardMove({
|
|
357
|
+
nodes: TREE,
|
|
358
|
+
rows,
|
|
359
|
+
row: rowFor(rows, 'simpsons'),
|
|
360
|
+
direction: 'in',
|
|
361
|
+
});
|
|
362
|
+
expect(offer.target).toBeNull();
|
|
363
|
+
expect(offer.reason).toBe('There is no folder above it to move into');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('🔴 → refuses a LOCKED folder with the consumer‘s words, exactly as a drop does', () => {
|
|
367
|
+
// `dinos` is `acceptsDrop: false`. `travels` is the row after it, so `dinos` is the
|
|
368
|
+
// nearest folder above — reachable by keyboard and refused by pointer would be the bug.
|
|
369
|
+
const offer = resolveKeyboardMove({
|
|
370
|
+
nodes: TREE,
|
|
371
|
+
rows,
|
|
372
|
+
row: rowFor(rows, 'travels'),
|
|
373
|
+
direction: 'in',
|
|
374
|
+
lockedReason: 'That album is locked',
|
|
375
|
+
});
|
|
376
|
+
expect(offer.target).toBeNull();
|
|
377
|
+
expect(offer.reason).toBe('That album is locked');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('← promotes to the folder its own folder is in', () => {
|
|
381
|
+
expect(
|
|
382
|
+
resolveKeyboardMove({
|
|
383
|
+
nodes: TREE,
|
|
384
|
+
rows,
|
|
385
|
+
row: rowFor(rows, 'simpsons/screencaps/s04'),
|
|
386
|
+
direction: 'out',
|
|
387
|
+
}).target,
|
|
388
|
+
).toEqual({ kind: 'into', parentId: 'simpsons' });
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('← at depth 1 is the top level, and at depth 0 is a refusal with words', () => {
|
|
392
|
+
expect(
|
|
393
|
+
resolveKeyboardMove({
|
|
394
|
+
nodes: TREE,
|
|
395
|
+
rows,
|
|
396
|
+
row: rowFor(rows, 'simpsons/posters'),
|
|
397
|
+
direction: 'out',
|
|
398
|
+
}).target,
|
|
399
|
+
).toEqual({ kind: 'root' });
|
|
400
|
+
const offer = resolveKeyboardMove({
|
|
401
|
+
nodes: TREE,
|
|
402
|
+
rows,
|
|
403
|
+
row: rowFor(rows, 'travels'),
|
|
404
|
+
direction: 'out',
|
|
405
|
+
});
|
|
406
|
+
expect(offer.target).toBeNull();
|
|
407
|
+
expect(offer.reason).toBe('It is already at the top level');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('🔴 the consumer‘s `canDrop` gets the last word in both directions', () => {
|
|
411
|
+
const canDrop = () => 'No route for that on the server';
|
|
412
|
+
for (const direction of ['in', 'out'] as const) {
|
|
413
|
+
const offer = resolveKeyboardMove({
|
|
414
|
+
nodes: TREE,
|
|
415
|
+
rows,
|
|
416
|
+
row: rowFor(rows, 'simpsons/posters'),
|
|
417
|
+
direction,
|
|
418
|
+
canDrop,
|
|
419
|
+
});
|
|
420
|
+
expect(offer.target).toBeNull();
|
|
421
|
+
expect(offer.reason).toBe('No route for that on the server');
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('a shut folder does not hide its own SIBLINGS, so the level is still whole', () => {
|
|
426
|
+
// The visible rows are what the level is read off. `screencaps` being shut removes its
|
|
427
|
+
// children, never its peers — which is what makes reading siblings off `rows` correct.
|
|
428
|
+
const shut = flattenFileTree(TREE, ['simpsons/screencaps']);
|
|
429
|
+
expect(
|
|
430
|
+
resolveKeyboardMove({
|
|
431
|
+
nodes: TREE,
|
|
432
|
+
rows: shut,
|
|
433
|
+
row: rowFor(shut, 'simpsons/posters'),
|
|
434
|
+
direction: 'in',
|
|
435
|
+
}).target,
|
|
436
|
+
).toEqual({ kind: 'into', parentId: 'simpsons/screencaps' });
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
describe('describeDrop', () => {
|
|
441
|
+
test('names the destination, because "where to" is the question a drag asks', () => {
|
|
442
|
+
expect(describeDrop(TREE, { kind: 'into', parentId: 'simpsons/screencaps' })).toBe(
|
|
443
|
+
'Into screencaps',
|
|
444
|
+
);
|
|
445
|
+
expect(describeDrop(TREE, { kind: 'root' })).toBe('To the top level');
|
|
446
|
+
expect(describeDrop(TREE, { kind: 'before', siblingId: 'dinos', parentId: null })).toBe(
|
|
447
|
+
'Above Dinos',
|
|
448
|
+
);
|
|
449
|
+
expect(describeDrop(TREE, { kind: 'after', siblingId: 'dinos', parentId: null })).toBe(
|
|
450
|
+
'Below Dinos',
|
|
451
|
+
);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('🔴 no target is SILENT — a refusal has its own words and must not be doubled', () => {
|
|
455
|
+
expect(describeDrop(TREE, null)).toBe('');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('a target the tree no longer holds still says something true', () => {
|
|
459
|
+
// The tree can be re-derived from the server between the press and the release.
|
|
460
|
+
expect(describeDrop(TREE, { kind: 'into', parentId: 'gone' })).toBe('Into this folder');
|
|
461
|
+
});
|
|
462
|
+
});
|