Skip to content
Draft
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
3c06704
build: allow a pure-Python wheel, and drop pkg_resources
sspickle Aug 10, 2026
b64d142
feat: a wasm/Web-Worker transport — the host supplies the pipe
sspickle Aug 10, 2026
ae16fc7
feat: host-agnostic browser front-end ported from glowcomm.js
sspickle Aug 10, 2026
58c22ff
fix: don't clobber glow's __context, and drop (not forward) synthesiz…
sspickle Aug 10, 2026
379672f
feat(worker): eager boot + async rate/sleep + loud deferrals for wasm…
sspickle Aug 10, 2026
3772014
fix(worker): render cap, rate(0) parity, fixture isolation, stale com…
sspickle Aug 10, 2026
502d6f8
feat(worker): port glowcomm's event capture — mouse, keys, camera
sspickle Aug 10, 2026
0a6ddbe
fix(worker): tell the front-end when the clock stops; poll the canvas…
sspickle Aug 10, 2026
393c1d6
feat(worker): poll() — a pacing tick that stays quiet while rate() dr…
sspickle Aug 10, 2026
47875af
fix(worker): the loud-deferral list was five constructs short, and sl…
sspickle Aug 10, 2026
ec6d59d
fix(worker): rate(N) slept a full period ON TOP of the student's loop…
sspickle Aug 10, 2026
68eff8c
fix(worker): the await is right, the reason given for it was wrong
sspickle Aug 10, 2026
2504893
docs(worker): the mechanism was corrected everywhere EXCEPT the code …
sspickle Aug 10, 2026
c8fd10a
ci: build the pure-Python (py3-none-any) wheel as a pinned-artifact f…
sspickle Aug 15, 2026
77521bb
ci: never publish pre-releases to PyPI
sspickle Aug 15, 2026
6c357e6
ci: fix invalid if under on.release — the guard belongs on jobs only
sspickle Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: don't clobber glow's __context, and drop (not forward) synthesiz…
…ed events

Two review findings on the port.

1. The canvas case handed glow a raw DOM element and replaced __context on
   every canvas cmd. __context is glow's own scratch space (canvas_selected,
   canvas_all, print_container), and upstream ran its container lookup at most
   once, so replacing it wipes state belonging to canvases already on the page.
   glow also stores the container as a jQuery object — the setter is
   `glowscript_container = $(value)` and the print path calls
   `canvas.container.css(...)`, which throws on a bare element. Now merges into
   any existing __context and wraps with glow.$ / $ / jQuery, warning if it
   cannot find one.

2. Four of the six deferred stubs forwarded a synthesized payload to opts.send.
   Those payloads are not protocol-valid: control_handler emitted {idx, widget}
   with idx/value missing, and process_binding/process_waitfor emitted
   {event: undefined}. vpython.py handle_msg (vpython.py:398-427) routes any
   event with a 'widget' key through object_registry[evt['idx']] and reads
   evt['value']/evt['text'] before calling the user's bind callback, and routes
   everything else through object_registry[evt['canvas']] into
   canvas.handle_event's `ev = evt['event']` (vpython.py:3288). A partial event
   raises inside the kernel's message loop, so once a live send is wired any
   widget click or bound event would crash the run. control_handler,
   process_binding, process_waitfor and process_pause are now warn-and-drop.

   send_pick and send_compound keep forwarding — their payloads are byte-faithful
   to glowcomm.js, and both are synchronous barriers Python blocks on.

Pinned by the consumer's unit suite, which now fails against the previous commit
on both points ("expected undefined to deeply equal [ 'pre-existing' ]" and
"expected [ [ { idx: undefined, ... } ] ] to deeply equal []").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G7y9rTA1r8r8EhEnPSQenR
  • Loading branch information
sspickle and claude committed Aug 15, 2026
commit 58c22ff9e52153b963003014bb8a91776d007c0b
59 changes: 48 additions & 11 deletions vpython/vpython_libraries/glowcomm_host.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,35 +64,55 @@ function createGlowFrontend(opts) {
// Comm. Wiring them to opts.send is a later task; until then they are loud.
// ---------------------------------------------------------------------------

function not_wired(feature, events) {
function not_wired(feature) {
if (typeof console !== 'undefined') console.warn('glowcomm_host: ' + feature + ' not wired yet');
if (send) send(events);
}

// These two CAN be forwarded: the payloads below are byte-faithful to
// glowcomm.js, so Python's handle_msg understands them as-is. Both are
// synchronous barriers on the Python side (it blocks for the answer), so a
// host that supplies send() gets working compound/text/extrusion and pick
// even before the full event channel is ported.

function send_pick(cvs, p, seg) {
not_wired('pick', [{event: 'pick', 'canvas': cvs, 'pick': p, 'segment': seg}]);
not_wired('pick');
var evt = {event: 'pick', 'canvas': cvs, 'pick': p, 'segment':seg};
if (send) send([evt]);
}

function send_compound(cvs, pos, size, up) {
not_wired('compound/extrusion/text measurement', [{event: '_compound', 'canvas': cvs,
'pos': [pos.x, pos.y, pos.z],
'size': [size.x, size.y, size.z], 'up': [up.x, up.y, up.z]}]);
not_wired('compound/extrusion/text measurement');
var evt = {event: '_compound', 'canvas': cvs, 'pos': [pos.x, pos.y, pos.z],
'size': [size.x, size.y, size.z], 'up': [up.x, up.y, up.z]};
if (send) send([evt]);
}

// These four must NOT forward anything. Their real payloads are built by the
// deleted process()/control_handler(), which read live mouse/key/widget state;
// anything this file could synthesize is a partial event, and a partial event
// does not fail politely on the Python side. vpython.py handle_msg() sends
// every event without a 'widget' key down `cvs = object_registry[evt['canvas']]`
// (vpython.py:425) and then canvas.handle_event()'s `ev = evt['event']`
// (vpython.py:3288); an event WITH a 'widget' key indexes object_registry by
// evt['idx'] and then reads evt['value']/evt['text'], and calls the user's
// bind callback. Either way a synthesized stub raises KeyError inside the
// kernel's message loop. Warning and dropping is the honest behaviour until
// Task 9 ports the real event capture.

function process_binding(event) { // event associated with a previous bind command
not_wired('event bindings', [{event: event && event.type, bind: true}]);
not_wired('event bindings');
}

function process_waitfor(event) {
not_wired('waitfor', [{event: event && event.type, bind: true}]);
not_wired('waitfor');
}

function process_pause() {
not_wired('pause', [{event: 'click'}]);
not_wired('pause');
}

function control_handler(obj) { // button, menu, slider, radio, checkbox, winput
not_wired('widgets', [{idx: obj && obj.idx, widget: obj && obj.objName}]);
not_wired('widgets');
}

var waitfor_canvas = null;
Expand Down Expand Up @@ -443,8 +463,25 @@ function createGlowFrontend(opts) {
// glowcomm.js looked up Jupyter's '#glowscript' div here. The
// host tells us where its scene goes instead; GlowScript reads
// the mount point off the global __context.
//
// Two things matter. (1) __context is GlowScript's own scratch
// space — canvas_selected, canvas_all, print_container — so
// merge into it; replacing it wipes state belonging to canvases
// already on the page (upstream did the lookup at most once,
// this runs on every canvas cmd). (2) glow stores a *jQuery*
// object (`glowscript_container` set does $(value), and the
// print-area path calls container.css(...)), so a raw element
// has to be wrapped or the first print() throws.
if (container !== null) {
globalThis.__context = { glowscript_container: container };
var jq = glow.$ || globalThis.$ || globalThis.jQuery;
var ctx = globalThis.__context || (globalThis.__context = {});
if (jq) ctx.glowscript_container = jq(container);
else {
if (typeof console !== 'undefined') console.warn(
'glowcomm_host: no jQuery found to wrap the container; ' +
'GlowScript expects $(container) and print() will fail');
ctx.glowscript_container = container;
}
}
glowObjs[idx] = glow.canvas(cfg);
glowObjs[idx]['idx'] = idx;
Expand Down