taleem-player 1.0.10 → 1.0.11

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/README.md CHANGED
@@ -1,25 +1,29 @@
1
+
1
2
  # Taleem Player
2
3
 
3
4
  **Taleem Player** converts **Taleem JSON slide data** into **web-based presentations**.
4
5
 
5
6
  It renders the **same Taleem JSON** in multiple display modes using a single, stable rendering engine.
6
7
 
7
- > **Stable · deck-v1 frozen**
8
- > The slide language, schema, and registry are complete.
8
+ > **Stable · deck-v1 frozen**
9
+ > The slide language, schema, and registry are complete.
9
10
  > Internal improvements may continue without breaking public contracts.
10
11
 
11
12
  ---
12
- Demo & Documentation
13
- 👉 https://bilza2023.github.io/taleem/
13
+
14
+ ## Demo & Documentation
15
+
16
+ 👉 [https://bilza2023.github.io/taleem/](https://bilza2023.github.io/taleem/)
14
17
 
15
18
  The live demo shows:
16
19
 
17
- - Browser Mode (index-based rendering)
18
- - Player Mode (time-based rendering)
19
- - Real production Taleem JSON
20
- - Shared CSS across all modes
21
- - No screenshots. No mock data.
22
- - What you see is the real engine running in the browser.
20
+ * Browser Mode (index-based rendering)
21
+ * Player Mode (time-based rendering)
22
+ * Real production Taleem JSON
23
+ * Shared CSS across all modes
24
+ * No screenshots. No mock data.
25
+ * What you see is the real engine running in the browser.
26
+
23
27
  ---
24
28
 
25
29
  ## What this project is
@@ -27,8 +31,9 @@ The live demo shows:
27
31
  Taleem Player is a **presentation engine**, not a slide editor.
28
32
 
29
33
  It takes **validated Taleem decks** and turns them into:
30
- - index-based presentations
31
- - time-based presentations
34
+
35
+ * index-based presentations
36
+ * time-based presentations
32
37
 
33
38
  The system is **declarative**, **predictable**, and **CSS-driven**.
34
39
 
@@ -38,7 +43,7 @@ The system is **declarative**, **predictable**, and **CSS-driven**.
38
43
 
39
44
  ```bash
40
45
  npm install taleem-player
41
- ````
46
+ ```
42
47
 
43
48
  ---
44
49
 
@@ -137,6 +142,87 @@ They never change slide meaning.
137
142
 
138
143
  ---
139
144
 
145
+ ## Validation & Authoring Utilities (offline)
146
+
147
+ Taleem Player also exposes **optional authoring utilities** for **offline use**
148
+ during deck creation, build steps, or publishing pipelines.
149
+
150
+ These utilities are **not runtime concerns** and are intentionally kept
151
+ separate from the player rendering path.
152
+
153
+ ```js
154
+ import {
155
+ validateDeckV1,
156
+ validatePlayerDeckV1,
157
+ normalizeDeckForPlayerV1,
158
+ } from "taleem-player/validation";
159
+ ```
160
+
161
+ ---
162
+
163
+ ### `validateDeckV1(deck)`
164
+
165
+ Schema-level validation.
166
+
167
+ * Ensures the deck conforms to **deck-v1**
168
+ * Validates slide types and data shapes
169
+ * Does **not** validate timing semantics
170
+
171
+ Returns `{ ok, value }` or `{ ok: false, errors }`.
172
+
173
+ ---
174
+
175
+ ### `validatePlayerDeckV1(deck)`
176
+
177
+ Player-level semantic validation.
178
+
179
+ * Assumes schema validation has already passed
180
+ * Enforces:
181
+
182
+ * slide start / end correctness
183
+ * strict slide sequencing
184
+ * `showAt` within slide bounds
185
+ * EQ slide timing rules
186
+ * Does **not** mutate or fix data
187
+
188
+ This validator answers:
189
+
190
+ > “Can time safely move forward without ambiguity?”
191
+
192
+ ---
193
+
194
+ ### `normalizeDeckForPlayerV1(deck, options?)`
195
+
196
+ Offline normalization utility.
197
+
198
+ * Patches missing or invalid timing
199
+ * Assigns deterministic mock timings
200
+ * Ensures the deck is **safe for player playback**
201
+ * Intended for build steps, CLIs, or publishing workflows
202
+
203
+ This is **not validation** — it is a controlled fixer.
204
+
205
+ ---
206
+
207
+ ### Usage model
208
+
209
+ **Static editors / CDN usage**
210
+
211
+ * no validation
212
+ * no fixing
213
+ * UX hints only
214
+
215
+ **Authoring / build / publish step**
216
+
217
+ ```
218
+ normalize → validate → publish
219
+ ```
220
+
221
+ These utilities are **explicit opt-in** and are never executed
222
+ by the player runtime.
223
+
224
+ ---
225
+
140
226
  ## CSS
141
227
 
142
228
  ```js
@@ -186,7 +272,8 @@ The core registry supports **additive registration only**.
186
272
 
187
273
  The `eq` slide type is implemented and tested.
188
274
 
189
- It represents **structured symbolic content** and intentionally marks the **upper boundary** of the system.
275
+ It represents **structured symbolic content** and intentionally marks the
276
+ **upper boundary** of the system.
190
277
 
191
278
  Anything more complex than this belongs in a **separate product**, not in the core player.
192
279
 
@@ -405,9 +405,9 @@
405
405
  /* ---------- Side panel items ---------- */
406
406
 
407
407
  .slide.eq.imageRightBulletsLeft .eq-explain-item {
408
- font-size: 1.4rem;
409
- line-height: 1.45;
410
- text-align: center;
408
+ font-size: 1.6rem;
409
+ line-height: 1.75;
410
+ text-align:justify;
411
411
 
412
412
  word-wrap: break-word;
413
413
  overflow-wrap: break-word;
@@ -415,6 +415,8 @@
415
415
  background: rgba(255, 255, 255, 0.06);
416
416
  padding: 14px 18px;
417
417
  border-radius: 12px;
418
+
419
+
418
420
  }
419
421
 
420
422
  /* ---------- Side panel image ---------- */
@@ -13960,7 +13960,179 @@ function validateDeckV1(deck) {
13960
13960
  const r = zodDeckV1.safeParse(deck);
13961
13961
  return r.success ? { ok: true, value: r.data } : { ok: false, errors: r.error.issues };
13962
13962
  }
13963
+
13964
+ // src/validation/validatePlayerDeckV1.js
13965
+ function validatePlayerDeckV1(deck) {
13966
+ const errors = [];
13967
+ if (!deck || !Array.isArray(deck.deck)) {
13968
+ return fail("DECK_INVALID", "deck.deck must be an array");
13969
+ }
13970
+ const slides = deck.deck;
13971
+ validateSlideTiming(slides, errors);
13972
+ validateSlideSequence(slides, errors);
13973
+ validateShowAtRanges(slides, errors);
13974
+ validateEqSlides(slides, errors);
13975
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
13976
+ }
13977
+ function validateSlideTiming(slides, errors) {
13978
+ slides.forEach((slide, i) => {
13979
+ if (typeof slide.start !== "number" || typeof slide.end !== "number") {
13980
+ errors.push(err(
13981
+ "SLIDE_TIME_MISSING",
13982
+ `Slide ${i} must have numeric start/end`
13983
+ ));
13984
+ return;
13985
+ }
13986
+ if (slide.start >= slide.end) {
13987
+ errors.push(err(
13988
+ "SLIDE_TIME_ORDER",
13989
+ `Slide ${i} start (${slide.start}) must be < end (${slide.end})`
13990
+ ));
13991
+ }
13992
+ if (i === 0 && slide.start !== 0) {
13993
+ errors.push(err(
13994
+ "DECK_START_NOT_ZERO",
13995
+ `First slide must start at 0 (got ${slide.start})`
13996
+ ));
13997
+ }
13998
+ });
13999
+ }
14000
+ function validateSlideSequence(slides, errors) {
14001
+ for (let i = 0; i < slides.length - 1; i++) {
14002
+ const a = slides[i];
14003
+ const b = slides[i + 1];
14004
+ if (a.end !== b.start) {
14005
+ errors.push(err(
14006
+ "SLIDE_SEQUENCE_BREAK",
14007
+ `Slide ${i} ends at ${a.end} but next starts at ${b.start}`
14008
+ ));
14009
+ }
14010
+ if (b.start < a.start) {
14011
+ errors.push(err(
14012
+ "SLIDE_TIME_REVERSE",
14013
+ `Slide ${i + 1} starts before previous slide`
14014
+ ));
14015
+ }
14016
+ }
14017
+ }
14018
+ function validateShowAtRanges(slides, errors) {
14019
+ slides.forEach((slide, si) => {
14020
+ let lastShowAt = null;
14021
+ if (!Array.isArray(slide.data)) return;
14022
+ slide.data.forEach((item, di) => {
14023
+ if (typeof item.showAt !== "number") return;
14024
+ if (item.showAt < slide.start || item.showAt >= slide.end) {
14025
+ errors.push(err(
14026
+ "SHOWAT_OUT_OF_RANGE",
14027
+ `Slide ${si} item ${di} showAt ${item.showAt} outside [${slide.start}, ${slide.end})`
14028
+ ));
14029
+ }
14030
+ if (lastShowAt !== null && item.showAt < lastShowAt) {
14031
+ errors.push(err(
14032
+ "SHOWAT_ORDER",
14033
+ `Slide ${si} item ${di} showAt decreases (${item.showAt} < ${lastShowAt})`
14034
+ ));
14035
+ }
14036
+ lastShowAt = item.showAt;
14037
+ });
14038
+ });
14039
+ }
14040
+ function validateEqSlides(slides, errors) {
14041
+ slides.forEach((slide, si) => {
14042
+ if (slide.type !== "eq") return;
14043
+ let lastLineShowAt = null;
14044
+ slide.data.forEach((line, li) => {
14045
+ if (typeof line.showAt === "number") {
14046
+ if (line.showAt < slide.start || line.showAt >= slide.end) {
14047
+ errors.push(err(
14048
+ "EQ_LINE_SHOWAT_RANGE",
14049
+ `EQ slide ${si} line ${li} showAt ${line.showAt} outside slide range`
14050
+ ));
14051
+ }
14052
+ if (lastLineShowAt !== null && line.showAt < lastLineShowAt) {
14053
+ errors.push(err(
14054
+ "EQ_LINE_ORDER",
14055
+ `EQ slide ${si} line ${li} showAt decreases`
14056
+ ));
14057
+ }
14058
+ lastLineShowAt = line.showAt;
14059
+ }
14060
+ if (Array.isArray(line.spItems)) {
14061
+ line.spItems.forEach((sp, spi) => {
14062
+ if ("showAt" in sp) {
14063
+ errors.push(err(
14064
+ "EQ_SPITEM_SHOWAT_FORBIDDEN",
14065
+ `EQ slide ${si} line ${li} spItem ${spi} must not define showAt`
14066
+ ));
14067
+ }
14068
+ });
14069
+ }
14070
+ });
14071
+ });
14072
+ }
14073
+ function err(code, message) {
14074
+ return { code, message };
14075
+ }
14076
+ function fail(code, message) {
14077
+ return { ok: false, errors: [{ code, message }] };
14078
+ }
14079
+
14080
+ // src/validation/normalizeDeckForPlayerV1.js
14081
+ function normalizeDeckForPlayerV1(deck, opts = {}) {
14082
+ const {
14083
+ slideDuration = 5,
14084
+ // default seconds per slide
14085
+ itemStep = 1
14086
+ // default gap between showAt items
14087
+ } = opts;
14088
+ let time3 = 0;
14089
+ const slides = deck.deck.map((slide) => {
14090
+ const start = isNumber(slide.start) ? slide.start : time3;
14091
+ const end = isNumber(slide.end) && slide.end > start ? slide.end : start + slideDuration;
14092
+ const fixedSlide = {
14093
+ ...slide,
14094
+ start,
14095
+ end,
14096
+ data: fixShowAt(slide.data, start, end, itemStep)
14097
+ };
14098
+ time3 = end;
14099
+ return fixedSlide;
14100
+ });
14101
+ return {
14102
+ ...deck,
14103
+ deck: slides
14104
+ };
14105
+ }
14106
+ function fixShowAt(data, start, end, step) {
14107
+ if (!Array.isArray(data)) return data;
14108
+ let t = start;
14109
+ return data.map((item) => {
14110
+ const showAt2 = isNumber(item.showAt) ? clamp(item.showAt, start, end - 1e-4) : t;
14111
+ t = showAt2 + step;
14112
+ if (item.name === "line" && Array.isArray(item.spItems)) {
14113
+ return {
14114
+ ...item,
14115
+ showAt: showAt2,
14116
+ spItems: item.spItems.map((sp) => stripShowAt(sp))
14117
+ };
14118
+ }
14119
+ return { ...item, showAt: showAt2 };
14120
+ });
14121
+ }
14122
+ function stripShowAt(obj) {
14123
+ if (!obj || typeof obj !== "object") return obj;
14124
+ const { showAt: showAt2, ...rest } = obj;
14125
+ return rest;
14126
+ }
14127
+ function clamp(v, min, max) {
14128
+ return Math.min(Math.max(v, min), max);
14129
+ }
14130
+ function isNumber(v) {
14131
+ return typeof v === "number" && !Number.isNaN(v);
14132
+ }
13963
14133
  export {
14134
+ normalizeDeckForPlayerV1,
13964
14135
  validateDeckV1,
14136
+ validatePlayerDeckV1,
13965
14137
  zodDeckV1
13966
14138
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taleem-player",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "type": "module",
5
5
  "main": "./dist/taleem-player.umd.js",
6
6
  "module": "./dist/taleem-player.esm.js",