ggblab 0.9.3__py3-none-any.whl

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 (26) hide show
  1. ggblab/__init__.py +44 -0
  2. ggblab/_version.py +4 -0
  3. ggblab/comm.py +243 -0
  4. ggblab/construction.py +179 -0
  5. ggblab/errors.py +142 -0
  6. ggblab/ggbapplet.py +293 -0
  7. ggblab/parser.py +486 -0
  8. ggblab/persistent_counter.py +175 -0
  9. ggblab/schema.py +114 -0
  10. ggblab/utils.py +109 -0
  11. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/build_log.json +730 -0
  12. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/install.json +5 -0
  13. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/package.json +210 -0
  14. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/schemas/ggblab/package.json.orig +205 -0
  15. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/schemas/ggblab/plugin.json +8 -0
  16. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/lib_index_js.bbfa36bc62ee08eb62b2.js +465 -0
  17. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/lib_index_js.bbfa36bc62ee08eb62b2.js.map +1 -0
  18. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/remoteEntry.2d29364aef8b527d773e.js +568 -0
  19. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/remoteEntry.2d29364aef8b527d773e.js.map +1 -0
  20. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/style.js +4 -0
  21. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/style_index_js.aab9f5416f41ce79cac3.js +492 -0
  22. ggblab-0.9.3.data/data/share/jupyter/labextensions/ggblab/static/style_index_js.aab9f5416f41ce79cac3.js.map +1 -0
  23. ggblab-0.9.3.dist-info/METADATA +768 -0
  24. ggblab-0.9.3.dist-info/RECORD +26 -0
  25. ggblab-0.9.3.dist-info/WHEEL +4 -0
  26. ggblab-0.9.3.dist-info/licenses/LICENSE +29 -0
@@ -0,0 +1,768 @@
1
+ Metadata-Version: 2.4
2
+ Name: ggblab
3
+ Version: 0.9.3
4
+ Summary: A JupyterLab extension that embeds GeoGebra for dual-coded geometry and Python programming.
5
+ Project-URL: Homepage, https://github.com/moyhig-ecs/ggblab#readme
6
+ Project-URL: Bug Tracker, https://github.com/moyhig-ecs/ggblab/issues
7
+ Project-URL: Repository, https://github.com/moyhig-ecs/ggblab
8
+ Author-email: Manabu Higashida <manabu@higashida.net>
9
+ License: BSD 3-Clause License
10
+
11
+ Copyright (c) 2025, ggblab
12
+ All rights reserved.
13
+
14
+ Redistribution and use in source and binary forms, with or without
15
+ modification, are permitted provided that the following conditions are met:
16
+
17
+ 1. Redistributions of source code must retain the above copyright notice, this
18
+ list of conditions and the following disclaimer.
19
+
20
+ 2. Redistributions in binary form must reproduce the above copyright notice,
21
+ this list of conditions and the following disclaimer in the documentation
22
+ and/or other materials provided with the distribution.
23
+
24
+ 3. Neither the name of the copyright holder nor the names of its
25
+ contributors may be used to endorse or promote products derived from
26
+ this software without specific prior written permission.
27
+
28
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
32
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
36
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+ License-File: LICENSE
39
+ Keywords: jupyter,jupyterlab,jupyterlab-extension
40
+ Classifier: Framework :: Jupyter
41
+ Classifier: Framework :: Jupyter :: JupyterLab
42
+ Classifier: Framework :: Jupyter :: JupyterLab :: 4
43
+ Classifier: Framework :: Jupyter :: JupyterLab :: Extensions
44
+ Classifier: Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt
45
+ Classifier: License :: OSI Approved :: BSD License
46
+ Classifier: Programming Language :: Python
47
+ Classifier: Programming Language :: Python :: 3
48
+ Classifier: Programming Language :: Python :: 3.10
49
+ Classifier: Programming Language :: Python :: 3.11
50
+ Classifier: Programming Language :: Python :: 3.12
51
+ Classifier: Programming Language :: Python :: 3.13
52
+ Classifier: Programming Language :: Python :: 3.14
53
+ Requires-Python: >=3.10
54
+ Requires-Dist: aiofiles
55
+ Requires-Dist: copier
56
+ Requires-Dist: ipykernel
57
+ Requires-Dist: ipylab
58
+ Requires-Dist: networkx
59
+ Requires-Dist: polars
60
+ Requires-Dist: websockets
61
+ Requires-Dist: xmlschema
62
+ Requires-Dist: xmltodict
63
+ Description-Content-Type: text/markdown
64
+
65
+ # ggblab — A JupyterLab extension that embeds GeoGebra for dual-coded geometry and Python programming
66
+
67
+ [![PyPI](https://img.shields.io/pypi/v/ggblab.svg)](https://pypi.org/project/ggblab/)
68
+ [![Python](https://img.shields.io/pypi/pyversions/ggblab.svg)](https://pypi.org/project/ggblab/)
69
+ [![Tests](https://github.com/moyhig-ecs/ggblab/actions/workflows/tests.yml/badge.svg)](https://github.com/moyhig-ecs/ggblab/actions/workflows/tests.yml)
70
+ [![Coverage](https://codecov.io/gh/moyhig-ecs/ggblab/branch/main/graph/badge.svg)](https://codecov.io/gh/moyhig-ecs/ggblab)
71
+ [![License](https://img.shields.io/pypi/l/ggblab.svg)](LICENSE)
72
+ [![Documentation Status](https://readthedocs.org/projects/ggblab/badge/?version=latest)](https://ggblab.readthedocs.io/en/latest/?badge=latest)
73
+ [![JupyterHub](https://img.shields.io/badge/JupyterHub-Supported-brightgreen)](#cloud-deployment)
74
+
75
+ A JupyterLab extension that embeds a GeoGebra applet in your notebook and lets you drive it from Python—enabling **dual coding** where geometric visualization and computational thinking reinforce each other. Learn programming through geometric construction; reason about geometry through Python code. Grounded in cognitive science (Dual Coding Theory, Transfer of Learning), ggblab bridges visual and symbolic domains so knowledge transfers across disciplines.
76
+
77
+ The panel can be launched from the Command Palette or Launcher for default settings, but to enable kernel↔widget communication reliably ggblab launches the widget programmatically from a notebook (via ipylab) so communication settings are passed before initialization. You then call GeoGebra commands/functions asynchronously from Python via IPython Comm plus an optional Unix-socket/TCP WebSocket bridge.
78
+
79
+ ### Features
80
+
81
+ - **Dual Coding System**: Geometric visualization + Python code in a unified workspace—students learn through both visual and symbolic representations
82
+ - Programmatic launch via `GeoGebra().init()` (recommended), which uses ipylab to pass communication settings before widget initialization (Command ID: `ggblab:create`, label: "React Widget"). Command Palette/Launcher work only with fixed arguments and are suitable for default settings.
83
+ - Call GeoGebra commands (`command`) and API functions (`function`) from Python through the `GeoGebra` helper
84
+ - **Domain Bridge**: Construction dependencies in GeoGebra map isomorphically to variable scoping in Python—teach computational thinking through geometric structure
85
+ - **Transfer of Learning**: Knowledge learned in geometric context transfers to computational thinking and vice versa. Dual representations strengthen understanding across both domains.
86
+ - Combined IPython Comm + Unix domain socket (POSIX) / TCP WebSocket channel for fast data exchange
87
+ - Frontend watches add/remove/rename/clear events and dialog messages and forwards them to the kernel
88
+ - Settings schema is wired up (no user options yet) for future configuration
89
+ - Single Comm target name `ggblab-comm`; multiplexing via multiple targets is avoided because IPython Comm cannot receive during cell execution, so reliability comes from the out-of-band socket channel instead.
90
+
91
+ ### Requirements
92
+
93
+ - JupyterLab >= 4.0
94
+ - Python >= 3.10
95
+ - Browser access to https://cdn.geogebra.org/apps/deployggb.js
96
+ - For development: Node.js and `jlpm`
97
+
98
+ ### Installation
99
+
100
+ ```bash
101
+ pip install ggblab
102
+ jupyter labextension list | grep ggblab
103
+ ```
104
+
105
+ #### Cloud/JupyterHub Support
106
+
107
+ This JupyterLab extension supports both local installs and managed cloud deployments:
108
+
109
+ - Managed JupyterHub on Kubernetes: install via `pip install ggblab` only — no manual labextension build steps required.
110
+ - All communication concerns are addressed and validated in cloud environments (IPython Comm + out-of-band sockets).
111
+ - End users on JupyterHub can use the extension immediately after `pip install`; developers can follow the Development Workflow for local builds.
112
+
113
+ For deployment guidance and troubleshooting, see the [Cloud Deployment](#cloud-deployment) section.
114
+
115
+ Uninstall:
116
+
117
+ ```bash
118
+ pip uninstall ggblab
119
+ ```
120
+
121
+ ### Quick Start (UI)
122
+
123
+ 1. Open JupyterLab
124
+ 2. Run "React Widget" from the Command Palette (category "Tutorial") or click the Launcher tile under "example"
125
+ 3. A GeoGebra panel opens in the main area; layout restoration and launcher integration are enabled
126
+
127
+ Design note: ggblab is optimized for a side-by-side workflow—GeoGebra stays pinned in the main area while the notebook scrolls independently. Embedding the applet inside a notebook cell is possible but not recommended because it scrolls out of view during code edits.
128
+
129
+ ### Quick Start (Notebook/Python)
130
+
131
+ ```python
132
+ from ggblab.ggbapplet import GeoGebra
133
+
134
+ ggb = GeoGebra()
135
+ await ggb.init() # init IPython Comm/socket and open the GeoGebra panel
136
+
137
+ await ggb.command("A=(0,0)") # create a point
138
+ value = await ggb.function("getValue", ["A"]) # call GeoGebra API
139
+ print(value)
140
+ ```
141
+
142
+ `init()` fetches the current kernel ID, starts the IPython Comm/WebSocket server, and triggers the frontend command `ggblab:create` to open the panel. `command` sends GeoGebra commands; `function` calls GeoGebra API names (single name or list) and returns the result asynchronously.
143
+
144
+ ### Error Handling — Comprehensive Exception Hierarchy
145
+
146
+ ggblab implements a **sophisticated, multi-layer error handling system** that stands out as a remarkable achievement in transcending GeoGebra's limitations:
147
+
148
+ **The Challenge**: GeoGebra provides no formal Error API or machine-readable error schema. Error information comes purely as asynchronous event messages from the applet.
149
+
150
+ **The Breakthrough**: Rather than depending on missing GeoGebra APIs, ggblab implements a **schema-free, observation-driven error capture system** that:
151
+ 1. **Validates pre-flight** (syntax, semantics) before GeoGebra ever sees the command
152
+ 2. **Captures runtime errors asynchronously** via the dual-channel architecture (out-of-band socket)
153
+ 3. **Consolidates multiple error events** automatically
154
+ 4. **Distinguishes error sources** (pre-flight vs. runtime) through a rich exception hierarchy
155
+
156
+ **Result**: ggblab's error handling is **more robust and flexible than GeoGebra's native capabilities**.
157
+
158
+ ```python
159
+ from ggblab.errors import GeoGebraError, GeoGebraAppletError
160
+
161
+ # Pre-flight validation errors (caught before execution)
162
+ try:
163
+ ggb.check_syntax = True
164
+ ggb.check_semantics = True
165
+ await ggb.command("Circle(A, B)")
166
+ except GeoGebraSyntaxError as e:
167
+ print(f"Syntax error: {e.command}")
168
+ except GeoGebraSemanticsError as e:
169
+ print(f"Missing objects: {e.missing_objects}")
170
+
171
+ # Runtime errors from GeoGebra applet (caught after execution)
172
+ try:
173
+ await ggb.command("Unbalanced(")
174
+ except GeoGebraAppletError as e:
175
+ print(f"Applet error: {e.error_message}")
176
+
177
+ # Catch all GeoGebra errors
178
+ try:
179
+ await ggb.command("...")
180
+ except GeoGebraError:
181
+ # Handles all GeoGebra-related exceptions
182
+ pass
183
+ ```
184
+
185
+ **Error Types:**
186
+ - **GeoGebraSyntaxError**: Command cannot be tokenized (pre-flight)
187
+ - **GeoGebraSemanticsError**: Referenced objects don't exist in applet (pre-flight)
188
+ - **GeoGebraAppletError**: GeoGebra applet produces error events during execution (runtime)
189
+
190
+ **Architecture Achievements:**
191
+ - Pre-flight validation prevents invalid commands from reaching GeoGebra
192
+ - Runtime errors captured asynchronously via dual-channel communication (IPython Comm + WebSocket)
193
+ - Consecutive error events automatically consolidated into semantic units
194
+ - Full asyncio compliance with timeout handling and queue polling
195
+ - Zero dependency on external error schemas or APIs
196
+
197
+ See [ggblab/errors.py](ggblab/errors.py) for the complete exception hierarchy and [docs/architecture.md](docs/architecture.md#runtime-error-handling-geogebraappletererror) for implementation details.
198
+
199
+ ## Documentation
200
+
201
+
202
+ ggblab's design philosophy and implementation details are documented across several focused documents:
203
+
204
+ **Full documentation available at**: https://ggblab.readthedocs.io/
205
+
206
+ Note: Documentation has moved under docs/. Start at [docs/index.md](docs/index.md) or [philosophy.md](docs/philosophy.md). Legacy copies are retained in docs_archive/ (git-ignored) for reference.
207
+
208
+ ### Core Documentation
209
+
210
+ - **[philosophy.md](docs/philosophy.md)** - Design principles, scope boundaries, and educational vision
211
+ - Communication architecture maturity and stability assessment
212
+ - GeoGebra + Python complementarity framework
213
+ - Geometric Scene evolution inspired by Wolfram's GeometricScene paradigm
214
+ - Manim video export as the ultimate pedagogical goal
215
+ - Prioritized technical roadmap (Tiers 1-5) focused on learning value
216
+ - Success criteria for each version milestone (v0.8 - v1.5+)
217
+
218
+ - **[scoping.md](docs/scoping.md)** - **Core Educational Mission**: Variable scoping via geometric construction
219
+ - **The foundational insight**: Geometric dependencies (points → lines → circles) are isomorphic to programming scopes (global → function → nested)
220
+ - How GeoGebra's construction protocol naturally forms a scope tree
221
+ - Computational thinking pedagogy through geometric decomposition, pattern recognition, abstraction, and algorithm design
222
+ - Concrete lesson plans for teaching Python scoping using geometric constructions
223
+ - Classroom integration roadmap with assessment rubrics
224
+ - Cognitive science rationale: Dual Coding Theory, Transfer of Learning, Constructivism
225
+
226
+ - **[architecture.md](docs/architecture.md)** - Technical implementation details
227
+ - Dual-channel communication design (IPython Comm + Unix socket/TCP WebSocket)
228
+ - Message flow patterns and error handling strategies
229
+ - Dependency parser architecture with performance analysis
230
+ - Critical limitations of `parse_subgraph()` and recommended v1.0 algorithm replacement
231
+ - Resource cleanup and security considerations
232
+ - Testing strategies and development workflow
233
+
234
+ - **[TODO.md](TODO.md)** - Actionable development roadmap
235
+ - 7 priority areas: Parser, Type Safety, Error Handling, CI/CD, Documentation, Configuration, Monitoring
236
+ - Version targets (v0.7.3 - v1.0+) with concrete implementation tasks
237
+ - Blocking issues and dependency tracking
238
+ - Quick-fix vs. long-term architectural improvements
239
+
240
+ - **[ai_assessment.md](docs/ai_assessment.md)** - Independent AI evaluation of project quality and direction
241
+ - Comprehensive strengths and weaknesses analysis
242
+ - Technical, educational, and practical usability assessment
243
+ - Recommended actions and prioritization guidance
244
+ - Critical questions for project sustainability
245
+
246
+ ### Advanced Integration
247
+
248
+ - **[sympy_integration.md](docs/sympy_integration.md)** - Symbolic computation and code generation
249
+ - Bidirectional conversion: GeoGebra constructions ↔ SymPy Geometry objects
250
+ - Symbolic verification of geometric properties (collinearity, concyclicity, perpendicularity)
251
+ - Automatic Python code generation from constructions (reproducibility + version control)
252
+ - Advanced solvers: locus equations, envelope curves, constraint satisfaction
253
+ - Manim export pipeline: SymPy geometry → manim animation code
254
+ - Implementation roadmap (v1.1 - v1.5) with educational success criteria
255
+
256
+ ### API Reference
257
+
258
+ - **[API Documentation](https://ggblab.readthedocs.io/en/latest/api.html)** - Python API reference auto-generated from docstrings
259
+ - `GeoGebra` class: Main interface for controlling applets
260
+ - `ggb_comm`: Dual-channel communication layer
261
+ - `ggb_construction`: File loader and saver
262
+ - `ggb_parser`: Dependency graph analysis
263
+ - `ggb_schema`: XML schema loader
264
+
265
+ ### Quick Reference
266
+
267
+ | Document | Primary Audience | Key Insight |
268
+ |----------|-----------------|-------------|
269
+ | **[docs/scoping.md](docs/scoping.md)** | Educators, Students | Geometric construction teaches programming scoping |
270
+ | **[docs/philosophy.md](docs/philosophy.md)** | Contributors, Researchers | ggblab = GeoGebra → Timeline → Manim → Video pipeline |
271
+ | **[docs/sympy_integration.md](docs/sympy_integration.md)** | Math/CS Instructors | Symbolic proof + code generation + manim export |
272
+ | **[docs/architecture.md](docs/architecture.md)** | Developers | Dual-channel communication; parser needs v1.0 redesign |
273
+ | **[TODO.md](TODO.md)** | Contributors | Concrete next steps prioritized by learning value |
274
+ | **[API Reference](https://ggblab.readthedocs.io/en/latest/api.html)** | Developers | Complete Python API documentation |
275
+
276
+ ### Examples
277
+
278
+ - Sample notebook: [examples/example.ipynb](examples/example.ipynb)
279
+ - Demo video:
280
+
281
+ ![Demo video](https://github.com/user-attachments/assets/b02122bb-7fdd-42ac-bb53-9d58ab288973)
282
+
283
+ Run steps:
284
+
285
+ ```python
286
+ %load_ext autoreload
287
+ %autoreload 2
288
+
289
+ from ggblab import GeoGebra
290
+ import io
291
+
292
+ ggb = await GeoGebra().init() # open GeoGebra widget on the left
293
+
294
+ c = ggb.construction.load('/path/to/your.ggb') # supports .ggb, zip, JSON, XML
295
+ o = c.ggb_schema.decode(io.StringIO(c.geogebra_xml)) # geogebra_xml is auto-stripped to construction
296
+ o
297
+ ```
298
+
299
+ Note: Supports `.ggb` (base64-encoded zip), plain zip, JSON, and XML formats. The `geogebra_xml` is automatically narrowed to the `construction` element and scientific notation is normalized. Schema/decoding APIs may evolve.
300
+
301
+ ### Saving construction
302
+
303
+ Save the current construction (archive when Base64 is set, otherwise plain XML):
304
+
305
+ ```python
306
+ from ggblab import GeoGebra
307
+
308
+ ggb = await GeoGebra().init()
309
+ c = ggb.construction.load('/path/to/your.ggb')
310
+
311
+ # Save to XML (when no Base64 is set)
312
+ c.save('/tmp/construction.xml')
313
+
314
+ # Save to a .ggb file name; content depends on state:
315
+ # - if Base64 is set -> decoded archive (.ggb zip)
316
+ # - else -> plain XML bytes (extension does not enforce format)
317
+ c.save('/tmp/construction.ggb')
318
+ ```
319
+
320
+ #### Saving behavior and defaults
321
+
322
+ - `c.save()` with no arguments writes to the next available filename derived from the originally loaded `source_file` (e.g., `name_1.ggb`, `name_2.ggb`, ...). Use `c.save(overwrite=True)` to overwrite the original `source_file`.
323
+ - If `construction.base64_buffer` is set (e.g., from `getBase64()` or `load()`), `save()` writes the decoded archive; otherwise it writes the in-memory `geogebra_xml` as plain XML.
324
+ - Target file extension does not enforce format: if Base64 is absent, saving to a `.ggb` path will still write plain XML bytes.
325
+ - Note: `getBase64()` from the applet may not include non-XML artifacts present in the original `.ggb` archive (e.g., thumbnails or other resources). Saving after API-driven changes can therefore produce a leaner archive.
326
+
327
+ ### Use Cases (from examples/eg3_applet.ipynb)
328
+
329
+ #### 1) Algebraic commands and API functions
330
+
331
+ ```python
332
+ # Algebraic command
333
+ r = await ggb.command("O = (0, 0)")
334
+
335
+ # API functions
336
+ r = await ggb.function("getAllObjectNames")
337
+ r = await ggb.function("newConstruction")
338
+ ```
339
+
340
+ #### 2) Load .ggb and draw via Base64
341
+
342
+ ```python
343
+ # Load a .ggb (base64-encoded zip)
344
+ c = ggb.construction.load('path/to/file.ggb')
345
+
346
+ # Render in applet
347
+ await ggb.function("setBase64", [ggb.construction.base64_buffer.decode('utf-8')])
348
+ ```
349
+
350
+ #### 3) Layer visibility control
351
+
352
+ ```python
353
+ from itertools import zip_longest
354
+
355
+ layers = range(10)
356
+ await ggb.function("setLayerVisible", list(zip_longest(list(layers), [], fillvalue=False)))
357
+ layers = [9, 0]
358
+ await ggb.function("setLayerVisible", list(zip_longest(list(layers), [], fillvalue=True)))
359
+ ```
360
+
361
+ #### 4) XML attribute edit roundtrip
362
+
363
+ ```python
364
+ # Pull XML for object 'A'
365
+ r = await ggb.function("getXML", ['A'])
366
+
367
+ # Decode to schema dict, modify, and encode back
368
+ o2 = c.ggb_schema.decode(r)
369
+ o2['show'][0]['@object'] = False
370
+ x = xmlschema.etree_tostring(c.ggb_schema.encode(o2, 'element'))
371
+
372
+ # Apply to applet
373
+ await ggb.function("evalXML", [x])
374
+ ```
375
+
376
+ #### 5) Roundtrip save from applet state
377
+
378
+ ```python
379
+ # Fetch current applet state as base64 and save
380
+ r = await ggb.function("getBase64")
381
+ ggb.construction.base64_buffer = r.encode('ascii')
382
+ c.save() # next available filename based on source_file
383
+ # c.save(overwrite=True) # to overwrite the original
384
+ ```
385
+
386
+ ### Object Dependency Analysis (Parser)
387
+
388
+ ggblab includes a **dependency parser** (`ggblab.parser.ggb_parser`) that analyzes object relationships in GeoGebra constructions using **NetworkX graphs**. This enables:
389
+
390
+ - **Dependency tracking**: Build a directed graph of which objects depend on which others
391
+ - **Root/leaf identification**: Find independent starting objects and final dependent objects
392
+ - **Subgraph analysis**: Identify minimal construction sequences needed to derive specific objects
393
+
394
+ #### Basic Usage
395
+
396
+ ```python
397
+ from ggblab import GeoGebra
398
+ from ggblab.parser import ggb_parser
399
+ import networkx as nx
400
+
401
+ ggb = GeoGebra()
402
+ await ggb.init()
403
+
404
+ # Fetch construction protocol from applet
405
+ construction = {}
406
+ for obj_name in await ggb.function("getAllObjectNames"):
407
+ obj_info = await ggb.function(
408
+ ["getObjectType", "getCommandString", "getValueString", "getCaption", "getLayer"],
409
+ [obj_name]
410
+ )
411
+ construction[obj_name] = obj_info
412
+
413
+ # Parse into Polars DataFrame
414
+ parser = ggb_parser()
415
+ parser.initialize_dataframe(df=pl.DataFrame(construction, strict=False))
416
+ parser.parse() # Build dependency graph
417
+
418
+ # Access the NetworkX DiGraph
419
+ G = parser.G
420
+ print(f"Root objects: {parser.roots}") # Objects with no dependencies
421
+ print(f"Leaf objects: {parser.leaves}") # Objects that nothing depends on
422
+
423
+ # Traverse dependencies
424
+ for obj in parser.roots:
425
+ descendants = nx.descendants(G, obj) # All objects that depend on this one
426
+ print(f"{obj} -> {descendants}")
427
+ ```
428
+
429
+ #### Advanced: Subgraph Extraction
430
+
431
+ Extract minimal construction sequences needed for specific output objects:
432
+
433
+ ```python
434
+ # Analyze subgraph for focused construction steps
435
+ parser.parse_subgraph() # Builds G2 with simplified dependencies
436
+ G2 = parser.G2
437
+
438
+ # Reconstruct only necessary steps
439
+ nx.write_network_text(G2) # View simplified dependency tree
440
+ ```
441
+
442
+ #### Parser Components
443
+
444
+ - **`df`**: Polars DataFrame with columns `Type`, `Command`, `Value`, `Caption`, `Layer` (transposed from construction protocol)
445
+ - **`G` (NetworkX DiGraph)**: Full dependency graph; edges point from dependencies to dependents
446
+ - **`G2` (NetworkX DiGraph)**: Simplified subgraph with redundant dependencies removed
447
+ - **`ft` (dict)**: Tokenized command strings; maps object name → list of tokens (parsed by `tokenize_with_commas()`)
448
+ - **`roots` (list)**: Objects with `in_degree == 0` (no incoming dependencies)
449
+ - **`leaves` (list)**: Objects with `out_degree == 0` (nothing depends on them)
450
+
451
+ #### Example Notebook
452
+
453
+ See [examples/eg4_parse.ipynb](examples/eg4_parse.ipynb) for a complete example of loading a `.ggb`, building dependency graphs, and analyzing construction structure.
454
+
455
+ ### Architecture
456
+
457
+ - **Frontend** ([src/index.ts](src/index.ts), [src/widget.tsx](src/widget.tsx)): Registers the plugin `ggblab:plugin` and command `ggblab:create`. Creates a `GeoGebraWidget` ReactWidget that loads GeoGebra from the CDN, opens an IPython Comm target (default `test3`), executes commands/functions, and mirrors add/remove/rename/clear events plus dialog notices back to the kernel. Results can also be forwarded over the external socket when provided.
458
+ - **Backend** ([ggblab/ggbapplet.py](ggblab/ggbapplet.py), [ggblab/comm.py](ggblab/comm.py), [ggblab/construction.py](ggblab/construction.py), [ggblab/parser.py](ggblab/parser.py)): Initializes a singleton `GeoGebra`, spins up a Unix-socket/TCP WebSocket server, registers the IPython Comm target, and drives the frontend command via ipylab. `ggb_comm.send_recv` waits for responses; `ggb_construction` loads multiple file formats (`.ggb`, zip, JSON, XML) and provides `geogebra_xml` + `ggb_schema` for converting construction XML to schema objects. `ggb_parser` analyzes object dependencies using NetworkX directed graphs.
459
+ - **Styles** ([style/index.css](style/index.css), [style/base.css](style/base.css)): Ensure the embedded applet fills the available area.
460
+
461
+ #### Communication Architecture
462
+
463
+ **Dual-channel design**: ggblab uses two communication channels between the frontend and backend:
464
+
465
+ 1. **Primary channel (IPython Comm over WebSocket)**:
466
+ - Handles command/function calls and event notifications
467
+ - Managed by Jupyter/JupyterHub infrastructure with reverse proxy support
468
+ - Connection health guaranteed by Jupyter/JupyterHub
469
+ - **Limitation**: IPython Comm cannot receive messages while a notebook cell is executing
470
+
471
+ 2. **Out-of-band channel (Unix Domain Socket on POSIX / TCP WebSocket on Windows)**:
472
+ - Addresses the Comm limitation by enabling message reception during cell execution
473
+ - Allows GeoGebra applet responses to be received even when Python is busy executing code
474
+ - Connection is opened/closed per transaction (no persistent connection)
475
+ - No auto-reconnection needed due to transient nature
476
+
477
+ This dual-channel approach ensures that interactive operations (e.g., retrieving object values, updating constructions) remain responsive even during long-running cell execution.
478
+
479
+ See [architecture.md](docs/architecture.md) for detailed design rationale and implementation notes.
480
+
481
+ ##### Architecture Diagram
482
+
483
+ ```mermaid
484
+ flowchart TB
485
+ subgraph Browser
486
+ FE[JupyterLab Frontend + GeoGebra Applet]
487
+ end
488
+ subgraph Server
489
+ K[Python Kernel]
490
+ S["Socket Bridge (Unix or TCP WebSocket)"]
491
+ end
492
+ FE -- "IPython Comm (WebSocket)\nvia JupyterHub proxy" --> K
493
+ FE -- "Out-of-band socket (transient)" --> S
494
+ S --- K
495
+ FE -. "GeoGebra asset" .-> CDN[cdn.geogebra.org/apps/deployggb.js]
496
+ ```
497
+
498
+ #### Security & Compatibility
499
+
500
+ - Reverse proxy-friendly: Operates over JupyterLab's IPython Comm/WebSocket within the platform's auth/CSRF boundaries.
501
+ - CORS-aware CDN: GeoGebra is loaded from `https://cdn.geogebra.org/apps/deployggb.js` as a static asset; no cross-origin API calls from the browser beyond this script.
502
+ - Same-origin messaging: Kernel↔widget interactions remain within Jupyter's origin; no custom headers or cookies required.
503
+ - Optional socket bridge: Transient Unix/TCP bridge opens per transaction and closes immediately to avoid long-lived external listeners; improves responsiveness during cell execution.
504
+ - JupyterHub readiness: Validated in managed JupyterHub (Kubernetes) behind reverse proxies.
505
+
506
+ #### Error Handling and Limitations
507
+
508
+ **Primary channel (IPython Comm)**: Error handling is managed automatically by Jupyter/JupyterHub infrastructure. Connection failures are detected and handled transparently; kernel status is visible in the JupyterLab UI.
509
+
510
+ **Out-of-band channel**: The secondary channel has a **3-second timeout** for receiving responses. If no response arrives within this window, a `TimeoutError` is raised in Python:
511
+
512
+ ```python
513
+ try:
514
+ result = await ggb.function("getValue", ["a"])
515
+ except TimeoutError:
516
+ print("GeoGebra did not respond within 3 seconds")
517
+ ```
518
+
519
+ **GeoGebra API constraint**: The GeoGebra API does **not** provide explicit error response codes. Instead, errors are communicated through **dialog popups** displayed in the browser. The frontend monitors these dialog events and forwards error information via the primary Comm channel. For errors that do not trigger dialogs (e.g., malformed responses), the timeout is the primary error signal.
520
+
521
+ See [architecture.md § Error Handling](docs/architecture.md#error-handling) for details on error detection and recovery strategies.
522
+
523
+ ### Settings
524
+
525
+ The current settings schema ([schema/plugin.json](schema/plugin.json)) exposes no user options yet but is ready for future configuration.
526
+
527
+ ### Development Workflow
528
+
529
+ ```bash
530
+ pip install -e ".[dev]"
531
+ jupyter labextension develop . --overwrite
532
+ jlpm build # or `jlpm watch` during development
533
+ jupyter lab # run in another terminal
534
+ ```
535
+
536
+ To remove the dev link, uninstall and delete the `ggblab` symlink listed by `jupyter labextension list`.
537
+
538
+ ### Testing
539
+
540
+ **Automated Testing (GitHub Actions)**:
541
+ - Continuous integration configured via [.github/workflows/tests.yml](.github/workflows/tests.yml)
542
+ - Runs on `main` and `dev` branches on every push and pull request
543
+ - Tests across Python 3.10, 3.11, 3.12 on Ubuntu, macOS, and Windows
544
+ - Coverage reports uploaded to Codecov
545
+
546
+ **Running Tests Locally**:
547
+
548
+ ```bash
549
+ # Install test dependencies
550
+ pip install -e ".[dev]"
551
+ pip install pytest pytest-cov
552
+
553
+ # Run all tests
554
+ pytest tests/ -v
555
+
556
+ # Run specific test module
557
+ pytest tests/test_parser.py -v
558
+
559
+ # Run with coverage report
560
+ pytest tests/ --cov=ggblab --cov-report=html
561
+ ```
562
+
563
+ **Frontend Tests**:
564
+ - `jlpm install && jlpm test`
565
+
566
+ **Integration Tests (Playwright/Galata)**:
567
+ - See [ui-tests/README.md](ui-tests/README.md)
568
+ - Build with `jlpm build:prod`, then `cd ui-tests && jlpm install && jlpm playwright test`
569
+
570
+ ### Release
571
+
572
+ See [RELEASE.md](RELEASE.md) for publishing to PyPI/NPM or using Jupyter Releaser; bump versions with `hatch version`.
573
+
574
+ ### Known Issues and Gaps
575
+
576
+ #### Frontend Limitations
577
+
578
+ - **No explicit error handling UI**: Communication failures between frontend and backend are logged to console but not displayed to users. Currently relies on browser console for debugging.
579
+ - **Limited event notification**: Only monitors basic GeoGebra events (add/remove/rename/clear objects, dialogs). Advanced events like slider changes, conditional visibility toggles, or script execution results are not automatically propagated.
580
+ - **Hardcoded Comm target**: The Comm target name is hardcoded as `'test3'` with no option for customization without code changes.
581
+ - **TypeScript strict checks disabled**: Some type assertions use `any` type, reducing type safety. Widget props lack full interface documentation.
582
+ - **No input validation**: Commands and function arguments are not validated before sending to GeoGebra; invalid requests may cause silent failures.
583
+
584
+ #### Backend Limitations
585
+
586
+ - **Singleton pattern constraint**: Only one active GeoGebra instance per kernel session. Attempting to create multiple instances will reuse the same connection.
587
+ - **Out-of-band communication timeout**: The out-of-band socket channel has a 3-second timeout. If the frontend does not respond within this window, the backend raises a timeout exception.
588
+ - **Limited error handling on out-of-band channel**: GeoGebra API does not provide explicit error responses, so errors are communicated indirectly:
589
+ - GeoGebra displays error dialogs (native popups) when operations fail (e.g., invalid syntax in algebraic commands)
590
+ - The frontend monitors dialog events and forwards error messages via the primary Comm channel
591
+ - Errors without a dialog (e.g., malformed JSON responses) result in timeout exceptions or silent failures
592
+ - **Parser subgraph extraction (`parse_subgraph()`) performance issues**:
593
+ - **Combinatorial explosion**: Generates $2^n$ path combinations where $n$ = number of root objects. Performance degrades rapidly with 15+ independent roots.
594
+ - **Infinite loop risk**: May hang indefinitely under certain graph topologies.
595
+ - **Limited N-ary dependency support**: Only handles 1-ary and 2-ary dependencies; 3+ objects jointly creating an output are ignored.
596
+ - **Redundant computation**: Neighbor lookups are recalculated unnecessarily in loops.
597
+ - See [architecture.md § Dependency Parser Architecture](docs/architecture.md#dependency-parser-architecture) for detailed analysis and planned improvements.
598
+
599
+ #### General Limitations
600
+
601
+ - ✅ **Unit tests**: Comprehensive Python test suite with pytest (parser, GeoGebra applet, construction handling)
602
+ - ✅ **CI/CD pipeline**: Automated testing on all pull requests via GitHub Actions (Python 3.10+, multi-OS)
603
+ - 🔄 **Incomplete integration tests**: No Playwright tests yet for critical workflows (command execution, file loading, event handling)
604
+ - **Minimal documentation**: No dedicated developer guide beyond code comments; architecture rationale is not documented.
605
+
606
+ ### Project Assessment (Objective)
607
+
608
+ - **Maturity**: Early-stage (0.x). Core functionality works for driving GeoGebra via dual channels, but lacks automated verification and release safeguards.
609
+ - **Strengths**: Clear architecture docs; dual-channel communication to mitigate Comm blocking; supports multiple file formats; dependency parser groundwork.
610
+ - **Key Risks**: No CI, low test coverage (unit/integration absent); parser `parse_subgraph()` has performance/loop risks on large graphs; hardcoded Comm target; minimal UX for error surfacing.
611
+ - **Maintainability**: TypeScript not strict; some `any` and limited input validation; parser algorithm needs replacement for scale.
612
+ - **Operational Gaps**: No monitoring/telemetry; no retry/backoff around sockets; release process manual.
613
+
614
+ ### Future Enhancements and Roadmap
615
+
616
+ #### Short Term (v0.8.x)
617
+
618
+ 1. **Error Handling & User Feedback**
619
+ - Add user-facing error notifications for Comm/WebSocket failures
620
+ - Improve out-of-band error reporting: detect timeout conditions and propagate as Python exceptions with context
621
+ - Support for custom timeout configuration in `GeoGebra()` initialization
622
+ - Enhanced error message recovery from GeoGebra dialog content
623
+ - Provide more descriptive error messages in the UI when operations fail
624
+
625
+ 2. **Parser Optimization** (`v0.7.3`)
626
+ - Remove debug output; add optional logging via `logging` module
627
+ - Add early termination check to detect infinite loops in `parse_subgraph()`
628
+ - Cache neighbor computation to reduce redundant graph traversals
629
+ - Extend N-ary dependency support (currently limited to 1-2 parents; should support 3+)
630
+
631
+ 3. **Event System Expansion**
632
+ - Subscribe to additional GeoGebra events (slider value changes, object property changes, script execution)
633
+ - Expose event system to Python API via `ggb.on_event()` pattern
634
+ - Log all events with timestamps for debugging
635
+
636
+ 4. **Configuration & Customization**
637
+ - Add settings UI to choose Comm target name and socket configuration
638
+ - Allow custom GeoGebra CDN URL (for offline or private CDN scenarios)
639
+ - Implement widget position/size preferences (split-right, split-left, tab, etc.)
640
+
641
+ #### Medium Term (v1.0)
642
+
643
+ 1. **Type Safety & Code Quality**
644
+ - Enable TypeScript strict mode and eliminate `any` types
645
+ - Add JSDoc for all public TypeScript/Python APIs
646
+ - Increase test coverage to >80% for both frontend and backend
647
+ - Add comprehensive unit tests for parser, especially for edge cases and large graphs
648
+
649
+ 2. **Parser Algorithm Replacement**
650
+ - Replace `parse_subgraph()` with topological sort + reachability pruning approach
651
+ - Reduce time complexity from $O(2^n)$ to $O(n(n+m))$
652
+ - Support arbitrary N-ary dependencies (not limited to 2 parents)
653
+ - Eliminate infinite loop risk through deterministic algorithm
654
+ - See [architecture.md § Dependency Parser Architecture](docs/architecture.md#dependency-parser-architecture) for detailed design
655
+
656
+ 3. **Advanced Features**
657
+ - **Multi-panel support**: Allow multiple GeoGebra instances in different notebook cells
658
+ - **State persistence**: Save/restore GeoGebra construction state to notebook or file
659
+ - **Real-time collaboration**: Support multiple users viewing/editing the same construction
660
+ - **Animation API**: Programmatic animation of objects with timeline control
661
+ - **Custom tool definitions**: Allow users to define and persist custom GeoGebra tools
662
+
663
+ 4. **Integration Improvements**
664
+ - **Jupyter Widgets (ipywidgets) support**: Make GeoGebra embeddable in `ipywidgets` environments
665
+ - **Matplotlib/Plotly integration**: Export construction data to visualization libraries
666
+ - **NumPy/Pandas integration**: Bidirectional data sync with DataFrames
667
+
668
+ #### Long Term (v1.5+)
669
+
670
+ 1. **Performance & Scalability**
671
+ - WebSocket batching for high-frequency updates (e.g., animations)
672
+ - Caching layer for repeated function calls
673
+ - Support for serverless/container environments without persistent sockets
674
+ - Memoization of subgraph extraction results
675
+
676
+ 2. **ML/Data Science Features**
677
+ - Built-in geometry solvers with numerical optimization (scipy integration)
678
+ - Constraint solving interface
679
+ - Interactive visualization of mathematical models
680
+
681
+ 3. **Parser Enhancements**
682
+ - Weighted edges representing construction order preference
683
+ - Interactive subgraph selection UI
684
+ - Integration with constraint solving for optimal construction paths
685
+ - Interactive visualization of mathematical models
686
+
687
+ 3. **Ecosystem & Standards**
688
+ - JupyterHub compatibility testing and official support
689
+ - Jupyter Notebook (classic) extension variant
690
+ - Conda-forge packaging
691
+ - Official plugin for popular JupyterLab distributions (JupyterHub, Google Colab, etc.)
692
+
693
+ ### Contributing
694
+
695
+ Contributions are welcome! Please:
696
+
697
+ 1. Fork the repository
698
+ 2. Create a feature branch (`git checkout -b feature/xyz`)
699
+ 3. Commit with clear messages
700
+ 4. Run tests and linting: `jlpm lint && jlpm test`
701
+ 5. Submit a pull request
702
+
703
+ For major changes, please open an issue first to discuss.
704
+
705
+ ### License
706
+
707
+ BSD-3-Clause
708
+
709
+ ## Cloud Deployment
710
+
711
+ This section outlines how to deploy and operate ggblab in common cloud setups. ggblab is a prebuilt JupyterLab 4 federated extension packaged in Python, so cloud installs typically require only `pip install ggblab`.
712
+
713
+ ### JupyterHub (Kubernetes)
714
+
715
+ - Image bake (recommended): Add ggblab to your single-user image.
716
+
717
+ ```dockerfile
718
+ FROM quay.io/jupyter/base-notebook:latest
719
+ RUN pip install --no-cache-dir ggblab
720
+ ```
721
+
722
+ - Runtime install (quick test): From a user session terminal, install and restart the server.
723
+
724
+ ```bash
725
+ pip install -U ggblab
726
+ jupyter labextension list | grep ggblab
727
+ # Stop the server from the menu or via Control Panel, then start again
728
+ ```
729
+
730
+ - Notes:
731
+ - No Node.js or `jlpm build` is required in cloud environments; the extension is prebuilt and registered via Python packaging.
732
+ - Verify installation with `jupyter labextension list` — ggblab should appear as enabled and OK.
733
+ - If users share a base image, prefer baking ggblab into the image to avoid per-user installs.
734
+
735
+ #### Admin Tips (JupyterHub)
736
+
737
+ - Prefer image bake: reduce per-user variance and avoid cold-start installs.
738
+ - Restart single-user servers after runtime install: use Control Panel or admin culling to ensure extension loads.
739
+ - Ensure same environment: `pip` must target the environment used by `jupyter lab` (check `which jupyter` and `python -m site`).
740
+ - Allow egress to GeoGebra CDN: whitelist `cdn.geogebra.org` in cluster/network policies.
741
+ - Monitor logs: check Hub and single-user server logs for proxy/WebSocket errors during Comm operations.
742
+ - Version pinning: bake a specific ggblab version in images; use `pip install -U ggblab` only when you intentionally roll forward.
743
+ - Dev vs prod: reserve `pip install -e ".[dev]"` for development images; production should use pinned releases.
744
+ - No inbound ports: the out-of-band socket bridge is transient and initiated from the kernel; no extra public ports need exposure.
745
+
746
+
747
+
748
+ ### Generic Cloud VM
749
+
750
+ - Install in your environment and start JupyterLab:
751
+
752
+ ```bash
753
+ pip install ggblab
754
+ jupyter lab
755
+ ```
756
+
757
+ ### Troubleshooting
758
+
759
+ - Extension not visible:
760
+ - Confirm JupyterLab >= 4 and that you are installing into the same environment used by JupyterLab.
761
+ - Run `jupyter labextension list` to verify ggblab is enabled.
762
+ - Fully restart JupyterLab; a simple browser refresh may not load new extensions.
763
+ - Network/CDN restrictions:
764
+ - ggblab loads GeoGebra from `https://cdn.geogebra.org/apps/deployggb.js`. Ensure your cluster egress policy allows this domain.
765
+ - Communication checks:
766
+ - ggblab uses IPython Comm and an optional socket bridge. These work in managed JupyterHub environments; if you see timeouts, check proxy/network policies and consider increasing operation timeouts.
767
+
768
+ For detailed deployment guidance, environment checks, common pitfalls, and verification steps, see [docs/cloud-deployment-guide.md](docs/cloud-deployment-guide.md).