···204204205205function act({ event: e }) {
206206 if (!e?.is) return;
207207- // Dump first ~15 events of any kind so we can see what actually propagates
208208- // into the piece from jweb~. `name` is the AC event type string.
209209- if (eventsSeen < 15) {
210210- eventsSeen += 1;
211211- const name = e.name || "?";
212212- console.log(`🎹 evt#${eventsSeen} ${name} key=${e.key || ""}`);
213213- }
214214- // Click-to-test-note: tap anywhere on the device UI fires C4 so we can
215215- // verify the Max bridge path without depending on keyboard focus.
207207+ // Keyboard input is handled natively in the Max patcher ([key] / [keyup]
208208+ // objects direct to [noteout]) for sub-ms latency — no worker round-trip.
209209+ // The piece intentionally does NOT listen for keyboard events here to
210210+ // avoid double-firing.
211211+ //
212212+ // Click-to-test-note: tapping the device UI fires C4 so we can smoke-test
213213+ // the iframe→BIOS→Max bridge path without depending on keyboard focus.
216214 if (e.is("touch")) {
217215 pressLocalKey("c");
218216 return;
···220218 if (e.is("lift")) {
221219 releaseLocalKey("c");
222220 return;
223223- }
224224- for (const key of Object.keys(KEY_TO_PITCH)) {
225225- if (e.is(`keyboard:down:${key}`)) {
226226- pressLocalKey(key);
227227- return;
228228- }
229229- if (e.is(`keyboard:up:${key}`)) {
230230- releaseLocalKey(key);
231231- return;
232232- }
233221 }
234222}
235223···321309322310 // Footer hint
323311 if (H - y > 12) {
324324- ink(...fgDim).write("click=C4 | keys c-b=C4..B4", { x: 4, y: H - 8 });
312312+ ink(...fgDim).write("keys=native | click=C4 | ws=relay", { x: 4, y: H - 8 });
325313 }
326314}
327315