@joewinke/jatui 0.10.0 → 0.12.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.
@@ -2,10 +2,12 @@ export type { VideoChapter, VideoSpeakerTurn, VideoTranscriptSegment, VideoBookm
2
2
  export type { BandAxis, BandState } from '../video/band/axis';
3
3
  export type { VideoLumaTrack } from '../video/band/luma';
4
4
  export type { VideoMarker } from '../video/band/markers';
5
+ export type { NightMode, NightWindow } from '../video/night/clock';
5
6
  import type { VideoBookmark, VideoChapter, VideoSpeakerTalkTime, VideoSpeakerTurn, VideoTranscriptSegment, VideoWaveform } from '../video/types';
6
7
  import { type BandState } from '../video/band/axis';
7
8
  import { type VideoLumaTrack } from '../video/band/luma';
8
9
  import { type VideoMarker } from '../video/band/markers';
10
+ import { type NightMode, type NightWindow } from '../video/night/clock';
9
11
  type $$ComponentProps = {
10
12
  src: string;
11
13
  poster?: string | null;
@@ -97,10 +99,49 @@ type $$ComponentProps = {
97
99
  onAddBookmark?: (playbackSec: number, label: string) => void;
98
100
  /** Right-click on the band. The host owns the panel; this only hit-tests. */
99
101
  onBookmarkContext?: (sec: number, existing: VideoBookmark | null, clientX: number, clientY: number) => void;
102
+ /**
103
+ * Night dimming (mos-5lzns.33) — a METER, not a preference: ~1×/s while
104
+ * playing it reads the frame's actual brightness (32×18 canvas) and pulls
105
+ * the picture down in proportion, warming it through multiply layers so
106
+ * blacks stay black. A dark scene passes through untouched.
107
+ *
108
+ * `'auto'` arms it from the local clock (20:00–07:00; 18:00–08:00 when the
109
+ * OS prefers dark), `'on'` always, `'off'` never. Omitted (default) ⇒ the
110
+ * feature does not exist on this player: no N key, no button, no layers.
111
+ * Bindable — `N` and the moon button cycle it; `onNightModeChange` reports.
112
+ *
113
+ * Frame reading needs a CORS-clean load: pass `crossorigin="anonymous"`
114
+ * for cross-origin media. A tainted canvas runs night on a fixed level
115
+ * (no meter, no bias light) and calls `onNightBlind`.
116
+ */
117
+ night?: NightMode;
118
+ /** How far night goes, 0.2–1 (default 0.7). Bindable — scrolling the moon button sets it. */
119
+ nightDepth?: number;
120
+ /** Override the auto window, local hours `{ from, to }` (wraps midnight when from > to). */
121
+ nightWindow?: NightWindow | null;
122
+ /** While night acts, spill the frame's own colours, blurred, behind the player. */
123
+ biasLight?: boolean;
124
+ /** Read the `luma` track ~1s ahead so dimming starts BEFORE a bright cut. Only ever adds dimming. */
125
+ predictive?: boolean;
126
+ /**
127
+ * Photosensitivity guard: across the `luma` track's flash intervals,
128
+ * drop the picture to about 55% brightness (instant in, released per
129
+ * tick — never a jump back up). Always at full strength: it does NOT
130
+ * follow `nightDepth`, and it acts whether night is on, off or absent,
131
+ * whenever the track carries `flashes`. `false` to disable.
132
+ *
133
+ * It REDUCES flashes; it does not make content WCAG 2.3.1-safe. Flash
134
+ * DETECTION is the host's job — the player guards only the intervals the
135
+ * host supplies in `luma.flashes`.
136
+ */
137
+ strobeGuard?: boolean;
138
+ onNightModeChange?: (mode: NightMode) => void;
139
+ /** Frame readback was refused (CORS-tainted canvas) — night fell back to a fixed level. */
140
+ onNightBlind?: () => void;
100
141
  class?: string;
101
142
  };
102
143
  declare const VideoPlayer: import("svelte").Component<$$ComponentProps, {
103
144
  bandState: () => BandState;
104
- }, "">;
145
+ }, "night" | "nightDepth">;
105
146
  type VideoPlayer = ReturnType<typeof VideoPlayer>;
106
147
  export default VideoPlayer;
package/dist/index.d.ts CHANGED
@@ -12,7 +12,12 @@ export type { ChapterBlock } from './components/replay/ChapterTimeline.svelte';
12
12
  export { default as TranscriptPlayer } from './components/TranscriptPlayer.svelte';
13
13
  export type { TranscriptSegment, TranscriptBookmark, TranscriptChapter, TranscriptSpeakerTalkTime, } from './components/TranscriptPlayer.svelte';
14
14
  export { default as VideoPlayer } from './components/VideoPlayer.svelte';
15
- export type { VideoChapter, VideoSpeakerTurn, VideoTranscriptSegment, VideoBookmark, VideoWaveform, VideoSpeakerTalkTime, VideoLumaTrack, VideoMarker, BandState, BandAxis, } from './components/VideoPlayer.svelte';
15
+ export type { VideoChapter, VideoSpeakerTurn, VideoTranscriptSegment, VideoBookmark, VideoWaveform, VideoSpeakerTalkTime, VideoLumaTrack, VideoMarker, BandState, BandAxis, NightMode, NightWindow, } from './components/VideoPlayer.svelte';
16
+ export { buildSilenceMap, boostAt, estimateSaving, type SilenceMap, type SilenceWave, type SilenceOpts, type BoostOpts } from './video/advisors/smart-speed';
17
+ export { speakerPace, suggestRates, rateAt, describeRates, type SpeakerPace, type SpeakerPaceRow, type RateMap, type RateTurn, type RateSegment, type SuggestOpts } from './video/advisors/speaker-rate';
18
+ export { snapResume, resumeLabel, type ResumePoint, type ResumeReason, type ResumeCtx, type ResumeOpts } from './video/advisors/resume-point';
19
+ export { createQuietChain, amountFor, type QuietChain, type QuietAudioContext, type AmountOpts } from './video/advisors/quiet-listening';
20
+ export { qrMatrix, qrCanvas, type QrMatrix, type QrCanvasOpts } from './video/advisors/qr';
16
21
  export { default as Flythrough } from './components/Flythrough.svelte';
17
22
  export { default as LessonEmbed } from './components/LessonEmbed.svelte';
18
23
  export type { Collection, CollectionCut, CollectionBeat, } from './components/LessonEmbed.svelte';
package/dist/index.js CHANGED
@@ -13,6 +13,16 @@ export { default as TranscriptPlayer } from './components/TranscriptPlayer.svelt
13
13
  // optional so a bare mp4 still gets the full playback UX. TranscriptPlayer
14
14
  // stays exported unchanged — meetings already depends on it.
15
15
  export { default as VideoPlayer } from './components/VideoPlayer.svelte';
16
+ // Pure advisors (mos-5lzns.27): the extension-library self-test modules,
17
+ // ported verbatim. They measure and advise; the player's feature tasks
18
+ // (.31 audio / .32 rate / .34 presence) consume them. qrCanvas is the one
19
+ // DOM-touching surface (its only guard is a missing document), everything
20
+ // else is node-testable pure.
21
+ export { buildSilenceMap, boostAt, estimateSaving } from './video/advisors/smart-speed';
22
+ export { speakerPace, suggestRates, rateAt, describeRates } from './video/advisors/speaker-rate';
23
+ export { snapResume, resumeLabel } from './video/advisors/resume-point';
24
+ export { createQuietChain, amountFor } from './video/advisors/quiet-listening';
25
+ export { qrMatrix, qrCanvas } from './video/advisors/qr';
16
26
  // Presentation-primitive video trio (unified-video-pipeline PRD): the frozen
17
27
  // surface academy + mardukos consume. VideoPlayer (above) doubles as the full
18
28
  // player AND the presentation player — extended with aspect/autoplay/loop +
@@ -21,13 +21,18 @@ props + callback surfaces.
21
21
  | `deepLink.ts` | .28 (done) | `?t=` parse/format/read/write (replaceState, throttled, other params preserved). |
22
22
  | `settings.ts` | shared — knobs added by .30–.34 | `coerce` (never throws, never junk) + `VIDEO_SETTINGS` registry. A knob lands WITH its reader. |
23
23
  | `audio.ts` | **.31** | Loudness matching, quiet listening, limiter — audio-graph contract is in the file header. |
24
- | `night.ts` | **.33** | Night dimming, bias light, predictive dimming (luma track), strobe guard. |
24
+ | `night.ts` + `night/` | **.33** (done) | `night.ts` is the barrel. `meter.ts` — 32×18 frame bytes → Rec.709 luma + blown share, asymmetric smoothing, bias-light cells from the same bytes. `levels.ts` — luma → brightness/saturation/warmth/vignette off `NIGHT_FLOOR` 0.34, blind fixed level, the light-theme surround. `clock.ts` — local-clock `auto` window + the `N` auto/on/off cycle (no hyprsunset). `track.ts` — prediction (only ever adds dimming), flash intervals, `composeNight` (darker at once, brighter released per tick — never a jump). |
25
25
  | `presence.ts` | **.34** | Wind-down (Idle Detection), resume-at-sentence, FLIP open. |
26
+ | `advisors/` | **.27 (done)** | The five extension-library pure advisors, ported verbatim: `smart-speed` (silence map + boost), `speaker-rate` (measured wpm + suggested rates), `resume-point` (sentence-snapping + the label), `quiet-listening` (tone-only chain + `amountFor`), `qr` (ISO 18004 encoder v1–10). Each has its own README row in `advisors/README.md`; the `--self-test` suites live on as vitest in `tests/unit/advisors/` (`npm run test:advisors`). |
26
27
 
27
28
  Sub-task map: **.30** scrubbing/one-axis/ribbon/markers → `band.ts` (+ its own
28
29
  paint layers); **.31** audio graph → `audio.ts`; **.32** rate composer →
29
30
  `rate.ts` advisors; **.33** night → `night.ts`; **.34** presence/wind-down →
30
- `presence.ts`.
31
+ `presence.ts`. **.27** (landed) ports the advisors themselves — .31's chain
32
+ builder and `amountFor` live in `advisors/quiet-listening`, .32's two advisor
33
+ modules in `advisors/smart-speed` + `advisors/speaker-rate`, .34's snapping in
34
+ `advisors/resume-point`; the wiring into the component stays with each
35
+ feature task.
31
36
 
32
37
  ## The advisor doctrine (from extension-library FEATURES.md — keep it)
33
38
 
@@ -43,7 +48,8 @@ paint layers); **.31** audio graph → `audio.ts`; **.32** rate composer →
43
48
  MediaElementAudioSourceNode, never the element). Knobs arrive as an
44
49
  `opts` argument; a bad argument returns a safe neutral value rather than
45
50
  throwing. This is what keeps every module node-testable under
46
- `npm run test:unit`.
51
+ `npm run test:unit` (the advisor vitest suites run under
52
+ `npm run test:advisors`).
47
53
  4. **The rate composer is the shape to follow** (`rate.ts`): `rate = base ×
48
54
  speaker × silence`, advisors collapse to 1, the UI reports only `base`.
49
55
 
@@ -51,4 +57,12 @@ paint layers); **.31** audio graph → `audio.ts`; **.32** rate composer →
51
57
 
52
58
  Every pure module gets a `tests/unit/` node:test file (Node ≥ 22.18 strips
53
59
  types natively). Canvas/WAAPI work stays out of unit tests by construction —
54
- that's the point of the split.
60
+ that's the point of the split.
61
+
62
+ The advisor suites (`.27`) are the one deliberate exception to "node:test":
63
+ they came from the extension's `--self-test` blocks (string-printing PASS/FAIL
64
+ suites with their own helpers), and were ported to **vitest** in
65
+ `tests/unit/advisors/*.test.ts` — run with `npm run test:advisors`. The dir is
66
+ deliberately kept out of `test:unit`'s glob (its script names the node:test
67
+ files explicitly): vitest's `expect()` throws without the vitest process
68
+ around it, so node:test importing these files would fail the wrong runner.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Pure advisors for VideoPlayer — ported from extension-library `lib/`
3
+ * (mos-5lzns.27). Behaviour-verbatim ports of the five self-contained
4
+ * `--self-test` modules; each `--self-test` suite became a vitest file in
5
+ * `tests/unit/advisors/`.
6
+ *
7
+ * The advisor doctrine (src/lib/video/README.md): these modules measure and
8
+ * advise, never act. They read no settings, touch no DOM, never write
9
+ * `playbackRate`, and every function returns a safe neutral value rather than
10
+ * throwing on malformed input. Knobs arrive as `opts`; the component decides
11
+ * and applies.
12
+ *
13
+ * | Module | Answers | Consumed by |
14
+ * |---|---|---|
15
+ * | `smart-speed` | where the silence is, and the boost multiplier inside it | rate composer's `silence` advisor (.32) |
16
+ * | `speaker-rate` | each speaker's measured wpm, the rates that even them out | rate composer's `speaker` advisor (.32) |
17
+ * | `resume-point` | the nearest real boundary at-or-before a stored time + the line to say | resume-at-sentence (.34) |
18
+ * | `quiet-listening` | the loudness-compensated tone graph + how much of it is wanted | audio graph (.31) |
19
+ * | `qr` | the deep link as a scannable symbol | "continue on phone" (host UI) |
20
+ */
@@ -0,0 +1,95 @@
1
+ /**
2
+ * QR Code (ISO/IEC 18004) encoder: versions 1–10, byte mode, no deps. Ported
3
+ * verbatim from extension-library `lib/qr.js` (mos-5lzns.27).
4
+ *
5
+ * Why hand-rolled. The extension ran under a strict CSP where every CDN-hosted
6
+ * QR library was unreachable by construction, and there was no build step to
7
+ * vendor one through. The alternative — pasting a minified blob into the repo —
8
+ * ships a black box into a codebase whose whole premise is that you can read
9
+ * it. This is the spec, written out, and PROVEN by the vitest suite that
10
+ * replaced the `--self-test` block (a reader that decodes our own output, plus
11
+ * structural assertions against published tables) rather than trusted.
12
+ *
13
+ * What it is for: a QR of the current deep link — the player's `?t=` substrate
14
+ * (video/deepLink.ts) plus the host's own file/URL part — so the video you are
15
+ * watching moves from the desk to a phone mid-sentence. That URL is 60–120
16
+ * bytes over a tailnet hostname — a version 3–7 symbol at ECC M. Versions above
17
+ * 10 would buy capacity this will never use and cost another row in four tables
18
+ * each, so the ceiling is deliberate and qrMatrix throws at it instead of
19
+ * quietly growing.
20
+ *
21
+ * Guard philosophy, since it cuts both ways here: garbage OPTIONS degrade to
22
+ * defaults (an unknown ecc string is just "M"), because a slightly
23
+ * wrong-looking QR still scans. Text that genuinely cannot be represented
24
+ * THROWS, because the failure mode of the alternative is a symbol that scans
25
+ * perfectly and sends the phone to a truncated URL — silent, plausible, and
26
+ * wrong.
27
+ *
28
+ * qrMatrix(text, { ecc = "M", minVersion = 1 }) -> { size, modules }
29
+ * qrCanvas(text, { scale = 4, margin = 4, dark, light }) -> HTMLCanvasElement
30
+ *
31
+ * The self-test's independent READER (mask/format re-derivation, de-interleave,
32
+ * round-trip decode) lives with the vitest suite in tests/unit/advisors/ —
33
+ * deliberately NOT built on the encoder's own reserved map or chosen mask, and
34
+ * deliberately not shipped in dist.
35
+ */
36
+ declare const MAX_VERSION = 10;
37
+ declare const MODE_BYTE = 4;
38
+ declare const PAD_BYTES: number[];
39
+ declare const ECC_BITS: Record<string, number>;
40
+ export type EccLevel = "L" | "M" | "Q" | "H";
41
+ declare const BLOCKS: Record<EccLevel, number[][]>;
42
+ declare const TOTAL_CW: number[];
43
+ declare const ALIGN_POS: number[][];
44
+ declare const MASKS: ((r: number, c: number) => boolean)[];
45
+ declare const GF_EXP: Uint8Array<ArrayBuffer>;
46
+ declare const GF_LOG: Uint8Array<ArrayBuffer>;
47
+ declare const gfMul: (a: number, b: number) => number;
48
+ declare function rsGenerator(n: number): number[];
49
+ declare function rsEncode(data: Uint8Array, ecLen: number): Uint8Array;
50
+ declare function rsSyndromes(block: number[], ecLen: number): Uint8Array;
51
+ declare function formatBits(ecc: string, mask: number): number;
52
+ declare function versionBits(version: number): number;
53
+ declare const charCountBits: (version: number) => 8 | 16;
54
+ declare const dataCodewords: (version: number, ecc: EccLevel) => number;
55
+ declare const maxBytes: (version: number, ecc: EccLevel) => number;
56
+ declare function toBytes(text: unknown): Uint8Array;
57
+ declare function encodeData(bytes: Uint8Array, version: number, ecc: EccLevel): Uint8Array;
58
+ declare function interleave(data: Uint8Array, version: number, ecc: EccLevel): Uint8Array;
59
+ interface DrawnPattern {
60
+ size: number;
61
+ modules: boolean[][];
62
+ fn: boolean[][];
63
+ }
64
+ declare function drawFunctionPatterns(version: number): DrawnPattern;
65
+ declare function placeData(modules: boolean[][], fn: boolean[][], size: number, cw: Uint8Array): number;
66
+ declare function drawFormatBits(m: boolean[][], size: number, ecc: string, mask: number): void;
67
+ declare const P3A: number[];
68
+ declare const P3B: number[];
69
+ declare function penalty(m: boolean[][], size: number): number;
70
+ interface BuiltMatrix {
71
+ size: number;
72
+ modules: boolean[][];
73
+ mask: number;
74
+ version: number;
75
+ ecc: EccLevel;
76
+ codewords: Uint8Array;
77
+ }
78
+ declare function buildMatrix(bytes: Uint8Array, version: number, ecc: EccLevel, forcedMask: number | null): BuiltMatrix;
79
+ export interface QrMatrix {
80
+ /** Module count per side (21 for v1, +4 per version). */
81
+ size: number;
82
+ /** size×size grid; true = dark module. */
83
+ modules: boolean[][];
84
+ }
85
+ export declare function qrMatrix(text: unknown, opts?: unknown): QrMatrix;
86
+ export interface QrCanvasOpts {
87
+ ecc?: string;
88
+ minVersion?: number;
89
+ scale?: number;
90
+ margin?: number;
91
+ dark?: string;
92
+ light?: string;
93
+ }
94
+ export declare function qrCanvas(text: unknown, opts?: QrCanvasOpts | null): HTMLCanvasElement;
95
+ export { MAX_VERSION, MODE_BYTE, PAD_BYTES, ECC_BITS, BLOCKS, TOTAL_CW, ALIGN_POS, MASKS, P3A, P3B, GF_EXP, GF_LOG, gfMul, rsGenerator, rsEncode, rsSyndromes, formatBits, versionBits, charCountBits, dataCodewords, maxBytes, toBytes, encodeData, interleave, drawFunctionPatterns, placeData, drawFormatBits, penalty, buildMatrix, };