Skip to content

canvas: carry click modifiers into press Msg arms - #318

Open
MohakBajaj wants to merge 1 commit into
vercel-labs:mainfrom
MohakBajaj:press-modifiers
Open

canvas: carry click modifiers into press Msg arms#318
MohakBajaj wants to merge 1 commit into
vercel-labs:mainfrom
MohakBajaj:press-modifiers

Conversation

@MohakBajaj

Copy link
Copy Markdown
Contributor

The gap

WidgetPointerEvent already carries modifiers, and the platform layer
already fills them — but nothing forwards them to app code. UiApp.Options
has on_key, on_pinch and on_drop and no pointer hook, and markup's
on-press carries a tag plus one payload path. So cmd-click and
shift-click are not expressible in either core language, in markup or in a
Zig builder view.

I hit this building a capture inbox: multi-select had to fall back to a
context-menu item, because the modifiers the runtime already knew were
dropped one layer before the app.

The change

Carry them on the existing press channel, the way on-drag already fills
phase and geometry around an authored sourceId. A Msg arm that declares
the four booleans shift / control / alt / super beside at most
one
authored payload field receives them:

| { readonly kind: "focus_item"; readonly id: number;
    readonly shift: boolean; readonly control: boolean;
    readonly alt: boolean; readonly super: boolean }
<panel on-press="focus_item:{row.id}">   <!-- markup is unchanged -->

Markup fills id; the runtime fills the rest at dispatch. A bare
on-press="clear" names the modifier-only form. An arm without all four
booleans is an ordinary payload and takes exactly the path it takes today,
so this is additive — no new attribute, no new vocabulary in the closed
grammar.

What is touched

  • ui_markup_reflect.zigdeclaredWidgetPressRecord, pressPayloadFieldName
  • ui.zigmsgForPointerClickModified + the withPressModifiers
    injector; the three-argument msgForPointerClick stays as the
    unmodified entry point
  • both engines (ui_markup_view.zig, ui_markup_compiled.zig) fill the
    authored field and leave the modifiers to dispatch
  • ui_markup_contract.zig — the press payload class and
    MsgTag.press_payload, so a binding is kind-checked against the
    authored field rather than the record; artifact version 6 → 7
  • automation — widget-click <view> <id> [cmd|shift|cmd+shift], so the
    channel is drivable from tests and smoke runs
  • docs page + native-ui / automation skills

Verification

  • zig build test passes. The new dispatch test was confirmed to actually
    execute by forcing it to fail first, then reverting.
  • zig build test-examples-native passes, including kanban (the
    adjacent on-drag path) and the TypeScript-core examples that exercise
    the contract.
  • Driven end to end in a real app built against this branch: plain click
    clears, shift-click makes a range, shift-click again replaces it,
    cmd-click adds discontiguously, plain click clears. Standard macOS list
    selection.

scripts/gate.sh fast is green except bench-check, which fails the same
budgets worse on clean main on this machine (keystroke-big-view
3647µs vs 3567µs with the change, against a 3000µs budget) — it is not a
quiet Apple Silicon benchmark host, not a regression here.

Notes for review

  • The artifact version bump is the invasive part. Every app's
    zig-out/model-contract.zon regenerates on the next build. It degrades
    loudly and never falsely passes (I watched an app report "could not be
    parsed — run native test"), but if you would rather have a different
    compatibility story, say so and I will rework it.
  • clickCount is deliberately excluded. The runtime has it and it
    would be nearly free, but on-double-press already covers that ground
    and a narrower first change seemed easier to judge. Happy to add it.
  • CHANGELOG.md is untouched, per AGENTS.md.

🤖 Generated with Claude Code

`WidgetPointerEvent` already knows which modifiers were held at click
time, but nothing forwarded them to app code: `UiApp.Options` has
`on_key`, `on_pinch` and `on_drop` and no pointer hook, so cmd-click and
shift-click were not expressible in either core language.

Carry them on the existing press channel, the way `on-drag` already fills
phase and geometry around an authored `sourceId`. A Msg arm that declares
the four booleans `shift`/`control`/`alt`/`super` beside at most one
authored payload field receives them; markup keeps its ordinary spelling,
so `on-press="select:{row.id}"` fills `id` and the runtime fills the rest.
An arm without all four booleans is an ordinary payload and takes exactly
the path it takes today, so this is additive.

- reflect: `declaredWidgetPressRecord` and `pressPayloadFieldName`
- ui: `msgForPointerClickModified` plus the `withPressModifiers` injector,
  with the three-argument form kept as the unmodified entry point
- both engines fill the authored field and leave the modifiers to dispatch
- contract: the `press` payload class and `MsgTag.press_payload`, so a
  binding is kind-checked against the authored field; artifact version 7
- automation: `widget-click <view> <id> [cmd|shift|cmd+shift]`, so the
  channel is drivable from tests and smoke runs

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

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.

1 participant