Skip to content

Commit 6ab151d

Browse files
author
Roy Lin
committed
feat: add strict TSX protocol handshake
1 parent e0878fc commit 6ab151d

13 files changed

Lines changed: 1578 additions & 23 deletions

File tree

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ contract.
9898

9999
## TSX to native, without a browser
100100

101-
The proposed TypeScript path follows
101+
The planned TypeScript path follows
102102
[Nub](https://github.com/nubjs/nub)'s strongest runtime idea: keep stock Node,
103103
transform `.tsx` through its normal loader pipeline, and let a narrow Rust
104104
boundary own native work. A3S adds a standard automatic JSX runtime and a
@@ -136,10 +136,16 @@ function Counter() {
136136
await createApp(Counter).run();
137137
```
138138

139-
This sample documents the target API; it is not runnable yet. The proposed
140-
design keeps component state and callbacks in Node, keeps platform/GPU handles
141-
inside a separate Rust process, reuses resolved `ProtocolUiFrameV1` records,
142-
and sends complete frames so Rust remains the only native reconciler. Read the
139+
This sample documents the target API; it is not runnable yet. The Rust-side
140+
session entrance is now executable: strict `hello`/`welcome` DTOs negotiate the
141+
renderer, capabilities, debug channels, one in-flight render, and a JSON
142+
payload limit no larger than 16 MiB; incremental and blocking codecs enforce a
143+
little-endian `u32` length prefix and reject empty, oversized, truncated,
144+
invalid UTF-8, duplicate-field, unknown-kind, and unsupported-protocol input
145+
before a session is bound. The remaining design keeps component state and callbacks in Node,
146+
keeps platform/GPU handles inside a separate Rust process, reuses resolved
147+
`ProtocolUiFrameV1` records, and sends complete frames so Rust remains the only
148+
native reconciler. Read the
143149
[TSX native runtime architecture](docs/tsx-native-runtime.md) for protocol,
144150
identity, failure recovery, packaging, and T0-T5 delivery gates.
145151

@@ -349,7 +355,7 @@ independently.
349355
| 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 |
350356
| M5 · Default cutover | Planned | Make self-drawn content the default, then delete the three legacy widget renderers |
351357
| 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 |
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 |
358+
| T0-T5 · TSX native authoring | T1 Rust foundation in progress | Strict handshake, bounded length-prefixed framing, message sequencing, and revision-scoped drop-policy DTO/resolver bridge landed; render/event messages, TypeScript declarations, JSX runtime, Node callback transport, state runtime, self-drawn native window, packages, and stable SDK remain |
353359
| 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 |
354360

355361
The dependency-ordered plan and acceptance gates are in the
@@ -493,6 +499,7 @@ src/
493499
|- rsx_app/ ComponentCx, hooks, components, and binding scope
494500
|- rsx_ui/ built-in semantic design-system registry
495501
|- protocol.rs versioned frame, event, action, ACK, and recovery boundary
502+
|- tsx_protocol/ strict handshake, message sequencing, and bounded framing
496503
|- native.rs portable NativeElement UI IR
497504
|- layout/ deterministic records, style projection, diffs, and tests
498505
|- drawing.rs Graphics boundary and reference/GPU renderer wrappers

docs/architecture.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,19 @@ 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+
The optional TSX process boundary begins in the feature-independent
965+
`tsx_protocol` module rather than in a Node or native-backend feature. Its
966+
strict `hello`/`welcome` messages carry the fixed `a3s.gui.tsx` protocol id,
967+
version range, opaque session id, per-sender message id, render revision,
968+
renderer request/selection, capabilities, debug channels, and negotiated
969+
limits. `TsxHostHandshakeV1` mutates from unbound to negotiated only after the
970+
complete response fits the negotiated limit. Both stream and incremental JSON
971+
codecs validate a little-endian `u32` length before allocation, enforce the
972+
16 MiB protocol-v1 JSON-payload ceiling, and poison an incremental decoder
973+
after any framing or strict-JSON violation. This Rust transport foundation has
974+
no Node, Nub, N-API, Graphics, legacy renderer, or OS-toolkit dependency;
975+
render/event session messages and the Node implementation remain T1 work.
976+
964977
Drop policies that participate in hit testing use a separate synchronous
965978
protocol-v1 exchange rather than an action invocation. A
966979
`ProtocolDropPolicyQueryV1` carries the session, committed render revision,

docs/react-aria-native.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,9 @@ The first shared interaction milestone is available in the portable runtime:
186186
timeout/disconnect/failure, mismatched response metadata, wrong decision
187187
types, and disallowed operations all fail closed to `cancel`. Strict protocol
188188
v1 DTOs and `ProtocolDropPolicyResolverV1` bridge this contract without Rust
189-
evaluating JavaScript. The Node callback registry/transport, external
189+
evaluating JavaScript. The separate TSX boundary now also has strict atomic
190+
handshake negotiation and bounded length-prefixed JSON framing, but its
191+
render/event messages and Node callback registry/transport, external
190192
files/directories and cross-application transfer, drag previews, and full
191193
conformance evidence remain separate M7 work, so no affected family is
192194
marked conformant.

docs/roadmap.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,9 +623,10 @@ Gates:
623623

624624
## P0-T TSX Native Authoring Track
625625

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.
626+
Status: architecture accepted; the Rust-side strict handshake, bounded framing,
627+
message sequencing, and revision-scoped drop-policy protocol/resolver adapter
628+
have landed. Render/event transport messages, generated TypeScript, the JSX
629+
runtime, Node callback registry, and visible TSX application remain.
629630

630631
This track is dependency-coupled to the renderer and H0-H5 host programs
631632
without blocking Rust RSX work. Headless protocol and JSX-runtime work can
@@ -657,10 +658,16 @@ Graphics, interaction, accessibility, or capability checks.
657658

658659
### T1 - Headless JSX and protocol slice
659660

661+
Status: Rust transport foundation in progress. Strict `hello`/`welcome` DTOs,
662+
atomic negotiation, the fixed protocol/session/message/revision envelope,
663+
16 MiB-capped little-endian length framing, incremental decoding, and the first
664+
canonical JSON fixture have landed without Node or Graphics dependencies.
665+
660666
- generated TypeScript declarations from versioned Rust DTOs
661667
- automatic `jsx-runtime` and `jsx-dev-runtime` exports
662668
- deterministic child, prop, key, and event normalization
663-
- bounded length-prefixed IPC, handshake, revisions, event sequencing, and
669+
- extend the landed bounded length-prefixed framing, handshake, and message-id
670+
sequencing with render revisions, event sequencing, process I/O, and
664671
structured diagnostics
665672
- static TSX counter rendered through the current headless semantic path
666673

docs/tsx-native-runtime.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,17 @@ JSON is the protocol-1 payload encoding because it matches the current Serde
377377
contract and is easy to inspect. Each message is framed as a little-endian
378378
`u32` byte length followed by UTF-8 JSON. The receiver rejects zero-length,
379379
oversized, truncated, invalid UTF-8, duplicate-field, unknown-kind, and
380-
unsupported-version messages before state mutation. The initial maximum frame
381-
size is explicit in the handshake and bounded to 16 MiB or less.
380+
unsupported-version messages before state mutation. The initial maximum JSON
381+
payload size is explicit in the handshake and bounded to 16 MiB or less.
382+
383+
Landed Rust foundation: `tsx_protocol` defines strict direction-specific
384+
control messages with the fixed `a3s.gui.tsx` identifier, atomic
385+
`TsxHostHandshakeV1` negotiation, exact per-sender `TsxMessageSequenceV1`, and
386+
blocking plus incremental framed JSON codecs. The decoder validates a declared
387+
length before allocating, becomes unusable after a framing/JSON violation, and
388+
has a checked end-of-stream path for partial headers and payloads. A canonical
389+
`hello-v1.json` fixture pins the current wire spelling. Render/event/command
390+
messages, actual local process I/O, and the TypeScript peer are still pending.
382391

383392
### Messages
384393

@@ -440,10 +449,9 @@ packages/typescript/
440449
`- tests/
441450
442451
src/tsx_protocol/
443-
|- envelope.rs
444-
|- message.rs
445-
|- framing.rs
446-
|- limits.rs
452+
|- message.rs strict control DTOs and common envelope metadata
453+
|- handshake.rs atomic capability, renderer, and limit negotiation
454+
|- framing.rs limits plus blocking and incremental JSON framing
447455
`- tests.rs
448456
449457
src/platform_host/ shared zero-widget host contract and OS shells
@@ -522,9 +530,9 @@ minimum M4 text/input slice.
522530

523531
### T0 - Contract and Architecture
524532

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.
533+
Status: architecture accepted; the Rust-side strict handshake/framing boundary
534+
and drop-policy DTO/resolver adapter are implemented. The remaining parity
535+
fixtures, application message set, and Node-side transport are pending.
528536

529537
- accept process, ownership, identity, protocol, and packaging decisions
530538
- pin cross-language golden frame and event fixtures
@@ -536,7 +544,12 @@ cannot bypass Native IR, layout, Graphics, interaction, or accessibility.
536544

537545
### T1 - Headless Protocol and JSX Core
538546

539-
- add the bounded framed transport and handshake DTOs in Rust
547+
Status: Rust transport foundation in progress. `hello`/`welcome`, atomic limit
548+
and renderer negotiation, exact message-id sequencing, 16 MiB-capped framing,
549+
incremental decoding, and the first canonical JSON fixture have landed.
550+
551+
- extend the landed bounded framing and handshake DTOs with render/event/
552+
command messages and actual local process I/O
540553
- connect the landed strict drop-policy query/response DTOs to that transport
541554
and the Node callback registry
542555
- generate TypeScript protocol declarations from Rust DTOs

justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ check-core-graph:
331331
set -euo pipefail
332332
333333
core_graph="$(cargo tree --locked --no-default-features --prefix none)"
334-
if grep -Eq '^(a3s-graphics|wgpu) ' <<<"$core_graph"; then
335-
echo "graphics dependencies entered the semantic-only graph" >&2
334+
if grep -Eq '^(a3s-graphics|wgpu|napi|napi-derive|neon|neon-build|node-bindgen|node-bindgen-macro|deno_core|rusty_v8) ' <<<"$core_graph"; then
335+
echo "graphics or embedded JavaScript/Node dependencies entered the semantic-only graph" >&2
336336
exit 1
337337
fi
338338

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ mod semantic_event;
6767
pub mod semantic_ui;
6868
pub mod style;
6969
pub mod svg;
70+
pub mod tsx_protocol;
7071
pub mod web;
7172
#[cfg(feature = "winui")]
7273
pub mod winui;
@@ -477,6 +478,7 @@ pub use style::{
477478
VisibilityMode, WhiteSpaceMode, WordBreakMode,
478479
};
479480
pub use svg::{SVG_ELEMENTS, SVG_TAG_METADATA_KEY};
481+
pub use tsx_protocol::*;
480482
pub use web::WebProps;
481483
#[cfg(feature = "winui")]
482484
pub use winui::{

0 commit comments

Comments
 (0)