@joewinke/jatui 0.8.6 → 0.9.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.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Settings plumbing for VideoPlayer (mos-5lzns.28) — ported discipline from
3
+ * extension-library's `settings.js`, adapted to a prop-driven component.
4
+ *
5
+ * The extension's registry exists so the settings panel is generated from one
6
+ * array and "a setting added there appears in the UI by existing". jatui's
7
+ * player is prop-driven (the HOST owns the panel), so this module owns the
8
+ * smaller half of the contract that still matters here:
9
+ *
10
+ * · `coerce` never throws and never returns junk. A bool accepts
11
+ * true/"1"/"true"; a range parses and clamps to [min, max]; an enum must
12
+ * match a declared option or the value is discarded. Anything
13
+ * unrecognised returns undefined and the caller falls back to the
14
+ * declared default. A corrupted blob degrades to defaults, not to a
15
+ * broken player.
16
+ * · One registry (`VIDEO_SETTINGS`) lists every knob the player's automatic
17
+ * behaviours read, with the sub-task that owns each. A knob that is here
18
+ * but unread by any module is a switch that visibly does nothing — the
19
+ * extension calls that "declared but not yet wired" and refuses to fix it
20
+ * quietly. Keep the same discipline: land the knob WITH its reader.
21
+ *
22
+ * Nothing under src/lib/video/ reads a setting directly except through the
23
+ * value its owning feature passes in as an `opts` argument — the advisor
24
+ * rule (see README). That keeps every module node-testable and keeps the
25
+ * arbitration in the component.
26
+ */
27
+ export type SettingType = 'bool' | 'range' | 'enum';
28
+ export interface SettingSpec {
29
+ id: string;
30
+ type: SettingType;
31
+ default: boolean | number | string;
32
+ /** range only */
33
+ min?: number;
34
+ max?: number;
35
+ step?: number;
36
+ /** enum only */
37
+ options?: string[];
38
+ /** Which sub-task wires the reader. A spec without a live reader is a lie. */
39
+ owner: string;
40
+ /** Set true once the owning task lands (check: grep the module for the id). */
41
+ wired?: boolean;
42
+ }
43
+ /**
44
+ * The registry. Populated as the behaviours land — .30 oneAxis, .31 audio,
45
+ * .32 rate/speaker/silence, .33 night, .34 presence. An empty registry is
46
+ * the honest state while every behaviour is prop-driven.
47
+ */
48
+ export declare const VIDEO_SETTINGS: SettingSpec[];
49
+ /** Coerce a raw stored value against a spec — never throws, never returns junk. */
50
+ export declare function coerce<T extends SettingSpec>(spec: T, raw: unknown): T['default'] | undefined;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Settings plumbing for VideoPlayer (mos-5lzns.28) — ported discipline from
3
+ * extension-library's `settings.js`, adapted to a prop-driven component.
4
+ *
5
+ * The extension's registry exists so the settings panel is generated from one
6
+ * array and "a setting added there appears in the UI by existing". jatui's
7
+ * player is prop-driven (the HOST owns the panel), so this module owns the
8
+ * smaller half of the contract that still matters here:
9
+ *
10
+ * · `coerce` never throws and never returns junk. A bool accepts
11
+ * true/"1"/"true"; a range parses and clamps to [min, max]; an enum must
12
+ * match a declared option or the value is discarded. Anything
13
+ * unrecognised returns undefined and the caller falls back to the
14
+ * declared default. A corrupted blob degrades to defaults, not to a
15
+ * broken player.
16
+ * · One registry (`VIDEO_SETTINGS`) lists every knob the player's automatic
17
+ * behaviours read, with the sub-task that owns each. A knob that is here
18
+ * but unread by any module is a switch that visibly does nothing — the
19
+ * extension calls that "declared but not yet wired" and refuses to fix it
20
+ * quietly. Keep the same discipline: land the knob WITH its reader.
21
+ *
22
+ * Nothing under src/lib/video/ reads a setting directly except through the
23
+ * value its owning feature passes in as an `opts` argument — the advisor
24
+ * rule (see README). That keeps every module node-testable and keeps the
25
+ * arbitration in the component.
26
+ */
27
+ /**
28
+ * The registry. Populated as the behaviours land — .30 oneAxis, .31 audio,
29
+ * .32 rate/speaker/silence, .33 night, .34 presence. An empty registry is
30
+ * the honest state while every behaviour is prop-driven.
31
+ */
32
+ export const VIDEO_SETTINGS = [
33
+ // { id: 'player.oneAxis', type: 'bool', default: true, owner: 'mos-5lzns.30' },
34
+ // { id: 'audio.level', type: 'bool', default: true, owner: 'mos-5lzns.31' },
35
+ // { id: 'player.speakerRate', type: 'bool', default: false, owner: 'mos-5lzns.32' },
36
+ // { id: 'night.mode', type: 'enum', default: 'auto', options: ['auto','on','off'], owner: 'mos-5lzns.33' },
37
+ // { id: 'sleep.enabled', type: 'bool', default: true, owner: 'mos-5lzns.34' },
38
+ ];
39
+ /** Coerce a raw stored value against a spec — never throws, never returns junk. */
40
+ export function coerce(spec, raw) {
41
+ if (spec.type === 'bool') {
42
+ if (raw === true || raw === '1' || raw === 'true')
43
+ return true;
44
+ if (raw === false || raw === '0' || raw === 'false')
45
+ return false;
46
+ return undefined;
47
+ }
48
+ if (spec.type === 'range') {
49
+ const n = typeof raw === 'number' ? raw : Number(raw);
50
+ if (!Number.isFinite(n))
51
+ return undefined;
52
+ const min = spec.min ?? -Infinity;
53
+ const max = spec.max ?? Infinity;
54
+ return Math.min(max, Math.max(min, n));
55
+ }
56
+ // enum: must match a declared option or the value is discarded
57
+ if (typeof raw !== 'string')
58
+ return undefined;
59
+ if (!spec.options?.includes(raw))
60
+ return undefined;
61
+ return raw;
62
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Pure time/format helpers for VideoPlayer (mos-5lzns.28 internal split).
3
+ *
4
+ * DOM-free and dependency-free — the same "advisor" discipline the
5
+ * extension-library player uses for its `lib/` modules: pure functions,
6
+ * safe on garbage input, node-testable.
7
+ */
8
+ /** `formatTime(90.4, false)` → `"1:30"`; with hours → `"0:01:30"`. */
9
+ export declare function formatTime(totalSeconds: number, withHours: boolean): string;
10
+ /** `formatDuration(3730)` → `"1h 2m"`; minutes scale → `"3m 40s"`; below → `"12s"`. */
11
+ export declare function formatDuration(totalSeconds: number): string;
12
+ export declare function truncate(s: string, n: number): string;
13
+ /**
14
+ * `aspect` → a CSS `aspect-ratio` value. Accepts `"W:H"`, a bare ratio number,
15
+ * or null/"auto" (⇒ null; the video's intrinsic ratio drives the box). Shared
16
+ * with <Flythrough>/<LessonEmbed> conceptually, kept as a pure function so the
17
+ * primitive has no cross-file coupling for one small helper.
18
+ */
19
+ export declare function aspectRatioCss(aspect: string | number | null | undefined): string | null;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Pure time/format helpers for VideoPlayer (mos-5lzns.28 internal split).
3
+ *
4
+ * DOM-free and dependency-free — the same "advisor" discipline the
5
+ * extension-library player uses for its `lib/` modules: pure functions,
6
+ * safe on garbage input, node-testable.
7
+ */
8
+ /** `formatTime(90.4, false)` → `"1:30"`; with hours → `"0:01:30"`. */
9
+ export function formatTime(totalSeconds, withHours) {
10
+ const t = Number.isFinite(totalSeconds) && totalSeconds > 0 ? totalSeconds : 0;
11
+ const pad = (n) => String(n).padStart(2, '0');
12
+ const s = Math.floor(t % 60);
13
+ if (withHours) {
14
+ return `${Math.floor(t / 3600)}:${pad(Math.floor((t % 3600) / 60))}:${pad(s)}`;
15
+ }
16
+ return `${Math.floor(t / 60)}:${pad(s)}`;
17
+ }
18
+ /** `formatDuration(3730)` → `"1h 2m"`; minutes scale → `"3m 40s"`; below → `"12s"`. */
19
+ export function formatDuration(totalSeconds) {
20
+ const s = Math.round(Math.max(0, totalSeconds));
21
+ const h = Math.floor(s / 3600);
22
+ const m = Math.floor((s % 3600) / 60);
23
+ if (h > 0)
24
+ return `${h}h ${m}m`;
25
+ if (m > 0)
26
+ return `${m}m ${s % 60}s`;
27
+ return `${s}s`;
28
+ }
29
+ export function truncate(s, n) {
30
+ return s.length > n ? s.slice(0, n - 1).trimEnd() + '…' : s;
31
+ }
32
+ /**
33
+ * `aspect` → a CSS `aspect-ratio` value. Accepts `"W:H"`, a bare ratio number,
34
+ * or null/"auto" (⇒ null; the video's intrinsic ratio drives the box). Shared
35
+ * with <Flythrough>/<LessonEmbed> conceptually, kept as a pure function so the
36
+ * primitive has no cross-file coupling for one small helper.
37
+ */
38
+ export function aspectRatioCss(aspect) {
39
+ if (aspect == null)
40
+ return null;
41
+ const trimmed = String(aspect).trim();
42
+ if (trimmed === '' || trimmed === 'auto')
43
+ return null;
44
+ const colon = trimmed.match(/^(\d+(?:\.\d+)?)\s*[:/]\s*(\d+(?:\.\d+)?)$/);
45
+ if (colon) {
46
+ const w = parseFloat(colon[1]);
47
+ const h = parseFloat(colon[2]);
48
+ if (w > 0 && h > 0)
49
+ return `${w} / ${h}`;
50
+ }
51
+ const num = Number(trimmed);
52
+ if (Number.isFinite(num) && num > 0)
53
+ return String(num);
54
+ return null;
55
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * VideoPlayer public data contracts (mos-5lzns.28 internal split).
3
+ *
4
+ * Moved verbatim from `components/VideoPlayer.svelte` so the intelligence
5
+ * modules under `src/lib/video/` can type their inputs without importing a
6
+ * .svelte file. The component re-exports these (`export type … from`),
7
+ * so the package's public type surface — `import type { VideoChapter, … }
8
+ * from 'jatui'` — is unchanged.
9
+ *
10
+ * Every field that feeds a pane is OPTIONAL at the component level: a bare
11
+ * mp4 with none of this still gets the band, wheel volume and the keyboard
12
+ * map. Panes light up as their data arrives; nothing shifts layout.
13
+ */
14
+ /** A model-authored marker: the video's structure. */
15
+ export interface VideoChapter {
16
+ startSec: number;
17
+ title: string;
18
+ /** Optional captured still, shown in the band's hover card. */
19
+ thumbUrl?: string | null;
20
+ }
21
+ /** A diarized turn (or an ad read, `ad: true`). Structurally accepts meeting data. */
22
+ export interface VideoSpeakerTurn {
23
+ startSec: number;
24
+ endSec: number;
25
+ speakerIdentity?: string | null;
26
+ speakerName?: string | null;
27
+ /** Explicit oklch hue (0–360). Omit to take the next palette colour. */
28
+ hue?: number | null;
29
+ /** Ad read — shares the lane but reads as not-a-person. */
30
+ ad?: boolean;
31
+ }
32
+ /** Transcript line. Structurally compatible with TranscriptPlayer's `TranscriptSegment`. */
33
+ export interface VideoTranscriptSegment {
34
+ id: string;
35
+ startSec: number;
36
+ endSec: number;
37
+ speakerIdentity?: string | null;
38
+ speakerName?: string | null;
39
+ text: string;
40
+ }
41
+ /** A user-authored marker. Structurally compatible with `TranscriptBookmark`. */
42
+ export interface VideoBookmark {
43
+ playbackSec: number;
44
+ label?: string | null;
45
+ note?: string | null;
46
+ participantName?: string | null;
47
+ }
48
+ /** Precomputed audio envelope. `peaks` are normalised against their own max. */
49
+ export interface VideoWaveform {
50
+ peaks: number[];
51
+ /** Seconds the peaks span. Defaults to the video's duration. */
52
+ durationSec?: number;
53
+ }
54
+ /** Per-speaker totals. Structurally compatible with `TranscriptSpeakerTalkTime`. */
55
+ export interface VideoSpeakerTalkTime {
56
+ speakerIdentity?: string | null;
57
+ speakerName?: string | null;
58
+ seconds: number;
59
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * VideoPlayer public data contracts (mos-5lzns.28 internal split).
3
+ *
4
+ * Moved verbatim from `components/VideoPlayer.svelte` so the intelligence
5
+ * modules under `src/lib/video/` can type their inputs without importing a
6
+ * .svelte file. The component re-exports these (`export type … from`),
7
+ * so the package's public type surface — `import type { VideoChapter, … }
8
+ * from 'jatui'` — is unchanged.
9
+ *
10
+ * Every field that feeds a pane is OPTIONAL at the component level: a bare
11
+ * mp4 with none of this still gets the band, wheel volume and the keyboard
12
+ * map. Panes light up as their data arrives; nothing shifts layout.
13
+ */
14
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joewinke/jatui",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "description": "Shared Svelte 5 component library for JAT projects",
6
6
  "type": "module",