← Blog
0.7.0 · APPS · BROWSER · EVENT

Event Loop — ev_overlay Click/Keydown → DOM Hit-Test → Dispatch (sigil-apps dacb955)

June 22, 2026 · sigil-apps dacb955 · Sigil-Docs
browser events dom input 0.7.0

apps/web/event_loop.sg (sigil-apps dacb955) is the T8 sprint deliverable — the browser event loop that connects input events to DOM nodes. It reads ev_overlay events (24B each: type|x|y) from ev_buf @0x300000, hit-tests DOM layout boxes at lb_buf @0x740000 for click events, and dispatches to a focused node for keydown. Hit counters accumulate at hit_buf @0x7B0000. Test: 5 events → 4 dispatched (click h1/p/img + keydown→h1-focus, 1 miss). EVENT-LOOP-PASS 4/4. 13480B arm-el0.


Event format and memory layout

ev_overlay event: 24B at ev_buf @0x300000 + i×24

FieldOffsetTypeValues
type+0i321=click, 2=keydown, 3=mousemove
x+8i32pixel X (click/move), keycode (keydown)
y+16i32pixel Y (click/move), 0 (keydown)
ev_overlay source
ev_overlay events arrive from the compositor (comp_present writes to ev_buf after each frame). The event loop drains the buffer ev_count events at a time.
mousemove (type=3)
Read and discarded in this sprint. Only click (type=1) and keydown (type=2) are dispatched.

DOM layout box: 32B at lb_buf @0x740000 + node_idx×32

FieldOffsetTypeMeaning
x+0i32left edge (pixels)
y+4i32top edge (pixels)
w+8i32width (pixels)
h+12i32height (pixels)
node_idx+16i32which DOM node this box belongs to
flags+20i321=visible, 0=hidden
lb_buf ownership
lb_buf is written by layout_box.sg (f33524f). event_loop.sg reads it read-only — it does not modify layout.

Hit-testing algorithm

ev_loop_click_hit(ex, ey) → node_idx

walk lb_buf
For a click event at pixel (ex, ey), walks all lb_count layout boxes at lb_buf. For each box where flags=1 (visible): tests ex >= box.x && ex < box.x + box.w && ey >= box.y && ey < box.y + box.h.
first match wins
Returns the node_idx of the first box whose bounds contain (ex, ey). Hit-test order matches lb_buf order, which is DOM order (depth-first). First match wins — innermost-painted element gets the event, consistent with standard browser hit-test semantics.
miss → -1
Returns -1 if no box matches.

hit_buf @0x7B0000 — per-node hit counters

hit_buf layout
hit_buf holds one i32 per DOM node (indexed by node_idx). On a successful hit for node N: hit_buf[N] += 1. On a miss: no counter incremented.
ev_loop_hit_count
ev_loop_hit_count(node_idx): returns hit_buf[node_idx]. Hit counters are monotonic — they accumulate across all ev_loop_tick() calls for the session. Used by the test to verify dispatch: after the 5-event test, check that hit counts match expected dispatch targets.

Keydown dispatch + focused node

Focus model: single focused node

ev_focused_node
Global i32 at ev_state @0x7C0000 + 0, initialised to 0 (first DOM node is default focus). ev_loop_focus(node_idx): sets ev_focused_node = node_idx.
no tabindex / blur
There is no tabindex, no :focus CSS selector, no blur event in this sprint — focus is a single int that the event loop reads on keydown.

ev_loop_keydown(keycode) — dispatch to focused node

focus update on click
On keydown (type=2): reads ev_focused_node. Calls ev_loop_focus_set(node_idx) if a prior click updated focus — any successful click hit sets focus to the hit node.
keycode field
The x field of the keydown event carries the keycode (raw HID keycode from hid_kb_report), but event_loop.sg does not decode it — it only dispatches the event to the focused node. Keycode decoding is deferred to a future T9 sprint (text input).
hit counter
Increments hit_buf[ev_focused_node] on dispatch.

EVENT-LOOP-PASS test

Test sequence: 5 events injected, 4 dispatched, 1 intentional miss.

EventtypexyExpectedResult
1click5020hit h1 (node 0)✅ hit_buf[0]=1
2click5060hit p (node 1)✅ hit_buf[1]=1
3click50100hit img (node 2)✅ hit_buf[2]=1
4keydown0x04 (keycode A)0dispatch to focused node (h1=0)✅ hit_buf[0]=2
5click999999miss (out of bounds)✅ no counter incremented

4/4 dispatched (1 intentional miss). EVENT-LOOP-PASS. 13480B arm-el0.


Pipeline position

event_loop.sg slots between comp_present (which writes to ev_buf) and any future input handler (text input, scroll, hover). The browser event pipeline is now:

input hardware
  → hid_kb_report / compositor overlay
  → ev_buf @0x300000
  → event_loop.sg  hit-test (lb_buf @0x740000) + dispatch
  → hit_buf @0x7B0000  per-node counters
  → (T9: text insertion into DOM)

event_loop.sg does not own the layout boxes and does not write to the DOM — it is a pure dispatch layer. It reads ev_buf, reads lb_buf, and writes only hit_buf and the ev_focused_node slot in ev_state. The T9 sprint will read the focused node and keycode to perform text insertion.