Skip to content

ZONA touch zones: Touch blocks and the zona library - #1672

Open
kkerti wants to merge 77 commits into
stablefrom
feat/zona-sandbox
Open

kkerti wants to merge 77 commits into
stablefrom
feat/zona-sandbox

Conversation

@kkerti

@kkerti kkerti commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Touch zones for ZONA. The user places faders, buttons and XY pads on the 9×9 surface with Touch blocks in the touch element's Setup. A bundled Lua library, zona.lua 0.11.0, runs them on the module. The Library block from #1669 (merged) loads it. The spec is docs/zona-touch-zones.md. It describes the code as built and lists what was dropped on the way.

A zone is one line: its cells and its options. The library reads the sensor, sends one output per zone, and lights the LEDs. There is no body and no Touch Start.

System Setup   Library block   _Z=gr("zona")                              (#1669)
Touch Setup    Touch Fader     _Z.fader(self,"f1",0,0,1,9,{})
               Touch XY Pad    _Z.xy(self,"p1",3,0,4,4,{msg="mouse"})
               Touch Button    _Z.button(self,"b1",1,7,2,2,{msg="pad"})
Touch Timer    Touch Tick      _Z.tick(self,{})   glide, spring, rate limit, mouse moves, LEDs

The first zone line of a Setup run takes the element: sensor range, touch_cb, a timer kick. The first tick or touch after Setup makes the zones live.

Output Fader Button XY pad
MIDI CC (7/14-bit), Pitch bend Note, CC two CCs; fingers 1–5 on channels ch+k−1
Mouse wheel Left/Right/Middle; Toggle is a drag lock trackpad, tap to click
Gamepad axis −128…127 button two axes; Spring makes a joystick

Channels, CC numbers and gamepad targets are automatic in Setup order unless set. The face shows only the chosen output's fields and the resolved autos. The control name overlay draws the zones on the module.

src/content/libraries/zona.lua              # the library, 17,914 bytes compressed (test ceiling 20,480)
src/renderer/config-blocks/Touch*.svelte    # Touch Fader, Button, XY Pad, Tick
src/renderer/config-blocks/touch/           # model, parse, validate, autos, face
src/renderer/main/.../overlays/TouchZonesOverlay.svelte
src/renderer/tests/zona-vm.js               # Lua VM harness (wasmoon)
src/renderer/tests/zona-lib.test.js         # the library
src/renderer/tests/zone-editor.test.js      # parse, validate, autos, overlay, face
docs/zona-touch-zones.md                    # the spec

Shared editor code touched:

 ActionBlockInformation
+  category "touch"
+  defaultLuaIn        # a new zone gets the next free name
+  elementTypes, eventTypes, moduleTypes   # picker filtering (ActionPickerFilter.ts)
+  companions          # the first zone brings Touch Tick
+  uniqueIn            # a pasted or dropped zone is renamed (GridEvent.makeUnique)
+  contextProblem      # a duplicate zone name makes the action invalid (ActionData.isValid)

Blocks that don't set these fields behave as before.

Evidence

  • Unit tests: npm test passes: 7 files, 93 tests. The ZONA tests run the compressed library in a Lua VM and check that the CC, channel and gamepad targets the editor shows are what the library sends.
  • Device, from the commits:
    • 0.11.0 on the ZONA: a cmd="pb" fader sends pitch bend, cmd="cc" and default buttons send CC 33 and note 34, an XY sends CC 35/36. The faces show Output MIDI and the Command select.
    • 0.9.4 on the ZONA: a tap's click is held for a tick, so taps click and a double tap double-clicks. A press and release in the same scan were about 1 ms apart and no click registered.
  • Not yet hand-tested on the device: mouse and gamepad outputs. Please check a trackpad XY (Speed scales it), a wheel fader, a mouse button with Toggle as a drag lock, a gamepad fader axis, an XY joystick with Spring, and a gamepad button in a gamepad viewer.

Merge Danger

Door: two-way

New blocks and a new library file. Removing them leaves nothing behind in configs that don't use them. The library is pre-1.0, so its line format may still change before release. There is no migration for lines from earlier sandbox builds: they show as invalid or as code.

Blast Radius: ZONA

The Touch blocks are offered only on ZONA. Other modules see no new blocks. The shared pieces above are optional fields no other block sets. isValid() now also runs an optional per-block check, and paste, snippet and drop call makeUnique, which only acts on blocks that declare uniqueIn.

🤖 Generated with Claude Code

kkerti and others added 30 commits October 12, 2026 15:37
Drop virtual events from the core, add a generic page-folder Library
block, per-page library copies, read-only zone overlay, multi-output
zones, resolution, capture and clamping, mapping mode and MIDI RX sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zones become container blocks whose body holds ordinary action blocks,
with self as the zone answering the element methods (gms autos, gmrr,
glc/glp). Rename capture modes to focus/live, model the knob on
TouchOSC's Radial, enter mapping mode through a window after a utility
press, reuse the MIDI block's Receive section for RX sync, leave SysEx
to user code via Z.set, and resolve the remaining open questions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Confirmed on firmware 1.5.5. Update the resolution background, the
positions hint, the knob note and the risks, and add the default
txma/tyma value to the phase 0 checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 2.1: firmware event wrapper; how action strings and page files travel
  (compressScript = shortify + minify, also inside string literals)
- 6.1: compressed canonical Library line; global/file name rules
- 6.2: zone names exclude firmware lookup names
- 6.4: library uploaded like a File Manager .lua save; full-path invalidate
- 6.5: whole-event round-trip test rule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- libraries/registry.ts: bundled libraries (zona), version read from source
- libraries/library-script.ts: parse/serialize the Library line in the
  compressed form the module stores; reject reserved, keyword and firmware
  lookup names, and file names the minifier would rewrite
- libraries/zona/zona.lua: stub, Z.VERSION 0.1.0
- tests: round-trip, validation, whole-event compression property check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- config-blocks/Library.svelte: rows of global/file, validation with
  reasons, shadowing/duplicate warnings, registry + page-folder file
  suggestions, read-only fallback for non-canonical scripts; never
  rewrites an unchanged script
- ActionBlockInformation: optional elementTypes/eventTypes
- ActionPickerFilter.ts: isAllowedIn(); blocks with declared placement are
  not offered in virtual-event (function body) tabs
- library-script.ts: name problem reasons, shadowsBuiltinGlobal,
  formatLibraryScript; reserved names fail closed
- tests: picker filter, name reasons, reserved loop
- RFC 6.1/6.2: top-level placement rule

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- libraries/library-status.ts: status from folder listing + bound global
  (missing / not loaded / present / older / newer), button per status
- libraries/library-device.ts: list page libraries, read bound globals
  (rawget, no require), install via the File Manager save path with
  full-path invalidate, re-run Setup via ele[i].ini, log version on change
- registry.ts: compareVersions; version reader skips block comments and
  works on compressed sources
- Library.svelte: per-row status, Install/Update/Reload, inline errors
- FileManager: shared WRITE_CHUNK_SIZE (no behaviour change)
- RFC 6.1/6.4: status list, Setup re-run, bound-global read-back, plain
  require decision, v1 limit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e res, taps

Findings and decisions from building the zone core (phase 2, slice D):
- On firmware 1.5.5 the global gms takes exactly 4 arguments; 14-bit CC
  and NRPN are expanded in the Lua element method, so the zone gms does
  the same.
- The firmware runs Lua 5.5.0: never assign to a for-loop variable.
- Taps arrive as evt 9 (down+up in one scan); 3 and 7 also end a contact.
- Auto p1 counts CC slots (xy takes 2), so an XY pad no longer collides
  with the next zone.
- min/max default to the wire range (0-16383 for any res > 7); res sets
  the step count inside it.
- A press or release run on a continuous zone reports the main value, and
  the duplicate filter resets on press.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Grows the ZONA library from the version stub into the zone core:
Z.zone with fader/button/xy/knob presets, Z.start, Z.touch with focus
capture (edge clamp, leave), axis/gate/point readers, res/min/max, and a
zone self that answers gms (autos, CC slots, duplicate filter, 14-bit and
NRPN modes), get_auto_value/mode, ind and the value getters.

Tests run the compressed library in a wasmoon Lua VM with firmware
stubs. zone-model.ts holds the defaults table, checked against the
library's. Hardware check on the ZONA is still pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ware

Notes from building modes, knob and steps (phase 2, slice E1):
- d() is in the zone's value units.
- drag does not change mode; the block face writes mode="rel" with it.
- Glide speed is per tick, so glide time scales with the Touch Tick
  interval.
- Zones step like the Button Mode block (calculateStepValuesFirmwareStyle).
  The firmware has more states for some ranges; that mismatch is an
  editor issue of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the mode stage (abs, rel, glide) between reader and value, with
Z.tick driving glide through one approach function. Live zones pick up
contacts that drag across them; several contacts in one zone follow the
contact option (new, move, grab). The knob reads the angle over a 300
degree arc with pot-like pickup after the gap, or vertical/horizontal
drag. Gate zones get button-mode steps and radio groups (Z.group,
empty), and every zone gets the d() getter with fractional carry.

Verified in the wasmoon harness and with a Lua 5.5 compile check;
hardware check on the ZONA is still pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tick-driven repaint

Notes from building the painters (phase 2, slice E2):
- The serpentine LED order lives in the firmware's lookup, so cell (x, y)
  is glag(0, x+y*9) with y=0 the top row.
- shortify rewrites human names inside comments too, not only strings.
- Zone colour goes through the firmware's Lua color_curve, matching
  Simple Color on an element.
- LEDs repaint only from Z.tick, so the Touch Tick block comes with the
  first zone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zones now draw their value on the LEDs: fill, dot, comet, crosshair,
target and a knob arc, through one painter core that sends only cells
whose colour or phase changed. Simple Color and Simple Intensity work
inside a zone body. Repaints run from Z.tick with an LED divider; the
release option clears, fades or keeps the drawing. Sensitivity adds a
deadband, smoothing that converges on the tick, and a rate limit flushed
by the tick (off until the first tick).

A new test checks that library comments survive compressScript, which
rewrites firmware human names in comments too.

Verified in the wasmoon harness and with a Lua 5.5 compile check;
hardware check on the ZONA is still pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mapping window counted in ticks, RX keying and mode notes, Touch Utility
needs Touch Tick. Hardware: tick cost, global name clashes in the Library
block, stale midirx_cb after a config change (open), size budget 20 KB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Z.start runs each zone body once muted so gmrr registers RX; Z.rx decodes
7-bit, 14-bit, NRPN and pitch bend into value and LED sync (touch wins,
duplicate filter updated) and chains a user midirx_cb. Z.util and Z.tick
implement the mapping window and mapping mode: one source, one message
identity, other outputs muted, held zones released, a dim frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Touch blocks write Z. in v1; gtt is one-shot, so Z.start kicks the timer
and Z.tick re-arms it first; stale midirx_cb accepted for v1; examples in
the form the editor writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…timer

Option schema per kind, exact round-trip for zone, group and tick lines
(defaults left out, unknown options kept), per-zone and surface
validators. zona.lua 0.6.0: Z.start kicks the one-shot touch timer and
Z.tick re-arms it with cfg.ms, defaults in Z.tdef.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RAM, not file size, limits layouts on the module (measured). The Library
block calls a library's unload(self) and frees the global before re-running
Setup; a failed re-run leaves it unbound until the page reloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Options through a metatable chain, shared colour curves, lazy per-zone
tables, no per-surface LED/owner maps. The first zone of a Setup run frees
the previous surface; leftovers of a failed run are dropped at the next
tick or touch. Z.unload releases contacts, callbacks and the surface. The
Library block's Update/Reload unloads and frees the bound globals before
re-running Setup, and is disabled while the block has unsent edits.
RAM budget tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…name runs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….lua 0.7.1

Touch Fader/Button/XY/Knob insert the zone line, a default MIDI block and
Touch End, with the next free name. One schema-driven face per zone,
Group and Tick faces, Start and Utility. Blocks declare moduleTypes, so the
picker offers them only on ZONA. GridAction.updateData checks brackets on
the syntax preprocessor output when it wraps the script, so open zone
lines can be edited. zona.lua 0.7.1: a repeated pending zone name starts
a new run; a zone created inside a zone body raises an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ith the first zone

Paste, snippet insert and drag-drop rename zones whose name is taken; a
duplicate zone name marks both zones invalid so the Setup isn't sent.
Composite blocks can declare what their body accepts (picker and drop);
zone bodies take output blocks only. The first zone from the picker adds
Touch Start and Touch Tick when missing, and Touch Start stays last. The
Touch Start face shows surface warnings and the Touch Utility hint.
Fixes addActions without an index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d clash

Zones keep their own auto channel instead of the page's midi_auto_ch
(user decision).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lash warning

MIDI previews and auto suggestions inside a zone show what the zone sends
(TS mirror of the library's autos, checked in the Lua VM). Inside a zone
the default mode registers RX with the auto mode, like gms. Zone getters
in MIDI, Gamepad and Mouse value fields. Library block: a missing page
folder shows Install, which creates it; a row warns when another script on
the page assigns its global. Touch Utility adds Touch Tick on the touch
element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The control name overlay draws the touch Setup's zones on the ZONA: a
rectangle per zone with its name, kind icon, group badge and issue
outline, highlighted while its block or a block in its body is selected.
Clicking a zone shows the touch Setup and selects its block. The overlay
follows page changes and loads the Setup when it isn't loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hardware

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lt for non-CC

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kkerti and others added 16 commits October 26, 2026 19:24
…text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A row picks a bundled library from a list and shows its global as
text. zona is listed first, so Add Library on a ZONA picks it. RFC 6.1
updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the CTO-round loader: libraries declare LIB_NAME and
LIB_VERSION, Library blocks go in any element's Setup, each row loads
through a guard (_Z=r("zona")), and bundled sources live in
src/content/libraries with the loader in src/renderer/lib/library.ts.

zona.lua moves to src/content/libraries with its header (_Z, 0.8.7)
and is bundled next to the test library. The picker keeps the
sandbox's declarative filter and takes library-block's Setup check.
Zone tests use the guarded row format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dependency bumps from stable (electron 42, vite 7.3.7, vitest 4.1.12
and others); the lockfile keeps wasmoon for the zona VM tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Library blocks can sit in any element's Setup now, so Touch Start warns
only when no Library block that runs before it loads _Z: the System
Setup, Setups of lower-numbered elements, or blocks above it in the
touch Setup. The version lives in the file's LIB_VERSION header, so
zona no longer keeps its own copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each zone block's LED section has a colour field: the picker Simple
Color uses and a hex text field, written to the zone line as
col="rrggbb". The library paints the zone in that colour from the
first frame; without it the zone takes the module's default colour. The
collapsed header shows the colour as a dot. Simple Color in the body
still overrides it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gestions

A select lists only its values, with titles (CC, Note, Pitch bend,
None), and shows the kind's default when the line leaves the option
out. Number fields offer suggestions like the MIDI block: channels with
the resolved auto, CC names or notes by the message, resolutions with
their ranges, common max values, and Off/On for the 0/1 options. The
number is titled CC or Note by the message, and Receive becomes
"Receive MIDI".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The suite pinned many details while the RFC was being worked out. Keep one
test per thing a user would notice: each kind sends its MIDI, the defaults
match the editor, rx, Setup re-run, unload, LEDs on/off, size and RAM, and
the editor's parse, validate, autos and overlay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A zone is its options: one line ending in nil), sent by the library. The
library drops the body call, the per-finger self, the getters, the start run
and dynamic colour (col only). The Touch blocks become single blocks; Touch
End, body picker rules and the getter suggestions in the Mouse and Gamepad
blocks go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each zone picks one output in an Output select, and its face shows only that
output's fields. Mouse: an XY pad is a trackpad, a fader scrolls the wheel and
a button presses a mouse button (toggle: drag lock). Moves are summed and sent
once per tick, at most 127 steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A short touch that barely moves on a Mouse XY clicks left on lift, unless a
mouse button zone holds left. Tap to click is on by default; tap=0 turns it
off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Output = Gamepad: a fader or XY sends gamepad axes over the full range,
centred, and a button a gamepad button. Axes and buttons are numbered
automatically in Setup order, like the CC autos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A zone is its options with one Output (MIDI, Mouse or Gamepad) sent by the
library; the body, its getters and the start run are gone. Describes the
Mouse trackpad, wheel and button, tap to click, the Gamepad autos and the
face fields per output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Editor preview — https://pr-1672-grid-editor.intechstudio.workers.dev

Deployed from 3084a5d. The URL stays the
same for the life of this PR; each push replaces what it serves.

@github-actions

Copy link
Copy Markdown

kkerti and others added 12 commits October 28, 2026 15:09
A press and release sent in the same scan reach the computer about 1 ms
apart, inside one 10 ms HID poll, and the click is lost. The release now
goes out on the second Touch Tick after the press. Checked on the ZONA:
taps click, a double tap double-clicks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone lines pass the touch element: _Z.fader(self,"f1",0,0,1,9,{}). The
first zone of a Setup run drops the previous surface and takes the
element (sensor range, touch_cb, timer kick); the first tick or touch
after Setup makes the pending zones live. The Touch Start block, its
order warnings and the picker's keepLast are gone; the library warning
moves to the first zone's face.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
None was left from the body model, where a body read the zone's value
and sent it. Without a body nothing reads a None zone, so it goes from
the Output select, the library and the RFC. A msg="none" line shows
the usual unknown-value problem on its Output field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Output select is MIDI, Mouse or Gamepad. A MIDI zone picks its
message with the new Command option (cmd): CC or Pitch bend on a fader,
Note or CC on a button, CC on an XY. A pitch bend fader is now
{cmd="pb"}. Old msg="cc"/"note"/"pb" lines show as invalid.

Checked on the ZONA: a cmd="pb" fader sends pitch bend, cmd="cc" and
default buttons send CC 33 and note 34, an XY sends CC 35/36; the faces
show Output MIDI and the Command select.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The RFC had drifted through eight revisions. The new document describes
the code on the branch: the zone line, the options, the outputs, how
zona.lua behaves, the editor blocks, validation, picker and overlay, the
tests and the files. Its last section lists what the RFC had that the
code does not. Code comments that cited RFC sections point at the new
document.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Z.has in zona.lua: nothing read it. Companion.elementType and
companionElement: no block set it, and its comment named Touch Utility,
a block that no longer exists. A companion goes to the block's own
element.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ditor

Removes the Touch Tick and XY blocks and the zone timer, merges the fader, XY
pad and button into one block holding several zones, adds the position preview
and a 14-bit CC command, spring in percent, Library update prompt, and the
zona.lua 0.19.0 changes behind them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FUXbWVjHhrGx67gEQdhn1

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants