pygeomatic 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (57) hide show
  1. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/PKG-INFO +11 -1
  2. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/README.md +10 -0
  3. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/pyproject.toml +1 -1
  4. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/registry.py +6 -0
  5. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/tex.py +149 -19
  6. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/.gitignore +0 -0
  7. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/LICENSE +0 -0
  8. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/__init__.py +0 -0
  9. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/article.py +0 -0
  10. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/coercions.json +0 -0
  11. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/coercions.py +0 -0
  12. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/emit.py +0 -0
  13. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/extensions.py +0 -0
  14. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/__init__.py +0 -0
  15. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/helpers.py +0 -0
  16. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/__init__.py +0 -0
  17. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/annotation_functions.py +0 -0
  18. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/array.py +0 -0
  19. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/autograd_functions.py +0 -0
  20. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/basic_figures.py +0 -0
  21. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/boolean_functions.py +0 -0
  22. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/complex_functions.py +0 -0
  23. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/curve_functions.py +0 -0
  24. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/intersections.py +0 -0
  25. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/ode_functions.py +0 -0
  26. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/planar_geometry.py +0 -0
  27. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/polygons.py +0 -0
  28. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/property_functions.py +0 -0
  29. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/rotation_functions.py +0 -0
  30. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/scalar_functions.py +0 -0
  31. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/special_functions.py +0 -0
  32. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/tangent_functions.py +0 -0
  33. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/tensor_functions.py +0 -0
  34. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/translation_functions.py +0 -0
  35. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/__init__.py +0 -0
  36. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/abs.py +0 -0
  37. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/add.py +0 -0
  38. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/create_overload.py +0 -0
  39. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/div.py +0 -0
  40. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/exp.py +0 -0
  41. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/log.py +0 -0
  42. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/mul.py +0 -0
  43. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/neg.py +0 -0
  44. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/pow.py +0 -0
  45. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/sqrt.py +0 -0
  46. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/sub.py +0 -0
  47. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/generate.py +0 -0
  48. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/inference.py +0 -0
  49. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/macros.json +0 -0
  50. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/macros.py +0 -0
  51. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/nodes.py +0 -0
  52. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/palette.py +0 -0
  53. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/parse.py +0 -0
  54. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/prompting.py +0 -0
  55. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/runner.py +0 -0
  56. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/store.py +0 -0
  57. {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/system_nodes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygeomatic
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Python mirror of the geomatic DSL function library; records calls for deterministic DSL emission
5
5
  Project-URL: Homepage, https://github.com/TinyVolt/pygeomatic
6
6
  Project-URL: Repository, https://github.com/TinyVolt/pygeomatic
@@ -227,6 +227,16 @@ Move it: {row 1}(r = gm.scalar(1)) · {row 2}(r = gm.scalar(2))
227
227
  behind a click. Palette names (`"pink"`, `"BLUE"`) resolve to hex; raw
228
228
  `"#f472b6"` / CSS names pass through. Full reference:
229
229
  [docs/tex-highlight-ergonomics.md](docs/tex-highlight-ergonomics.md).
230
+ - **Reveal** (fade a part in): same selector machine, painting **opacity**
231
+ instead of color, so changing a gate node — reassigning it (`b = gm.bool_(True)`,
232
+ `k = gm.scalar(1)`) or animating it (`gm.animate(k, 3)`) in a CommandLink —
233
+ makes a piece of the formula appear. Three targets — an over/underbrace + label
234
+ (`t.underbrace.reveal(b)`, body stays visible; `.label` / `.body` for just
235
+ that part), a derivation line-by-line (`d.rows().reveal(rows < k)` — `k` lines
236
+ shown), or a matrix's rows/columns (`M.reveal(M.cols() < k)`). A bare gate
237
+ node/bool is the all-or-nothing case (`gm.bool_(False)` makes a false gate); a
238
+ scalar gives an animatable sweep. Full reference:
239
+ [docs/tex-reveal.md](docs/tex-reveal.md).
230
240
 
231
241
  Two rules that will bite otherwise:
232
242
 
@@ -206,6 +206,16 @@ Move it: {row 1}(r = gm.scalar(1)) · {row 2}(r = gm.scalar(2))
206
206
  behind a click. Palette names (`"pink"`, `"BLUE"`) resolve to hex; raw
207
207
  `"#f472b6"` / CSS names pass through. Full reference:
208
208
  [docs/tex-highlight-ergonomics.md](docs/tex-highlight-ergonomics.md).
209
+ - **Reveal** (fade a part in): same selector machine, painting **opacity**
210
+ instead of color, so changing a gate node — reassigning it (`b = gm.bool_(True)`,
211
+ `k = gm.scalar(1)`) or animating it (`gm.animate(k, 3)`) in a CommandLink —
212
+ makes a piece of the formula appear. Three targets — an over/underbrace + label
213
+ (`t.underbrace.reveal(b)`, body stays visible; `.label` / `.body` for just
214
+ that part), a derivation line-by-line (`d.rows().reveal(rows < k)` — `k` lines
215
+ shown), or a matrix's rows/columns (`M.reveal(M.cols() < k)`). A bare gate
216
+ node/bool is the all-or-nothing case (`gm.bool_(False)` makes a false gate); a
217
+ scalar gives an animatable sweep. Full reference:
218
+ [docs/tex-reveal.md](docs/tex-reveal.md).
209
219
 
210
220
  Two rules that will bite otherwise:
211
221
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pygeomatic"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Python mirror of the geomatic DSL function library; records calls for deterministic DSL emission"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -217,6 +217,12 @@ def _resolve_arg(fdef: FunctionDef, p: P, arg: Any, store: Store) -> tuple[ArgTo
217
217
  if p.type == "Bool":
218
218
  node = _implicit_bool(arg, store)
219
219
  return node.ref, node
220
+ if p.type == "Any":
221
+ # A bare Python bool in an untyped slot is just its 0/1 value (bool
222
+ # is an int subclass), like passing 1/0 — e.g. `gm.bool_(True)`
223
+ # records `\bool 1`. Scalar/Text params stay strict (they reject a
224
+ # bool below), so this only relaxes genuinely untyped parameters.
225
+ return int(arg), int(arg)
220
226
  raise TypeError(
221
227
  f"\\{fdef.keyword}: parameter '{p.name}' expects {p.type}, got bool"
222
228
  )
@@ -2,10 +2,19 @@
2
2
 
3
3
  A `$$…$$` formula in a markdown article (given an id by a `%id:` line as its
4
4
  first line) is made addressable and reactive
5
- NOT by any DSL command but by declarative *bindings* recorded here: a schema
6
- slot can show a store node's live value (`energy.int.upper.bind(a)`), and matrix
7
- cells can be highlighted by selectors built from store nodes
8
- (`M.highlight(M.rows().eq(r), color="pink")`).
5
+ NOT by any DSL command but by declarative *bindings* recorded here. Three
6
+ effects, all driven by *changing a store node* (reassigning it `b = gm.bool_(True)`,
7
+ `k = gm.scalar(1)` or `gm.animate(k, 3)` in a CommandLink):
8
+
9
+ - **value** — a schema slot shows a store node's live value
10
+ (`energy.int.upper.bind(a)`);
11
+ - **highlight** — matrix cells painted a color by a selector over store nodes
12
+ (`M.highlight(M.rows().eq(r), color="pink")`);
13
+ - **reveal** — a part of the formula fades in when a node says so: an
14
+ over/underbrace (`t.underbrace.reveal(b)`), a derivation line-by-line
15
+ (`d.rows().reveal(rows < k)`), or a matrix's rows/columns
16
+ (`M.reveal(M.cols() < k)`). Same selector machine as highlight, painting
17
+ opacity instead of color.
9
18
 
10
19
  Three properties define the shape (see the texatlas design memo):
11
20
 
@@ -80,6 +89,13 @@ def _register_builtin_schema() -> None:
80
89
  register_tex_schema("prod", ("lower", "upper", "body"))
81
90
  register_tex_schema("frac", ("num", "denom"))
82
91
  register_tex_schema("sqrt", ("body",))
92
+ # Over/underbrace are one browser family (`horizBrace`, keyed off `isOver`);
93
+ # here they are two named families with the same slots. The BARE family
94
+ # address (`t.underbrace`) is the annotation — brace glyph + label; the body
95
+ # stays visible. `.body` / `.label` address just those parts. Used for the
96
+ # reveal effect (`t.underbrace.reveal(gate)`), not for value binding.
97
+ register_tex_schema("underbrace", ("body", "label"))
98
+ register_tex_schema("overbrace", ("body", "label"))
83
99
 
84
100
 
85
101
  _register_builtin_schema()
@@ -143,6 +159,40 @@ def _highlight_entry(selector_json: dict, color: str, matrix: int) -> dict:
143
159
  return entry
144
160
 
145
161
 
162
+ def _align_index(align: int) -> int:
163
+ """Validate an author-supplied align occurrence index (a non-negative int).
164
+
165
+ This is which equation-layout array (`aligned`/`align`/`split`/`alignat`/
166
+ `gather`/`CD`) in the formula a reveal fades line-by-line, counted in source
167
+ order — the arrays that `matrix`/highlight deliberately SKIP. Python does not
168
+ parse the LaTeX; the author supplies the integer (default 0)."""
169
+ if isinstance(align, bool) or not isinstance(align, int) or align < 0:
170
+ raise TexError(f"align index must be a non-negative int, got {align!r}")
171
+ return align
172
+
173
+
174
+ def _reveal_mode(mode: str) -> str:
175
+ """Validate a reveal paint mode. `"fade"` (default) paints opacity and keeps
176
+ the layout; `"collapse"` also removes the slot's space (avoid it for matrix
177
+ cells — it breaks the grid/brackets)."""
178
+ if mode not in ("fade", "collapse"):
179
+ raise TexError(f"reveal mode must be 'fade' or 'collapse', got {mode!r}")
180
+ return mode
181
+
182
+
183
+ def _reveal_entry(target: dict, selector: "Selector", mode: str) -> dict:
184
+ """A `RevealBinding` wire entry: exactly one target descriptor
185
+ (`slot` | `align` | `matrix`) plus a selector giving each cell an opacity
186
+ weight in [0, 1]. `mode` is omitted when `"fade"` (the default) so the wire
187
+ JSON stays minimal. A bare gate node/bool is accepted as the selector (the
188
+ `{node}` leaf — the degenerate all-or-nothing case)."""
189
+ entry: dict = dict(target)
190
+ entry["selector"] = _as_selector(selector)
191
+ if _reveal_mode(mode) != "fade":
192
+ entry["mode"] = mode
193
+ return entry
194
+
195
+
146
196
  def _resolve_color(color: str) -> str:
147
197
  """Resolve a palette name to its hex before it crosses the wire; pass any
148
198
  other CSS color string (`#f472b6`, `pink`, `rgb(...)`) through unchanged."""
@@ -279,6 +329,33 @@ rows: AxisExpr = _NamedAxis("row")
279
329
  cols: AxisExpr = _NamedAxis("col")
280
330
 
281
331
 
332
+ class _TexAxis(_NamedAxis):
333
+ """A row/col axis that remembers its formula, returned by `t.rows()` /
334
+ `t.cols()`. It behaves exactly like the free `rows`/`cols` axes for building
335
+ selectors (`t.rows().eq(r)`), and additionally carries `.reveal(...)` to fade
336
+ in an equation-layout / derivation block line-by-line (the `align` target).
337
+ The axis identity does not enter the align target — the whole aligned block
338
+ is the target and the selector (e.g. `rows < k`) picks which lines show."""
339
+
340
+ def __init__(self, tex_id: str, axis: str) -> None:
341
+ super().__init__(axis)
342
+ self._tex_id = tex_id
343
+
344
+ def reveal(
345
+ self, selector: "Selector", *, align: int = 0, mode: str = "fade"
346
+ ) -> None:
347
+ """Progressively reveal the lines of an equation-layout / derivation
348
+ block: `d.rows().reveal(rows < k)` shows `k` lines (`k = 0` shows none).
349
+
350
+ `align` is the 0-based occurrence index of which equation-layout array to
351
+ reveal (source order, counting only `aligned`/`align`/`split`/`alignat`/
352
+ `gather`/`CD` — the arrays highlight/`matrix` skip); default 0. `mode` is
353
+ `"fade"` (opacity, keeps layout) or `"collapse"` (also removes space)."""
354
+ _bindings(self._tex_id)["reveals"].append(
355
+ _reveal_entry({"align": _align_index(align)}, selector, mode)
356
+ )
357
+
358
+
282
359
  class Selector:
283
360
  """A highlight selector: `(cell, env) -> weight`. Combine with `and_`/`or_`
284
361
  (min/max) or fade the whole selection with `scale`."""
@@ -302,10 +379,18 @@ class Selector:
302
379
  return self._json_
303
380
 
304
381
 
305
- def _as_selector(value: "Selector") -> dict:
306
- if not isinstance(value, Selector):
307
- raise TexError(f"expected a selector, got {type(value).__name__}")
308
- return value._json_
382
+ def _as_selector(value: Union["Selector", GNode, str]) -> dict:
383
+ """The selector JSON for a value that is either a built `Selector` or a bare
384
+ gate node/id. A bare node lowers to the `{node}` `SelectorExpr` leaf
385
+ (`weight = clamp(0, v, 1)`), so a bool gate is expressible without a dummy
386
+ comparison — e.g. `t.underbrace.reveal(b)` or `(rows == 2) & b`."""
387
+ if isinstance(value, Selector):
388
+ return value._json_
389
+ if isinstance(value, (GNode, str)):
390
+ return {"node": _node_id(value, what="selector gate")}
391
+ raise TexError(
392
+ f"expected a selector or a gate node, got {type(value).__name__}"
393
+ )
309
394
 
310
395
 
311
396
  class _Region(Selector):
@@ -376,6 +461,18 @@ class _Slot:
376
461
  entry["fmt"] = fmt
377
462
  _bindings(self._tex_id)["values"].append(entry)
378
463
 
464
+ def reveal(self, selector: "Selector", *, mode: str = "fade") -> None:
465
+ """Fade this slot in when the `selector` says so (opacity from its weight
466
+ in [0, 1]). A bare gate node/bool is the all-or-nothing case
467
+ (`t.underbrace.label.reveal(b)`); pass a real selector for a progressive
468
+ sweep. `mode` is `"fade"` (default, keeps layout) or `"collapse"` (also
469
+ removes the slot's space). For a bare over/underbrace address
470
+ (`t.underbrace.reveal(b)`) the browser reveals the brace glyph + label
471
+ while the body stays visible; `.label` / `.body` address just that part."""
472
+ _bindings(self._tex_id)["reveals"].append(
473
+ _reveal_entry({"slot": self._address}, selector, mode)
474
+ )
475
+
379
476
 
380
477
  class _Family:
381
478
  """A LaTeX command family occurrence (`tex.int`, `tex.ints[1]`). Bind the
@@ -393,6 +490,11 @@ class _Family:
393
490
  def bind(self, node: Union[GNode, str], *, show: str = "value", fmt: Optional[str] = None) -> None:
394
491
  _Slot(self._tex_id, self._base).bind(node, show=show, fmt=fmt)
395
492
 
493
+ def reveal(self, selector: "Selector", *, mode: str = "fade") -> None:
494
+ """Reveal this whole family occurrence (the bare address, e.g. a brace's
495
+ glyph + label — see `_Slot.reveal`)."""
496
+ _Slot(self._tex_id, self._base).reveal(selector, mode=mode)
497
+
396
498
  def __getattr__(self, name: str) -> _Slot:
397
499
  # Only slot names reach here (real methods/attrs resolve first). Let
398
500
  # Python's own dunder probing (copy, pickling, ...) miss cleanly.
@@ -448,13 +550,15 @@ class Tex:
448
550
 
449
551
  # -- matrix highlights --------------------------------------------------
450
552
 
451
- def rows(self) -> AxisExpr:
452
- """The cell's row index, as an axis expression."""
453
- return _NamedAxis("row")
553
+ def rows(self) -> _TexAxis:
554
+ """The cell's row index, as an axis expression. Also carries `.reveal()`
555
+ to fade in a derivation / equation-layout block line-by-line
556
+ (`d.rows().reveal(rows < k)`)."""
557
+ return _TexAxis(self.id, "row")
454
558
 
455
- def cols(self) -> AxisExpr:
559
+ def cols(self) -> _TexAxis:
456
560
  """The cell's column index, as an axis expression."""
457
- return _NamedAxis("col")
561
+ return _TexAxis(self.id, "col")
458
562
 
459
563
  def highlight(
460
564
  self, selector: Selector, *, color: str = "COLOR-YELLOW", matrix: int = 0
@@ -484,6 +588,28 @@ class Tex:
484
588
  _highlight_entry(_as_selector(selector), color, matrix)
485
589
  )
486
590
 
591
+ def reveal(
592
+ self, selector: Selector, *, matrix: int = 0, mode: str = "fade"
593
+ ) -> None:
594
+ """Progressively reveal the rows/columns of a matrix — the same selector
595
+ machine as `highlight`, painting opacity instead of color:
596
+ `M.reveal(M.cols() < k)` shows `k` columns (`k = 0` shows none). A bare
597
+ gate node/bool reveals the whole matrix all-or-nothing.
598
+
599
+ `matrix` is the 0-based occurrence index of which matrix to reveal (same
600
+ source-order counting rule as `highlight`'s `matrix=`; unlike highlight
601
+ it is always written to the wire — it is the target discriminator). Only
602
+ `mode="fade"` is supported: `collapse` would break the grid and brackets,
603
+ so opacity keeps the shape stable while cells fade in."""
604
+ if _reveal_mode(mode) == "collapse":
605
+ raise TexError(
606
+ "matrix reveal supports only mode='fade' — collapse would remove "
607
+ "cell space and break the grid/brackets; opacity keeps the shape"
608
+ )
609
+ _bindings(self.id)["reveals"].append(
610
+ _reveal_entry({"matrix": _matrix_index(matrix)}, selector, mode)
611
+ )
612
+
487
613
  def __getitem__(self, key) -> _Region:
488
614
  """numpy-style cell region: `M[3:, 4:]`, `M[:, c]`, `M[r, ...]`.
489
615
 
@@ -571,16 +697,18 @@ def tex(tex_id: str) -> Tex:
571
697
 
572
698
 
573
699
  def _bindings(tex_id: str) -> dict:
574
- return current_store().tex_bindings.setdefault(tex_id, {"values": [], "highlights": []})
700
+ return current_store().tex_bindings.setdefault(
701
+ tex_id, {"values": [], "highlights": [], "reveals": []}
702
+ )
575
703
 
576
704
 
577
705
  def harvest_tex_bindings(store=None) -> dict:
578
706
  """The session's recorded texatlas bindings as the wire manifest
579
- `{ texId: { "values": [...], "highlights": [...] } }` — the exact JSON the
580
- TypeScript runtime consumes (empty arrays and formulas with no bindings are
581
- dropped). This is how the publish compiler snapshots bindings into an
582
- article without the author calling any emit; it is the tex analogue of
583
- `gm.emit()` for the command tape."""
707
+ `{ texId: { "values": [...], "highlights": [...], "reveals": [...] } }` — the
708
+ exact JSON the TypeScript runtime consumes (empty arrays and formulas with no
709
+ bindings are dropped). This is how the publish compiler snapshots bindings
710
+ into an article without the author calling any emit; it is the tex analogue
711
+ of `gm.emit()` for the command tape."""
584
712
  store = store or current_store()
585
713
  manifest: dict = {}
586
714
  for tex_id, b in store.tex_bindings.items():
@@ -589,6 +717,8 @@ def harvest_tex_bindings(store=None) -> dict:
589
717
  entry["values"] = b["values"]
590
718
  if b["highlights"]:
591
719
  entry["highlights"] = b["highlights"]
720
+ if b.get("reveals"):
721
+ entry["reveals"] = b["reveals"]
592
722
  if entry:
593
723
  manifest[tex_id] = entry
594
724
  return manifest
File without changes
File without changes