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.
Files changed (533) hide show
  1. package/dist/client/index.js +1 -1
  2. package/dist/core/category/index.d.ts.map +1 -1
  3. package/dist/core/category/index.js +2 -1
  4. package/dist/core/category/index.js.map +1 -1
  5. package/dist/core/category/types.js +1 -1
  6. package/dist/core/category/types.js.map +1 -1
  7. package/dist/core/file-tree/fileTreeModel.d.ts +49 -0
  8. package/dist/core/file-tree/fileTreeModel.d.ts.map +1 -1
  9. package/dist/core/file-tree/fileTreeModel.js +84 -0
  10. package/dist/core/file-tree/fileTreeModel.js.map +1 -1
  11. package/dist/core/layout/index.js +1 -1
  12. package/dist/core/layout/index.js.map +1 -1
  13. package/dist/core/media/index.js +1 -1
  14. package/dist/core/media/index.js.map +1 -1
  15. package/dist/core/media/types.js +1 -1
  16. package/dist/core/media/types.js.map +1 -1
  17. package/dist/core/media/uploadLimits.js +1 -1
  18. package/dist/core/media/uploadLimits.js.map +1 -1
  19. package/dist/core/sharing/model.d.ts +1 -1
  20. package/dist/ctgr/index.js +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/react/activity-bar/ActivityBar.d.ts +17 -0
  24. package/dist/react/activity-bar/ActivityBar.d.ts.map +1 -1
  25. package/dist/react/activity-bar/ActivityBar.js.map +1 -1
  26. package/dist/react/analytics/index.d.ts +1 -1
  27. package/dist/react/analytics/index.js +1 -1
  28. package/dist/react/chat/MessageComposer.d.ts.map +1 -1
  29. package/dist/react/chat/MessageComposer.js +5 -4
  30. package/dist/react/chat/MessageComposer.js.map +1 -1
  31. package/dist/react/components/Button.d.ts.map +1 -1
  32. package/dist/react/components/Button.js +3 -2
  33. package/dist/react/components/Button.js.map +1 -1
  34. package/dist/react/components/Checkbox.d.ts +19 -3
  35. package/dist/react/components/Checkbox.d.ts.map +1 -1
  36. package/dist/react/components/Checkbox.js +29 -12
  37. package/dist/react/components/Checkbox.js.map +1 -1
  38. package/dist/react/components/CodeArea.d.ts.map +1 -1
  39. package/dist/react/components/CodeArea.js +2 -1
  40. package/dist/react/components/CodeArea.js.map +1 -1
  41. package/dist/react/components/Collapsible.d.ts.map +1 -1
  42. package/dist/react/components/Collapsible.js +2 -1
  43. package/dist/react/components/Collapsible.js.map +1 -1
  44. package/dist/react/components/ComboboxField.d.ts.map +1 -1
  45. package/dist/react/components/ComboboxField.js +3 -2
  46. package/dist/react/components/ComboboxField.js.map +1 -1
  47. package/dist/react/components/CopyButton.d.ts.map +1 -1
  48. package/dist/react/components/CopyButton.js +2 -1
  49. package/dist/react/components/CopyButton.js.map +1 -1
  50. package/dist/react/components/DataTable/ColumnChecklist.d.ts.map +1 -1
  51. package/dist/react/components/DataTable/ColumnChecklist.js +2 -1
  52. package/dist/react/components/DataTable/ColumnChecklist.js.map +1 -1
  53. package/dist/react/components/DataTable/Pagination.d.ts.map +1 -1
  54. package/dist/react/components/DataTable/Pagination.js +2 -1
  55. package/dist/react/components/DataTable/Pagination.js.map +1 -1
  56. package/dist/react/components/DataTable/SortHeader.d.ts.map +1 -1
  57. package/dist/react/components/DataTable/SortHeader.js +2 -1
  58. package/dist/react/components/DataTable/SortHeader.js.map +1 -1
  59. package/dist/react/components/DataTable/TableFilterBuilder.d.ts +61 -0
  60. package/dist/react/components/DataTable/TableFilterBuilder.d.ts.map +1 -0
  61. package/dist/react/components/DataTable/{FilterBuilder.js → TableFilterBuilder.js} +26 -3
  62. package/dist/react/components/DataTable/TableFilterBuilder.js.map +1 -0
  63. package/dist/react/components/DataTable/Toolbar.d.ts +1 -1
  64. package/dist/react/components/DataTable/Toolbar.js +3 -3
  65. package/dist/react/components/DataTable/Toolbar.js.map +1 -1
  66. package/dist/react/components/DataTable/cells/LinkCell.d.ts.map +1 -1
  67. package/dist/react/components/DataTable/cells/LinkCell.js +2 -1
  68. package/dist/react/components/DataTable/cells/LinkCell.js.map +1 -1
  69. package/dist/react/components/DataTable/index.d.ts +1 -1
  70. package/dist/react/components/DataTable/index.d.ts.map +1 -1
  71. package/dist/react/components/DataTable/index.js +4 -1
  72. package/dist/react/components/DataTable/index.js.map +1 -1
  73. package/dist/react/components/DragHandle.d.ts.map +1 -1
  74. package/dist/react/components/DragHandle.js +2 -1
  75. package/dist/react/components/DragHandle.js.map +1 -1
  76. package/dist/react/components/EmojiField.d.ts.map +1 -1
  77. package/dist/react/components/EmojiField.js +2 -1
  78. package/dist/react/components/EmojiField.js.map +1 -1
  79. package/dist/react/components/EventTimeline.d.ts +3 -2
  80. package/dist/react/components/EventTimeline.d.ts.map +1 -1
  81. package/dist/react/components/EventTimeline.js +3 -2
  82. package/dist/react/components/EventTimeline.js.map +1 -1
  83. package/dist/react/components/IconButton.d.ts +1 -1
  84. package/dist/react/components/IconButton.js +1 -1
  85. package/dist/react/components/InfoHint.d.ts.map +1 -1
  86. package/dist/react/components/InfoHint.js +5 -4
  87. package/dist/react/components/InfoHint.js.map +1 -1
  88. package/dist/react/components/InheritBadge.js +1 -1
  89. package/dist/react/components/InheritBadge.js.map +1 -1
  90. package/dist/react/components/InlineEditableText.d.ts.map +1 -1
  91. package/dist/react/components/InlineEditableText.js +2 -1
  92. package/dist/react/components/InlineEditableText.js.map +1 -1
  93. package/dist/react/components/MeterList.d.ts.map +1 -1
  94. package/dist/react/components/MeterList.js +2 -1
  95. package/dist/react/components/MeterList.js.map +1 -1
  96. package/dist/react/components/PathText.d.ts +2 -2
  97. package/dist/react/components/PathText.d.ts.map +1 -1
  98. package/dist/react/components/PathText.js +2 -1
  99. package/dist/react/components/PathText.js.map +1 -1
  100. package/dist/react/components/RatingField.d.ts.map +1 -1
  101. package/dist/react/components/RatingField.js +2 -1
  102. package/dist/react/components/RatingField.js.map +1 -1
  103. package/dist/react/components/SecretInput.js +3 -2
  104. package/dist/react/components/SecretInput.js.map +1 -1
  105. package/dist/react/components/SegmentedControl.d.ts.map +1 -1
  106. package/dist/react/components/SegmentedControl.js +2 -1
  107. package/dist/react/components/SegmentedControl.js.map +1 -1
  108. package/dist/react/components/StarRating.d.ts.map +1 -1
  109. package/dist/react/components/StarRating.js +9 -5
  110. package/dist/react/components/StarRating.js.map +1 -1
  111. package/dist/react/components/StatStrip.d.ts.map +1 -1
  112. package/dist/react/components/StatStrip.js +2 -1
  113. package/dist/react/components/StatStrip.js.map +1 -1
  114. package/dist/react/components/StatTile.d.ts.map +1 -1
  115. package/dist/react/components/StatTile.js +3 -2
  116. package/dist/react/components/StatTile.js.map +1 -1
  117. package/dist/react/components/Tabs.d.ts.map +1 -1
  118. package/dist/react/components/Tabs.js +2 -1
  119. package/dist/react/components/Tabs.js.map +1 -1
  120. package/dist/react/components/TruncatedText.d.ts +2 -1
  121. package/dist/react/components/TruncatedText.d.ts.map +1 -1
  122. package/dist/react/components/TruncatedText.js +2 -1
  123. package/dist/react/components/TruncatedText.js.map +1 -1
  124. package/dist/react/components/calendar/MonthGrid.d.ts.map +1 -1
  125. package/dist/react/components/calendar/MonthGrid.js +2 -1
  126. package/dist/react/components/calendar/MonthGrid.js.map +1 -1
  127. package/dist/react/components/calendar/WeekView.d.ts.map +1 -1
  128. package/dist/react/components/calendar/WeekView.js +2 -1
  129. package/dist/react/components/calendar/WeekView.js.map +1 -1
  130. package/dist/react/components/revealOnHover.d.ts.map +1 -1
  131. package/dist/react/components/revealOnHover.js +2 -1
  132. package/dist/react/components/revealOnHover.js.map +1 -1
  133. package/dist/react/file-tree/FileTree.d.ts +50 -1
  134. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  135. package/dist/react/file-tree/FileTree.js +234 -6
  136. package/dist/react/file-tree/FileTree.js.map +1 -1
  137. package/dist/react/file-tree/fileTree.css +52 -0
  138. package/dist/react/filter-rail/FilterRail.d.ts.map +1 -1
  139. package/dist/react/filter-rail/FilterRail.js +5 -4
  140. package/dist/react/filter-rail/FilterRail.js.map +1 -1
  141. package/dist/react/folder-tree/FolderTreeRail.d.ts.map +1 -1
  142. package/dist/react/folder-tree/FolderTreeRail.js +2 -1
  143. package/dist/react/folder-tree/FolderTreeRail.js.map +1 -1
  144. package/dist/react/index.d.ts +5 -1
  145. package/dist/react/index.d.ts.map +1 -1
  146. package/dist/react/index.js +5 -1
  147. package/dist/react/index.js.map +1 -1
  148. package/dist/react/layoutEngine/index.js +1 -1
  149. package/dist/react/layoutEngine/index.js.map +1 -1
  150. package/dist/react/lib/field.d.ts.map +1 -1
  151. package/dist/react/lib/field.js +3 -2
  152. package/dist/react/lib/field.js.map +1 -1
  153. package/dist/react/lib/floatingStyles.d.ts.map +1 -1
  154. package/dist/react/lib/floatingStyles.js +2 -1
  155. package/dist/react/lib/floatingStyles.js.map +1 -1
  156. package/dist/react/lib/tapTarget.d.ts +96 -0
  157. package/dist/react/lib/tapTarget.d.ts.map +1 -0
  158. package/dist/react/lib/tapTarget.js +96 -0
  159. package/dist/react/lib/tapTarget.js.map +1 -0
  160. package/dist/react/media/VideoTrimmer.d.ts +1 -1
  161. package/dist/react/media/VideoTrimmer.js +1 -1
  162. package/dist/react/media/denseGallery.css +3 -3
  163. package/dist/react/media/ffmpegWasm.js +2 -2
  164. package/dist/react/media/ffmpegWasm.js.map +1 -1
  165. package/dist/react/media/videoPlayer.css +42 -0
  166. package/dist/react/namespace/NamespaceTargetCapsule.d.ts +2 -2
  167. package/dist/react/namespace/NamespaceTargetCapsule.d.ts.map +1 -1
  168. package/dist/react/nav/BreadcrumbTrail.d.ts.map +1 -1
  169. package/dist/react/nav/BreadcrumbTrail.js +4 -3
  170. package/dist/react/nav/BreadcrumbTrail.js.map +1 -1
  171. package/dist/react/nav/HubTileGrid.d.ts.map +1 -1
  172. package/dist/react/nav/HubTileGrid.js +2 -1
  173. package/dist/react/nav/HubTileGrid.js.map +1 -1
  174. package/dist/react/nav/SideNav.d.ts.map +1 -1
  175. package/dist/react/nav/SideNav.js +4 -3
  176. package/dist/react/nav/SideNav.js.map +1 -1
  177. package/dist/react/nav/SideNavCustomize.d.ts.map +1 -1
  178. package/dist/react/nav/SideNavCustomize.js +2 -1
  179. package/dist/react/nav/SideNavCustomize.js.map +1 -1
  180. package/dist/react/palette/CommandPalette.d.ts.map +1 -1
  181. package/dist/react/palette/CommandPalette.js +3 -2
  182. package/dist/react/palette/CommandPalette.js.map +1 -1
  183. package/dist/server/drivers/pooledControlStores.pg.test.js +1 -0
  184. package/dist/server/drivers/pooledControlStores.pg.test.js.map +1 -1
  185. package/dist/server/drivers/pooledIdentityStore.pg.test.js +1 -0
  186. package/dist/server/drivers/pooledIdentityStore.pg.test.js.map +1 -1
  187. package/dist/server/drivers/retentionPolicy.pg.test.js +1 -0
  188. package/dist/server/drivers/retentionPolicy.pg.test.js.map +1 -1
  189. package/dist/server/guard/argon2Binding.d.ts +1 -1
  190. package/dist/server/guard/argon2Binding.js +1 -1
  191. package/dist/server/incidents/store.d.ts +3 -3
  192. package/dist/server/index.js +1 -1
  193. package/dist/server/index.js.map +1 -1
  194. package/dist/server/master-lock/masterLock.d.ts +1 -1
  195. package/dist/server/media-bun/index.js +1 -1
  196. package/dist/server/media-bun/index.js.map +1 -1
  197. package/dist/server/paths/appDataHome.d.ts +20 -0
  198. package/dist/server/paths/appDataHome.d.ts.map +1 -0
  199. package/dist/server/paths/appDataHome.js +69 -0
  200. package/dist/server/paths/appDataHome.js.map +1 -0
  201. package/dist/server/retention/archive.d.ts +3 -4
  202. package/dist/server/retention/archive.d.ts.map +1 -1
  203. package/dist/server/retention/archive.js +20 -15
  204. package/dist/server/retention/archive.js.map +1 -1
  205. package/dist/server/retention/index.d.ts +1 -1
  206. package/dist/server/retention/index.js +1 -1
  207. package/dist/server/retention/policy.d.ts +2 -2
  208. package/dist/server/retention/policy.d.ts.map +1 -1
  209. package/dist/server/retention/policy.js +1 -1
  210. package/dist/server/retention/store.d.ts.map +1 -1
  211. package/dist/server/retention/store.js +27 -20
  212. package/dist/server/retention/store.js.map +1 -1
  213. package/dist/server/runtime/localHost.d.ts +10 -5
  214. package/dist/server/runtime/localHost.d.ts.map +1 -1
  215. package/dist/server/runtime/localHost.js +12 -8
  216. package/dist/server/runtime/localHost.js.map +1 -1
  217. package/dist/server/runtime/meterRollup.pg.test.js +1 -0
  218. package/dist/server/runtime/meterRollup.pg.test.js.map +1 -1
  219. package/dist/server/runtime/recipe.d.ts +4 -4
  220. package/dist/server/runtime/retentionJob.pg.test.js +1 -0
  221. package/dist/server/runtime/retentionJob.pg.test.js.map +1 -1
  222. package/dist/server/search/pgSearchProvider.pg.test.js +1 -0
  223. package/dist/server/search/pgSearchProvider.pg.test.js.map +1 -1
  224. package/dist/shared/errors.d.ts +1 -1
  225. package/dist/shared/errors.js +1 -1
  226. package/dist/styles-static.css +391 -0
  227. package/dist/styles.css +27 -0
  228. package/package.json +19 -167
  229. package/scripts/generateStaticStyles.ts +122 -0
  230. package/scripts/publicSurface.ts +455 -0
  231. package/scripts/testTmpRoot.ts +34 -5
  232. package/src/client/index.ts +1 -1
  233. package/src/core/category/index.ts +2 -1
  234. package/src/core/category/types.ts +1 -1
  235. package/src/core/file-tree/fileTreeModel.spec.ts +134 -0
  236. package/src/core/file-tree/fileTreeModel.ts +104 -0
  237. package/src/core/layout/index.ts +1 -1
  238. package/src/core/media/index.ts +1 -1
  239. package/src/core/media/types.ts +1 -1
  240. package/src/core/media/uploadLimits.ts +1 -1
  241. package/src/core/sharing/model.ts +1 -1
  242. package/src/ctgr/index.ts +1 -1
  243. package/src/declaredDepsAreImported.spec.ts +87 -0
  244. package/src/demoFixture.spec.ts +133 -0
  245. package/src/index.ts +1 -1
  246. package/src/lockfileIsTracked.spec.ts +50 -0
  247. package/src/namedSubpathsResolve.spec.ts +119 -0
  248. package/src/publicSurface.spec.ts +72 -0
  249. package/src/react/activity-bar/ActivityBar.tsx +17 -0
  250. package/src/react/analytics/index.ts +1 -1
  251. package/src/react/chat/MessageComposer.tsx +5 -4
  252. package/src/react/components/Button.tsx +3 -2
  253. package/src/react/components/Checkbox.spec.tsx +51 -0
  254. package/src/react/components/Checkbox.tsx +68 -26
  255. package/src/react/components/CodeArea.tsx +2 -1
  256. package/src/react/components/CodeChip.spec.tsx +2 -2
  257. package/src/react/components/Collapsible.tsx +2 -1
  258. package/src/react/components/ComboboxField.tsx +3 -2
  259. package/src/react/components/CopyButton.tsx +2 -1
  260. package/src/react/components/CopyableText.spec.tsx +1 -1
  261. package/src/react/components/DataTable/ColumnChecklist.tsx +2 -1
  262. package/src/react/components/DataTable/Pagination.tsx +2 -1
  263. package/src/react/components/DataTable/SortHeader.tsx +2 -1
  264. package/src/react/components/DataTable/{FilterBuilder.tsx → TableFilterBuilder.tsx} +33 -5
  265. package/src/react/components/DataTable/Toolbar.tsx +3 -3
  266. package/src/react/components/DataTable/cells/LinkCell.tsx +2 -1
  267. package/src/react/components/DataTable/index.ts +8 -1
  268. package/src/react/components/DragHandle.tsx +2 -1
  269. package/src/react/components/EmojiField.tsx +2 -1
  270. package/src/react/components/EventTimeline.tsx +3 -2
  271. package/src/react/components/FileLocation.spec.tsx +1 -1
  272. package/src/react/components/IconButton.tsx +1 -1
  273. package/src/react/components/InfoHint.tsx +5 -4
  274. package/src/react/components/InheritBadge.tsx +1 -1
  275. package/src/react/components/InlineEditableText.tsx +2 -1
  276. package/src/react/components/MeterList.tsx +2 -1
  277. package/src/react/components/PathText.tsx +4 -3
  278. package/src/react/components/RatingField.tsx +2 -1
  279. package/src/react/components/SecretInput.tsx +3 -3
  280. package/src/react/components/SegmentedControl.tsx +2 -2
  281. package/src/react/components/StarRating.spec.tsx +2 -2
  282. package/src/react/components/StarRating.tsx +9 -5
  283. package/src/react/components/StatStrip.tsx +2 -1
  284. package/src/react/components/StatTile.tsx +3 -3
  285. package/src/react/components/Tabs.spec.tsx +3 -2
  286. package/src/react/components/Tabs.tsx +2 -1
  287. package/src/react/components/TruncatedText.tsx +2 -1
  288. package/src/react/components/calendar/MonthGrid.spec.tsx +2 -1
  289. package/src/react/components/calendar/MonthGrid.tsx +2 -1
  290. package/src/react/components/calendar/WeekView.tsx +2 -1
  291. package/src/react/components/revealOnHover.ts +3 -2
  292. package/src/react/file-tree/FileTree.spec.tsx +248 -0
  293. package/src/react/file-tree/FileTree.tsx +304 -4
  294. package/src/react/file-tree/fileTree.css +52 -0
  295. package/src/react/file-tree/fileTreeGeometry.fixture.json +151 -0
  296. package/src/react/file-tree/fileTreeGeometry.spec.ts +33 -52
  297. package/src/react/filter-rail/FilterRail.spec.tsx +2 -1
  298. package/src/react/filter-rail/FilterRail.tsx +5 -4
  299. package/src/react/folder-tree/FolderTreeRail.tsx +2 -1
  300. package/src/react/index.ts +13 -3
  301. package/src/react/layoutEngine/index.ts +1 -1
  302. package/src/react/layoutEngine/types.ts +1 -1
  303. package/src/react/lib/field.ts +4 -2
  304. package/src/react/lib/floatingStyles.spec.ts +2 -1
  305. package/src/react/lib/floatingStyles.ts +3 -1
  306. package/src/react/lib/tapTarget.ts +100 -0
  307. package/src/react/media/MaskPainter.tsx +2 -2
  308. package/src/react/media/VideoTrimmer.tsx +1 -1
  309. package/src/react/media/denseGallery.css +3 -3
  310. package/src/react/media/ffmpegWasm.ts +2 -2
  311. package/src/react/media/videoPlayer.css +42 -0
  312. package/src/react/namespace/NamespaceTargetCapsule.tsx +2 -2
  313. package/src/react/nav/BreadcrumbTrail.tsx +4 -3
  314. package/src/react/nav/HubTileGrid.tsx +2 -1
  315. package/src/react/nav/SideNav.tsx +4 -3
  316. package/src/react/nav/SideNavCustomize.tsx +2 -1
  317. package/src/react/palette/CommandPalette.tsx +3 -2
  318. package/src/server/ctgr.ts +1 -1
  319. package/src/server/drivers/pooledControlStores.pg.test.ts +1 -0
  320. package/src/server/drivers/pooledIdentityStore.pg.test.ts +1 -0
  321. package/src/server/drivers/retentionPolicy.pg.test.ts +1 -0
  322. package/src/server/guard/argon2Binding.ts +1 -1
  323. package/src/server/incidents/store.ts +3 -3
  324. package/src/server/index.ts +1 -1
  325. package/src/server/master-lock/masterLock.spec.ts +18 -2
  326. package/src/server/master-lock/masterLock.ts +1 -1
  327. package/src/server/media-bun/image.spec.ts +1 -0
  328. package/src/server/media-bun/index.ts +1 -1
  329. package/src/server/media-bun/video.spec.ts +1 -0
  330. package/src/server/paths/appDataHome.spec.ts +87 -0
  331. package/src/server/paths/appDataHome.ts +84 -0
  332. package/src/server/retention/archive.spec.ts +26 -4
  333. package/src/server/retention/archive.ts +20 -15
  334. package/src/server/retention/index.ts +1 -1
  335. package/src/server/retention/policy.ts +2 -2
  336. package/src/server/retention/store.spec.ts +38 -10
  337. package/src/server/retention/store.ts +26 -19
  338. package/src/server/runtime/localHost.spec.ts +22 -3
  339. package/src/server/runtime/localHost.ts +12 -9
  340. package/src/server/runtime/meterRollup.pg.test.ts +1 -0
  341. package/src/server/runtime/recipe.ts +4 -4
  342. package/src/server/runtime/retentionJob.pg.test.ts +1 -0
  343. package/src/server/search/pgSearchProvider.pg.test.ts +1 -0
  344. package/src/server/storage/binaryServerProcessing.integration.spec.ts +6 -2
  345. package/src/server/storage/chunked.integration.spec.ts +5 -2
  346. package/src/server/storage/fileUploadClient-chunked.integration.spec.ts +5 -2
  347. package/src/server/storage/files/adapter.photo.display.spec.ts +1 -0
  348. package/src/server/storage/imageThumbnail.spec.ts +1 -0
  349. package/src/server/storage/transcode.integration.spec.ts +5 -2
  350. package/src/server/storage/videoProcessor.posterOnly.spec.ts +1 -0
  351. package/src/shared/errors.ts +1 -1
  352. package/src/skippedTestsAreAnswered.spec.ts +215 -0
  353. package/src/styles-static.css +391 -0
  354. package/src/styles.css +27 -0
  355. package/src/stylesStaticMatches.spec.ts +134 -0
  356. package/src/tapTargetStylesheet.spec.ts +150 -0
  357. package/src/testTmpRoot.spec.ts +102 -0
  358. package/dist/core/duplicates/index.d.ts +0 -116
  359. package/dist/core/duplicates/index.d.ts.map +0 -1
  360. package/dist/core/duplicates/index.js +0 -178
  361. package/dist/core/duplicates/index.js.map +0 -1
  362. package/dist/core/namespace/helpers.d.ts +0 -14
  363. package/dist/core/namespace/helpers.d.ts.map +0 -1
  364. package/dist/core/namespace/helpers.js +0 -29
  365. package/dist/core/namespace/helpers.js.map +0 -1
  366. package/dist/core/namespace/index.d.ts +0 -4
  367. package/dist/core/namespace/index.d.ts.map +0 -1
  368. package/dist/core/namespace/index.js +0 -6
  369. package/dist/core/namespace/index.js.map +0 -1
  370. package/dist/core/namespace/memoryStore.d.ts +0 -19
  371. package/dist/core/namespace/memoryStore.d.ts.map +0 -1
  372. package/dist/core/namespace/memoryStore.js +0 -70
  373. package/dist/core/namespace/memoryStore.js.map +0 -1
  374. package/dist/core/namespace/types.d.ts +0 -44
  375. package/dist/core/namespace/types.d.ts.map +0 -1
  376. package/dist/core/namespace/types.js +0 -8
  377. package/dist/core/namespace/types.js.map +0 -1
  378. package/dist/game/core/DisposeRegistry.d.ts +0 -42
  379. package/dist/game/core/DisposeRegistry.d.ts.map +0 -1
  380. package/dist/game/core/DisposeRegistry.js +0 -67
  381. package/dist/game/core/DisposeRegistry.js.map +0 -1
  382. package/dist/game/core/drawBudget.d.ts +0 -26
  383. package/dist/game/core/drawBudget.d.ts.map +0 -1
  384. package/dist/game/core/drawBudget.js +0 -55
  385. package/dist/game/core/drawBudget.js.map +0 -1
  386. package/dist/game/core/fixedTimestep.d.ts +0 -38
  387. package/dist/game/core/fixedTimestep.d.ts.map +0 -1
  388. package/dist/game/core/fixedTimestep.js +0 -47
  389. package/dist/game/core/fixedTimestep.js.map +0 -1
  390. package/dist/game/core/index.d.ts +0 -9
  391. package/dist/game/core/index.d.ts.map +0 -1
  392. package/dist/game/core/index.js +0 -8
  393. package/dist/game/core/index.js.map +0 -1
  394. package/dist/game/core/inputCollector.d.ts +0 -82
  395. package/dist/game/core/inputCollector.d.ts.map +0 -1
  396. package/dist/game/core/inputCollector.js +0 -176
  397. package/dist/game/core/inputCollector.js.map +0 -1
  398. package/dist/game/core/resizeCanvas.d.ts +0 -19
  399. package/dist/game/core/resizeCanvas.d.ts.map +0 -1
  400. package/dist/game/core/resizeCanvas.js +0 -29
  401. package/dist/game/core/resizeCanvas.js.map +0 -1
  402. package/dist/game/react/index.d.ts +0 -3
  403. package/dist/game/react/index.d.ts.map +0 -1
  404. package/dist/game/react/index.js +0 -5
  405. package/dist/game/react/index.js.map +0 -1
  406. package/dist/game/react/useGameEngine.d.ts +0 -64
  407. package/dist/game/react/useGameEngine.d.ts.map +0 -1
  408. package/dist/game/react/useGameEngine.js +0 -78
  409. package/dist/game/react/useGameEngine.js.map +0 -1
  410. package/dist/react/canvas/CanvasNavHUD.d.ts +0 -64
  411. package/dist/react/canvas/CanvasNavHUD.d.ts.map +0 -1
  412. package/dist/react/canvas/CanvasNavHUD.js +0 -58
  413. package/dist/react/canvas/CanvasNavHUD.js.map +0 -1
  414. package/dist/react/canvas/fitLabel.d.ts +0 -53
  415. package/dist/react/canvas/fitLabel.d.ts.map +0 -1
  416. package/dist/react/canvas/fitLabel.js +0 -104
  417. package/dist/react/canvas/fitLabel.js.map +0 -1
  418. package/dist/react/canvas/index.d.ts +0 -4
  419. package/dist/react/canvas/index.d.ts.map +0 -1
  420. package/dist/react/canvas/index.js +0 -4
  421. package/dist/react/canvas/index.js.map +0 -1
  422. package/dist/react/canvas/lod.d.ts +0 -40
  423. package/dist/react/canvas/lod.d.ts.map +0 -1
  424. package/dist/react/canvas/lod.js +0 -37
  425. package/dist/react/canvas/lod.js.map +0 -1
  426. package/dist/react/components/DataTable/FilterBuilder.d.ts +0 -38
  427. package/dist/react/components/DataTable/FilterBuilder.d.ts.map +0 -1
  428. package/dist/react/components/DataTable/FilterBuilder.js.map +0 -1
  429. package/dist/react/seo/HeadProvider.d.ts +0 -14
  430. package/dist/react/seo/HeadProvider.d.ts.map +0 -1
  431. package/dist/react/seo/HeadProvider.js +0 -44
  432. package/dist/react/seo/HeadProvider.js.map +0 -1
  433. package/dist/react/seo/applyHeadToDocument.d.ts +0 -3
  434. package/dist/react/seo/applyHeadToDocument.d.ts.map +0 -1
  435. package/dist/react/seo/applyHeadToDocument.js +0 -66
  436. package/dist/react/seo/applyHeadToDocument.js.map +0 -1
  437. package/dist/react/seo/index.d.ts +0 -3
  438. package/dist/react/seo/index.d.ts.map +0 -1
  439. package/dist/react/seo/index.js +0 -7
  440. package/dist/react/seo/index.js.map +0 -1
  441. package/dist/react/timeline/ClipView.d.ts +0 -29
  442. package/dist/react/timeline/ClipView.d.ts.map +0 -1
  443. package/dist/react/timeline/ClipView.js +0 -53
  444. package/dist/react/timeline/ClipView.js.map +0 -1
  445. package/dist/react/timeline/Playhead.d.ts +0 -16
  446. package/dist/react/timeline/Playhead.d.ts.map +0 -1
  447. package/dist/react/timeline/Playhead.js +0 -26
  448. package/dist/react/timeline/Playhead.js.map +0 -1
  449. package/dist/react/timeline/TimeRuler.d.ts +0 -21
  450. package/dist/react/timeline/TimeRuler.d.ts.map +0 -1
  451. package/dist/react/timeline/TimeRuler.js +0 -31
  452. package/dist/react/timeline/TimeRuler.js.map +0 -1
  453. package/dist/react/timeline/TimelineSurface.d.ts +0 -29
  454. package/dist/react/timeline/TimelineSurface.d.ts.map +0 -1
  455. package/dist/react/timeline/TimelineSurface.js +0 -56
  456. package/dist/react/timeline/TimelineSurface.js.map +0 -1
  457. package/dist/react/timeline/TrackLane.d.ts +0 -31
  458. package/dist/react/timeline/TrackLane.d.ts.map +0 -1
  459. package/dist/react/timeline/TrackLane.js +0 -32
  460. package/dist/react/timeline/TrackLane.js.map +0 -1
  461. package/dist/react/timeline/index.d.ts +0 -6
  462. package/dist/react/timeline/index.d.ts.map +0 -1
  463. package/dist/react/timeline/index.js +0 -12
  464. package/dist/react/timeline/index.js.map +0 -1
  465. package/dist/server/private-media/index.d.ts +0 -7
  466. package/dist/server/private-media/index.d.ts.map +0 -1
  467. package/dist/server/private-media/index.js +0 -13
  468. package/dist/server/private-media/index.js.map +0 -1
  469. package/dist/server/seo/index.d.ts +0 -3
  470. package/dist/server/seo/index.d.ts.map +0 -1
  471. package/dist/server/seo/index.js +0 -6
  472. package/dist/server/seo/index.js.map +0 -1
  473. package/dist/server/seo/mountSeoRoutes.d.ts +0 -9
  474. package/dist/server/seo/mountSeoRoutes.d.ts.map +0 -1
  475. package/dist/server/seo/mountSeoRoutes.js +0 -19
  476. package/dist/server/seo/mountSeoRoutes.js.map +0 -1
  477. package/dist/server/seo/serveHeadInjectedIndex.d.ts +0 -9
  478. package/dist/server/seo/serveHeadInjectedIndex.d.ts.map +0 -1
  479. package/dist/server/seo/serveHeadInjectedIndex.js +0 -20
  480. package/dist/server/seo/serveHeadInjectedIndex.js.map +0 -1
  481. package/dist/upload.d.ts +0 -148
  482. package/dist/upload.d.ts.map +0 -1
  483. package/dist/upload.js +0 -159
  484. package/dist/upload.js.map +0 -1
  485. package/dist/verify/runtime/index.d.ts +0 -16
  486. package/dist/verify/runtime/index.d.ts.map +0 -1
  487. package/dist/verify/runtime/index.js +0 -16
  488. package/dist/verify/runtime/index.js.map +0 -1
  489. package/scripts/demoCheck.ts +0 -42
  490. package/src/core/duplicates/index.spec.ts +0 -116
  491. package/src/core/duplicates/index.ts +0 -203
  492. package/src/core/namespace/helpers.ts +0 -31
  493. package/src/core/namespace/index.ts +0 -12
  494. package/src/core/namespace/memoryStore.ts +0 -77
  495. package/src/core/namespace/namespace.spec.ts +0 -117
  496. package/src/core/namespace/types.ts +0 -53
  497. package/src/game/core/DisposeRegistry.spec.ts +0 -53
  498. package/src/game/core/DisposeRegistry.ts +0 -70
  499. package/src/game/core/drawBudget.spec.ts +0 -51
  500. package/src/game/core/drawBudget.ts +0 -75
  501. package/src/game/core/fixedTimestep.spec.ts +0 -91
  502. package/src/game/core/fixedTimestep.ts +0 -79
  503. package/src/game/core/index.ts +0 -16
  504. package/src/game/core/inputCollector.spec.ts +0 -29
  505. package/src/game/core/inputCollector.ts +0 -245
  506. package/src/game/core/resizeCanvas.ts +0 -31
  507. package/src/game/react/index.ts +0 -10
  508. package/src/game/react/useGameEngine.ts +0 -125
  509. package/src/react/canvas/CanvasNavHUD.spec.tsx +0 -62
  510. package/src/react/canvas/CanvasNavHUD.tsx +0 -210
  511. package/src/react/canvas/fitLabel.spec.ts +0 -82
  512. package/src/react/canvas/fitLabel.ts +0 -150
  513. package/src/react/canvas/index.ts +0 -15
  514. package/src/react/canvas/lod.ts +0 -53
  515. package/src/react/seo/HeadProvider.tsx +0 -67
  516. package/src/react/seo/applyHeadToDocument.ts +0 -73
  517. package/src/react/seo/index.ts +0 -12
  518. package/src/react/seo/seo.spec.tsx +0 -101
  519. package/src/react/timeline/ClipView.tsx +0 -146
  520. package/src/react/timeline/Playhead.tsx +0 -63
  521. package/src/react/timeline/TimeRuler.tsx +0 -85
  522. package/src/react/timeline/TimelineSurface.tsx +0 -157
  523. package/src/react/timeline/TrackLane.tsx +0 -87
  524. package/src/react/timeline/index.ts +0 -12
  525. package/src/react/timeline/timeline.spec.tsx +0 -175
  526. package/src/server/private-media/index.ts +0 -45
  527. package/src/server/seo/index.ts +0 -8
  528. package/src/server/seo/mountSeoRoutes.ts +0 -37
  529. package/src/server/seo/seo.spec.ts +0 -128
  530. package/src/server/seo/serveHeadInjectedIndex.ts +0 -36
  531. package/src/upload.spec.ts +0 -300
  532. package/src/upload.ts +0 -288
  533. package/src/verify/runtime/index.ts +0 -64
@@ -14,6 +14,37 @@
14
14
  * {@link FileTreeProps.allowReorder}, and everything that made it good — the gesture, the
15
15
  * spring-open, the typeahead, the in-place rename — is here unchanged.
16
16
  *
17
+ * ── 🔴 The graduation verdict, 2026-09-13: this one stays, and it grew ──────────────────
18
+ * `cursedesk` has a `FileTree.tsx` of its own — the same ancestor, kept in step by hand ever
19
+ * since. The pair was compared to decide which should be the shared one, and this is: it is
20
+ * the app's tree with the workbook/sheet/note/merge vocabulary generalised away, and it has
21
+ * external drags, `canDrop`, `acceptsDrop`, reordering, filtering, access marks, counts and
22
+ * the three render slots, none of which the app's has.
23
+ *
24
+ * But the app's carried SIX behaviours this did not, every one of them general, and adopting
25
+ * this tree without them would have been a regression on a 200-row library rather than a
26
+ * graduation. They are here now and each says so where it lives:
27
+ *
28
+ * 1. **Reveal** — the selected row is scrolled into view, and a shut folder holding it is
29
+ * marked and scrolled to instead of being forced open (see the effect below).
30
+ * 2. **The path is marked** — `data-ancestor` on the folders between the root and the
31
+ * selection, `data-holds-open` on the shut one holding it (`openPath`).
32
+ * 3. **Escape cancels a drag** (`beginDrag`), which `pointercancel` never did.
33
+ * 4. **⌥→ / ⌥← move the focused row** (`resolveKeyboardMove`) — the keyboard's half of the
34
+ * drag, and the only half touch has.
35
+ * 5. **The drag chip says where it is going**, not only what is held (`describeDrop`).
36
+ * 6. **The click a finished drag leaves behind is swallowed** (`draggedJustNow`), and the
37
+ * keyboard comes back from a rename instead of being dropped on `<body>`.
38
+ *
39
+ * Two more joined them because they were the same gap wearing a different hat: `onRowMenu`
40
+ * (Shift+F10 / the Menu key, since the tool shelf is deliberately not a tab stop) and
41
+ * `aria-posinset`/`aria-setsize`.
42
+ *
43
+ * What did NOT come across is the app's vocabulary — workbooks, merge-into-a-tab, notes,
44
+ * derived extensions, `activeNoteId`/`activeMediaId` as separate props. A feature the app
45
+ * needs and a design system should not have is an argument for a prop, not for a copy: the
46
+ * three claimants collapse into one `selectedId`, and a kind-specific glyph is `renderIcon`.
47
+ *
17
48
  * ── Pointer events, not HTML5 drag-and-drop, for INTERNAL drags ─────────────────────────
18
49
  * The ghost image is a translucent screenshot nobody asked for, `dragover` fires at the
19
50
  * mercy of the browser, and touch does not have HTML5 dnd at all. The drag only *starts*
@@ -37,6 +68,7 @@ import type {
37
68
  } from 'react';
38
69
  import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
39
70
  import {
71
+ describeDrop,
40
72
  descendantIdsOf,
41
73
  type DropOffer,
42
74
  type FileTreeDrop,
@@ -46,6 +78,7 @@ import {
46
78
  flattenFileTree,
47
79
  matchTypeahead,
48
80
  resolveDrop,
81
+ resolveKeyboardMove,
49
82
  } from '../../core/file-tree/fileTreeModel';
50
83
  import { Input } from '../components/Input';
51
84
  import { Tooltip } from '../components/Tooltip';
@@ -159,6 +192,22 @@ export interface FileTreeProps {
159
192
  onDraftCommit?: (name: string) => void;
160
193
  onDraftCancel?: () => void;
161
194
 
195
+ /**
196
+ * The focused row's own menu, reached by **Shift+F10** or the **Menu** key — the keyboard's
197
+ * route to whatever {@link renderTools} draws.
198
+ *
199
+ * 🔴 The shelf is deliberately NOT a tab stop of its own (that would be one extra stop per
200
+ * row, which is what the roving-tabindex rule above exists to prevent), so without this the
201
+ * ⋯ menu on every row is unreachable without a pointer. Shift+F10 and the Menu key are what
202
+ * every desktop application answers a context menu on, so it is not a shortcut anybody has
203
+ * to be told about.
204
+ *
205
+ * The point is handed in viewport coordinates — the focused row's bottom-right corner —
206
+ * which is what a popover anchors to. Wire it to the SAME call the shelf's own button
207
+ * makes, so the two cannot drift into meaning different things.
208
+ */
209
+ onRowMenu?: (node: FileTreeNode, at: { x: number; y: number }) => void;
210
+
162
211
  /** Trailing controls for a row — the tool shelf. Quiet at rest, never invisible. */
163
212
  renderTools?: (node: FileTreeNode) => ReactNode;
164
213
  /** Leading glyph. Defaults to a folder/file mark. */
@@ -276,6 +325,7 @@ export function FileTree({
276
325
  draft,
277
326
  onDraftCommit,
278
327
  onDraftCancel,
328
+ onRowMenu,
279
329
  renderTools,
280
330
  renderIcon,
281
331
  renderAccessory,
@@ -295,6 +345,17 @@ export function FileTree({
295
345
  /** Folders this drag opened by hovering, to shut again if it never drops. */
296
346
  const sprung = useRef<string[]>([]);
297
347
  const typeahead = useRef({ word: '', at: 0 });
348
+ /**
349
+ * Swallow the `click` a finished drag leaves behind.
350
+ *
351
+ * 🔴 A pointerdown and a pointerup on the same row still produce a click even when the
352
+ * pointer travelled the length of the list in between — and with `setPointerCapture` the
353
+ * click is delivered to the row the gesture STARTED on whatever it ended over. So without
354
+ * this, dropping a row into a folder also selects the row that was dropped, which in a
355
+ * consumer that navigates on select means the drag takes you somewhere. The drag is the
356
+ * gesture; the click is debris from it.
357
+ */
358
+ const draggedJustNow = useRef(false);
298
359
 
299
360
  const [ownCollapsed, setOwnCollapsed] = useState<readonly string[]>([]);
300
361
  const collapsed = collapsedProp ?? ownCollapsed;
@@ -310,6 +371,15 @@ export function FileTree({
310
371
  const [focusedId, setFocusedId] = useState<string | null>(null);
311
372
  /** The row an EXTERNAL drag is over, drawn with the same marker an internal one uses. */
312
373
  const [externalOverId, setExternalOverId] = useState<string | null>(null);
374
+ /**
375
+ * The last thing a KEYBOARD move said, for the live region.
376
+ *
377
+ * A pointer drag states its refusal on the chip while the row is still in the air; ⌥← on a
378
+ * top-level row has no chip and nowhere else to say "it is already at the top level", so a
379
+ * keyboard route with no voice would be a control that silently does nothing — the exact
380
+ * reading of "broken" this tree's whole drop design exists to avoid.
381
+ */
382
+ const [notice, setNotice] = useState('');
313
383
 
314
384
  const shown = useMemo(() => (filter ? filterFileTree(nodes, filter) : nodes), [nodes, filter]);
315
385
  /*
@@ -321,6 +391,52 @@ export function FileTree({
321
391
  [shown, collapsed, filter],
322
392
  );
323
393
 
394
+ /**
395
+ * The folders between the root and the selected row, and the shut one HOLDING it.
396
+ *
397
+ * A tree with most of its folders shut says nothing about where you are, and the one state
398
+ * where that matters most is the state where the selected row is not drawn at all. So the
399
+ * path is marked (`data-ancestor`) and the folder actually holding the selection is marked
400
+ * more strongly (`data-holds-open`) — which is also the thing the reveal below scrolls to
401
+ * when the row itself does not exist on screen.
402
+ *
403
+ * Computed over the whole tree rather than over `rows`, because a shut folder's children
404
+ * are not in `rows` — which is exactly the case this is for.
405
+ */
406
+ const openPath = useMemo(() => {
407
+ const path = new Set<string>();
408
+ let holder: string | null = null;
409
+ if (selectedId === null) return { path, holder };
410
+ const walk = (list: readonly FileTreeNode[], ancestors: string[]): boolean => {
411
+ for (const node of list) {
412
+ if (node.id === selectedId) {
413
+ for (const id of ancestors) path.add(id);
414
+ holder = ancestors.at(-1) ?? null;
415
+ return true;
416
+ }
417
+ if (node.children && walk(node.children, [...ancestors, node.id])) return true;
418
+ }
419
+ return false;
420
+ };
421
+ walk(shown, []);
422
+ return { path, holder };
423
+ }, [shown, selectedId]);
424
+
425
+ /**
426
+ * How many siblings a row has, by parent — `aria-setsize`, without an O(n²) scan per row.
427
+ *
428
+ * Every sibling of a visible row is itself visible (a shut folder hides its children, never
429
+ * its peers), so counting the rows on screen is counting the level.
430
+ */
431
+ const setSizes = useMemo(() => {
432
+ const counts = new Map<string, number>();
433
+ for (const row of rows) {
434
+ const key = row.parentId ?? '';
435
+ counts.set(key, (counts.get(key) ?? 0) + 1);
436
+ }
437
+ return counts;
438
+ }, [rows]);
439
+
324
440
  const toggle = useCallback(
325
441
  (id: string) => {
326
442
  const set = new Set(collapsed);
@@ -340,6 +456,51 @@ export function FileTree({
340
456
  [collapsed, setCollapsed],
341
457
  );
342
458
 
459
+ /**
460
+ * Bring the selected row into view — the explorer's *reveal*.
461
+ *
462
+ * 🔴 Every other cue for "where am I" is worth nothing in the one case that matters most:
463
+ * a long list where the selected row is eight hundred pixels above the fold. A consumer
464
+ * changes `selectedId` from a command palette, a tab, a Back button, a deep link or a
465
+ * recents list — all of them **without the pointer ever going near the tree** — so nothing
466
+ * was scrolling it. `cursedesk`'s tree has had this since it was written and this one did
467
+ * not, which would have made adopting it a regression on a 200-row library.
468
+ *
469
+ * Three deliberate limits, each of which VS Code gets right or wrong depending on a setting:
470
+ *
471
+ * 1. 🔴 **A shut folder stays shut.** Its `data-holds-open` marker is scrolled to instead.
472
+ * Auto-expanding is what `explorer.autoReveal` does and it is the behaviour people turn
473
+ * off, because it silently undoes a collapse they chose — and `collapsed` here is often
474
+ * a CONTROLLED value the consumer persists, so expanding would write it open for good.
475
+ * 2. **Only when the row is not already visible.** A list that jumps a few pixels every
476
+ * time you click a row you can already see reads as a bug.
477
+ * 3. **Never mid-drag.** Scrolling the list under a drag moves the drop target out from
478
+ * under the pointer.
479
+ */
480
+ /*
481
+ * 🔴 The dependency list is deliberately neither what this reads nor a superset of it.
482
+ *
483
+ * `selectedId` is not read in the body at all — the row is found through the DOM, because
484
+ * `data-holds-open` is the answer precisely when the selected row has none — but it is the
485
+ * one thing whose CHANGE should reveal. And `drag` IS read, as a guard rather than a
486
+ * trigger: adding it would reveal the moment a drag ends, which is limit 3 above.
487
+ */
488
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `selectedId` is the trigger and not a read; `drag` is a guard and not a trigger — see just above.
489
+ useEffect(() => {
490
+ const scroller = scrollRef.current;
491
+ if (!scroller || drag?.active) return;
492
+ const row =
493
+ scroller.querySelector<HTMLElement>('.cbft-row[data-selected]') ??
494
+ scroller.querySelector<HTMLElement>('.cbft-row[data-holds-open]');
495
+ if (!row?.scrollIntoView) return;
496
+ const box = row.getBoundingClientRect();
497
+ const frame = scroller.getBoundingClientRect();
498
+ if (box.top >= frame.top && box.bottom <= frame.bottom) return;
499
+ // `nearest` on both axes: the least movement that makes the row visible, and no sideways
500
+ // travel in a list that does not scroll sideways.
501
+ row.scrollIntoView({ block: 'nearest', inline: 'nearest' });
502
+ }, [selectedId]);
503
+
343
504
  // ── The drag ──────────────────────────────────────────────────────────────────────────
344
505
 
345
506
  const stopAutoScroll = useCallback(() => {
@@ -373,6 +534,10 @@ export function FileTree({
373
534
  (commit: boolean, state: DragState | null) => {
374
535
  stopAutoScroll();
375
536
  stopSpring();
537
+ // Whatever happens next, a gesture that actually travelled owes the row a swallowed
538
+ // click — see `draggedJustNow`. Set for a cancelled drag too: the click arrives either
539
+ // way, and Escape landing you on a different row would be its own surprise.
540
+ if (state?.active) draggedJustNow.current = true;
376
541
  if (commit && state?.active && state.offer.target && onMove) {
377
542
  onMove(state.id, state.offer.target);
378
543
  sprung.current = [];
@@ -468,13 +633,32 @@ export function FileTree({
468
633
  target.removeEventListener('pointermove', move);
469
634
  target.removeEventListener('pointerup', up);
470
635
  target.removeEventListener('pointercancel', cancel);
636
+ window.removeEventListener('keydown', abort);
471
637
  endDrag(commit, state);
472
638
  };
473
639
  const up = finish(true);
474
640
  const cancel = finish(false);
641
+ /*
642
+ * 🔴 Escape puts the row back — the way out of a gesture you did not mean to start.
643
+ *
644
+ * Every drag surface has it and this one did not: the only way to abandon a drag here
645
+ * was to find somewhere the tree refuses and release there, which is a thing you have
646
+ * to already understand the rules to do. `pointercancel` fires when the SYSTEM takes
647
+ * the pointer away, never when a person changes their mind.
648
+ *
649
+ * On `window` rather than the row, because a pointer capture is in force and the
650
+ * keyboard's focus is wherever it was before the press — quite possibly not this tree.
651
+ * It is removed by `finish` on every exit path, including this one.
652
+ */
653
+ const abort = (keyEvent: KeyboardEvent) => {
654
+ if (keyEvent.key !== 'Escape') return;
655
+ keyEvent.preventDefault();
656
+ cancel();
657
+ };
475
658
  target.addEventListener('pointermove', move);
476
659
  target.addEventListener('pointerup', up);
477
660
  target.addEventListener('pointercancel', cancel);
661
+ window.addEventListener('keydown', abort);
478
662
  },
479
663
  [
480
664
  allowReorder,
@@ -512,6 +696,33 @@ export function FileTree({
512
696
  if (id) rowRefs.current.get(id)?.focus();
513
697
  }, []);
514
698
 
699
+ /*
700
+ * ── The keyboard comes back from a rename ─────────────────────────────────────────────
701
+ *
702
+ * Owner, of the tree this one was ported from: *"I clicked a file, pressed enter, changed
703
+ * the title, pressed enter, and it saved. Then I pressed enter and it should have started
704
+ * renaming again but didn't. I think the focus gets lost on saving a rename."*
705
+ *
706
+ * The word "lost" is literal: committing unmounts the `<input>` that had focus, and a
707
+ * browser hands focus to `<body>` when that happens. `<body>` is not in the tree, so the
708
+ * next Enter goes to the application — which has no answer for it — rather than to the row
709
+ * that was just renamed. Every key this tree defines is gone until something is clicked.
710
+ *
711
+ * 🔴 **Only when focus was actually lost.** Blur commits too, so the ordinary way to end a
712
+ * rename is to click something else, and stealing focus back would undo that click.
713
+ * `document.body` (or nothing at all) is the signature of the unmount; anything else means
714
+ * a person is already somewhere on purpose.
715
+ */
716
+ const wasRenaming = useRef<string | null>(null);
717
+ useEffect(() => {
718
+ const previous = wasRenaming.current;
719
+ wasRenaming.current = renamingId ?? null;
720
+ if (!previous || renamingId) return;
721
+ const active = document.activeElement;
722
+ if (active && active !== document.body) return;
723
+ focusRow(previous);
724
+ }, [renamingId, focusRow]);
725
+
515
726
  const onKeyDown = useCallback(
516
727
  (event: React.KeyboardEvent<HTMLElement>, row: FileTreeRow) => {
517
728
  if (renamingId) return;
@@ -520,6 +731,53 @@ export function FileTree({
520
731
  const next = rows[Math.min(rows.length - 1, Math.max(0, index + delta))];
521
732
  if (next) focusRow(next.node.id);
522
733
  };
734
+
735
+ /*
736
+ * The keyboard's route to the row's own menu — see {@link FileTreeProps.onRowMenu}.
737
+ * Before the switch, because `ContextMenu` and Shift+F10 are not arrow keys and the
738
+ * shelf is deliberately not a tab stop that could answer them itself.
739
+ */
740
+ if (onRowMenu && (event.key === 'ContextMenu' || (event.shiftKey && event.key === 'F10'))) {
741
+ event.preventDefault();
742
+ const box = rowRefs.current.get(row.node.id)?.getBoundingClientRect();
743
+ onRowMenu(row.node, { x: box?.right ?? 0, y: box?.bottom ?? 0 });
744
+ return;
745
+ }
746
+
747
+ /*
748
+ * ⌥→ and ⌥← MOVE the focused row — the keyboard's half of the drag, and the only half
749
+ * that exists on touch (a press-and-drag there scrolls the list, so the gesture is
750
+ * deliberately not offered). See `resolveKeyboardMove` for what the two directions mean
751
+ * and why there is no ⌥↑/⌥↓.
752
+ *
753
+ * 🔴 Before the arrow cases below, so one key never means two things. Everything else
754
+ * in this handler is bare-key, which is what keeps ⇧⏎/⌘F and the rest free for the app.
755
+ */
756
+ if (
757
+ onMove &&
758
+ event.altKey &&
759
+ !event.metaKey &&
760
+ !event.ctrlKey &&
761
+ (event.key === 'ArrowLeft' || event.key === 'ArrowRight')
762
+ ) {
763
+ event.preventDefault();
764
+ const offer = resolveKeyboardMove({
765
+ nodes: shown,
766
+ rows,
767
+ row,
768
+ direction: event.key === 'ArrowRight' ? 'in' : 'out',
769
+ ...(lockedReason ? { lockedReason } : {}),
770
+ ...(canDrop ? { canDrop } : {}),
771
+ });
772
+ if (offer.target) {
773
+ setNotice(describeDrop(shown, offer.target));
774
+ onMove(row.node.id, offer.target);
775
+ } else {
776
+ setNotice(offer.reason ?? '');
777
+ }
778
+ return;
779
+ }
780
+
523
781
  switch (event.key) {
524
782
  case 'ArrowDown':
525
783
  event.preventDefault();
@@ -617,7 +875,21 @@ export function FileTree({
617
875
  focusRow((rows[found] as FileTreeRow).node.id);
618
876
  }
619
877
  },
620
- [focusRow, onDelete, onOpen, onRenameStart, onSelect, renamingId, rows, toggle],
878
+ [
879
+ canDrop,
880
+ focusRow,
881
+ lockedReason,
882
+ onDelete,
883
+ onMove,
884
+ onOpen,
885
+ onRenameStart,
886
+ onRowMenu,
887
+ onSelect,
888
+ renamingId,
889
+ rows,
890
+ shown,
891
+ toggle,
892
+ ],
621
893
  );
622
894
 
623
895
  // ── External drags (Finder files, gallery tiles) ──────────────────────────────────────
@@ -825,6 +1097,11 @@ export function FileTree({
825
1097
  role='treeitem'
826
1098
  aria-selected={isSelected}
827
1099
  aria-level={row.depth + 1}
1100
+ /* A screen reader says "3 of 7" only if it is told the level's size. Both
1101
+ come from `shown`, so a filtered tree counts what it drew rather than
1102
+ what it hid. */
1103
+ aria-posinset={row.index + 1}
1104
+ aria-setsize={setSizes.get(row.parentId ?? '') ?? 1}
828
1105
  {...(row.expandable ? { 'aria-expanded': row.expanded } : {})}
829
1106
  tabIndex={
830
1107
  node.id === (focusedId ?? selectedId ?? rows[0]?.node.id) ? 0 : -1
@@ -839,6 +1116,13 @@ export function FileTree({
839
1116
  data-dragged={isDragged || undefined}
840
1117
  data-into={isDropTarget || undefined}
841
1118
  data-marker={marker ?? undefined}
1119
+ /* On the way to the selection, and — when it is shut — the one HOLDING it.
1120
+ See `openPath`: with the selected row not drawn at all, this bar is the
1121
+ only thing in the tree that says where you are. */
1122
+ data-ancestor={openPath.path.has(node.id) || undefined}
1123
+ data-holds-open={
1124
+ (row.expandable && !row.expanded && openPath.holder === node.id) || undefined
1125
+ }
842
1126
  data-access={node.access && node.access !== 'normal' ? node.access : undefined}
843
1127
  style={{ '--cbft-depth': row.depth } as React.CSSProperties}
844
1128
  onPointerDown={(event) => {
@@ -847,6 +1131,11 @@ export function FileTree({
847
1131
  }}
848
1132
  onClick={(event) => {
849
1133
  if (inTools(event.target)) return;
1134
+ // The click a finished drag left behind — see `draggedJustNow`.
1135
+ if (draggedJustNow.current) {
1136
+ draggedJustNow.current = false;
1137
+ return;
1138
+ }
850
1139
  onSelect(node);
851
1140
  }}
852
1141
  onDoubleClick={(event) => {
@@ -1033,6 +1322,11 @@ export function FileTree({
1033
1322
  * The chip that follows the cursor. There is no browser drag image with pointer
1034
1323
  * events, so this is the only thing saying the gesture is working — and it is also
1035
1324
  * where a refusal is stated, in words, while the row is still in the air.
1325
+ *
1326
+ * 🔴 It says WHERE IT IS GOING, not only what is held (2026-09-13). "Into Projects" or
1327
+ * "To the top level" is the answer to the question a drag actually asks, and it is the
1328
+ * difference between a cue that is decoration and one that stops a wrong drop before
1329
+ * the pointer is lifted. The name alone tells you the gesture started, which you knew.
1036
1330
  */}
1037
1331
  {drag?.active ? (
1038
1332
  <div
@@ -1041,12 +1335,18 @@ export function FileTree({
1041
1335
  style={{ left: `${drag.x}px`, top: `${drag.y}px` }}
1042
1336
  aria-hidden='true'
1043
1337
  >
1044
- {drag.offer.reason ?? nameOf(shown, drag.id)}
1338
+ <span className='cbft-chip-name'>{nameOf(shown, drag.id)}</span>
1339
+ {/* The refusal wins the slot: while something cannot land, "why not" outranks
1340
+ "where to", and there is never both to say. */}
1341
+ <span className='cbft-chip-where'>
1342
+ {drag.offer.reason ?? describeDrop(shown, drag.offer.target)}
1343
+ </span>
1045
1344
  </div>
1046
1345
  ) : null}
1047
- {/* The same words, for a screen reader, which cannot see the chip. */}
1346
+ {/* The same words, for a screen reader, which cannot see the chip — and, between drags,
1347
+ whatever the last keyboard move said. See `notice`. */}
1048
1348
  <span className='cbft-sr' role='status' aria-live='polite'>
1049
- {drag?.active ? (drag.offer.reason ?? '') : ''}
1349
+ {drag?.active ? (drag.offer.reason ?? describeDrop(shown, drag.offer.target)) : notice}
1050
1350
  </span>
1051
1351
  </div>
1052
1352
  );
@@ -333,6 +333,39 @@
333
333
  outline-offset: -2px;
334
334
  }
335
335
 
336
+ /*
337
+ * ── 🔴 Where you are, when the row you are on is not drawn ──────────────────────────────
338
+ *
339
+ * A tree with most of its folders shut says nothing about where you are, and the state where
340
+ * that matters most is the one where the selected row is not on screen at all. So the folders
341
+ * BETWEEN the root and the selection are weighted a step, and the shut folder actually
342
+ * holding it takes a mark of its own.
343
+ *
344
+ * Weight rather than colour for the ancestors: they are context, and three tinted rows down a
345
+ * sidebar would compete with the one neutral fill that says *this is where you are*. 550 is
346
+ * `file-tree-geometry`'s `maxWeight` — the ceiling the "use thinner fonts" note put on this
347
+ * tree, and the reason this is not 600.
348
+ */
349
+ .cbft-row[data-ancestor] .cbft-name {
350
+ font-weight: 550;
351
+ }
352
+
353
+ /*
354
+ * The shut folder holding the selection. A 2px bar on the inside edge — the same language the
355
+ * activity rail uses for the view you are in, so a person learns it once — and deliberately
356
+ * NOT the selected row's fill: this folder is not what is selected, it is what the selection
357
+ * is inside. The reveal scrolls to this element when the selected row does not exist.
358
+ */
359
+ .cbft-row[data-holds-open]::before {
360
+ content: '';
361
+ position: absolute;
362
+ inset-block: 2px;
363
+ inset-inline-start: 0;
364
+ inline-size: 2px;
365
+ border-radius: 999px;
366
+ background: var(--accent-solid, var(--accent));
367
+ }
368
+
336
369
  /* The row that is in the air — still drawn, so the tree does not jump, but clearly lifted. */
337
370
  .cbft-row[data-dragged] {
338
371
  opacity: 0.4;
@@ -615,6 +648,25 @@
615
648
 
616
649
  /* ── The drag chip ───────────────────────────────────────────────────────────────────── */
617
650
 
651
+ /*
652
+ * Two parts, not one string: what is held, and where it is going. The destination is drawn a
653
+ * step quieter because the name is what identifies the chip and the destination is what you
654
+ * are checking — and because a refusal takes the same slot, where the colour below is what
655
+ * carries the "no".
656
+ */
657
+ .cbft-chip-name {
658
+ font-weight: 500;
659
+ }
660
+
661
+ .cbft-chip-where {
662
+ margin-inline-start: 0.375rem;
663
+ color: var(--muted-foreground);
664
+ }
665
+
666
+ .cbft-chip[data-refused] .cbft-chip-where {
667
+ color: inherit;
668
+ }
669
+
618
670
  .cbft-chip {
619
671
  position: fixed;
620
672
  /* Offset off the cursor so it never covers the row being aimed at. */
@@ -0,0 +1,151 @@
1
+ {
2
+ "$comment": [
3
+ "THE SIDEBAR FILE TREE, AS NUMBERS — the design this tree implements, committed HERE.",
4
+ "",
5
+ "🔴 WHY IT IS A FILE IN THIS REPO AND NOT A LOOKUP. Until 2026-09-13 the spec beside this",
6
+ "file read these numbers out of a checkout in the owner's home directory, and skipped its",
7
+ "cross-repo assertions whenever that directory was missing. cursedbelt is PUBLISHED: a",
8
+ "package whose suite reaches into one person's private tree passes for them and is silently",
9
+ "half-skipped for everybody else. A test that quietly does nothing is worse than no test.",
10
+ "",
11
+ "So the design is vendored. Every assertion in `fileTreeGeometry.spec.ts` now runs",
12
+ "unconditionally, on any machine, with no fleet beside the checkout.",
13
+ "",
14
+ "🔴 The numbers are asserted against THIS tree's implementation, never against themselves.",
15
+ "A test that read `true` out of this file and then asserted `true` would pass on a tree that",
16
+ "does neither thing. Change a value here and the stylesheet reds until it follows.",
17
+ "",
18
+ "The same design is implemented a second time in cursed-embeddable-spreadsheet, which is a",
19
+ "separate published package and cannot depend on this one. Keeping the two in step is a",
20
+ "human step at the point the design changes: edit this file, then both stylesheets."
21
+ ],
22
+ "geometry": {
23
+ "$comment": "Pixels. Same meaning either side; the prefixes differ (--cbft-* / --cs-tree-*).",
24
+ "row": 22,
25
+ "pad": 3,
26
+ "inset": 8,
27
+ "glyph": 16,
28
+ "indent": 8,
29
+ "gap": 6,
30
+ "guideOffset": 4
31
+ },
32
+ "scrollbar": {
33
+ "$comment": [
34
+ "🔴 THE TREE'S OWN SCROLLBAR, added 2026-09-11. Owner: \"The scrollbar should be very thin",
35
+ "in the file tree and against the right edge. Currently it has space between the scrolling",
36
+ "marker and the edge. File tree changes need to be done in ces too.\"",
37
+ "",
38
+ "Two separate defects, one per repo, and neither is visible from the other side:",
39
+ "",
40
+ " - ces had a GLOBAL rule, `.cs-root ::-webkit-scrollbar { width: 11px }` with the thumb",
41
+ " carrying `border: 3px solid var(--cs-bg)`. That border IS the space he is describing:",
42
+ " a 3px ring of page background on each side leaves a 5px marker floating in an 11px",
43
+ " gutter. Correct for a document scroller, far too loud down a 22px-row tree.",
44
+ " - cursedbelt's `.cbft-scroll` declared nothing at all, so it drew the platform default",
45
+ " — ~15px on macOS with the thumb inset by the engine.",
46
+ "",
47
+ "And in cursedbelt there was a third, which is the 'against the right edge' half: the tree",
48
+ "sat inside `FilterRail`'s `px-2` body, so the scroller's own edge was 8px short of the",
49
+ "rail's. A filling section now bleeds that padding back out (`-mx-2`) and the tree supplies",
50
+ "its own `inset` for the ROWS — which is why `inset` above is unchanged and the row text",
51
+ "still lines up under the section heading.",
52
+ "",
53
+ "🔴 `thumbInset: 0` is the assertion that matters and the one most likely to be undone by",
54
+ "somebody 'tidying' the thumb with a border or a margin to round it off. The marker touches",
55
+ "the edge; that is the whole request."
56
+ ],
57
+ "width": 6,
58
+ "thumbInset": 0
59
+ },
60
+ "invariants": {
61
+ "$comment": [
62
+ "The relationships, which are what actually break. A number can be changed on purpose;",
63
+ "these cannot be broken on purpose, and each one has a symptom nothing else reports."
64
+ ],
65
+ "indent === 8": "VS Code's own tree step, and a MEASUREMENT the owner made on screen rather than a value derived from anything here — so nothing may recompute it. It was briefly `glyph` (16) on a reading of \"the subdir and file icons should start at the right edge of the parent folder's arrow icon … bumped over by 1 icon space\", which is literally true only at one-glyph-per-step. He saw that and said: \"I want vscode 8px style so it matches how theirs looks.\" In VS Code a child's glyph box starts at the MIDDLE of its parent's, not past its edge.",
66
+ "indent is INDEPENDENT of glyph": "They were coupled for one day and must not be recoupled. What makes 8px read as VS Code is the single glyph column, not the step: the old four-track layout spent 44px of chrome before every name, so 8px of step was invisible beside it; at 22px of chrome the step is the thing you see.",
67
+ "no density or pointer block overrides indent": "cb had 12px under `@media (pointer: coarse)` and ces 12px under `[data-density=comfortable]`. A step that is 8 on a desktop and 12 on a phone is not one look. Row height and padding DO vary — those are about how much room a row is given; the step is about reading the hierarchy and reads the same everywhere.",
68
+ "guideOffset === indent / 2": "The indentation guide is painted down the MIDDLE of each indent step. Break it and every guide line sits in the gutter between two levels instead of under the chevron it connects to.",
69
+ "gap === 6": "One character at 13px, and VS Code's own figure — owner: \"a gap about the size of a single character\", and \"the gap is the same for files from their icons and dirs from their arrows.\" The second clause is why the tree has ONE glyph column rather than a chevron track plus an icon track.",
70
+ "row === glyph + pad * 2": "The row's height is its line box plus its padding, and the line box is set to the glyph. Break it and the row's height becomes a function of the host app's inherited line-height — the same component drawing a different height in each app.",
71
+ "scrollbar.width is declared by the TREE, not inherited": "Both repos draw a scrollbar somewhere up the tree — ces globally on `.cs-root`, cursedbelt by platform default — and both were wrong here for opposite reasons. A tree whose gutter is inherited is a tree whose gutter changes when somebody restyles a dialog.",
72
+ "scrollbar.thumbInset === 0": "The marker touches the right edge. A `border` or a `margin` on the thumb is how the space comes back: ces's global thumb carries `border: 3px solid var(--cs-bg)` on purpose for document scrollers, and the tree must override it rather than inherit it. Owner: \"against the right edge … Currently it has space between the scrolling marker and the edge.\"",
73
+ "nothing may pad the SCROLLER horizontally": "The scrollbar is painted at the scroll container's border-box edge, so any ancestor padding moves it inward — which is what `FilterRail`'s `px-2` was doing. Indentation is the ROW's job (`inset`), never the scroller's."
74
+ },
75
+ "typography": {
76
+ "$comment": [
77
+ "Owner, 2026-09-10: \"use thinner fonts and less bright font in dark mode. It looks too",
78
+ "glaring.\" Both halves are needed: the colour is the brightness, and antialiasing is the",
79
+ "WEIGHT — on a dark ground macOS stem-darkening renders 13px type visibly heavier than",
80
+ "the same weight on white, so dimming alone leaves fat letterforms in a quieter grey.",
81
+ "",
82
+ "🔴 The two repos already differed on the second half, and each keeps its own answer:",
83
+ "ces has carried `-webkit-font-smoothing: antialiased` on `.cs-root` since the beginning,",
84
+ "so a tree-scoped copy there would be inert and is deliberately absent — its \"thinner\" is",
85
+ "the weight ladder alone. cursedbelt's framework sets no base, so its tree declares one",
86
+ "under `:where(.dark) .cbft`. Each repo's test asserts ITS arrangement; this field records",
87
+ "the intent, not a string to grep for.",
88
+ "",
89
+ "The dark foreground is expressed as a target rather than a literal because the two apps",
90
+ "theme from different palettes; each derives its own value near it. VS Code's dark themes",
91
+ "all sit close to #cccccc for this surface while keeping full white for the selected row."
92
+ ],
93
+ "restWeight": 400,
94
+ "maxWeight": 550,
95
+ "darkForegroundTarget": "#cbd5e1",
96
+ "darkFontSmoothing": "antialiased",
97
+ "selectedRowKeepsFullForeground": true
98
+ },
99
+ "behaviour": {
100
+ "$comment": [
101
+ "Not numbers — the two things a row DOES that both trees must agree on. Added",
102
+ "2026-09-11 from the owner's batch: \"Make them use an arrow icon next to a dr even when",
103
+ "the dir is empty. For meda files show a thumbnail instead of the file type icon to the",
104
+ "left of the name.\"",
105
+ "",
106
+ "🔴 These are asserted against the IMPLEMENTATION on each side, not compared flag to",
107
+ "flag. A test that read `true` here and asserted `true` would pass on a tree that does",
108
+ "neither. So: flip a flag below and the repo whose code still does the old thing goes",
109
+ "red, which is the only arrangement where this file can refuse anything."
110
+ ],
111
+ "everyFolderDrawsATwisty": {
112
+ "value": true,
113
+ "why": [
114
+ "The chevron says FOLDER, not OCCUPIED — VS Code's own rule, and it opens an empty",
115
+ "folder onto nothing rather than hiding the control.",
116
+ "",
117
+ "Both trees defaulted to `children.length > 0` and it was wrong twice. For a LAZY tree",
118
+ "it is a trap door: a row whose contents load only while it is open has no children",
119
+ "while shut, so it loses its caret and can never be reopened — measured in",
120
+ "apps/collections, which had to pass an explicit `expandable` to escape it. For an",
121
+ "EAGER tree it draws a brand-new empty folder as a leaf, so the one mark that says",
122
+ "'this is a place' is missing exactly while the folder is being filled.",
123
+ "",
124
+ "An explicit opt-out survives on each side (cb: `expandable: false`) for a row that is",
125
+ "a folder in name and can never hold anything."
126
+ ]
127
+ },
128
+ "mediaRowDrawsItsThumbnail": {
129
+ "value": true,
130
+ "why": [
131
+ "A folder of photographs is a column of identical camera glyphs: the glyph box costs",
132
+ "16px and says nothing. The thumbnail is the only thing that tells one row from the",
133
+ "next without opening it.",
134
+ "",
135
+ "🔴 THREE constraints, each of which has already cost something somewhere in the fleet:",
136
+ " 1. It takes the SAME glyph box, not a new track and not a taller row. `row === glyph",
137
+ " + pad * 2` above is what keeps a tree scannable; a row that grows per file is not.",
138
+ " 2. It falls back to the type icon ONCE per URL and never retries. A derived",
139
+ " thumbnail's ABSENCE is the normal 'not rendered yet' state, so a broken-image",
140
+ " glyph is a lie about the archive (apps/family shipped and reverted exactly that on",
141
+ " 2026-08-14), and re-pointing `src` from inside `onError` is an infinite request",
142
+ " loop with every row in view retrying at once.",
143
+ " 3. It is a THUMBNAIL, never the original. This is a 16px box; a 300-row folder",
144
+ " pointed at full-size objects is 300 full downloads to draw a rail.",
145
+ "",
146
+ "A FOLDER's glyph box is its twisty and no picture may displace it — the row would lose",
147
+ "the only control that opens it."
148
+ ]
149
+ }
150
+ }
151
+ }