Self-validating generation, STEM scene library, 212 interactive curriculum demos, and a step-by-step tutor#3
Open
Maxpeng59 wants to merge 44 commits into
Open
Self-validating generation, STEM scene library, 212 interactive curriculum demos, and a step-by-step tutor#3Maxpeng59 wants to merge 44 commits into
Maxpeng59 wants to merge 44 commits into
Conversation
…pairs
Reduce runtime crashes that exhaust the repair loop, and cut the latency of
both generation and repair, so fewer scenes hit the slow LLM repair path and
the ones that do converge (or fail fast) instead of grinding the whole budget.
Sandbox (sandbox-worker.js):
- Invented helpers (H/cam/view) degrade to a chainable no-op in any chain
order; cam/view rebind to the proxy so real().invented() is safe too.
- Per-frame error tolerance: a scene that renders then throws on an occasional
frame keeps running; only a first-frame failure or ~0.5s of sustained
throwing escalates to repair.
- Heartbeat on the first clean frame (load "ready" signal ~300ms sooner).
- offendingLine(): pull the exact failing source line out of the stack.
Client (app.js, index.html):
- MAX_REPAIRS 3->2; forward the offending line to /api/repair.
- Non-convergence guards: bail when a repair repeats the same error or returns
unchanged code, instead of grinding the full (slow) repair budget.
- Bump app.js cache-buster v10->v11 (was stranding all client changes behind
the stale ?v=10 for returning users).
Server (main.py):
- Generator-aware retry budget (cloud=2, local Ollama=3).
- Credit ".axes(" as a label so well-labeled plots stop triggering false
"unlabeled" regenerations.
- VISUALLM_CLAUDE_EFFORT / VISUALLM_CLAUDE_MAX_TOKENS env knobs (default high/32k).
- _repair_hint(): error-class fix guidance + a minimal-change directive, wired
into every repair provider; fold the offending line into the repair error.
Tests: +5 (repair hints, repair-visualization line folding). Suite 36->41, green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two production-robustness gaps in the public HTTP server, independent of the generation→repair work on this branch: - do_POST dispatched to handlers with no catch-all, so an UNEXPECTED exception (a bug, a new SDK error type — distinct from the RuntimeErrors that handlers already turn into 503s) escaped, dropped the client connection, and spilled a bare traceback to stderr with no response. Now logged and returned as a clean 500. - send_json's writes weren't guarded, so a client disconnecting mid-response (common during a slow generation) surfaced BrokenPipeError as an unhandled traceback. Now swallowed. Test: +1 (unexpected handler exception -> 500). Suite 41->42, green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The two complaints — "still errors after 3 tries" and "took a long time" — both trace to the same root: the browser was the validator, so every bad scene cost a full client round-trip + a slow model repair. This restructures the pipeline around catching and fixing errors WITHOUT model calls. - validate_scene.js: headless server-side scene validator. Runs untrusted generated code in a hardened Node `vm` (fresh empty context — no process/ require/import reachable; classic constructor.constructor escape contained; hard 2s timeout) and reports throws / blank / no-motion / no-labels in ~50ms. - main.py evaluate_scene(): single source of truth, validator-authoritative, static-gate fallback when node is absent. _try_generate now repairs fatal scenes SERVER-SIDE (exact runtime error folded into the prompt) so the browser receives an already-runnable scene. - autofix_code(): deterministically prefixes bare Math.* calls / PI / TAU outside strings & comments — fixes the #1 "X is not defined" throw with ZERO model round-trips. Wired into sanitize_code. - scene_library.py + library_match(): curated, hand-verified STEM corpus with keyword retrieval. Strong match short-circuits to an instant, known-correct scene (Fourier prompt: 5ms vs a 10-60s generation). This is the practical, retrieval-augmented realization of "feed it STEM data". - Exact-prompt cache of validated scenes (instant repeats), only caches clean scenes. plan_visualization chain: cache -> strong library -> models -> library fallback -> placeholder. - Frontend: elapsed-seconds ticker during generation; curated/cached badges. - Dockerfile installs Node so the validator runs in production too. - Health reports validator + library_size. 66 tests pass (24 new). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The weak local model's most common SEMANTIC failure isn't a crash — it's drawing real content in the wrong coordinate space (e.g. v.line(v.X(x), ...), which double-maps every point off the canvas). It runs, it "paints", it has labels, so the old checks passed it — but the screen shows only axes. The validator now tracks, per content draw, whether it lands on-canvas (data- space view/cam methods convert via X/Y or project() first, exactly like the real helpers). paint>0 with zero on-screen => onscreen:false, which evaluate_scene treats as fatal and repairs with a coordinate-space hint. 'paint' now counts CONTENT draws only (grid/axes/background are scaffold), so an axes-only "scene" is correctly seen as blank. All 6 library scenes pass; 72 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- scene_library_generated.json: 42 scenes authored by the stem-scene-library multi-agent workflow (one author per domain + adversarial per-scene review), every one re-validated through validate_scene.js (runs, paints on-screen, animates, labeled). Covers ~24 domains: quantum, linear algebra, waves, algorithms, astronomy, neuroscience, control systems, probability, etc. - Hand-written electromagnetism scenes (dipole field lines, RC circuit) to cover the two UI example chips the workflow batch missed. - library_match: weight title/tag tokens, drop stopwords, dominance gap so an ambiguous near-tie doesn't fast-path; high-confidence matches fast-path even when a duplicate-topic scene also scores well. Library fallback now requires real relevance (>=2.0) instead of any score, so a failed generation never shows a wrong-topic curated scene. - Dockerfile copies the generated JSON; README documents the reliability/speed architecture + how to regenerate the library. 69 tests pass. 7/10 example chips now render instantly from the verified library; the rest generate. Verified in-browser: dipole + 3D moon-phases scenes render correctly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Watched the local model write a Doppler scene with pos = t*4 (unbounded): the source and all wavefronts sail off the right edge within seconds, leaving only a static observer line. It passed validation because early frames (t<=3) still had content on-screen. Now the validator samples a late frame (t=30) and tracks the FINAL frame's on-screen fraction: if <15% of the last frame's content is on canvas, the scene "drifted off and never loops" -> fatal, repaired with a hint to bound/loop the motion (t % period, Math.sin(t)). All 50 library scenes still pass; 71 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ge conflicts) PR #1 was squash-merged into main (948adc3); this branch kept its original commits (b58ae9e, b426c44) plus newer work - validator drift-detection, the 50-scene verified library, and the stem-viz-plugin - so re-merging main conflicted on main.py, tests/test_main.py, and validate_scene.js. For all three the branch is a content-superset of main (it contains PR #1's exact commits, which main carries as the squash, plus additive work on top), so the conflicts were resolved in favor of the branch. Verified: 71/71 tests pass and the merged validator still runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live re-run exposed the subtler case: the local model wrote xSource = -speed*t again, and fixed labels (title, origin marker) kept the last-frame on-screen fraction just above 15%, so the moving subject sailed off-screen undetected. - Validator now also flags a COLLAPSE: content abundant on-screen early (>=5 draws in some early frame) but the late frame retains <40% of that peak => the subject drifted away while only fixed annotations remain. Catches the exact generated Doppler; no false-flags across the 50-scene library. - System prompt Rule 1 now bans `x = speed*t` outright and requires looping/ bounded motion (Math.sin(t), t % period) so moving subjects stay in frame — fixing the root cause for every generation, not just repairs. - 72 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bake the product's teaching philosophy into the generator. Scenes must build
understanding of the mechanism (sweep the parameter, explain in labels, provoke
prediction) rather than just computing and displaying the answer to a student's
specific problem. Applied consistently to SCENE_SYSTEM_PROMPT ("Style and
pedagogy") and the stem-viz skill (SKILL.md + concepts.md).
Guarded against over-correction: scenes stay concrete and labeled, and a live
readout of a CHANGING quantity remains good teaching (it makes the relationship
visible). The line is illuminate-the-mechanism vs. hand-over-the-answer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make it runnable without touching the terminal after a one-time key setup. - launch.py: loads .env, picks a free port, starts main.py, waits until /api/health is green, then opens the UI in a chrome-less app window (Chrome/Edge/Brave --app, else the default browser). Flags: --no-browser, --smoke. - VisualLM.command: macOS double-click wrapper (chmod +x). - desktop.py: optional TRUE native window via pywebview (one pip install). - .env.example: API-key template (.env stays gitignored). - README: new "Run it (as an app)" section + Files entries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
shutil.which("node") is `str | None`, and pyright/Pylance can't carry the
non-None proof across the separate node_validator_available() helper (it's a
module global), so the subprocess.run arg list was typed `list[str | None]`
(reportCallIssue + reportArgumentType). Rebind to a local and guard it inline
so it narrows to `str`. Behavior-identical — the inline check is De
Morgan-equivalent to the old `not node_validator_available()`. pyright now
reports 0 errors on main.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
make_app.sh assembles VisualLM.app — a proper bundle (Info.plist + a generated multi-resolution .icns from make_icon.py, a dependency-free stdlib PNG writer). Its launcher resolves the repo by the bundle's own location and opens VisualLM in a native window (pywebview if installed) or an app-style browser window otherwise. The python3 path is baked at build time so a Finder launch — which gets a minimal PATH — still finds the right interpreter. The .app is a git-ignored build artifact; run ./make_app.sh to (re)build. A fully self-contained PyInstaller bundle is the next step (in progress). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
app_main.py runs the VisualLM server IN-PROCESS (server in a daemon thread, window on the main thread) instead of spawning `python main.py` — required for a PyInstaller bundle, where sys.executable is the app binary, not a Python interpreter. main.py's BASE_DIR now resolves to sys._MEIPASS when frozen so the static assets + validate_scene.js load from the bundle (no-op unfrozen). Opens a native pywebview window if installed, else an app-style browser window. Verified: `app_main.py --smoke` serves /api/health; 72/72 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a committed PyInstaller spec + build_app.sh that freeze app_main.py into a standalone double-click VisualLM.app — bundles Python, the static assets, validate_scene.js, and the scene library; runs the server in-process and serves from the bundle (BASE_DIR -> sys._MEIPASS). Verified: the frozen binary's `--smoke` boots the server and serves index.html from the bundle (~22 MB app). Also: app_main.py --smoke now confirms static assets serve (the real bundle-path test), and the packaged app reads .env from next to VisualLM.app or ~/.visuallm/ (when frozen, its __file__ is inside the bundle). Build artifacts (build/, dist/) stay git-ignored. 72/72 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tests/test_launcher.py (stdlib, headless): .env loading (quote-stripping, skipping comments/junk lines, never overriding an existing shell env var, missing-file no-op), free_port (returns the preferred port when free; falls back to a bindable port when it's taken), wait_healthy (true against a live 200 server, false on a dead port within the timeout), and app_main._env_files (unfrozen -> local .env; frozen -> alongside VisualLM.app + ~/.visuallm). The new launcher/app code was previously untested. Suite 72 -> 81, green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n names
autofix_code blindly prefixed every bare PI / TAU / Math-fn name. A scene that
aliases one — `const PI = Math.PI` or `const TAU = 6.28` — was rewritten to
`const Math.PI = ...` (a SYNTAX error), and a local `function log(){}` /
`const log = ...` became `Math.log`. Now we collect the names the scene declares
itself (const/let/var/function) and never rewrite those; genuinely bare calls
and constants are still fixed. Regression test added; suite 81 -> 82, green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_rewrite_declared_names renamed a reserved binding (H/ctx/t) after ANY comma in
a const/let/var span — including expression commas inside ()/[]/{}. So a very
common pattern like `const y = H.lerp(a, b, t);` became `H.lerp(a, b, _t)` (an
undefined reference), and `const p = [Math.cos(t), t]` lost its `t` — silently
feeding the server-side repair loop and burning round-trips. The docstring
already said "top level of the statement" but the regex never enforced bracket
depth.
Replaced the naive regex with a depth-aware scan that renames only genuine
declarators (at depth 0, right after const/let/var or a top-level comma).
Real redeclarations still rename (`const W = H.W, H = H.H` -> `_H`; `let H, ctx`
-> `let _H, _ctx`). Also removed a SyntaxWarning (a `\s` in a non-raw docstring).
2 regression tests; suite 82 -> 84, green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `function scene(...) { ... }` unwrap (rfind-based) and the fence strip run
on every generated scene but were only tested on the trivial case. Verified and
pinned the tricky edges: nested arrow-function braces, a '}' inside a string
literal (rfind must still land on the function's own closing brace), and a
fenced + wrapped scene where both layers are stripped. No code change — coverage
only. Suite 84 -> 86, green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he why When a prompt clearly names an Algebra 1 → Precalculus topic, VisualLM now shows a known-correct interactive demo instead of generating code. The graph renders instantly, the student drags sliders to fill in the values (live, no recompile), and a plain-language explanation teaches the idea behind the formula — building understanding of the mechanism rather than solving a specific problem. - demo_library.py: 12 validated demos (lines, systems, absolute value, exponential growth/decay, quadratic vertex form, discriminant/roots, polynomial end behavior, log/exp inverse, function transformations, unit circle, sine wave, ellipse), each with editable params + a teaching explanation and key points. - main.py: demo_match() classifies a prompt to its best demo via the existing library scorer (mode-neutral so a 2D demo isn't penalized); _demo_scene() shapes it into a response carrying its params + explanation; plan_visualization() serves the demo right after the exact-prompt cache, ahead of the curated/generative paths. - sandbox-worker.js: scenes read live params from a P global; a new `params` message updates them in place without recompiling the scene. - app.js: renders the slider panel + explanation below the graph; moving a slider pushes a live param update to the worker (setParams). Worker bumped to v17, app.js to v18. - validate_scene.js: P proxy so headless validation handles demo code. - tests: 7 new tests (well-formed params that the code actually reads, every demo runnable in the validator, topic routing incl. discriminant vs vertex, scene shape, plan routing). Full suite: 93 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tutor can now SOLVE problems, not just chat. New prompt mode (triggered by
"solve / find / calculate / how do I"): a structured walkthrough that teaches
the method —
Goal: what we solve for (symbol + unit)
What you need: the formula + each known value AND where it comes from
(problem / constant / read off the animation)
Steps: numbered, each substitutes the needed value right there, shows
the intermediate result, and points to where it appears on screen
Answer: result + a sanity check
A persistent "→ Solve it step by step" quick-action chip fills the chat box
(so the student can append their own numbers) and is styled to stand out.
Local models ignore the "no LaTeX" rule, so the frontend stripper is the real
safety net — hardened to handle the gaps seen live: \frac{{a}}{{b}} (doubled
braces), \, \; \quad spacing macros, ^\circ -> °, \mathrm/\operatorname, and
stray residue backslashes.
Also: projectile/DNA example chips now fast-path to their curated scenes
(added "launched/angle/degrees" and "structure" keywords); _MEIPASS read via
getattr to silence Pylance. 93 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Authored by a multi-agent workflow (one agent per topic-batch, each self- validating its code via validate_scene.js), then RE-validated here through the same gate — all 200 pass (runs, paints on-screen, animates, labeled, has sliders). Covers the full requested curriculum: Algebra 1 (60), Algebra 2 (70), Precalculus (70). With the 12 hand-written demos that's 212 total. Each demo is a parameterized "fill in the values" scene: sliders feed P.<name> into the code, the graph updates live, and an explanation builds intuition. Even non-graphable topics (order of operations, translating expressions, word problems, matrices, probability) became visual + interactive — worked examples with step sliders, number-line / bar / area models, tape diagrams. - demo_library_generated.json: the 200 validated demos (data). - demo_library.py: loads + merges the generated set with the hand-written base. - app.js: demos now labeled "<area> demo • drag the sliders to explore". Verified in-browser: Law of Sines (live triangle, correct c≈0.48 at A=115°) and Order of Operations (stepped worked example) render with working sliders. 93 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per request for a "dumb-proofed" explanation: the solve-mode prompt now tells the tutor to assume a total beginner — define every term in plain words, NEVER skip an arithmetic/algebra move (show subtract-from-both-sides then divide as separate lines, not a jump to the answer), say WHY each move is allowed, and split any two-move line into two steps. 93 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ran an adversarial multi-agent audit (29 agents) over all 200 generated demos checking MATHEMATICAL correctness against each topic — the thing the runtime validator can't see. Result: 189/200 correct as-is (94.5%), 11 flagged with genuine bugs, all fixed and re-validated: - pc-vector-operations: read sliders P.by/P.scalar that didn't exist (NaN). - a2-quadratic-inequalities: shaded between roots + printed wrong inequality when a<0. - a1-slope-from-a-table: highlight wrapped last row -> first, wrong slope. - a1-domain-and-range: range bar/readout hardcoded instead of computed. - pc-sinusoidal-modeling, pc-identify-conic-from-equation: major. - 5 minor (order-of-operations != symbol when a=0/c=1, log readout, matrix scalar clamp/label, exact-trig near-zero, ellipse readout label). Every fix re-checked through validate_scene.js. All 200 still validate, 0 structural issues, 93 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Dockerfile didn't COPY demo_library.py / demo_library_generated.json, so a production deploy would silently load ZERO demos (the import falls back to an empty list). Now copies demo_library.py + a *_generated.json glob, so the scene library, the 212 curriculum demos, and the upcoming physics set all ship automatically. Verified in PORT-injected prod mode: a "completing the square" request serves the demo with its sliders over the wire. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses "quite laggy" with four universal wins: - Self-correcting frame scheduler in the worker: schedule the next tick for a fixed ~16.7ms PERIOD, not 16.7ms AFTER the work. A 10ms scene was running at ~37fps (10+16.7); now it targets 60. - Cap canvas DPR at 1.5 (was 2.0): on Retina, dpr=2 means 4x the pixels to fill every frame, which dominates fill-heavy 3D scenes. 1.5 stays crisp and ~halves the fill work. - Suspend rendering when the tab is hidden (visibilitychange -> worker stops the loop) — no CPU/battery spent animating an invisible canvas; doesn't suspend mid-load so the run watchdog still settles. - Drop backdrop-filter: blur on the two overlays sitting over the canvas — the browser was recomputing the blur every animated frame. Solid pills read just as well at zero per-frame cost. Verified in-browser: heavy 40x40 surface renders smoothly, effective DPR 1.49, no console errors. 93 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same pipeline as the math set: a multi-agent workflow authored one parameterized demo per intro-physics topic (kinematics, forces, energy, momentum, rotation, gravitation, oscillations, waves, sound, fluids, thermo, electrostatics, circuits, magnetism, optics, modern), each self-validating its code; then re-validated here through validate_scene.js — all 120 pass (run, paint on-screen, animate, labeled, sliders with real units). - physics_library_generated.json: the 120 validated physics demos. - demo_library.py: loads + merges math demos AND physics. DEMO_LIBRARY is now 332 (12 base + 200 math + 120 physics). - Physics topics route correctly (projectile, F=ma, SHM, Ohm's law, Snell, Doppler, momentum, centripetal, photoelectric, RC, torque, buoyancy...). A physical-correctness audit (same adversarial review that fixed 11 math demos) is running; its fixes land in a follow-up. 93 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The app is origin-agnostic (relative paths, CSP 'self'), so a custom domain needs only DNS. Document the Render custom-domain + registrar DNS flow, and note the site works with no API key (demos/library served without model calls). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review of all 120 physics demos: 111/120 correct (92.5%), 9 fixed and re-validated. Real conceptual bugs caught: - ph-orbits-keplers-laws: planet swept at constant angular speed (violates Kepler's 2nd law / equal areas). - ph-normal-force: normal arrow not perpendicular to the incline surface. - ph-concave-convex-mirrors: focal point and center drawn behind the mirror. - ph-lenzs-law: loop area pinned to slider max. - ph-doppler-effect: wavefronts microscopic, no visible bunching, source motion decoupled from the v_src slider. - 4 minor (density pressure bar, wave-speed crest on a node, transformer secondary, inclined-plane direction). Every fix re-checked through validate_scene.js; all 120 still validate. 93 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Sound batch produced 120 of 121 topics. Hand-wrote the missing one (ph-standing-waves-pipes-strings: harmonics f_n = n v / (2L) with nodes, fixed ends, breathing standing-wave pattern), validated via validate_scene.js. All 121 physics topics now covered. DEMO_LIBRARY total: 333 (12 + 200 math + 121 physics). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two new capabilities wired into plan_visualization (step 1.4, before the
demo/library/generative paths), in a new chemistry.py module:
- Molecule mode: a chemical formula ("CH4", "C6H12O6") or known name
("benzene", "ethanol") renders a 3D ball-and-stick structure (CPK colors,
depth-sorted spheres, multi-order bonds, element labels). Geometry comes
from a VSEPR generator (correct single-centre hydride/halide angles from
valence electrons) plus a curated library: 12 computed seeds + 52
workflow-generated molecules (organics, oxoacids, polyatomic ions,
multi-center inorganics) that were adversarially chemist-verified, then
gated deterministically (atom multiset == formula, sane bond lengths, no
overlaps, single connected graph) and render-validated via the headless
node validator.
- Balance mode: a reaction ("C3H8 + O2 -> CO2 + H2O") is balanced exactly via
the rational null space of the element-composition matrix, then rendered as
reactant cards (with coefficient badges) -> products + a per-element
conservation tally. Layout is width-responsive so 6-species redox reactions
fit (e.g. 2 KMnO4 + 16 HCl -> 2 KCl + 2 MnCl2 + 8 H2O + 5 Cl2).
Detection is conservative — formulas/reactions/known names only, never
hijacking math or physics prompts.
Also refine the AI tutor's Claude call: adaptive thinking + effort=medium +
max_tokens 8000 (was no-thinking / 2000) for stronger step-by-step,
beginner-proof math explanations.
Ships chemistry.py + chemistry_molecules_generated.json via the Dockerfile.
23 new tests (parser, exact balancer incl. permanganate redox, VSEPR shapes,
conservative detection, node render-validation of every library molecule);
full suite 116 passing. Verified end-to-end in the browser preview.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
25 entries refined from the correctness audit (20 demo, 4 physics, 1 scene). Every demo and scene still passes the headless node validator (runs, paints, animates, stays on-screen) via the existing test_every_* gates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A second flavor of VisualLM under web/ that runs 100% in the browser with no server, no install, and no API key — meant for GitHub Pages so it's shareable by link. What works fully client-side (verified parity with the Python desktop app): - Chemistry: formula -> 3D ball-and-stick molecule (VSEPR + 52-molecule library), reaction -> exact integer-balanced equation with conservation tally. Ported from chemistry.py to web/js/chemistry.js (BigInt-exact balancing). - 333 interactive curriculum demos + 50 curated scenes, with the same keyword matcher (web/js/matching.js) and live sliders. - A window.fetch shim (web/js/browser-backend.js) answers /api/* locally so the desktop app.js/sandbox-worker.js/styles.css run essentially unchanged. Free-form AI generation and the AI tutor stay desktop-only (they need server-side model keys); the browser edition shows a friendly "use the desktop app" card. Data bundles (web/data/*.json) are exported from the Python libraries, scene code pre-sanitized with the real sanitizer so browser == desktop output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87 new interactive curriculum demos (44 Geometry, 43 Calculus) generated by a 40-agent workflow, each self-validated then correctness-audited, then independently re-validated through validate_scene.js (all 87 pass: ok/painted/text/onscreen). 4 audit fixes applied; calc-deriv-ln given a slider. - Geometry: angles, triangles (Pythagorean, special, similarity, trig, laws of sines/cosines), polygon angles, circle theorems (inscribed/central, arcs, tangents, chords, power of a point), coordinate geometry, transformations, 3D volume/surface area, Heron's, and more. - Calculus (front half): limits (intuition, one-sided, factoring, squeeze, at infinity, continuity, IVT), derivatives (limit def, power/product/quotient/ chain, trig/exp/ln, implicit, higher-order), and applications (tangent line, linear approx, related rates, extrema, concavity, optimization, MVT/Rolle, L'Hopital, Newton's method, kinematics). Calculus demos carry deeper 4-6 sentence explanations. Wired into demo_library.py (geometry_calculus_generated.json -> 420 demos total) and re-exported to web/data/demos.json for the browser edition. 116 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A new "worked solution" path: when a prompt is a SPECIFIC numeric problem (real numbers + labels) rather than a topic name, it renders an animated slide deck that solves it gradually WITH the student's own numbers — distinct from the topic demos (which still teach the general method for "law of sines"). First solver: triangles. Parses given sides/angles, classifies the case (SAS / SSS / ASA / AAS / SSA), solves every unknown including the ambiguous SSA case (0, 1, or 2 triangles), and steps through set-up -> law used -> computation, drawing the triangle to scale. e.g. "a=12, b=14, angle A=40" -> the ambiguous case with BOTH solutions shown. - solver.py + web/js/solver.js (verified py/js parity: same cases, solutions, step titles across 12 inputs; all scenes pass validate_scene.js). - Routes in plan_visualization (desktop) and the browser planner after chemistry, before the topic demo. Requires numbers+labels so topics don't trigger it. - app.js / web/app.js: "Worked solution" confidence label + engine name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The browser edition loaded all 420 demos (~1.3MB) up front and held every
scene body in memory — the source of the load-time lag, and it would only get
worse with more demos. Split into:
- web/data/demo_index.json (~160KB): id/area/topic/title/equation/keywords
for ALL demos, loaded once for matching.
- web/data/demos/<id>.json (~2.7KB each): the heavy body (code/params/
explanation/bullets), fetched only when that demo is selected, and cached.
Upfront payload drops ~1.5MB -> ~410KB; only one small file loads per demo.
planner.plan() is now async (awaits the per-demo fetch); the fetch shim awaits
it. Verified: index loads, demos/<id>.json fetched on demand, demo renders.
(Desktop edition already sends one scene at a time, so it's unaffected.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
182 new interactive demos (97 AP Physics, 85 AP Chemistry) from a workflow, each self-validated then independently re-validated through validate_scene.js (all 182 pass ok/painted/text/onscreen, all have sliders). - AP Physics: kinematics, dynamics, energy, momentum, rotation, SHM/waves, fluids, thermodynamics, electrostatics, circuits, magnetism, induction, optics, and modern (photoelectric, de Broglie, half-life, E=mc^2). - AP Chemistry: atomic structure/PES, bonding/VSEPR, stoichiometry, gases, thermochemistry, kinetics, equilibrium/ICE, acids-bases/titration/buffers, thermo & electrochem (Gibbs, galvanic cells, Nernst). Wired into demo_library.py (ap_physics_chemistry_generated.json -> 602 demos total) and re-exported to the lazy-load browser structure (demo_index.json + web/data/demos/<id>.json). 116 tests pass. Note: the generation workflow hit a monthly spend limit near the end, so ~9 of the planned AP Chemistry topics (last two batches) and most correctness audits didn't run; the shipped 182 are all independently re-validated. The remaining topics + a correctness-audit pass can be added when the limit resets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A bare equation like "E=mc^2" tokenized to junk ({e,mc,2}) that missed
keyword/title matching, so it landed on an unrelated demo. Added equation-form
matching to the scorer: extract the equation token from the prompt, normalize it
(keeping '=' so the match is anchored and false-positive-resistant), and compare
to each demo's normalized equation / title / equation-keywords, with an
order-independent (commutative-product) fallback so "E=CM^2" also finds E=mc^2.
Now routes correctly: E=mc^2, E=CM^2, F=ma, F=am, V=IR, V=RI, PV=nRT,
a^2+b^2=c^2, E=hf, p=mv, v=fλ, KE=1/2mv^2, y=mx+b, Q=mcΔT, pH=-log[H+], F=kx,
W=Fd — and with surrounding prose ("show me E=mc^2").
main.py (_norm_eq/_prompt_equation/_equation_boost in _scene_score) and the
browser web/js/matching.js, with verified py/js parity across 24 inputs.
Non-equation prompts are unaffected; 116 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From a hands-on student evaluation:
P0 routing — typed equations + prose false-positives:
- Extend the worked-solution solver to NUMERIC quadratics and linear equations
(solver.py + web/js/solver.js, parity): "x^2-5x+6=0" now solves step-by-step
(standard form -> discriminant -> quadratic formula -> roots, parabola drawn)
instead of landing on a "0/0 limit" demo. Handles real/repeated/complex roots.
- Suppress weak prose matches: a multi-word sentence whose only match is a
common everyday word ("how do vaccines work", "power dynamics in a
relationship") no longer serves a confidently-wrong demo — it falls to the
honest help card / AI path. (_weak_prose_match in main.py + matching.js.)
P0 — step-by-step solver honesty: the solver now covers triangles + quadratics +
linear (not just triangles), and the chat suggestion chips scrollIntoView so a
click is visibly acknowledged instead of silently filling an off-screen box.
P1 — mobile reorder: on <=900px the workspace is Prompt -> Canvas -> Explanation
so the primary action isn't buried two screens down (styles.css).
P1 — two tutor markdown bugs (app.js + web/app.js): strip LaTeX \left/\right with
the backslash (was deleting the English words "left"/"right" from prose); and
require non-space inside * for italics so "gamma * m * c^2" isn't italicized.
P2 — badge overlap: move the orbit hint to the top-right so it can't collide with
the bottom-left status pills on a narrow 3D canvas.
Verified: quadratic/linear solver renders + py/js parity; prose -> help; mobile
order Prompt/Canvas/Explanation; regex fixes; 116 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "no clear match" help card was cluttered and redundant — it said
"Browser edition" three times (canvas title, the three example boxes, and the
two bottom overlay pills) and listed examples that duplicate the left panel.
- Strip the help canvas down to a single calm, centered call-to-action
("Type a formula, a reaction, or a topic / then press Visualize…") with a
subtle breathing accent. No example boxes, no "try one of these" line.
- Hide the canvas-overlay pills (scene tag + status) on the help card via a
`.canvas-frame.bare` class toggled from updatePanels when browser_help — the
card is self-explanatory, so the chrome was just noise.
planner.js (HELP_CODE) + app.js/web/app.js (bare toggle) + styles.css.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AI relies only on code (no local apps):
- Remove Ollama entirely from main.py (HTTP bridge, generate_with_ollama,
chat_with_ollama, OLLAMA_URL/PREFERRED_MODELS, and every Ollama branch in the
gen pipeline / repair / tutor / get_health). Provider chain is now cloud-only
(Claude > OpenAI > Gemini); rename OLLAMA_SCENE_PROMPT -> JSON_SCENE_PROMPT.
- With no key, chemistry/reactions/solver/demos route in pure code; a novel
free-form prompt returns the friendly fallback scene instead of erroring.
- get_health() no longer probes localhost; keyless state is a neutral
"Code-only" badge. Update .env.example, requirements.txt, launch.py, README,
build scripts, and browser-backend health. All 116 tests pass.
Canvas light theme (both editions):
- sandbox-worker.js gets THEMES.{dark,light} + applyTheme() mutating COLORS/
PALETTE in place so a running scene repaints live (no recompile). Theme is
passed via init/run messages + a new {type:"theme"} message on toggle.
- CSS: light .canvas-frame + overlay pills; molecule bond color -> H.colors.sub.
Value-selection controls, more professional + CSP-fixed:
- The demo parameter panel was styled via a JS-injected <style>, which the CSP
(style-src 'self') silently blocked -> it was unstyled in the browser. Move
the styles into styles.css (theme-aware via CSS variables): a clean card,
labelled header, compact name|slider|value-chip rows, custom slider thumbs.
Layout: shrink the top row + side columns and lift the AI tutor so the whole
page fits one viewport without zooming (clamped stage row, tighter paddings).
Desktop app + dependency installer:
- Add install.sh / install.bat: create a .venv, install anthropic + pywebview
(verified end-to-end). Desktop app runs native (pywebview) or app-style.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- App-shell: topbar + self-adapting top + drag handle + tutor fit one viewport with no whole-page scroll; each region scrolls internally. - Drag handle stretches/shrinks the tutor (mouse, touch, keyboard arrows), persisted to localStorage and restored pre-paint; the top self-adapts to whatever height remains. - Demo parameter sliders never clip — the canvas shrinks to make room. - Mobile reverts to a normal scrolling stack (resizer hidden) so no stacked card is trapped off-screen. - CSP: add `file:` to the fetch directives so a from-disk preview can load its own stylesheet/scripts. Inert over http(s) (a remote page can't load file:// resources), so the deployed site's security is unchanged. Web: styles.css?v=20, app.js?v=br9. Desktop: styles.css?v=19, app.js?v=29. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Study Packs / DLC — the Resources tab is now a pack hub (both editions): - 3 official packs by subject (Math Studio / Physics Lab / Chemistry Lab): lightweight area-filter manifests over the built-in demos + one hand-built experiment each. - Browse a pack, launch any demo or experiment into the canvas. - Import a .dlc.json (validated, sandboxed, persisted); export/download any pack. - Desktop: /api/packs, /api/pack, /api/demo, /api/dlc/* in main.py + a new dlc.py; an AI-analysis form that turns pasted material or a link into a generated custom pack (one Claude call per topic). Desktop-only for now; browser imports. - Browser: the same endpoints answered offline in browser-backend.js. Resizable top columns — drag the vertical handles to change the WIDTH of the Explanation / canvas / Prompt cards (height stays app-shell-governed); persisted. Quick Questions actually work now — scene-aware chips send on one click and get a real answer (offline from the scene's explanation in the browser; the AI tutor on desktop). Web bumps: styles v=24, app br13, planner v=6, browser-backend v=6. Desktop: styles v=21, app v=31. 116 tests pass; both editions preview-verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DLC export now downloads a .zip that unzips to a browsable folder — manifest.json + demos/<id>.json + experiments/<id>.json. Import accepts a .zip (reassembled client-side) or a raw .dlc.json (back-compat). Round-trips preserve everything, including demo code. Dependency-free STORE zip encoder/decoder (web/js/zip.js + root zip.js) so it works under the browser edition's strict CSP — validated against Python zipfile and system unzip. Purely a frontend packaging layer; the /api/dlc/* backend still exchanges DLC JSON, so no server format change. Web: zip.js?v=1, app.js?v=br14. Desktop: zip.js?v=1, app.js?v=32. Both editions preview-verified (import a .zip → installs + launches; export → re-importable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on the 3D engine + multi-provider work already in
main(Safety netsHEAD). The throughline: make VisualLM fast, reliable, and genuinely teachable — catch errors before the browser, serve instant correct content for known topics, cover the full Algebra→Precalc curriculum interactively, and make the tutor a beginner-proof step-by-step solver.Headless validator + server-side validate/repair
validate_scene.jsruns each generated scene in a hardened Nodevm(fresh empty context — noprocess/require/import; constructor-escape contained; 2s timeout) and reports throws / blank / off-screen / no-motion / no-labels in ~50ms.Math.*/PI/TAU(the Refine generation→repair pipeline: fewer crashes, faster loads, converging repairs #1 "X is not defined" crash) with zero model calls.Curated 50-scene STEM library + retrieval + cache
212 interactive curriculum demos (Algebra 1 / Algebra 2 / Precalculus)
P.<name>into the scene, the graph updates live, and an explanation builds intuition. Covers the full requested topic list: 60 / 70 / 70, 100% coverage, zero gaps.validate_scene.js(all 200 pass: run, paint on-screen, animate, labeled, sliders used).a<0).Tutor: step-by-step, beginner-proof
\frac{{}},\,/\quadspacing,^\circ→°,\mathrm, residue backslashes.Also in this branch
make_app.sh,make_icon.py, PyInstaller bundle, one-click launcher).stem-vizClaude Code plugin/skill wrapping the engine.Reviewer notes
refine-generation-repair-pipelinebranch (this is a superset).validate_scene.js; the test suite asserts the libraries run. 93 tests pass.🤖 Generated with Claude Code