cursedbelt 3.0.2 → 4.0.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 (250) hide show
  1. package/dist/react/components/ListToolbar.d.ts +94 -0
  2. package/dist/react/components/ListToolbar.d.ts.map +1 -0
  3. package/dist/react/components/ListToolbar.js +66 -0
  4. package/dist/react/components/ListToolbar.js.map +1 -0
  5. package/dist/react/file-tree/FileTree.d.ts +282 -3
  6. package/dist/react/file-tree/FileTree.d.ts.map +1 -1
  7. package/dist/react/file-tree/FileTree.js +799 -131
  8. package/dist/react/file-tree/FileTree.js.map +1 -1
  9. package/dist/react/file-tree/fileTree.css +151 -0
  10. package/dist/react/file-tree/fileTreeSelection.d.ts +187 -0
  11. package/dist/react/file-tree/fileTreeSelection.d.ts.map +1 -0
  12. package/dist/react/file-tree/fileTreeSelection.js +264 -0
  13. package/dist/react/file-tree/fileTreeSelection.js.map +1 -0
  14. package/dist/react/file-tree/index.d.ts +1 -0
  15. package/dist/react/file-tree/index.d.ts.map +1 -1
  16. package/dist/react/file-tree/index.js +8 -0
  17. package/dist/react/file-tree/index.js.map +1 -1
  18. package/dist/react/index.d.ts +1 -0
  19. package/dist/react/index.d.ts.map +1 -1
  20. package/dist/react/index.js +1 -0
  21. package/dist/react/index.js.map +1 -1
  22. package/dist/react/lib/form.d.ts +3 -0
  23. package/dist/react/lib/form.d.ts.map +1 -1
  24. package/dist/react/lib/form.js +43 -5
  25. package/dist/react/lib/form.js.map +1 -1
  26. package/dist/react/media/DenseGalleryShell.d.ts +19 -3
  27. package/dist/react/media/DenseGalleryShell.d.ts.map +1 -1
  28. package/dist/react/media/DenseGalleryShell.js +55 -15
  29. package/dist/react/media/DenseGalleryShell.js.map +1 -1
  30. package/dist/react/media/FocusViewer.d.ts.map +1 -1
  31. package/dist/react/media/FocusViewer.js +14 -14
  32. package/dist/react/media/FocusViewer.js.map +1 -1
  33. package/dist/react/media/FrameGrabber.d.ts +23 -0
  34. package/dist/react/media/FrameGrabber.d.ts.map +1 -1
  35. package/dist/react/media/FrameGrabber.js +67 -1
  36. package/dist/react/media/FrameGrabber.js.map +1 -1
  37. package/dist/react/media/MediaResize.d.ts +51 -19
  38. package/dist/react/media/MediaResize.d.ts.map +1 -1
  39. package/dist/react/media/MediaResize.js +20 -13
  40. package/dist/react/media/MediaResize.js.map +1 -1
  41. package/dist/react/media/VideoChapterEditor.d.ts +67 -0
  42. package/dist/react/media/VideoChapterEditor.d.ts.map +1 -0
  43. package/dist/react/media/VideoChapterEditor.js +148 -0
  44. package/dist/react/media/VideoChapterEditor.js.map +1 -0
  45. package/dist/react/media/VideoPlayer.d.ts +46 -4
  46. package/dist/react/media/VideoPlayer.d.ts.map +1 -1
  47. package/dist/react/media/VideoPlayer.js +124 -11
  48. package/dist/react/media/VideoPlayer.js.map +1 -1
  49. package/dist/react/media/VideoTurner.d.ts +15 -2
  50. package/dist/react/media/VideoTurner.d.ts.map +1 -1
  51. package/dist/react/media/VideoTurner.js +29 -2
  52. package/dist/react/media/VideoTurner.js.map +1 -1
  53. package/dist/react/media/denseGallery.css +104 -46
  54. package/dist/react/media/hlsSource.d.ts +57 -0
  55. package/dist/react/media/hlsSource.d.ts.map +1 -1
  56. package/dist/react/media/hlsSource.js +177 -1
  57. package/dist/react/media/hlsSource.js.map +1 -1
  58. package/dist/react/media/index.d.ts +2 -0
  59. package/dist/react/media/index.d.ts.map +1 -1
  60. package/dist/react/media/index.js +6 -0
  61. package/dist/react/media/index.js.map +1 -1
  62. package/dist/react/media/mediaResize.css +127 -54
  63. package/dist/react/media/pictureEditor.css +52 -6
  64. package/dist/react/media/videoChapterEditor.css +203 -0
  65. package/dist/react/media/videoChapters.d.ts +117 -0
  66. package/dist/react/media/videoChapters.d.ts.map +1 -0
  67. package/dist/react/media/videoChapters.js +95 -0
  68. package/dist/react/media/videoChapters.js.map +1 -0
  69. package/dist/react/media-gallery/GalleryTable.d.ts +52 -1
  70. package/dist/react/media-gallery/GalleryTable.d.ts.map +1 -1
  71. package/dist/react/media-gallery/GalleryTable.js +40 -9
  72. package/dist/react/media-gallery/GalleryTable.js.map +1 -1
  73. package/dist/react/media-gallery/MediaGallery.d.ts +107 -4
  74. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  75. package/dist/react/media-gallery/MediaGallery.js +484 -104
  76. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  77. package/dist/react/media-gallery/MediaMetaEditor.d.ts +48 -0
  78. package/dist/react/media-gallery/MediaMetaEditor.d.ts.map +1 -0
  79. package/dist/react/media-gallery/MediaMetaEditor.js +66 -0
  80. package/dist/react/media-gallery/MediaMetaEditor.js.map +1 -0
  81. package/dist/react/media-gallery/index.d.ts +2 -0
  82. package/dist/react/media-gallery/index.d.ts.map +1 -1
  83. package/dist/react/media-gallery/index.js +5 -0
  84. package/dist/react/media-gallery/index.js.map +1 -1
  85. package/dist/react/media-gallery/mediaGallery.css +180 -4
  86. package/dist/react/media-gallery/mediaMeta.d.ts +62 -0
  87. package/dist/react/media-gallery/mediaMeta.d.ts.map +1 -0
  88. package/dist/react/media-gallery/mediaMeta.js +56 -0
  89. package/dist/react/media-gallery/mediaMeta.js.map +1 -0
  90. package/dist/react/media-gallery/playbackPreferences.d.ts +77 -0
  91. package/dist/react/media-gallery/playbackPreferences.d.ts.map +1 -1
  92. package/dist/react/media-gallery/playbackPreferences.js +42 -0
  93. package/dist/react/media-gallery/playbackPreferences.js.map +1 -1
  94. package/dist/react/media-gallery/types.d.ts +34 -0
  95. package/dist/react/media-gallery/types.d.ts.map +1 -1
  96. package/dist/scripts/guardrailsEnforce.d.ts +29 -0
  97. package/dist/scripts/guardrailsEnforce.d.ts.map +1 -0
  98. package/dist/styles-areas/activity-bar.css +30 -0
  99. package/dist/styles-areas/analytics.css +672 -0
  100. package/dist/styles-areas/auth.css +154 -0
  101. package/dist/styles-areas/calendar.css +416 -0
  102. package/dist/styles-areas/chart-table.css +41 -0
  103. package/dist/styles-areas/charts.css +89 -0
  104. package/dist/styles-areas/chat.css +364 -0
  105. package/dist/styles-areas/clipboard.css +26 -0
  106. package/dist/styles-areas/code-editor.css +130 -0
  107. package/dist/styles-areas/companion-link.css +70 -0
  108. package/dist/styles-areas/core.css +2503 -0
  109. package/dist/styles-areas/dashboard-grid.css +84 -0
  110. package/dist/styles-areas/data-table.css +657 -0
  111. package/dist/styles-areas/deep-link.css +26 -0
  112. package/dist/styles-areas/diff-viewer.css +30 -0
  113. package/dist/styles-areas/disk-usage.css +262 -0
  114. package/dist/styles-areas/emoji.css +154 -0
  115. package/dist/styles-areas/fields.css +582 -0
  116. package/dist/styles-areas/file-tree.css +33 -0
  117. package/dist/styles-areas/filter-rail.css +235 -0
  118. package/dist/styles-areas/folder-tree.css +219 -0
  119. package/dist/styles-areas/keep-awake.css +26 -0
  120. package/dist/styles-areas/layout-engine.css +239 -0
  121. package/dist/styles-areas/markdown.css +87 -0
  122. package/dist/styles-areas/master-detail.css +239 -0
  123. package/dist/styles-areas/master-lock.css +26 -0
  124. package/dist/styles-areas/media-gallery.css +351 -0
  125. package/dist/styles-areas/media.css +658 -0
  126. package/dist/styles-areas/nav.css +488 -0
  127. package/dist/styles-areas/notifications.css +245 -0
  128. package/dist/styles-areas/overlays.css +133 -0
  129. package/dist/styles-areas/palette.css +140 -0
  130. package/dist/styles-areas/pdf-viewer.css +47 -0
  131. package/dist/styles-areas/problem-detail.css +97 -0
  132. package/dist/styles-areas/rich-text.css +167 -0
  133. package/dist/styles-areas/sharing.css +153 -0
  134. package/dist/styles-areas/spreadsheet.css +37 -0
  135. package/dist/styles-areas/stats.css +281 -0
  136. package/dist/styles-areas/test-report.css +133 -0
  137. package/dist/styles-areas/virtual.css +30 -0
  138. package/dist/styles-areas/wizard.css +381 -0
  139. package/dist/styles-areas/workbench.css +543 -0
  140. package/dist/styles-areas/workbook-viewer.css +44 -0
  141. package/dist/styles-static.css +34 -23
  142. package/dist/styles.css +34 -23
  143. package/package.json +111 -31
  144. package/scripts/checkDistExports.ts +67 -0
  145. package/scripts/cssRules.ts +125 -0
  146. package/scripts/fixtureAppCss.ts +344 -0
  147. package/scripts/generateAreaStyles.ts +253 -0
  148. package/scripts/guardrailsEnforce.spec.ts +62 -0
  149. package/scripts/guardrailsEnforce.ts +67 -3
  150. package/scripts/styleAreas.ts +585 -0
  151. package/scripts/verify.ts +5 -0
  152. package/src/barrelsReachNoOptionalPeer.spec.ts +117 -9
  153. package/src/docsMatchTheSplit.spec.ts +98 -0
  154. package/src/fixtureAppCss.spec.ts +177 -0
  155. package/src/namedSubpathsResolve.spec.ts +35 -0
  156. package/src/publishShape.spec.ts +67 -0
  157. package/src/react/components/ListToolbar.spec.tsx +172 -0
  158. package/src/react/components/ListToolbar.tsx +151 -0
  159. package/src/react/file-tree/FileTree.spec.tsx +1009 -0
  160. package/src/react/file-tree/FileTree.tsx +1459 -309
  161. package/src/react/file-tree/fileTree.css +151 -0
  162. package/src/react/file-tree/fileTreeSelection.spec.ts +327 -0
  163. package/src/react/file-tree/fileTreeSelection.ts +321 -0
  164. package/src/react/file-tree/index.ts +21 -0
  165. package/src/react/index.ts +1 -0
  166. package/src/react/lib/form.ts +51 -8
  167. package/src/react/media/DenseGalleryShell.tsx +108 -36
  168. package/src/react/media/FocusViewer.tsx +15 -8
  169. package/src/react/media/FrameGrabber.spec.tsx +175 -0
  170. package/src/react/media/FrameGrabber.tsx +67 -0
  171. package/src/react/media/MediaResize.spec.tsx +84 -14
  172. package/src/react/media/MediaResize.tsx +51 -19
  173. package/src/react/media/VideoChapterEditor.tsx +428 -0
  174. package/src/react/media/VideoPlayer.spec.tsx +111 -1
  175. package/src/react/media/VideoPlayer.tsx +158 -11
  176. package/src/react/media/VideoTurner.spec.tsx +67 -0
  177. package/src/react/media/VideoTurner.tsx +70 -15
  178. package/src/react/media/denseGallery.css +104 -46
  179. package/src/react/media/hlsSource.recovery.spec.ts +213 -0
  180. package/src/react/media/hlsSource.spec.ts +8 -0
  181. package/src/react/media/hlsSource.ts +189 -1
  182. package/src/react/media/index.ts +14 -0
  183. package/src/react/media/mediaResize.css +127 -54
  184. package/src/react/media/pictureEditor.css +52 -6
  185. package/src/react/media/videoChapterEditor.css +203 -0
  186. package/src/react/media/videoChapters.spec.ts +176 -0
  187. package/src/react/media/videoChapters.ts +181 -0
  188. package/src/react/media-gallery/GalleryTable.tsx +93 -3
  189. package/src/react/media-gallery/MediaGallery.spec.tsx +1156 -10
  190. package/src/react/media-gallery/MediaGallery.tsx +813 -95
  191. package/src/react/media-gallery/MediaMetaEditor.tsx +237 -0
  192. package/src/react/media-gallery/galleryTable.spec.ts +41 -1
  193. package/src/react/media-gallery/index.ts +7 -0
  194. package/src/react/media-gallery/mediaGallery.css +180 -4
  195. package/src/react/media-gallery/mediaMeta.spec.ts +95 -0
  196. package/src/react/media-gallery/mediaMeta.ts +101 -0
  197. package/src/react/media-gallery/playbackPreferences.spec.ts +172 -0
  198. package/src/react/media-gallery/playbackPreferences.ts +100 -0
  199. package/src/react/media-gallery/types.ts +34 -0
  200. package/src/shippedFilesAreTracked.spec.ts +69 -0
  201. package/src/styles-areas/activity-bar.css +30 -0
  202. package/src/styles-areas/analytics.css +672 -0
  203. package/src/styles-areas/auth.css +154 -0
  204. package/src/styles-areas/calendar.css +416 -0
  205. package/src/styles-areas/chart-table.css +41 -0
  206. package/src/styles-areas/charts.css +89 -0
  207. package/src/styles-areas/chat.css +364 -0
  208. package/src/styles-areas/clipboard.css +26 -0
  209. package/src/styles-areas/code-editor.css +130 -0
  210. package/src/styles-areas/companion-link.css +70 -0
  211. package/src/styles-areas/core.css +2503 -0
  212. package/src/styles-areas/dashboard-grid.css +84 -0
  213. package/src/styles-areas/data-table.css +657 -0
  214. package/src/styles-areas/deep-link.css +26 -0
  215. package/src/styles-areas/diff-viewer.css +30 -0
  216. package/src/styles-areas/disk-usage.css +262 -0
  217. package/src/styles-areas/emoji.css +154 -0
  218. package/src/styles-areas/fields.css +582 -0
  219. package/src/styles-areas/file-tree.css +33 -0
  220. package/src/styles-areas/filter-rail.css +235 -0
  221. package/src/styles-areas/folder-tree.css +219 -0
  222. package/src/styles-areas/keep-awake.css +26 -0
  223. package/src/styles-areas/layout-engine.css +239 -0
  224. package/src/styles-areas/markdown.css +87 -0
  225. package/src/styles-areas/master-detail.css +239 -0
  226. package/src/styles-areas/master-lock.css +26 -0
  227. package/src/styles-areas/media-gallery.css +351 -0
  228. package/src/styles-areas/media.css +658 -0
  229. package/src/styles-areas/nav.css +488 -0
  230. package/src/styles-areas/notifications.css +245 -0
  231. package/src/styles-areas/overlays.css +133 -0
  232. package/src/styles-areas/palette.css +140 -0
  233. package/src/styles-areas/pdf-viewer.css +47 -0
  234. package/src/styles-areas/problem-detail.css +97 -0
  235. package/src/styles-areas/rich-text.css +167 -0
  236. package/src/styles-areas/sharing.css +153 -0
  237. package/src/styles-areas/spreadsheet.css +37 -0
  238. package/src/styles-areas/stats.css +281 -0
  239. package/src/styles-areas/test-report.css +133 -0
  240. package/src/styles-areas/virtual.css +30 -0
  241. package/src/styles-areas/wizard.css +381 -0
  242. package/src/styles-areas/workbench.css +543 -0
  243. package/src/styles-areas/workbook-viewer.css +44 -0
  244. package/src/styles-static.css +34 -23
  245. package/src/styles.css +34 -23
  246. package/src/stylesAreas.spec.ts +247 -0
  247. package/src/stylesUtilitiesMatches.spec.ts +6 -77
  248. package/src/testFilesRunInParallel.spec.ts +79 -0
  249. package/src/typecheckCachesAreSeparate.spec.ts +128 -0
  250. package/src/verifyGraph.spec.ts +6 -0
@@ -177,12 +177,52 @@
177
177
  container-type: size;
178
178
  }
179
179
 
180
+ /* 🔴 The turned picture surface is SIZED to the swapped panel, not merely bounded by it.
181
+ Owner, `ideas/collections-09-14.md`: *"If I rotate a portait style video 90 degrees it should
182
+ be changed to fit the wide screen panel … so I can watch it like it was recorded with wide
183
+ screen."* — a rotation on its own leaves the footage the size it already was.
184
+
185
+ These used to be `max-block-size`/`max-inline-size`, which can only SHRINK. Measured in
186
+ Chromium on 2026-09-16 against `demo`'s 9:16 clip in a 505×284 panel: the player gives its
187
+ `<video>` a 16:9 box of its own (284×160 there), `object-fit: contain` draws the portrait
188
+ film as a 90×160 pillar inside it, and the max-* pair had nothing to bind on — so the turn
189
+ painted 160×90 of film in a 505×284 panel, which is the tall thin picture in the middle of a
190
+ black stage that was reported. `e2e/video-turn.spec.ts` measures exactly that rectangle.
191
+
192
+ Width from the container's BLOCK size and height from its INLINE size — the axis swap, said
193
+ in the only units that can say it — so after the rotation the box is the panel again. The
194
+ fit itself is then `object-fit: contain`'s, which is what keeps this honest for footage whose
195
+ aspect is not the panel's inverse: a 16:9 clip turned on its end is letterboxed whole rather
196
+ than blown up and cropped, and a square one is left alone. A scale factor on the transform
197
+ cannot make that distinction — it does not know the film's shape — which is why the fit lives
198
+ on the box and only the rotation travels in `--cbmg-turn`. */
180
199
  .cbmg-stage-turn[data-turned='true'] video,
181
200
  .cbmg-stage-turn[data-turned='true'] .vds-poster img {
182
201
  max-width: none;
183
202
  max-height: none;
184
- max-block-size: 100cqi;
185
- max-inline-size: 100cqb;
203
+ max-block-size: none;
204
+ max-inline-size: none;
205
+ width: 100cqb;
206
+ height: 100cqi;
207
+ /* The player sets its own 16:9 on the element; an explicit pair of sides must win over it,
208
+ or the swap above is computed and then thrown away. */
209
+ aspect-ratio: auto;
210
+ object-fit: contain;
211
+ }
212
+
213
+ /* 🔴 …and it has to be turned about the PANEL's centre, which means it has to sit at it first.
214
+ A rotation pivots on the element's own middle, so an element the player has left against the
215
+ left edge of its provider box lands a quarter of the panel off to one side — measured in
216
+ Chromium on 2026-09-16: a correctly sized 505×284 of film, painted at x=278 in a panel
217
+ starting at x=388, i.e. 110px of it outside the frame and a black band the same width down
218
+ the other edge. The wrapper above centres what IS its child; on the Vidstack path the child
219
+ is the player, and the element being turned is two boxes further in. Same reach as
220
+ `.vds-poster` / `.vds-controls` elsewhere in this file, and inert on the native stage, whose
221
+ `<video>` is already the wrapper's own flex-centred child. */
222
+ .cbmg-stage-turn[data-turned='true'] [data-media-provider] {
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
186
226
  }
187
227
 
188
228
  /* Inside the turned frame the stage element must stop claiming its own 70vh, or the flex
@@ -322,6 +362,63 @@
322
362
  background: color-mix(in srgb, var(--accent-solid) 12%, transparent);
323
363
  }
324
364
 
365
+ /* ── The tile the STAGE is showing ───────────────────────────────────────────────────────
366
+ *
367
+ * Owner, `ideas/collections-09-14.md`: *"I want to … still see the playing video in the list
368
+ * and nothing about the list changes as I pick other videos."* The strip under the stage used
369
+ * to drop the open file; it keeps it now and marks it here.
370
+ *
371
+ * 🔴 `::before`, where the selection tint is `::after`, so a tile that is BOTH ticked and
372
+ * playing draws both marks instead of one silently replacing the other. Neither pseudo is a
373
+ * `:first-child`, so `.cbmg-selectable > :first-child { height: 100% }` above is untouched.
374
+ */
375
+ .cbmg-selectable.is-playing::before {
376
+ content: '';
377
+ position: absolute;
378
+ inset: 0;
379
+ /* guardrails-ignore no-raw-zindex: the same tile-local stacking the selection tint uses —
380
+ the mark over its own tile, inside the isolated `.cbmg-selectable` context. */
381
+ z-index: 1;
382
+ pointer-events: none;
383
+ border-radius: 8px;
384
+ /* Thicker than the selection's 2px: this one has to be findable while scrolling past it,
385
+ which is the whole reason the tile stays in the list. */
386
+ outline: 3px solid var(--accent-solid);
387
+ outline-offset: -3px;
388
+ background: color-mix(in srgb, var(--accent-solid) 10%, transparent);
389
+ }
390
+ /* A filled capsule with its own foreground token (DESIGN.md §3 — never a thin outline), at
391
+ the 12px floor (§4). `pointer-events: none` because the whole tile is one button and a
392
+ label that swallowed the click would make its corner dead. */
393
+ .cbmg-playing-chip {
394
+ position: absolute;
395
+ top: 6px;
396
+ left: 6px;
397
+ /* guardrails-ignore no-raw-zindex: tile-local stacking, over this tile's own mark. */
398
+ z-index: 2;
399
+ display: inline-flex;
400
+ align-items: center;
401
+ gap: 4px;
402
+ max-width: calc(100% - 12px);
403
+ padding: 3px 8px;
404
+ border-radius: 999px;
405
+ background: var(--accent-solid);
406
+ color: var(--accent-solid-foreground);
407
+ font-size: 12px;
408
+ font-weight: 600;
409
+ line-height: 1.2;
410
+ white-space: nowrap;
411
+ overflow: hidden;
412
+ text-overflow: ellipsis;
413
+ pointer-events: none;
414
+ }
415
+ /* The one corner collision: a document tile draws its type badge in this exact spot. The mark
416
+ wins for as long as it is there — the stage directly above is showing the file, and its
417
+ titlebar says what kind it is, so the badge is the redundant one of the two. */
418
+ .cbmg-selectable.is-playing .cbmg-doc-badge {
419
+ display: none;
420
+ }
421
+
325
422
  .cbmg-bulkbar {
326
423
  display: flex;
327
424
  align-items: center;
@@ -356,7 +453,7 @@
356
453
  frame grabber were each written inside ONE app's `detailActions`; these are the rules that
357
454
  came with them. Plain CSS under `.cbmg-`, like the rest of this file. */
358
455
 
359
- /* 🔴 The ☀ Picture adjustment reaches the stage through a custom property set on the stage
456
+ /* 🔴 The Picture adjustment reaches the stage through a custom property set on the stage
360
457
  anchor, NOT through a prop threaded into the player — the video stage is Vidstack's and is
361
458
  several components deep, so a prop would make every consumer of the player primitive learn
362
459
  about a control only the gallery offers.
@@ -544,6 +641,21 @@
544
641
  box-shadow: inset 3px 0 0 0 var(--accent);
545
642
  }
546
643
 
644
+ /* The row the stage is showing — the table's half of the grid tile's mark. Same shape as the
645
+ selected row so the two read as one family, and a step louder (the SOLID accent, a 4px bar)
646
+ because there is exactly one of these and it is the row the reader is looking for.
647
+ The name it is given is `.cbgd-sr-only` text in the name cell; `GalleryTable.tsx` says why a
648
+ visible capsule cannot live in a fixed-layout cell. */
649
+ .cbgt-row.is-playing {
650
+ background: color-mix(in srgb, var(--accent-solid) 12%, transparent);
651
+ box-shadow: inset 4px 0 0 0 var(--accent-solid);
652
+ }
653
+ /* …and it keeps that reading under the pointer, which the hover rule above would otherwise
654
+ take away by painting the plain hover fill over it. */
655
+ .cbgt-row.is-playing:hover {
656
+ background: color-mix(in srgb, var(--accent-solid) 18%, transparent);
657
+ }
658
+
547
659
  /* Fixed tracks, declared once. `table-layout: fixed` means these widths are the layout. */
548
660
  .cbgt-cell--check {
549
661
  width: 36px;
@@ -566,6 +678,14 @@
566
678
  .cbgt-cell--length {
567
679
  width: 78px;
568
680
  }
681
+ /* The exact byte count. Wider than the rounded Size beside it because the whole point of the
682
+ column is that nothing is dropped: a terabyte is 13 digits plus 4 separators, and a count
683
+ that truncated to "1,402,653,1…" would be exactly as useless for telling two files apart as
684
+ the rounded "1.4 GB" it exists to replace. `.cbgt-cell--num` already right-aligns it and
685
+ gives it tabular figures, which is what makes two counts comparable down a column. */
686
+ .cbgt-cell--bytes {
687
+ width: 132px;
688
+ }
569
689
  /* The date is the widest of the three numeric columns: "21 Aug 2026" at 12px does not fit
570
690
  the 96px the others use, and a DATE that truncates to "Aug 21, 2…" is a column that has
571
691
  stopped answering the question it exists for. Deliberately generous — every locale this
@@ -731,7 +851,14 @@
731
851
  /* ── Narrow screens ──────────────────────────────────────────────────────────────────────
732
852
  *
733
853
  * Kind and Length are the two columns a phone can lose without the row stopping being
734
- * useful — the name, the size and the date are what the owner is scanning for. */
854
+ * useful — the name, the size and the date are what the owner is scanning for.
855
+ *
856
+ * 🔴 The exact-bytes column goes with them, and it is the one that would hurt most to keep:
857
+ * it is the widest track in the table (132px), and these widths ARE the layout under
858
+ * `table-layout: fixed` — thumb + size + bytes + date already exceeds a 390px phone on their
859
+ * own, so keeping it would trade the file NAME for a digit count. The rounded Size stays, so
860
+ * the row still answers "how big"; comparing two counts to the byte is a desk instrument, and
861
+ * the desk is where the column is. */
735
862
  @media (max-width: 720px) {
736
863
  .cbgt-head th:first-child,
737
864
  .cbgt-row td:first-child {
@@ -742,6 +869,7 @@
742
869
  padding-right: 12px;
743
870
  }
744
871
  .cbgt-cell--kind,
872
+ .cbgt-cell--bytes,
745
873
  .cbgt-cell--length {
746
874
  display: none;
747
875
  }
@@ -782,3 +910,51 @@
782
910
  min-width: 0;
783
911
  min-height: 0;
784
912
  }
913
+
914
+ /*
915
+ * ── The DESCRIPTION, under the title on the stage ───────────────────────────────────────
916
+ *
917
+ * Owner, `ideas/collections-09-14.md`: *"In media galleries have an option for apps to tag
918
+ * media and aadd descriptions to media."*
919
+ *
920
+ * 🔴 It cannot ride the ` · ` facts line. `.cbgd-meta-line` is `white-space: nowrap` with an
921
+ * ellipsis because the SAME class captions every tile in the grid, so a sentence appended
922
+ * there is a sentence nobody can read past its first few words. These two rules are what let
923
+ * the titlebar's own wrapping flex row carry a second, wrapping line — the facts keep the
924
+ * nowrap behaviour they have always had, and the description is a block beneath them.
925
+ *
926
+ * The width cap is readability, not decoration: `DESIGN.md` §4 asks for a measure, and a
927
+ * description running the full width of a 1680px stage is one line of 200 characters.
928
+ */
929
+ .cbmg-stage-facts {
930
+ display: block;
931
+ }
932
+ .cbmg-stage-description {
933
+ display: block;
934
+ /* Overrides the nowrap/ellipsis inherited from `.cbgd-meta-line` — see above. */
935
+ white-space: normal;
936
+ max-width: 68ch;
937
+ margin-top: 2px;
938
+ font-size: 12px;
939
+ line-height: 1.45;
940
+ color: var(--muted-foreground);
941
+ }
942
+
943
+ /*
944
+ * The tag/description editor's body — see `MediaMetaEditor.tsx`. Plain CSS in this module's
945
+ * own stylesheet rather than utilities on the elements, for the reason the rest of this file
946
+ * records: the sheet is already loaded wherever the gallery is, and the dialog is otherwise
947
+ * built entirely from design-system components that bring their own chrome.
948
+ */
949
+ .cbmm {
950
+ display: flex;
951
+ flex-direction: column;
952
+ gap: 14px;
953
+ }
954
+ .cbmm-count {
955
+ display: flex;
956
+ align-items: center;
957
+ gap: 6px;
958
+ font-size: 13px;
959
+ color: var(--muted-foreground);
960
+ }
@@ -0,0 +1,62 @@
1
+ import type { MediaGalleryItem } from './types';
2
+ /**
3
+ * Tags and a description, expressed as a CHANGE rather than as a new value.
4
+ *
5
+ * ── Why a change and not a pair of fields ──────────────────────────────────────────────
6
+ * The owner's ask, `ideas/collections-09-14.md`, 2026-09-14: *"In media galleries have an
7
+ * option for apps to tag media and aadd descriptions to media."* The case he will actually
8
+ * hit is forty holiday photographs at once, and forty items do not share one tag list — each
9
+ * one has its own, and "the new tags" cannot be a single array without wiping thirty-nine
10
+ * lists that were never on screen.
11
+ *
12
+ * So the plural verb carries `addTags`/`removeTags`, which mean the same thing for one item
13
+ * as for forty, and a consumer writes ONE handler rather than a singular one plus a loop.
14
+ * That is the rule `tasks/Completed/240-*.md` states for the file tree's multi-select in as
15
+ * many words — *"One request for N ids, not N requests: the tree deliberately does not loop
16
+ * `onMove`, because a tree re-derived between each write is how a batch move half-lands."*
17
+ *
18
+ * 🔴 Everything here is case-INSENSITIVE and spelling-preserving. A vocabulary that mints
19
+ * `Bank`, `bank` and `BANK` as three tags is a vocabulary nothing can filter by, and the
20
+ * gallery's own chips would show all three. The first spelling seen wins; a tag that differs
21
+ * only in case from one already present is not an addition at all.
22
+ */
23
+ export interface MediaMetaChange {
24
+ /** Tags to add to EVERY item in the set. Already trimmed, de-duplicated, never empty strings. */
25
+ addTags: string[];
26
+ /** Tags to remove from EVERY item in the set. Matched case-insensitively. */
27
+ removeTags: string[];
28
+ /**
29
+ * The new description, or `''` to clear it. `undefined` means "not part of this change" —
30
+ * which is what a bulk tag always sends, because one sentence cannot describe forty files
31
+ * and writing it onto all of them would delete thirty-nine that were never read.
32
+ */
33
+ description?: string;
34
+ }
35
+ /** Trim, drop the empties, drop case-insensitive duplicates keeping the first spelling. */
36
+ export declare function cleanTags(tags: readonly string[]): string[];
37
+ /**
38
+ * What it takes to get from `before` to `after` — the single-item editor's save, expressed in
39
+ * the same two lists the bulk one sends, so both paths arrive at a consumer as one shape.
40
+ */
41
+ export declare function tagDiff(before: readonly string[] | undefined, after: readonly string[]): {
42
+ addTags: string[];
43
+ removeTags: string[];
44
+ };
45
+ /** Nothing to save. The editor's Save is disabled on this, so a no-op never reaches an app. */
46
+ export declare function isEmptyMetaChange(change: MediaMetaChange): boolean;
47
+ /**
48
+ * The merge a consumer would otherwise write itself — one item's row after the change lands.
49
+ *
50
+ * Exported for the same reason `fileTreeSelection.ts`'s rules are: the gallery persists
51
+ * nothing, so every app has to do this arithmetic, and four apps re-deriving "add these,
52
+ * remove those, keep the spelling" is four chances to mint a duplicate tag. `tags` comes back
53
+ * cleaned; `description` is omitted from the result when the change did not carry one, so a
54
+ * caller can spread the result over its row without clearing a description it never touched.
55
+ */
56
+ export declare function applyMetaChange(item: Pick<MediaGalleryItem, 'tags' | 'description'>, change: MediaMetaChange): {
57
+ tags: string[];
58
+ description?: string;
59
+ };
60
+ /** Every tag on any of these items, cleaned and in first-seen order — the "Remove" vocabulary. */
61
+ export declare function tagsOn(items: readonly Pick<MediaGalleryItem, 'tags'>[]): string[];
62
+ //# sourceMappingURL=mediaMeta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaMeta.d.ts","sourceRoot":"","sources":["../../../src/react/media-gallery/mediaMeta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2FAA2F;AAC3F,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAY3D;AAED;;;GAGG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACrC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAS7C;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAMlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,aAAa,CAAC,EACpD,MAAM,EAAE,eAAe,GACtB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAK1C;AAED,kGAAkG;AAClG,wBAAgB,MAAM,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAEjF"}
@@ -0,0 +1,56 @@
1
+ /** Trim, drop the empties, drop case-insensitive duplicates keeping the first spelling. */
2
+ export function cleanTags(tags) {
3
+ const seen = new Set();
4
+ const out = [];
5
+ for (const raw of tags) {
6
+ const tag = raw.trim();
7
+ if (!tag)
8
+ continue;
9
+ const key = tag.toLowerCase();
10
+ if (seen.has(key))
11
+ continue;
12
+ seen.add(key);
13
+ out.push(tag);
14
+ }
15
+ return out;
16
+ }
17
+ /**
18
+ * What it takes to get from `before` to `after` — the single-item editor's save, expressed in
19
+ * the same two lists the bulk one sends, so both paths arrive at a consumer as one shape.
20
+ */
21
+ export function tagDiff(before, after) {
22
+ const was = cleanTags(before ?? []);
23
+ const now = cleanTags(after);
24
+ const wasKeys = new Set(was.map((t) => t.toLowerCase()));
25
+ const nowKeys = new Set(now.map((t) => t.toLowerCase()));
26
+ return {
27
+ addTags: now.filter((tag) => !wasKeys.has(tag.toLowerCase())),
28
+ removeTags: was.filter((tag) => !nowKeys.has(tag.toLowerCase())),
29
+ };
30
+ }
31
+ /** Nothing to save. The editor's Save is disabled on this, so a no-op never reaches an app. */
32
+ export function isEmptyMetaChange(change) {
33
+ return (change.addTags.length === 0 &&
34
+ change.removeTags.length === 0 &&
35
+ change.description === undefined);
36
+ }
37
+ /**
38
+ * The merge a consumer would otherwise write itself — one item's row after the change lands.
39
+ *
40
+ * Exported for the same reason `fileTreeSelection.ts`'s rules are: the gallery persists
41
+ * nothing, so every app has to do this arithmetic, and four apps re-deriving "add these,
42
+ * remove those, keep the spelling" is four chances to mint a duplicate tag. `tags` comes back
43
+ * cleaned; `description` is omitted from the result when the change did not carry one, so a
44
+ * caller can spread the result over its row without clearing a description it never touched.
45
+ */
46
+ export function applyMetaChange(item, change) {
47
+ const removed = new Set(change.removeTags.map((t) => t.toLowerCase()));
48
+ const kept = cleanTags(item.tags ?? []).filter((tag) => !removed.has(tag.toLowerCase()));
49
+ const tags = cleanTags([...kept, ...change.addTags]);
50
+ return change.description === undefined ? { tags } : { tags, description: change.description };
51
+ }
52
+ /** Every tag on any of these items, cleaned and in first-seen order — the "Remove" vocabulary. */
53
+ export function tagsOn(items) {
54
+ return cleanTags(items.flatMap((item) => item.tags ?? []));
55
+ }
56
+ //# sourceMappingURL=mediaMeta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaMeta.js","sourceRoot":"","sources":["../../../src/react/media-gallery/mediaMeta.ts"],"names":[],"mappings":"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"}
@@ -133,4 +133,81 @@ export declare const nextPlaybackEnd: (current: PlaybackEndBehavior) => Playback
133
133
  export type GalleryViewMode = 'grid' | 'table';
134
134
  export declare function readViewMode(): GalleryViewMode;
135
135
  export declare function writeViewMode(mode: GalleryViewMode): void;
136
+ /**
137
+ * The level a player opens at, and whether it opens silent.
138
+ *
139
+ * ── The owner's spec, verbatim (`ideas/collections-09-14.md`, 2026-09-14) ───────────────
140
+ * *"Keep the media player volume where I set it after changing it during a session except if
141
+ * I refresh or first log in then continue starting it at mute. Then if I unmute it, keep that
142
+ * value as I change to other videos."*
143
+ *
144
+ * Three rules, and the middle one is the whole design:
145
+ *
146
+ * | fresh page load / first sign-in | muted, exactly as it is today |
147
+ * | he changes it, then opens another clip | that level, for the rest of the session |
148
+ * | he refreshes | muted again |
149
+ *
150
+ * ── 🔴 It is kept IN MEMORY. Not `localStorage`, and not `sessionStorage` either ────────
151
+ * The neighbours above are `localStorage` because they are ways of WORKING — how a folder
152
+ * plays through, how a stage is pinned — and a way of working survives a reload. Loudness is
153
+ * not: *"except if I refresh … continue starting it at mute"* is a request for this to be
154
+ * FORGOTTEN, so the only correct store is one that cannot outlive the page.
155
+ *
156
+ * 🔴 `sessionStorage` is NOT that store, however much its name suggests it. It is cleared
157
+ * when the TAB closes, not when the page loads — MDN, in as many words: *"the data survives
158
+ * page reloads and restores"*. Measured here 2026-09-16 in the repo's own Chromium (write,
159
+ * `page.reload()`, read: the value is still there), because the difference is invisible in a
160
+ * unit test and is precisely the row of the table above that he called out. A volume put
161
+ * there comes back LOUD on the one gesture he named as the one that must return to silence.
162
+ *
163
+ * A module-level `Map` dies with the document, which is what a person means by "this
164
+ * session". It is also why nothing here needs the `try` wrapping every reader above carries:
165
+ * there is no web storage to be denied by a sandboxed iframe or by Safari's private mode, and
166
+ * therefore no read that can throw the gallery render away over a remembered volume. The
167
+ * specs assert both stores stay untouched, so a later move onto one of them — which would
168
+ * silently restore loudness across a refresh — fails rather than passes.
169
+ *
170
+ * SSR-safe for the same reason: writes only ever happen in a browser event handler, so the
171
+ * map a server renders against is always empty and every server-rendered player starts muted.
172
+ *
173
+ * ── 🔴 Muted and the level are TWO facts ────────────────────────────────────────────────
174
+ * *"if I unmute it, keep that value"* — so a mute may not be stored as `volume: 0`. Pressing
175
+ * mute and pressing unmute has to leave the level where he put it, which it only does if the
176
+ * level survives the mute independently. Every media element already models these two
177
+ * separately; collapsing them here is what would break it.
178
+ */
179
+ export interface SessionVolume {
180
+ /** 0–1: what it plays at, and what UNMUTING returns to. */
181
+ volume: number;
182
+ /** Silent — whatever level is underneath. */
183
+ muted: boolean;
184
+ }
185
+ /**
186
+ * What a player opens at when the person has not touched the volume this session: full level,
187
+ * no sound. It is the level that matters — a restored `volume: 1` under a mute is what makes
188
+ * the control bar's unmute button behave the way it did before any of this existed.
189
+ */
190
+ export declare const DEFAULT_SESSION_VOLUME: SessionVolume;
191
+ /**
192
+ * Where a session volume is kept — the CALLER names it, for the same reason
193
+ * `media/stageResize.ts` makes the remembered HEIGHT a keyed value: there is more than one
194
+ * player in this package, and one shared key would make setting the level on one of them
195
+ * silently reach into the other. The gallery stage and the shared `VideoPlayer` primitive an
196
+ * app mounts on a page of its own are two players.
197
+ */
198
+ export declare const PLAYER_VOLUME_KEY = "cbvp:volume";
199
+ /** The gallery stage's own — both transports of it (Vidstack and the native `<video>`). */
200
+ export declare const STAGE_VOLUME_KEY = "cbmg:stage-volume";
201
+ /**
202
+ * What he set this session, or `null` for "he has not touched it".
203
+ *
204
+ * 🔴 `null` rather than {@link DEFAULT_SESSION_VOLUME}, and the distinction is load-bearing:
205
+ * a player that was never going to make a sound anyway (`autoPlay={false}`, which mutes
206
+ * nothing today) must keep behaving exactly as it did, and it only can if the caller can tell
207
+ * "nothing set" apart from "set to the default". Handing back the default here would silence
208
+ * every click-to-play consumer in the fleet on upgrade.
209
+ */
210
+ export declare function readSessionVolume(key: string): SessionVolume | null;
211
+ /** Remember it for the rest of this PAGE. Never longer — see {@link SessionVolume}. */
212
+ export declare function writeSessionVolume(key: string, value: SessionVolume): void;
136
213
  //# sourceMappingURL=playbackPreferences.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"playbackPreferences.d.ts","sourceRoot":"","sources":["../../../src/react/media-gallery/playbackPreferences.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,wDAAwD;AACxD,MAAM,MAAM,mBAAmB;AAC7B,8EAA8E;AAC5E,MAAM;AACR,oCAAoC;GAClC,QAAQ;AACV,sFAAsF;GACpF,MAAM;AACR,wFAAwF;GACtF,KAAK;AACP,2FAA2F;GACzF,SAAS,CAAC;AAEd,eAAO,MAAM,sBAAsB,EAAE,mBAAmB,EAMvD,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,mBAAmB,EACnB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAmB/C,CAAC;AAIF;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,mBAAmB,CASrD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAMpE;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,uCAAwC,CAAC;AAEzE,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAIrC;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAQvC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMrD;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAMtC;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAMnD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAMpD;AAED,uEAAuE;AACvE,eAAO,MAAM,eAAe,GAAI,SAAS,mBAAmB,KAAG,mBAGlD,CAAC;AAEd;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAI/C,wBAAgB,YAAY,IAAI,eAAe,CAM9C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAMzD"}
1
+ {"version":3,"file":"playbackPreferences.d.ts","sourceRoot":"","sources":["../../../src/react/media-gallery/playbackPreferences.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,wDAAwD;AACxD,MAAM,MAAM,mBAAmB;AAC7B,8EAA8E;AAC5E,MAAM;AACR,oCAAoC;GAClC,QAAQ;AACV,sFAAsF;GACpF,MAAM;AACR,wFAAwF;GACtF,KAAK;AACP,2FAA2F;GACzF,SAAS,CAAC;AAEd,eAAO,MAAM,sBAAsB,EAAE,mBAAmB,EAMvD,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,mBAAmB,EACnB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAmB/C,CAAC;AAIF;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,mBAAmB,CASrD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAMpE;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,uCAAwC,CAAC;AAEzE,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAIrC;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAQvC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMrD;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAMtC;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAMnD;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAMpD;AAED,uEAAuE;AACvE,eAAO,MAAM,eAAe,GAAI,SAAS,mBAAmB,KAAG,mBAGlD,CAAC;AAEd;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAI/C,wBAAgB,YAAY,IAAI,eAAe,CAM9C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAMzD;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAA0C,CAAC;AAEhF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAE/C,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAKpD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAKnE;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,CAO1E"}
@@ -210,4 +210,46 @@ export function writeViewMode(mode) {
210
210
  // Unremembered is still usable this session.
211
211
  }
212
212
  }
213
+ /**
214
+ * What a player opens at when the person has not touched the volume this session: full level,
215
+ * no sound. It is the level that matters — a restored `volume: 1` under a mute is what makes
216
+ * the control bar's unmute button behave the way it did before any of this existed.
217
+ */
218
+ export const DEFAULT_SESSION_VOLUME = { volume: 1, muted: true };
219
+ /**
220
+ * Where a session volume is kept — the CALLER names it, for the same reason
221
+ * `media/stageResize.ts` makes the remembered HEIGHT a keyed value: there is more than one
222
+ * player in this package, and one shared key would make setting the level on one of them
223
+ * silently reach into the other. The gallery stage and the shared `VideoPlayer` primitive an
224
+ * app mounts on a page of its own are two players.
225
+ */
226
+ export const PLAYER_VOLUME_KEY = 'cbvp:volume';
227
+ /** The gallery stage's own — both transports of it (Vidstack and the native `<video>`). */
228
+ export const STAGE_VOLUME_KEY = 'cbmg:stage-volume';
229
+ /** Per key, for this document only. See {@link SessionVolume} for why it is not a web store. */
230
+ const sessionVolumes = new Map();
231
+ /**
232
+ * What he set this session, or `null` for "he has not touched it".
233
+ *
234
+ * 🔴 `null` rather than {@link DEFAULT_SESSION_VOLUME}, and the distinction is load-bearing:
235
+ * a player that was never going to make a sound anyway (`autoPlay={false}`, which mutes
236
+ * nothing today) must keep behaving exactly as it did, and it only can if the caller can tell
237
+ * "nothing set" apart from "set to the default". Handing back the default here would silence
238
+ * every click-to-play consumer in the fleet on upgrade.
239
+ */
240
+ export function readSessionVolume(key) {
241
+ const held = sessionVolumes.get(key);
242
+ // A COPY: the caller hands this to a player that will go on changing it, and a shared
243
+ // object would let one surface's live state leak into the next one's starting value.
244
+ return held ? { ...held } : null;
245
+ }
246
+ /** Remember it for the rest of this PAGE. Never longer — see {@link SessionVolume}. */
247
+ export function writeSessionVolume(key, value) {
248
+ sessionVolumes.set(key, {
249
+ // Clamped here rather than at every call site: this lands on `HTMLMediaElement.volume`,
250
+ // which throws `IndexSizeError` outside 0–1.
251
+ volume: Math.min(1, Math.max(0, value.volume)),
252
+ muted: value.muted,
253
+ });
254
+ }
213
255
  //# sourceMappingURL=playbackPreferences.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"playbackPreferences.js","sourceRoot":"","sources":["../../../src/react/media-gallery/playbackPreferences.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"playbackPreferences.js","sourceRoot":"","sources":["../../../src/react/media-gallery/playbackPreferences.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { FileCategory } from 'cwip/file-kind';
2
2
  import type { ProcessingStatus } from 'cursedbelt-core/media';
3
+ import type { VideoChapter } from '../media/videoChapters';
3
4
  /**
4
5
  * Sort orders the gallery header offers.
5
6
  *
@@ -82,6 +83,25 @@ export interface MediaGalleryItem extends MediaGalleryItemMedia {
82
83
  durationSeconds?: number;
83
84
  /** Free-form tags — shown in the meta line and as filter chips when any item has them. */
84
85
  tags?: string[];
86
+ /**
87
+ * What this file IS, in the owner's own words — shown under the title on the stage.
88
+ *
89
+ * 🔴 The owner's ask, `ideas/collections-09-14.md`, 2026-09-14: *"In media galleries have an
90
+ * option for apps to tag media and aadd descriptions to media."* Tags were already readable
91
+ * here and there was no description field at all, so half of that sentence had nowhere to
92
+ * land even for an app that stored one.
93
+ *
94
+ * Like {@link tags} it is READ freely: an item that carries one renders it whether or not
95
+ * the app can write one. Authoring both is a capability — pass
96
+ * {@link MediaGalleryProps.onSaveMeta} and the control appears.
97
+ *
98
+ * 🔴 Deliberately NOT on the tile caption. That caption is two lines by design (see
99
+ * `galleryTileLine`) and is already contested between the tags and the measurement; a
100
+ * sentence there would either be an ellipsis or a third line on every tile in the grid.
101
+ * The stage has the room, and the stage is where somebody is actually reading about one
102
+ * file rather than scanning forty.
103
+ */
104
+ description?: string;
85
105
  /** Download href when it differs from `url` (e.g. `?download=1` variants). */
86
106
  downloadUrl?: string;
87
107
  /**
@@ -104,6 +124,20 @@ export interface MediaGalleryItem extends MediaGalleryItemMedia {
104
124
  flipX?: boolean;
105
125
  flipY?: boolean;
106
126
  };
127
+ /**
128
+ * The named sections of a video — YouTube chapters, stored by the app on its own row.
129
+ *
130
+ * 🔴 The owner's ask, 2026-09-15: *"allow putting markers on videos at points in time and
131
+ * giving them a title for the section for knowing what that part of the video is about, like
132
+ * youtube chapters."* Like {@link orientation}, this is metadata the app keeps beside the
133
+ * file rather than anything baked into it — a chapter costs no encode, applies the instant it
134
+ * is saved, and is free to be wrong.
135
+ *
136
+ * The stage passes them to the player, which draws the dividers on the scrub bar and fills
137
+ * its chapters menu (see `stagePlayableVideo`). Authoring them is a separate capability:
138
+ * pass {@link MediaGalleryProps.onSaveChapters} and the Sections control appears.
139
+ */
140
+ chapters?: VideoChapter[];
107
141
  /**
108
142
  * Lazily resolve this item's media URLs the first time a tile or stage needs them.
109
143
  * Called once per item id per gallery mount (cached); return `null` when the bytes
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/media-gallery/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,WAAW,GACX,MAAM,GACN,UAAU,CAAC;AAEf;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,oFAAoF;IACpF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,qBAAqB;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzE;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;CAC5D"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/media-gallery/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,WAAW,GACX,MAAM,GACN,UAAU,CAAC;AAEf;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,oFAAoF;IACpF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAiB,SAAQ,qBAAqB;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACzE;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;CAC5D"}
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bun
2
+ interface Violation {
3
+ file: string;
4
+ line: number;
5
+ snippet: string;
6
+ }
7
+ interface Rule {
8
+ name: string;
9
+ description: string;
10
+ check: (filePath: string, source: string) => Violation[];
11
+ }
12
+ export declare function walk(dir: string, out?: string[]): string[];
13
+ export declare function setReimplementedNames(names: readonly string[]): void;
14
+ export declare const RULES: Record<string, Rule>;
15
+ export declare const ALLOWED_FILE_CATEGORIES: Set<string>;
16
+ export interface FileAllow {
17
+ rules: Set<string>;
18
+ category: string;
19
+ reason: string;
20
+ line: number;
21
+ }
22
+ /** Parse honored file-level allowances from a source file's header, plus warnings for any
23
+ * malformed/unrecognized directive (which is NOT honored — its violations still fire). */
24
+ export declare function parseFileAllows(source: string): {
25
+ allows: FileAllow[];
26
+ warnings: string[];
27
+ };
28
+ export type { Rule, Violation };
29
+ //# sourceMappingURL=guardrailsEnforce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AAwKA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;AAu2BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAEpE;AAweD,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAkBtC,CAAC;AASF,eAAO,MAAM,uBAAuB,aAAkD,CAAC;AAGvF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AAED;2FAC2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA0C3F;AAED,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC"}