@joewinke/jatui 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -177,7 +177,7 @@ Item-by-item review surfaces (MOS review-ux, design mos-z8vgx). One `VerdictOpti
177
177
  | `ThumbnailGrid` | Generic thumbnail strip (chimaro port) — image tiles with text-tile fallback, +N overflow counter, remove/add affordances. Decoupled from any media library: pass `{id, url, alt}` items, or a `cell` snippet for full control of tile contents. `removeMode` — `'hover'` (default) reveals the × on hover/focus and keeps it visible on coarse (touch) pointers (ContextMenuTrigger pattern); `'always'` pins it for touch-first flows. `gridCols` (e.g. `'grid-cols-2 sm:grid-cols-4'`) switches the strip to a responsive CSS grid with column-filling tiles. Items take optional `progress` (0–100): an upload ring draws around the tile and a tiny % occupies the remove-× corner, cross-fading into the × at 100 (same slot, no layout shift; per-tile `role="progressbar"`). | `items: ThumbnailItem[]` (`{id, url?, alt, progress?}`), `max`, `size` (`xs\|sm\|md\|lg`), `removable`+`onRemove`, `removeMode` (`hover\|always`), `addable`+`onAdd`, `onSelect`, `gridCols`, `class`, `cell` snippet |
178
178
  | `MediaWorkbench` | Full-featured image editor modal — upload, AI generate, AI edit, background removal, annotation canvas. **Preferred over `SmartImageEditor` for new image fields.** Two-column modal: preview left, 4 action tabs right (Generate, Edit, Remove BG, Annotate). Save uploads blob to `uploadEndpoint` or emits `onComplete`. Requires proxy endpoints from jst-23w1l.2. | `value` (bindable `string \| null`), `uploadEndpoint?` (default `/api/media`), `onComplete?`, `accept?` |
179
179
  | `MediaLightbox` | Full-screen media preview — z-9040 backdrop, arrow-key paging when given an array, `+/-/0` zoom, download action, Escape/backdrop close. **Use this for the general "just show it big" case** — replaces hand-rolled `fixed inset-0 z-[9040]` modals. For annotated / markup-overlaid image previews, use `ImageLightbox` instead. | `src?`, `filename?`, `kind?: 'image' \| 'video'`, `downloadUrl?`, `items?: MediaItem[]`, `initialIndex?`, `onClose?`, `zIndex?` |
180
- | `VideoPlayer` | **The default video surface — reach for this over a bare `<video controls>`.** One timeline band carrying a waveform-envelope scrubber, chapter flags (top edge), bookmark diamonds (bottom edge), diarized speaker colouring and scroll-to-zoom, plus an optional transcript pane (click to seek, active line auto-scroll, solo/skip per speaker, `Shift+T` toggle) and a "Talk time" legend (2+ speakers only; one voice gets no legend and no per-row name). **`kind="audio"` renders no stage**, and **`transport="band"`** (the audio default) drops the native `<video controls>` bar for a transport row on the band (play/pause, `now / total`, speed, mute + volume, fullscreen for video), so the band is the ONE timeline. A double-click on the picture toggles fullscreen (band transport; with native controls the browser owns the gesture), a buffering spinner shows while the media is waiting, and volume/mute survive a reload through `settingsStore` (else guarded localStorage). **Every intelligence prop is optional** — a bare mp4 still gets the scrubber, hover-time card, wheel volume and the full keyboard map (Space/K, arrows/J/L, vol/mute, −/= speed + Backspace reset, </> frame step, [/] chapters, B bookmark, t theater, ? help overlay), and each pane lights up as its data arrives (nothing shifts layout when diarization lands late). Wheel means volume over the video (shift = seek, alt = speed; a wheel/keyboard volume change also flashes a clear volume OSD — glyph, level bar and %, "Muted" at 0, opacity-only fade) and zoom over the band; Shift+wheel over the transcript steps line by line (a plain wheel over the transcript scrolls the pane natively — the page is never trapped); `/` opens an in-transcript search (only when a transcript exists and focus is not in a text field; the omnibar yields): matches highlight in place with a live `n / m` count (`aria-live`), Enter/Shift+Enter walk next/previous (seek + the line follows), Esc closes the search first in the Esc layering. Right-click a transcript line (or `Menu` / `Shift+F10` on a focused one) for the **line menu** — Play from here, Copy text, Copy text with timestamp (`[0:02] text`), Copy link at this time when `buildTimeLink` supplies the URL builder (without it the item is absent; `?t=` shape via `buildTimeUrl`); Escape closes and returns focus to the line, the player's keys are dead while it floats, and in fill theater Esc closes the menu without leaving the theater. A QR button on the transport (when `buildTimeLink` exists) opens a **continue-on-phone** card — the deep link at the current second as a scannable SVG QR; clicking the code copies it (jatui-3k7cp). Internals live in `src/lib/video/` (band/keys/rate/deepLink/settings + contracts for audio/night/presence — see that README; gap audit vs the extension: `VIDEO-PLAYER-GAP-AUDIT.md`). | `src`, `kind?` (`'video' \| 'audio'`), `transport?` (`'native' \| 'band'`), `chapters?`, `speakers?`, `transcript?`, `bookmarks?`, `waveform?`, `talkTime?`, `startAt?`, `poster?`, `captionsSrc?`, `crossorigin?` (`'anonymous'` — what frame-reading features need), `syncUrlTime?` (`?t=` deep-link, opt-in), `transcriptPosition?` (`'below' \| 'side'`), `transcriptMaxHeight?` (pane bound — default `min(40vh, 28rem)` below, the player's height beside; the active-line follow scrolls only the pane; only a gesture that actually moves the PANE pauses it — a wheel that scrolls the page doesn't — behind a "Resume following" pill, and it resumes by itself on seek / line or chapter click / play after pause / `followResumeMs` (6000) idle while playing; a hovered chapter fills its whole span in the theme accent with full-height edges), `followResumeMs?`, `transcriptLineMenu?` (true — right-click/`Menu`/`Shift+F10` on a transcript line opens the line menu; `false` leaves transcript lines to the browser's own context menu), `buildTimeLink?` ((sec) → URL for "Copy link at this time"; the host owns the address shape), `qrContinue?` (true — the QR button: the host's `buildTimeLink` at the current second as an SVG QR card, click the code to copy; hidden entirely when no `buildTimeLink` — the player never guesses a URL), `theater?` (bindable — theater mode, NOT fullscreen: control-bar button + `t`, the HOST lays the page out full-width; a side transcript moves below; remembered per viewer as the `player.theater` setting via `settingsStore`, else guarded localStorage), `theaterMode?` (`'width'` default — the host widens its page \| `'fill'` — the player becomes a fixed full-viewport dialog: `role="dialog"` + `aria-modal`, page scroll locked, Tab trapped, Esc/the button exits and returns focus, largest 16:9 stage that fits, transcript beside it ≥64rem else below, z from `--jatui-z-theater`; a remembered theater never fills on load, only a viewer gesture), `theaterButton?` (true), `onTheaterChange?`, `onTimeUpdate?`, `onAddBookmark?`, `onBookmarkContext?` |
180
+ | `VideoPlayer` | **The default video surface — reach for this over a bare `<video controls>`.** One timeline band carrying a waveform-envelope scrubber, chapter flags (top edge), bookmark diamonds (bottom edge), diarized speaker colouring and scroll-to-zoom, plus an optional transcript pane (click to seek, active line auto-scroll, solo/skip per speaker, `Shift+T` toggle) and a "Talk time" legend (2+ speakers only; one voice gets no legend and no per-row name). **`kind="audio"` renders no stage**, and **`transport="band"`** (the audio default) drops the native `<video controls>` bar for a transport row on the band (play/pause, `now / total`, speed, mute + volume, fullscreen for video), so the band is the ONE timeline. A double-click on the picture toggles fullscreen (band transport; with native controls the browser owns the gesture), a buffering spinner shows while the media is waiting, and volume/mute survive a reload through `settingsStore` (else guarded localStorage). **Every intelligence prop is optional** — a bare mp4 still gets the scrubber, hover-time card, wheel volume and the full keyboard map (Space/K, arrows/J/L, vol/mute, −/= speed + Backspace reset, </> frame step, [/] chapters, B bookmark, t theater, ? help overlay), and each pane lights up as its data arrives (nothing shifts layout when diarization lands late). Wheel means volume over the video (shift = seek, alt = speed; a wheel/keyboard volume change also flashes a clear volume OSD — glyph, level bar and %, "Muted" at 0, opacity-only fade) and zoom over the band; Shift+wheel over the transcript steps line by line (a plain wheel over the transcript scrolls the pane natively — the page is never trapped); `/` opens an in-transcript search (only when a transcript exists and focus is not in a text field; the omnibar yields): matches highlight in place with a live `n / m` count (`aria-live`), Enter/Shift+Enter walk next/previous (seek + the line follows), Esc closes the search first in the Esc layering. Right-click a transcript line (or `Menu` / `Shift+F10` on a focused one) for the **line menu** — Play from here, Copy text, Copy text with timestamp (`[0:02] text`), Copy link at this time when `buildTimeLink` supplies the URL builder (without it the item is absent; `?t=` shape via `buildTimeUrl`); Escape closes and returns focus to the line, the player's keys are dead while it floats, and in fill theater Esc closes the menu without leaving the theater. A QR button on the transport (when `buildTimeLink` exists) opens a **continue-on-phone** card — the deep link at the current second as a scannable SVG QR; clicking the code copies it (jatui-3k7cp). Internals live in `src/lib/video/` (band/keys/rate/deepLink/settings + contracts for audio/night/presence — see that README; gap audit vs the extension: `VIDEO-PLAYER-GAP-AUDIT.md`). | `src`, `kind?` (`'video' \| 'audio'`), `transport?` (`'native' \| 'band'`), `chapters?`, `speakers?`, `transcript?`, `bookmarks?`, `waveform?`, `talkTime?`, `startAt?`, `poster?`, `captionsSrc?`, `crossorigin?` (`'anonymous'` — what frame-reading features need), `syncUrlTime?` (`?t=` deep-link, opt-in), `transcriptPosition?` (`'below' \| 'side'`), `transcriptMaxHeight?` (pane bound — default `min(40vh, 28rem)` below, the player's height beside; the active-line follow scrolls only the pane; only a gesture that actually moves the PANE pauses it — a wheel that scrolls the page doesn't — behind a "Resume following" pill, and it resumes by itself on seek / line or chapter click / play after pause / `followResumeMs` (6000) idle while playing; a hovered chapter fills its whole span in the theme accent with full-height edges), `followResumeMs?`, `transcriptLineMenu?` (true — right-click/`Menu`/`Shift+F10` on a transcript line opens the line menu; `false` leaves transcript lines to the browser's own context menu), `buildTimeLink?` ((sec) → URL for "Copy link at this time"; the host owns the address shape), `qrContinue?` (true — the QR button: the host's `buildTimeLink` at the current second as an SVG QR card, click the code to copy; hidden entirely when no `buildTimeLink` — the player never guesses a URL), `theater?` (bindable — theater mode, NOT fullscreen: control-bar button + `t`, the HOST lays the page out full-width; a side transcript moves below; remembered per viewer as the `player.theater` setting via `settingsStore`, else guarded localStorage), `theaterMode?` (`'width'` default — the host widens its page \| `'fill'` — the player becomes a fixed full-viewport dialog: `role="dialog"` + `aria-modal`, page scroll locked, Tab trapped, Esc/the button exits and returns focus, largest 16:9 stage that fits, transcript beside it ≥64rem else below, z from `--jatui-z-theater`; a remembered theater never fills on load, only a viewer gesture), `theaterButton?` (true), `onTheaterChange?`, `onTimeUpdate?`, `onAddBookmark?`, `bookmarkQuickAdd?` (true — with `onAddBookmark` wired, `false` hides only the built-in "+ Bookmark at …" row; the `B` key and its `?` help entry stay, for a host that owns the bookmark UI), `onBookmarkContext?` |
181
181
  | `MediaCapture` | One-tap audio/selfie-video/screen(+mic) recording — a host-agnostic PRIMITIVE. Owns permission requests, MIME negotiation, the record/stop state machine, live self-preview, and mixes screen+mic audio into one track via AudioContext. Never uploads anything — hands the finished clip to `onCapture` as a Blob and the host decides what to do with it. `onCapture` may be async; the component shows "processing" until it resolves. | `onCapture: (result: MediaCaptureResult) => void \| Promise<void>`, `modes?` (default all three), `maxDurationMs?` (default 120000), `label?`, `hint?`, `compact?`, `disabled?` |
182
182
  | `MultimodalCaptureField` | "Upload a file, or record yourself going over it" as one input surface — composes `FileDropzone` (capped to one file) with `MediaCapture`. File and recording are independent (either, both, or neither); neither half uploads anything, same "validate and hand back" contract as `FileDropzone`. | `onFileSelected?`, `onFileRemoved?`, `onCapture?`, `onRecordingRemoved?`, `accept?`, `maxFileSize?`, `modes?`, `maxDurationMs?`, `file?`/`fileUrl?`/`fileName?`, `recordingSummary?` |
183
183
  | `ListenControl` | "Listen to this page" — a compact pill that expands into a TWO-ROW playback bar over the host's PER-SENTENCE audio (TTS): row 1 = play/pause · chapter chip + current sentence (full remaining width) · ONE settings pill ("1.25× · Sarah" → speed 0.75–2× + voice menu) · close; row 2 = prev/next sentence and −15s/+15s · a document-wide progress bar with chapter ticks (a hatched tail marks the not-yet-synthesized part), time, close. The host supplies an async `source` (sentences/paragraphs with ids → audio URLs, streamed page by page so playback starts on the first chunk while the rest synthesizes; pages merge by id so late URLs fill in) and gets `onSegmentChange(id)` at every boundary for highlight + scroll-follow. Media Session (lock-screen/headset controls, one document-wide position), keyboard scoped to the player (Space/K, ←/→ sentences, Shift+←/→ ±15s, [ / ] speed, Escape), speed+voice persisted in localStorage, `bind:api` for hosts wiring their own global keys, reduced-motion aware, token-only theming, never overflows its host (border-box, `max-width: 100%`, `min-width: 0` chain), fixed sticky mini-bar (same two rows) on narrow screens. Internals in `src/lib/listen/` (one timeline across per-sentence files — `buildTimeline` offsets; see `video/` for the doctrine). Demo synthesizes real WAVs locally. | `source`, `voices?`, `voiceId?` (bindable), `chapters?`, `title?`, `triggerLabel?`, `open?` (bindable), `preload?`, `estimateSec?`, `storageKey?`, `onSegmentChange?`, `onOpenChange?`, `api?` (bindable), `class?`, `children?` (custom trigger) |
@@ -383,6 +383,7 @@
383
383
  onMarkerClick = undefined,
384
384
  onTimeUpdate = undefined,
385
385
  onAddBookmark = undefined,
386
+ bookmarkQuickAdd = true,
386
387
  onBookmarkContext = undefined,
387
388
  night = $bindable(),
388
389
  nightDepth = $bindable(NIGHT_DEPTH_DEFAULT),
@@ -638,6 +639,14 @@
638
639
  onTimeUpdate?: (video: HTMLVideoElement) => void;
639
640
  /** Present ⇒ the band grows an "add bookmark at current time" control. */
640
641
  onAddBookmark?: (playbackSec: number, label: string) => void;
642
+ /**
643
+ * Show the built-in quick-add row (the label input + "+ Bookmark at …"
644
+ * button) when `onAddBookmark` is wired — default `true`, so existing
645
+ * hosts see no change. `false` hides ONLY the row: the `B` key and its
646
+ * `?` help entry stay wired to `onAddBookmark`, for a host that owns the
647
+ * bookmark UI itself (MOS's watch page) and just wants the keypress.
648
+ */
649
+ bookmarkQuickAdd?: boolean;
641
650
  /** Right-click on the band. The host owns the panel; this only hit-tests. */
642
651
  onBookmarkContext?: (
643
652
  sec: number,
@@ -4703,7 +4712,7 @@
4703
4712
  </div>
4704
4713
  {/if}
4705
4714
 
4706
- {#if onAddBookmark}
4715
+ {#if onAddBookmark && bookmarkQuickAdd}
4707
4716
  <div class="vp-bookmark-add">
4708
4717
  <label for="vp-bookmark-label">Bookmark label</label>
4709
4718
  <input
@@ -6124,7 +6133,8 @@
6124
6133
  outline-offset: 1px;
6125
6134
  }
6126
6135
 
6127
- /* Add-bookmark row — only rendered when the host wired onAddBookmark. */
6136
+ /* Quick-add bookmark row — rendered when the host wired onAddBookmark AND
6137
+ did not hide it with bookmarkQuickAdd={false} (the B key still works). */
6128
6138
  .vp-bookmark-add {
6129
6139
  display: flex;
6130
6140
  align-items: center;
@@ -296,6 +296,14 @@ type $$ComponentProps = {
296
296
  onTimeUpdate?: (video: HTMLVideoElement) => void;
297
297
  /** Present ⇒ the band grows an "add bookmark at current time" control. */
298
298
  onAddBookmark?: (playbackSec: number, label: string) => void;
299
+ /**
300
+ * Show the built-in quick-add row (the label input + "+ Bookmark at …"
301
+ * button) when `onAddBookmark` is wired — default `true`, so existing
302
+ * hosts see no change. `false` hides ONLY the row: the `B` key and its
303
+ * `?` help entry stay wired to `onAddBookmark`, for a host that owns the
304
+ * bookmark UI itself (MOS's watch page) and just wants the keypress.
305
+ */
306
+ bookmarkQuickAdd?: boolean;
299
307
  /** Right-click on the band. The host owns the panel; this only hit-tests. */
300
308
  onBookmarkContext?: (sec: number, existing: VideoBookmark | null, clientX: number, clientY: number) => void;
301
309
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joewinke/jatui",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "private": false,
5
5
  "description": "Shared Svelte 5 component library for JAT projects",
6
6
  "type": "module",