@joewinke/jatui 0.10.0 → 0.11.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
@@ -242,6 +242,16 @@ if (await showConfirm({ title: "Delete?", body: warnBody, danger: true })) { ...
242
242
  ## Utilities
243
243
 
244
244
  ```typescript
245
+ // Video playback advisors (ported from extension-library — mos-5lzns.27).
246
+ // Pure functions: no DOM, no settings reads, never write playbackRate. The
247
+ // player's rate composer / resume-at-sentence / audio-graph tasks consume
248
+ // them; hosts can call them directly too.
249
+ import { buildSilenceMap, boostAt, estimateSaving } from "jatui" // silence map (smart speed)
250
+ import { speakerPace, suggestRates, rateAt, describeRates } from "jatui" // measured per-speaker wpm
251
+ import { snapResume, resumeLabel } from "jatui" // resume at a sentence, never a second
252
+ import { createQuietChain, amountFor } from "jatui" // loudness-compensated tone (WebAudio)
253
+ import { qrMatrix, qrCanvas } from "jatui" // deep-link QR, ISO 18004 v1–10, no deps
254
+
245
255
  // Date formatting
246
256
  import { formatRelativeTime, formatDate, formatShortDate, formatDateTime } from "jatui"
247
257
 
package/dist/index.d.ts CHANGED
@@ -13,6 +13,11 @@ export { default as TranscriptPlayer } from './components/TranscriptPlayer.svelt
13
13
  export type { TranscriptSegment, TranscriptBookmark, TranscriptChapter, TranscriptSpeakerTalkTime, } from './components/TranscriptPlayer.svelte';
14
14
  export { default as VideoPlayer } from './components/VideoPlayer.svelte';
15
15
  export type { VideoChapter, VideoSpeakerTurn, VideoTranscriptSegment, VideoBookmark, VideoWaveform, VideoSpeakerTalkTime, VideoLumaTrack, VideoMarker, BandState, BandAxis, } 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 +
@@ -23,11 +23,16 @@ props + callback surfaces.
23
23
  | `audio.ts` | **.31** | Loudness matching, quiet listening, limiter — audio-graph contract is in the file header. |
24
24
  | `night.ts` | **.33** | Night dimming, bias light, predictive dimming (luma track), strobe guard. |
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, };