ReviewOS

craft-native/craft

Tray left clicks and menu actions never reach JavaScript, despite polling being enabled

#420
Closed glennmichael123 opened this 7 days ago ·
7 days ago

Summary

With --system-tray, the status item renders and its menu opens on right click, but neither craft:tray:click nor craft:tray:menuAction ever fires in the page. Left-clicking the item does nothing at all, and clicking an item in the right-click menu does nothing.

Every precondition on the JavaScript side checks out, so the break appears to be in native delivery.

What was verified from the page

Instrumented the real app and reported out over HTTP:

tray.js evaluated; craft=true tray=true
render -> setTitle/setIcon/setMenu: shield
POLLING FLAG = true | deliverTrayClick=function | deliverAction=function
  • window.__craftEnableTrayPolling === true, so the bridge's setInterval(() => _post('tray','pollActions',''), 100) is running
  • window.__craftDeliverTrayClick and window.__craftDeliverAction are both functions
  • setIcon / setTitle / setTooltip / setMenu all succeed — the icon visibly changes to the symbol we set, so the tray handle is valid
  • A listener is attached via the injected bridge's tray.onClick (window.addEventListener('craft:tray:click', ...)) and via onMenuAction (_evt('craft:tray:menuAction'))
  • No page errors (window.onerror reported nothing)

Left-clicking the status item produces no log line from the click handler. Right-clicking opens the menu correctly, and choosing an item produces no log line either.

Native path appears correct

Reading v0.0.90:

  • tray.zig:387 — tray_click.install(statusItem) is called, with sendActionOn: set to left|right mouse up
  • tray.zig:505 macosSetMenu — correctly holds the page's menu for right click rather than attaching it, so the button action stays live
  • tray_click.zig trayClickCallback — sets click_pending = true for a left click
  • bridge_tray.zig pollActions — calls takePendingClick() and then:
bridge.evalJS("if(window.__craftDeliverTrayClick)window.__craftDeliverTrayClick('left');") catch |err| {
    log.debug("Failed to deliver tray click: {}", .{err});
};

Each step reads correctly, which is why I suspect the evalJS target: if the bridge's webview reference is not the tray app's window, the eval fails and the catch logs at debug, which is compiled out of release builds — so the failure is completely silent in a shipped binary.

The same evalJS path carries menu actions, which matches both symptoms failing together.

Reproduce

craft http://localhost:3000 --system-tray

with a page that does:

craft.tray.setMenu([{ label: 'Hello', action: 'hello' }])
craft.tray.onClick(() => console.log('left click'))
craft.tray.onMenuAction(d => console.log('action', d))

The icon and menu appear. Neither callback ever runs.

Note

Possibly related to #419, in that both are --system-tray paths that leave part of the app unwired — but the mechanisms look independent, and #419 has a known one-line cause while this one does not.

Environment

  • craft 0.0.90
  • macOS 26 (Darwin 25.3.0), arm64
7 days ago

Investigated on current main; I cannot reproduce the missing callbacks through the native target/action path. Added regression coverage in fcf20ea (three files), leaving this issue open rather than claiming a runtime fix.

The report describes v0.0.90. Chris changed both click and menu delivery in 6da2680 to evaluate directly into the primary WebKit page, with polling as a fallback. That change is already included in v0.0.359. The new test invokes the installed AppKit status-button and NSMenuItem target/actions, then observes the real injected craft.tray.onClick/onMenuAction listeners. It passes for a visible page, hidden page, document reload, and a second window that must not steal primary callbacks; it also checks for duplicate delivery.

Scope: the test creates the menu through the native TrayBridge.setMenu handler and invokes native control actions. It does not reproduce the full HTTP application, physical pointer input, or right-click menu tracking. Those remain relevant differences from the reported app.

Corrected two documentation examples that still listened for craft:tray:menu instead of craft:tray:menuAction. The report itself already uses the correct listener, so this documentation correction is not presented as its root cause.

Validation: callback cases passed in ReleaseSafe with the default JavaScript backend and with private APIs disabled. SDK 796 passed / 8 skipped, typecheck, 43 package checks, formatting, release tests 397 passed, lint 0 errors / 27 existing warnings. One full public-API suite attempt lost foreground in the separate window-activation test (the callback cases had passed); rerun passed. Release tests initially hit an existing 5-second subprocess deadline; unchanged rerun passed. Remote CI for this new commit is pending.

Please retest using the current executable and include command -v craft, craft --version, the exact launch command (including any --tray-popover flag), and a minimal failing page. --tray-popover intentionally consumes left clicks natively rather than dispatching onClick. If this still occurs on the current runtime, the fresh reproduction will let us target the remaining path without guessing.

6 days ago

Resumed #420 and closed the gaps in the earlier native callback regression in aac551a (four files, pushed to main).

The new test serves a real loopback HTTP page and creates its window through the URL startup path used by craft URL --system-tray. The page itself calls craft.tray.setMenu() and registers onClick / onMenuAction. Application-local AppKit right-button events open the native menu, keyboard events select its item through the actual tracking loop, and a subsequent left click must reach JavaScript after the menu detaches. Both visible and hidden/reloaded pages receive exactly one callback of each kind; polling must not duplicate them.

This now passes on current main. Closing the reported v0.0.90 regression as no longer reproducible on the maintained runtime, with regression coverage rather than a speculative runtime patch. Chris's direct primary-WebKit delivery change, 6da2680, is already included in published v0.0.359. No new release or tag was created.

Validation:

  • Full native WebKit target passed in ReleaseSafe with the default JavaScript backend, with js-runtime=false, and with js-runtime=false/private-apis=false.
  • SDK: 800 passed, 8 skipped, 0 failed.
  • Release-tooling tests: 399 passed; typecheck, 43 package checks, Zig formatting and lint passed (0 lint errors, 27 existing warnings).
  • Menu tracking runs in a separate, serialized process: initial same-process attempts passed the callback cases but interfered with the later activation test. The isolated full suites passed.
  • CI for this new commit is pending; these are local results.

Limits: events are injected into this application's AppKit queue, not physical pointer input, and the test does not spawn the CLI parser or reproduce the reporter's entire app. Existing secondary-window isolation coverage remains. If a current executable still fails, please reopen with command -v craft, craft --version, exact launch flags and a minimal page. --tray-popover intentionally handles left clicks natively instead of emitting onClick.

Sign in to comment on this issue.