Skip to content

Commit e0878fc

Browse files
author
Roy Lin
committed
feat: add revision-scoped drop policy resolution
1 parent a2c0fbe commit e0878fc

40 files changed

Lines changed: 1630 additions & 66 deletions

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,14 @@ versioned [component matrix](docs/react-aria-component-matrix.json) pins
7676
`onMove`, same-parent `onReorder`, multi-callback routing, equivalent adjacent
7777
boundaries, and self/descendant guards now follow the React Aria collection
7878
policy. Ordinary targets and collection items also share the exact 800ms
79-
`onDropActivate` hold lifecycle across pointer and keyboard drags; dynamic
80-
acceptance, OS/cross-application transfer, previews, pixels, accessibility,
81-
and real hosts still keep every affected family below conformance;
79+
`onDropActivate` hold lifecycle across pointer and keyboard drags. Dynamic
80+
`shouldAcceptItemDrop` and `getDropOperation` policies now resolve before hit
81+
feedback through a synchronous, revision-scoped boundary: high-level item
82+
drops are filtered again per transferred item, low-level `onDrop` retains
83+
override semantics, and missing, stale, timed-out, malformed, or disallowed
84+
answers fail closed to `cancel`. OS/cross-application transfer, previews,
85+
pixels, accessibility, and real hosts still keep every affected family below
86+
conformance;
8287
- Checkbox, Radio, and Switch Field/Button parts plus ToastList and
8388
ToastContent are recorded as eight explicit API gaps;
8489
- a component reaches `conformant` only with behavior, layout/hit, Graphics
@@ -320,7 +325,12 @@ independently.
320325
one React Aria-compatible `DropActivate` after 800ms without delaying the
321326
deadline as the pointer moves inside the same target; all paths share
322327
event-loop deadlines, stable action bubbling, keyed reconciliation, and
323-
reducer rollback in one staged interaction session
328+
reducer rollback in one staged interaction session. Generic and collection
329+
targets can additionally invoke typed `getDropOperation` policies, while
330+
collection `shouldAcceptItemDrop` runs at target resolution and once per item
331+
before high-level drop dispatch. Versioned frame/event/query correlation and
332+
the protocol-v1 exchange adapter reject stale or failed process answers as
333+
`cancel`; Rust never evaluates JavaScript
324334
- an identical-frame fast path that performs no layout, scene, host, or
325335
presentation work, plus semantic-only commits that skip pixel presentation
326336
- a software Graphics presenter and interactive shared
@@ -336,10 +346,10 @@ independently.
336346
| M1 · GUI integration | Complete | Pinned Graphics boundary, semantic-only dependency gate, renderer inventory, reference/GPU wrappers, first generic adapter |
337347
| M2 · GPU backend | Implementation landed | Graphics commit `8748fab`; Metal and Vulkan CI parity evidence remains |
338348
| M3 · Layout and Scene | Current | Generic calculator rectangle slice landed; full flex, stacking, redraw scheduling, cross-platform fingerprints, and thin-host presentation remain |
339-
| M4 · Text and interaction cutover | In progress | Stable-id raw input, long press, move, typed and collection drag/drop plus timed drop activation landed; shaping, glyphs, editing/IME, accessibility bridges, overlays, and complete calculator scenarios remain |
349+
| M4 · Text and interaction cutover | In progress | Stable-id raw input, long press, move, typed and collection drag/drop, timed drop activation, and fail-closed dynamic drop policy resolution landed; shaping, glyphs, editing/IME, accessibility bridges, overlays, and complete calculator scenarios remain |
340350
| M5 · Default cutover | Planned | Make self-drawn content the default, then delete the three legacy widget renderers |
341351
| H0-H5 · Thin platform hosts | H0 complete; H1 in progress | Atomic frames, lifecycle recovery, stable-id raw input/reducers, long press, captured move, typed drag/drop negotiation and timed target activation, zero-toolkit firewalls, and an interactive calculator landed; the Graphics raw-surface edge remains |
342-
| T0-T5 · TSX native authoring | Proposed | Automatic JSX runtime, versioned Node-to-host session, state/event runtime, self-drawn native window, packages, and stable SDK |
352+
| T0-T5 · TSX native authoring | First protocol slice landed | Rust-side revision-scoped drop-policy DTOs and resolver bridge landed; automatic JSX runtime, Node transport/callback registry, state/event runtime, self-drawn native window, packages, and stable SDK remain |
343353
| M6-M8 · React Aria components | Catalog pinned; conformance planned | 51/51 families mapped; collection DnD authoring/behavior slice landed; eight public parts, full software, accessibility, and three-OS self-drawn evidence remain |
344354

345355
The dependency-ordered plan and acceptance gates are in the

docs/architecture.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,17 @@ and events are rejected until delivery is acknowledged. Password values remain
961961
available to in-process reducers but are removed from commands, accessibility,
962962
responses, session debug output, and retained diagnostics.
963963

964+
Drop policies that participate in hit testing use a separate synchronous
965+
protocol-v1 exchange rather than an action invocation. A
966+
`ProtocolDropPolicyQueryV1` carries the session, committed render revision,
967+
event sequence, query sequence, policy id, typed generic/collection target,
968+
drag types, and allowed operations. `ProtocolDropPolicyResolverV1` validates
969+
the exact response envelope before returning it to the self-drawn runtime; a
970+
timeout, unavailable transport, handler failure, stale response, wrong
971+
decision type, or source-disallowed operation becomes `cancel`. This boundary
972+
supports React Aria `shouldAcceptItemDrop` and `getDropOperation` without
973+
serializing closures or executing JavaScript in Rust.
974+
964975
`NativeProtocolApp` remains the convenience state/reducer loop for the legacy
965976
in-process API. Strict-v1 is deliberately a transport-owned
966977
`NativeProtocolSession` primitive: the transport owns resend/ACK ordering and

docs/platform-hosts.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ src/platform_runtime/
215215
|- long_press_input.rs event-loop deadlines and terminal hold recognition
216216
|- move_input.rs captured incremental pointer and keyboard movement
217217
|- drag_drop.rs typed transfer data and source/target negotiation
218+
|- drop_policy.rs revision-scoped synchronous policy query/response
218219
|- drag_drop_activation_input.rs
219220
| 800ms target hold deadline and activation routing
220221
|- drag_drop_input.rs captured pointer drag lifecycle and target routing
@@ -402,11 +403,14 @@ Landed evidence:
402403
adjacent insertion boundaries. Ordinary targets and collection items emit
403404
`DropActivate` after an exact 800ms hold through the same host deadline;
404405
collection roots do not activate, and target exit/change/cancel/drop clears
405-
or restarts the timer
406+
or restarts the timer. Dynamic item acceptance and operation selection use a
407+
synchronous frame/event/query-scoped resolver, re-filter high-level item
408+
drops per transferred item, preserve low-level `onDrop` precedence, and map
409+
missing, stale, timed-out, malformed, or disallowed answers to `cancel`
406410
- reducer errors restore the staged interaction state and sequence before the
407411
event is exposed as successful; successful frame reconciliation preserves
408412
focused stable ids, while rejected frames do not touch them
409-
- 63 focused runtime/software tests and four recursive feature/source
413+
- 67 focused runtime/software tests and four recursive feature/source
410414
firewall tests pass without any legacy renderer or OS toolkit dependency
411415
- `self_drawn_calculator` reproduces layout fingerprint
412416
`16529597026056060935`, scene fingerprint `2100550662756266801`, and

docs/react-aria-native.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,22 @@ The first shared interaction milestone is available in the portable runtime:
174174
combined callbacks retain React Aria order, while low-level `onDrop`
175175
overrides high-level dispatch.
176176
Selected descendants are removed when their selected ancestor is dragged,
177-
and internal self/descendant targets are rejected. Dynamic item acceptance
178-
and operation callbacks, external files/directories and cross-application
179-
transfer, drag previews, and conformance evidence remain separate M7 work,
180-
so no affected family is marked conformant.
177+
and internal self/descendant targets are rejected.
178+
- Dynamic `shouldAcceptItemDrop` and `getDropOperation` identifiers are not
179+
ordinary action ids. The self-drawn runtime issues a synchronous typed query
180+
with committed frame, event, and query sequences before exposing a target as
181+
valid. Collection item-on targets run aggregate acceptance during feedback
182+
and per-item acceptance before high-level dispatch; low-level `onDrop`
183+
preserves its override and receives the complete drag-session item list once
184+
target-level type negotiation succeeds.
185+
A returned operation must be allowed by the source. Missing resolvers,
186+
timeout/disconnect/failure, mismatched response metadata, wrong decision
187+
types, and disallowed operations all fail closed to `cancel`. Strict protocol
188+
v1 DTOs and `ProtocolDropPolicyResolverV1` bridge this contract without Rust
189+
evaluating JavaScript. The Node callback registry/transport, external
190+
files/directories and cross-application transfer, drag previews, and full
191+
conformance evidence remain separate M7 work, so no affected family is
192+
marked conformant.
181193

182194
- `NativeInputModality` represents keyboard, mouse, touch, pen, virtual, and
183195
unknown input.
@@ -611,7 +623,7 @@ props:
611623
| Priority | Area | Required outcome |
612624
| --- | --- | --- |
613625
| P0 | Self-drawn component accounting | Keep all 51 React Aria 1.19.0 families in the executable matrix, implement the eight recorded public-part gaps, and require every upstream catalog delta to update code, matrix, tests, and milestones together. |
614-
| P0 | Shared self-drawn interaction | Extend the landed stable-id pointer, keyboard, Tab-focus, hover, press, scheduled generic long press, incremental captured move, typed source/target drag negotiation, collection item/between/root delegates plus reorder/move policy, cancellation, wheel, bubbling, and reducer path with dynamic item/operation acceptance, external file/directory transfer, drag previews, collection long-press selection mode, focus-scope restoration, overlay gestures, text editing/IME, and accessibility activation. |
626+
| P0 | Shared self-drawn interaction | Extend the landed stable-id pointer, keyboard, Tab-focus, hover, press, scheduled generic long press, incremental captured move, typed source/target drag negotiation, collection item/between/root delegates plus reorder/move and dynamic item/operation policy, cancellation, wheel, bubbling, and reducer path with external file/directory transfer, drag previews, collection long-press selection mode, focus-scope restoration, overlay gestures, text editing/IME, and accessibility activation. |
615627
| P0 | Native input conformance | WinUI's complete 98-case V1 manifest passes real OS automation. Populate the AppKit and GTK4 manifests with platform-run mouse, pen, touch where applicable, keyboard, assistive activation, disabled, cancellation, and keyed-rerender fixtures for every role currently marked native; then close or retain evidence-backed menu/item exceptions. |
616628
| P1 | Event propagation | Add platform-run conformance fixtures for conditional `Stop`/`Continue` across nested native controls. |
617629
| P1 | Focus management | Add platform-run conformance fixtures for post-mount `autoFocus`, nested containment, and restoration. |

docs/roadmap.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -526,10 +526,20 @@ Landed evidence:
526526
and exposes each collection as one Tab stop with arrow/Home/End navigation.
527527
ListBox, GridList, Tree, Table, and explicit DropIndicator authoring all lower
528528
to this shared self-drawn path
529+
- generic targets and collection targets expose a synchronous
530+
`getDropOperation` policy, and collection item-on targets expose
531+
`shouldAcceptItemDrop`. Queries carry the committed frame revision, event
532+
sequence, query sequence, stable policy id, typed target, drag types, and
533+
allowed operations. High-level collection drops filter each item again at
534+
drop time, low-level `onDrop` bypasses that high-level filter, and missing,
535+
stale, timed-out, malformed, or disallowed responses resolve to `cancel`.
536+
Protocol v1 includes strict query/response DTOs and an exchange adapter whose
537+
transport must own the bounded wait; the future Node runtime still owns
538+
callback execution
529539
- the shared 410x620 calculator preserves its reviewed layout and scene
530540
fingerprints, routes eight fake-host events through four reducer actions,
531541
commits the resulting frames, and reaches display value `10`
532-
- 63 focused runtime/software tests plus four recursive H1 firewall tests are
542+
- 67 focused runtime/software tests plus four recursive H1 firewall tests are
533543
included in `just verify`
534544

535545
Remaining H1 work:
@@ -538,10 +548,10 @@ Remaining H1 work:
538548
pinned Graphics commit `8748fab` owns only a surface-independent texture and
539549
readback today, so its safe host-owned surface attachment/recovery contract
540550
must land before GUI can implement this edge without duplicating `wgpu`
541-
- dynamic collection item/operation acceptance callbacks, native file/directory
542-
and cross-application transfer, drag previews, text editing, IME, overlay
543-
gestures, and component-specific pixel/accessibility/real-host conformance
544-
remain explicit M4 and M6-M8 work
551+
- native file/directory and cross-application transfer, drag previews, text
552+
editing, IME, overlay gestures, the Node-side policy callback transport, and
553+
component-specific pixel/accessibility/real-host conformance remain explicit
554+
M4 and M6-M8 work
545555

546556
Gates:
547557

@@ -613,7 +623,9 @@ Gates:
613623

614624
## P0-T TSX Native Authoring Track
615625

616-
Status: architecture proposed; implementation has not started.
626+
Status: architecture accepted; the first Rust-side revision-scoped drop-policy
627+
protocol and resolver adapter has landed, while the JSX runtime, Node callback
628+
registry, process transport, and visible TSX application have not started.
617629

618630
This track is dependency-coupled to the renderer and H0-H5 host programs
619631
without blocking Rust RSX work. Headless protocol and JSX-runtime work can
@@ -751,9 +763,10 @@ self-drawn conformance.
751763
- close the executable 1.19.0 behavior deltas: embedded-control keyboard
752764
navigation for GridList/Tree, Menu action key plus value, arbitrary Popover
753765
target rectangles, and multi-MIME/wildcard drag type negotiation
754-
- build on the landed shared collection root/item/insertion delegate and
755-
reorder/move policies with dynamic item/operation acceptance, OS transfer,
756-
drag previews, and software/accessibility/three-host conformance stories
766+
- build on the landed shared collection root/item/insertion delegate,
767+
reorder/move policies, and dynamic item/operation acceptance with OS
768+
transfer, drag previews, the Node policy transport, and
769+
software/accessibility/three-host conformance stories
757770

758771
### M8 - Date, color, tables, and advanced data
759772

docs/rsx-framework.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,11 @@ Status: initial implementation.
370370
Link, and text primitives
371371
- Rust function components stored as `.rsx` source modules and written with
372372
`ComponentCx` and `rsx!`
373+
- `UiDroppable` and `UiDropZone` accept a stable `getDropOperation` policy id.
374+
`UiListBox`, `UiGridList`, `UiTree`, and `UiTable` additionally accept
375+
`shouldAcceptItemDrop`; both lower to dedicated `data-*-policy` metadata and
376+
are resolved synchronously by the self-drawn runtime rather than registered
377+
as ordinary reducer actions
373378
- `UiTextarea`
374379
- class merging for static base classes plus caller `className`
375380
- focused module split for classes, component source modules, variants, and

docs/tsx-native-runtime.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,17 @@ Callbacks that decide hit testing before an event is dispatched are a distinct
293293
protocol concern. In particular, React Aria's `shouldAcceptItemDrop` and
294294
`getDropOperation` require a synchronous answer while resolving the current
295295
collection target. They must not be encoded as ordinary action ids, serialized
296-
closures, or post-event reducers. Before these APIs are exposed to TSX, the
297-
session protocol must define a revision-scoped policy decision/query contract
298-
with bounded execution, stale-revision rejection, and `cancel` as the safe
299-
timeout/failure result (or an equivalent declarative decision table). Node
300-
continues to own JavaScript callbacks; the Rust host never executes JS.
296+
closures, or post-event reducers. The Rust boundary now defines
297+
`SelfDrawnDropPolicyQuery`/`Response`, strict
298+
`ProtocolDropPolicyQueryV1`/`ResponseV1` envelopes, and
299+
`ProtocolDropPolicyResolverV1`. Every query identifies the committed frame,
300+
event, query sequence, policy id, typed target, drag types, and allowed
301+
operations; stale or malformed responses and source-disallowed operations fail
302+
closed. The exchange trait makes timeout, unavailable transport, and handler
303+
failure explicit and maps all three to `cancel`. Before these APIs are exposed
304+
to TSX, the Node runtime must bind its revision-scoped callback registry to a
305+
bounded implementation of that exchange. Node continues to own JavaScript
306+
callbacks; the Rust host never executes JS.
301307

302308
### Components and Hooks
303309

@@ -516,7 +522,9 @@ minimum M4 text/input slice.
516522

517523
### T0 - Contract and Architecture
518524

519-
Status: this proposal.
525+
Status: architecture accepted; the Rust-side drop-policy DTO and resolver
526+
adapter are implemented. The remaining T0 review fixtures and Node-side
527+
transport are pending.
520528

521529
- accept process, ownership, identity, protocol, and packaging decisions
522530
- pin cross-language golden frame and event fixtures
@@ -529,6 +537,8 @@ cannot bypass Native IR, layout, Graphics, interaction, or accessibility.
529537
### T1 - Headless Protocol and JSX Core
530538

531539
- add the bounded framed transport and handshake DTOs in Rust
540+
- connect the landed strict drop-policy query/response DTOs to that transport
541+
and the Node callback registry
532542
- generate TypeScript protocol declarations from Rust DTOs
533543
- publish local development exports for `jsx-runtime` and `jsx-dev-runtime`
534544
- implement element/child/prop normalization, keys, and action registration

examples/support/calculator/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub fn calculator_reduce(
3232
}
3333

3434
#[cfg(feature = "platform-runtime")]
35+
#[allow(dead_code)]
3536
pub fn calculator_reduce_self_drawn(
3637
component: &CalculatorComponent,
3738
state: &mut CalculatorState,
@@ -60,6 +61,7 @@ pub fn calculator_reduce_self_drawn(
6061
}
6162

6263
#[cfg(feature = "platform-runtime")]
64+
#[allow(dead_code)]
6365
fn reducer_node_id(stable_id: &str) -> HostNodeId {
6466
let mut fingerprint = 0xcbf29ce484222325_u64;
6567
for byte in stable_id.bytes() {

src/lib.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,13 @@ pub use platform_host::{
208208
pub use platform_runtime::{
209209
PlatformRenderFrame, PlatformScenePresenter, RecordingPreparedFrame, RecordingScenePresenter,
210210
SelfDrawnActionInvocation, SelfDrawnActionPropagation, SelfDrawnCollectionDropTarget,
211-
SelfDrawnDragContext, SelfDrawnDropItem, SelfDrawnDropOperation, SelfDrawnDropPosition,
212-
SelfDrawnElementInteraction, SelfDrawnEventContext, SelfDrawnFrameCommit,
213-
SelfDrawnFrameCommitStatus, SelfDrawnFrameSnapshot, SelfDrawnHostEventOutcome,
214-
SelfDrawnInputDispatch, SelfDrawnInteractionChange, SelfDrawnRuntimeStats,
215-
SelfDrawnWindowRuntime,
211+
SelfDrawnDragContext, SelfDrawnDropItem, SelfDrawnDropOperation, SelfDrawnDropPolicyDecision,
212+
SelfDrawnDropPolicyQuery, SelfDrawnDropPolicyRequest, SelfDrawnDropPolicyResolution,
213+
SelfDrawnDropPolicyResolver, SelfDrawnDropPolicyResponse, SelfDrawnDropPolicyTarget,
214+
SelfDrawnDropPosition, SelfDrawnElementInteraction, SelfDrawnEventContext,
215+
SelfDrawnFrameCommit, SelfDrawnFrameCommitStatus, SelfDrawnFrameSnapshot,
216+
SelfDrawnHostEventOutcome, SelfDrawnInputDispatch, SelfDrawnInteractionChange,
217+
SelfDrawnRuntimeStats, SelfDrawnWindowRuntime,
216218
};
217219
#[cfg(all(feature = "platform-runtime", feature = "software-reference"))]
218220
pub use platform_runtime::{

0 commit comments

Comments
 (0)