@solidrt/core 0.0.65 → 0.0.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -92,7 +92,7 @@ real design decision, not an afterthought:
92
92
 
93
93
  ```sh
94
94
  bun add @solidrt/core # the renderer
95
- bun add -d @solidrt/cli # the `srt` tool (see its AGENTS.md)
95
+ bun add -d @solidrt/cli # the `sol` tool (see its AGENTS.md)
96
96
  ```
97
97
 
98
98
  Core primitives alone are enough to build a full app. The optional extensions
@@ -320,7 +320,7 @@ that reads exactly like Solid fallout.
320
320
  input happened. Time anything about input with it (a press length, the
321
321
  gap between two taps, a speed from two positions), never with
322
322
  `performance.now()` in the handler: that is when the handler ran, and
323
- it does not follow the dev clock, `srt render` or a test. The stamp is
323
+ it does not follow the dev clock, `sol render` or a test. The stamp is
324
324
  the input's own time, not its delivery's: a move carries the time the
325
325
  pointer was at the position it reports, however late the frame that
326
326
  delivers it; a down, up, wheel or key the time the platform recorded.
@@ -616,7 +616,7 @@ that reads exactly like Solid fallout.
616
616
  seldom changes the picture. `frameTime()` is the current frame's `tick`
617
617
  read from anywhere (not reactive, requests nothing): the app's own
618
618
  clock for code with no tick in hand, where `performance.now()` would
619
- ignore the dev clock, `srt render` and tests. A JS
619
+ ignore the dev clock, `sol render` and tests. A JS
620
620
  tween loop or an animation library pushing interpolated values through
621
621
  signals is the single most expensive mistake available here - read
622
622
  agents/performance.md before writing either.
package/README.md CHANGED
@@ -35,7 +35,7 @@ render(() => <App />)
35
35
  Run the app:
36
36
 
37
37
  ```sh
38
- bun run srt run src/index.tsx
38
+ bun run sol run src/index.tsx
39
39
  ```
40
40
 
41
41
  Optionally, create a `tsconfig.json` to enable type recognition for SolidRT elements:
@@ -149,29 +149,31 @@ Rules, in order of leverage:
149
149
  anything needing what is behind it does not - and is split from content
150
150
  invalidation, so a params-only change re-runs the pass against the cached
151
151
  snapshot instead of re-rasterizing. A window shader's output is invisible
152
- to get_snapshot and every other MCP tool; `bun run srt render` is the only
152
+ to get_snapshot and every other MCP tool; `bun run sol render` is the only
153
153
  way to see it (see @solidrt/cli AGENTS.md).
154
- 8. `flux:wasm` runs a pure interpreter (wasmi, no JIT), so temper browser
155
- expectations - but do not write it off for compute. A genuinely numeric
156
- kernel (typed-array math, tight inner loops, no host calls inside the
157
- loop) compiled from a systems language can come out a real multiple
158
- faster than the same loop in interpreted JavaScript, and when profiling
159
- shows such a kernel is what the app is spending its time on, that
160
- multiple is worth having: measure the JS loop, port the kernel, measure
161
- again, keep whichever wins. What wasm does not do is speed up
162
- render-path work (rules 1-3 are that leverage), and every host call
163
- costs marshalling, so batch at the boundary - one call over a byte
164
- buffer, not a call per element. It is also the way to ship one compiled
165
- module across every target with no native toolchain, and it pairs with
166
- an isolate when a call runs long enough to block.
167
- 9. `flux:ffi` (dlopen of a native library) is a binding tool, not a
168
- performance tool. It needs a shared library compiled per platform and
169
- architecture and shipped under each target's packing rules (Android
170
- loads only what arrives inside the APK as a lib*.so), so reaching for
171
- it "to make something fast" buys a build-and-packaging problem on every
172
- platform the app targets. Use it when the app must call a native
173
- library that already exists and already ships for those targets; for
174
- speed, everything above comes first.
154
+ 8. `flux:wasm` compiles a module to machine code on devices that allow it
155
+ (`Flux.capabilities` lists `wasm-native`: desktop, 64-bit Android) and
156
+ interprets it elsewhere (iOS, 32-bit Android). Both lanes are the fast
157
+ lane for compute: measured on a desktop, an arm64 tablet and a 32-bit
158
+ TV, a numeric kernel (typed-array math, tight inner loops, no host calls
159
+ inside the loop) runs 18x to 960x faster than the same loop in
160
+ JavaScript when compiled and 4x to 88x when interpreted, float at the low
161
+ end, integer and SIMD-shaped loops at the high end. So when profiling
162
+ shows such a kernel is where the app spends its time, port it: measure
163
+ the JS loop, port the kernel, measure again, keep whichever wins. The
164
+ boundary is the cost, not the code: a host call out of the module costs
165
+ 3x to 7x a plain JavaScript call, two to three hundred iterations of a
166
+ tight loop, so batch at the boundary - one call per frame over a byte
167
+ buffer, never a call per element. What wasm does not do is speed up
168
+ render-path work (rules 1-3 are that leverage). It is also the way to
169
+ ship one compiled module across every target with no native toolchain,
170
+ and it pairs with an isolate when a call runs long enough to block.
171
+ 9. There is no native-library route from app code. A shared library is
172
+ compiled per platform, so it cannot travel in a portable app payload,
173
+ and Android loads only what arrived inside the APK. Binding one is a
174
+ custom runtime build: the runtime crates with the library registered
175
+ beside the built-in modules. For speed, everything above comes first;
176
+ for a proven library that has a wasm build, rule 8.
175
177
  10. Text costs shaping, once per word per style, through a shared word cache;
176
178
  line breaking is arithmetic over the shaped words. So re-breaking text
177
179
  every frame with prepareText + layoutNextLine (a shape that breathes, an
@@ -188,7 +190,7 @@ Rules, in order of leverage:
188
190
  ## Isolates: heavy work off the JS thread
189
191
 
190
192
  A long synchronous computation (a big parse, a simulation step, a blocking
191
- `flux:ffi`/`flux:wasm` call) freezes rendering and input for its duration.
193
+ `flux:wasm` call) freezes rendering and input for its duration.
192
194
  Move it into an isolate module: a file whose first statement is the
193
195
  `"use isolate"` directive runs in a second runtime on its own thread, and
194
196
  main calls its exports as async functions.
@@ -208,7 +210,7 @@ let n = await worker.crunch(bytes) // main keeps rendering meanwhil
208
210
  ```
209
211
 
210
212
  The bundler builds each such module as its own bundle and ships it with the
211
- app (dev pushes and `srt pack` alike). Rules: main may only `import type`
213
+ app (dev pushes and `sol pack` alike). Rules: main may only `import type`
212
214
  from an isolate module (a value import is a build error); arguments and
213
215
  results are copies (numbers, strings, byte buffers, arrays, plain objects -
214
216
  no functions, no class instances); the child has the non-gui `flux:*`
@@ -248,7 +250,7 @@ not infer it from the desktop number.
248
250
  - **Some paints cost a frame each on a tiled mobile GPU.** Measured on a
249
251
  mid-range 2020 tablet (Adreno 610, Impeller GLES) with ten panes sliding
250
252
  and resizing on a layout transition, each row by subtraction from the
251
- compositor's own present record (`srt android --census`), against a
253
+ compositor's own present record (`sol android --census`), against a
252
254
  frame that ran at 60 fps with a 13 ms GPU span:
253
255
  - a 3-stop linear-gradient fill per pane: +15 ms, 20 fps. A gradient is
254
256
  shaded per pixel per frame; the same tint as a flat `d-rect` costs
package/docs/index.md CHANGED
@@ -91,9 +91,13 @@ ancestor with `position="relative"`.
91
91
  ## Input
92
92
 
93
93
  Pointer, wheel, and key events are props on any element:
94
- `onPointerDown`, `onPointerMove`, `onPointerUp`, `onPointerEnter`,
95
- `onPointerLeave`, `onWheel`, `onKeyDown`, `onKeyUp`. Events travel from the
96
- hit leaf up to the root, and `stopPropagation()` ends the walk. Key events
94
+ `onPointerDown`, `onPointerMove`, `onPointerUp`, `onPointerCancel`,
95
+ `onPointerEnter`, `onPointerLeave`, `onWheel`, `onKeyDown`, `onKeyUp`.
96
+ Events travel from the hit leaf up to the root, and `stopPropagation()`
97
+ ends the walk. A pointer ends with an up or a cancel, never both: the
98
+ cancel is the system taking the touch away (a back swipe, palm rejection),
99
+ delivered along the pressed path with the pointer's last position, so a
100
+ press ends without firing and a drag ends without a fling. Key events
97
101
  bubble the same way, starting at the focused node - or at the window root
98
102
  when nothing is focused, so `onKeyDown` on the window is where app-global
99
103
  shortcuts live.
@@ -176,7 +180,7 @@ animation and audio, not the persistence moment.
176
180
  A link from outside (a custom scheme link the OS routed to the app, or one
177
181
  a dev tool sent) arrives raw: `env.launchLink` is the one this run of the
178
182
  app was started with (null without; a hot reload starts at the location
179
- the app last reported, see `reportLocation` in `srt:dev`), `onLink` fires
183
+ the app last reported, see `reportLocation` in `sol:dev`), `onLink` fires
180
184
  for one arriving while the app runs. What a link names is the app's to decide, and it is untrusted input:
181
185
  validate before acting on any part of it. A router turns links into screens
182
186
  for you; without one, parse it yourself. The scheme a packed app answers to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
4
4
  "license": "MIT",
5
5
  "funding": "https://github.com/sponsors/wellawaretech",
6
6
  "author": "Antoine van Wel",
@@ -33,8 +33,8 @@
33
33
  "AGENTS.md"
34
34
  ],
35
35
  "devDependencies": {
36
- "@solidrt/flux-types": "0.0.65",
37
- "@solidrt/test": "0.0.65"
36
+ "@solidrt/flux-types": "0.0.67",
37
+ "@solidrt/test": "0.0.67"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@solidjs/signals": "2.0.0-rc.13",
package/src/camera.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  import { createSignal, onCleanup } from "@solidjs/signals"
10
10
  import { listCameras, open } from "flux:camera"
11
11
  import type { TextureId } from "flux:gpu"
12
- import { on } from "srt:events"
12
+ import { on } from "sol:events"
13
13
 
14
14
  export type CameraFacing = "front" | "back" | "unknown"
15
15
 
package/src/core.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  import * as tree from "flux:rendertree"
2
2
  import type { TextureId } from "flux:gpu"
3
3
  import { createSignal, getOwner, onCleanup } from "@solidjs/signals"
4
- import { on } from "srt:events"
4
+ import { on } from "sol:events"
5
5
 
6
6
  let handlers = new Map<number, Map<string, Function>>()
7
7
 
8
8
  // Pointer-handler presence mirrored into the render tree, so the runtime can
9
9
  // skip building deliveries that would reach nobody (moves over static content
10
10
  // are the flood case). Bits match alloy's EventInterest (rendertree/hit.rs);
11
- // keep the two in sync. Down/up are recorded but the runtime never gates
12
- // them: focus and gesture side effects hang off them regardless of handlers.
11
+ // keep the two in sync. Down, up and cancel are recorded but the runtime
12
+ // never gates them: focus and gesture side effects hang off them regardless
13
+ // of handlers.
13
14
  const MOVE_BIT = 1
14
15
  const POINTER_INTEREST: Record<string, number> = {
15
16
  onPointerMove: MOVE_BIT,
@@ -18,6 +19,7 @@ const POINTER_INTEREST: Record<string, number> = {
18
19
  onPointerEnter: 8,
19
20
  onPointerLeave: 16,
20
21
  onWheel: 32,
22
+ onPointerCancel: 64,
21
23
  }
22
24
 
23
25
  let interests = new Map<number, number>()
package/src/double-tap.ts CHANGED
@@ -48,8 +48,8 @@ export interface TapSequenceOptions {
48
48
  }
49
49
 
50
50
  /** The tap bookkeeping under a double-tap: one first tap at a time,
51
- * decided by the second down, by travel, by a long hold, by the window
52
- * timer or by cancel(). */
51
+ * decided by the second down, by travel, by a long hold, by a cancelled
52
+ * pointer, by the window timer or by cancel(). */
53
53
  export function createTapSequence(options: TapSequenceOptions) {
54
54
  let first: { id: number; at: PointerPoint; downAt: number; upAt: number | null } | null = null
55
55
  let timer: ReturnType<typeof setTimeout> | null = null
@@ -99,6 +99,9 @@ export function createTapSequence(options: TapSequenceOptions) {
99
99
  settle(false)
100
100
  }, DOUBLE_TAP_MS)
101
101
  },
102
+ cancelled(e: PointerEvent) {
103
+ if (first && first.id === e.pointerId) settle(false)
104
+ },
102
105
  cancel: () => settle(false),
103
106
  }
104
107
  }
@@ -145,6 +148,10 @@ export function createDoubleTap(options: DoubleTapOptions) {
145
148
  if (second === e.pointerId) release()
146
149
  taps.up(e)
147
150
  },
151
+ onPointerCancel: (e: PointerEvent) => {
152
+ if (second === e.pointerId) release()
153
+ taps.cancelled(e)
154
+ },
148
155
  }
149
156
  return { handlers, cancel }
150
157
  }
@@ -1,5 +1,5 @@
1
1
  import { createSignal, runWithOwner } from "@solidjs/signals"
2
- import { on } from "srt:events"
2
+ import { on } from "sol:events"
3
3
  import { onPointerMove } from "./core"
4
4
  import { windowSize, safeArea, displayScale, windowFocused, keyboardHeight } from "./window"
5
5
 
@@ -16,7 +16,7 @@ import { windowSize, safeArea, displayScale, windowFocused, keyboardHeight } fro
16
16
  // runs, which is usually inside a component or a computation (a memo's first
17
17
  // read of env.inputDevices, a HUD effect's first read of pointerLocked()).
18
18
  // Created there, the fact would belong to that reader: sticky events replay
19
- // their cached value synchronously on subscribe (srt:events' on()), so the
19
+ // their cached value synchronously on subscribe (sol:events' on()), so the
20
20
  // on() call writes the signal it just created inside the reader's owned
21
21
  // scope - a render-time write, which the guard rejects - and a root or a
22
22
  // subscription created there is disposed with the reader. The facts are
@@ -274,7 +274,7 @@ export let env = {
274
274
  },
275
275
  /**
276
276
  * The link this process was started with, or null: a custom scheme link
277
- * the OS routed to the app (Android intent data; `srt render --link`),
277
+ * the OS routed to the app (Android intent data; `sol render --link`),
278
278
  * raw, exactly as it arrived. What it names is the app's to decide, and
279
279
  * it is untrusted input: validate before acting on any part of it. A link
280
280
  * arriving while the app runs is an event instead: `onLink`. Fixed for the
package/src/gamepad.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createSignal, runWithOwner } from "@solidjs/signals"
2
- import { on } from "srt:events"
2
+ import { on } from "sol:events"
3
3
 
4
4
  // Gamepad State: a reactive mirror of the runtime's sticky "gamepads" event.
5
5
  //
package/src/gpu.ts CHANGED
@@ -347,7 +347,7 @@ export function createMutableTexture(
347
347
  * `packed` as a keyword, so `vec4 packed = texture(...)` fails with a syntax
348
348
  * error that does not name the identifier - pick another name.
349
349
  *
350
- * A bad source is a runtime failure, not one `srt check` catches: the compile
350
+ * A bad source is a runtime failure, not one `sol check` catches: the compile
351
351
  * throws at this call, so a shader created in a component body takes the app
352
352
  * to the error window unless an `<Errored>` closer in the tree claims it.
353
353
  */
@@ -133,6 +133,7 @@ export interface PointerFeed extends InputDevice {
133
133
  onPointerDown(event: PointerEvent): void
134
134
  onPointerMove(event: PointerEvent): void
135
135
  onPointerUp(event: PointerEvent): void
136
+ onPointerCancel(event: PointerEvent): void
136
137
  onPointerLeave(event: PointerEvent): void
137
138
  onWheel(event: WheelEvent): void
138
139
  }
@@ -481,6 +482,17 @@ export function createPointerFeed(options: PointerFeedOptions = {}): PointerFeed
481
482
  opening = null
482
483
  }
483
484
  },
485
+ onPointerCancel(e) {
486
+ if (!downs.has(e.pointerId)) return
487
+ // The brackets close with no velocity and no swipe: the pointer
488
+ // ended without its lift.
489
+ transform.handlers.onPointerCancel(e)
490
+ released = null
491
+ lifted(e)
492
+ hold.cancelled(e)
493
+ taps.cancelled(e)
494
+ if (opening && opening.id === e.pointerId) opening = null
495
+ },
484
496
  onPointerLeave() {},
485
497
  onWheel(e) {
486
498
  if (e.deltaY === 0) return
package/src/long-press.ts CHANGED
@@ -36,7 +36,8 @@ export interface HoldTimerOptions {
36
36
  }
37
37
 
38
38
  /** The arm / slop / timer machine under a long-press: one pointer at a
39
- * time, disarmed by travel, by a lift, by a second pointer or by cancel(). */
39
+ * time, disarmed by travel, by a lift, by a cancelled pointer, by a second
40
+ * pointer or by cancel(). */
40
41
  export function createHoldTimer(options: HoldTimerOptions) {
41
42
  let armed: { id: number; at: PointerPoint; mods: { shiftKey: boolean; ctrlKey: boolean; altKey: boolean; metaKey: boolean; button?: number } } | null = null
42
43
  let timer: ReturnType<typeof setTimeout> | null = null
@@ -69,6 +70,9 @@ export function createHoldTimer(options: HoldTimerOptions) {
69
70
  up(e: PointerEvent) {
70
71
  if (armed && armed.id === e.pointerId) disarm()
71
72
  },
73
+ cancelled(e: PointerEvent) {
74
+ if (armed && armed.id === e.pointerId) disarm()
75
+ },
72
76
  cancel: disarm,
73
77
  }
74
78
  }
@@ -84,6 +88,10 @@ export interface LongPressOptions {
84
88
  onLongPressMove?: (dx: number, dy: number) => void
85
89
  /** The lift after a fire. */
86
90
  onLongPressEnd?: () => void
91
+ /** A fired long press ended without its lift: the system cancelled the
92
+ * pointer (onPointerCancel), or another recognizer took it in the
93
+ * arena. Fired instead of onLongPressEnd. */
94
+ onLongPressCancel?: () => void
87
95
  }
88
96
 
89
97
  export function createLongPress(options: LongPressOptions) {
@@ -99,16 +107,26 @@ export function createLongPress(options: LongPressOptions) {
99
107
  options.onLongPress?.(at)
100
108
  },
101
109
  })
102
- let cancel = () => {
103
- hold.cancel()
110
+ let release = () => {
104
111
  if (fired !== null) {
105
112
  arena.release(fired, owner)
106
113
  fired = null
107
114
  last = null
108
115
  }
109
116
  }
117
+ // The press ends without its lift, whatever took the pointer: the arena
118
+ // (cancel) or the system (onPointerCancel). An unmount releases alone.
119
+ let cancel = () => {
120
+ hold.cancel()
121
+ let won = fired !== null
122
+ release()
123
+ if (won) options.onLongPressCancel?.()
124
+ }
110
125
  let owner: ArenaOwner = { cancel }
111
- onSettled(() => cancel)
126
+ onSettled(() => () => {
127
+ hold.cancel()
128
+ release()
129
+ })
112
130
 
113
131
  let handlers = {
114
132
  onPointerDown: (e: PointerEvent) => {
@@ -126,12 +144,16 @@ export function createLongPress(options: LongPressOptions) {
126
144
  },
127
145
  onPointerUp: (e: PointerEvent) => {
128
146
  if (fired === e.pointerId) {
129
- cancel()
147
+ release()
130
148
  options.onLongPressEnd?.()
131
149
  return
132
150
  }
133
151
  hold.up(e)
134
152
  },
153
+ onPointerCancel: (e: PointerEvent) => {
154
+ if (fired === e.pointerId) cancel()
155
+ else hold.cancelled(e)
156
+ },
135
157
  }
136
158
  return { handlers, cancel }
137
159
  }
package/src/pan.ts CHANGED
@@ -35,6 +35,13 @@ export interface PanOptions {
35
35
  * finger rested before lifting or moved under FLING_MIN_VELOCITY.
36
36
  */
37
37
  onPanEnd?: (velocity: Velocity) => void
38
+ /**
39
+ * The pan ended without its result: the system cancelled the pointer
40
+ * (onPointerCancel), or another recognizer took it in the arena. Fired
41
+ * instead of onPanEnd, only after onPanStart; whoever animates on
42
+ * settles here, nothing flings.
43
+ */
44
+ onPanCancel?: () => void
38
45
  }
39
46
 
40
47
  // The pan recognizer: turns a drag into a movement-delta stream. On a down it
@@ -49,8 +56,9 @@ export interface PanOptions {
49
56
  // fling minimum) for momentum. Moves and the up arrive on the frozen down
50
57
  // path, so an active pan keeps streaming when the pointer leaves the node
51
58
  // or the window.
52
- // cancel() is the external-cancel hook; it ends an active pan without
53
- // onPanEnd. Options are read at event time. Single-pointer by design; for
59
+ // cancel() is the external-cancel hook, the arena's; it and a pointer cancel
60
+ // end an active pan with onPanCancel instead of onPanEnd. Options are read
61
+ // at event time. Single-pointer by design; for
54
62
  // multi-pointer pinch/rotate compose createTransform instead.
55
63
  //
56
64
  // Frames: the slop is finger travel, so it is measured in window pixels
@@ -87,7 +95,13 @@ export function createPan(options: PanOptions) {
87
95
  armed = null
88
96
  origin = null
89
97
  }
90
- let cancel = reset
98
+ // The pan ends without its result, whatever took the pointer: the arena
99
+ // (cancel) or the system (onPointerCancel). An unmount resets alone.
100
+ let cancel = () => {
101
+ let started = active != null
102
+ reset()
103
+ if (started) options.onPanCancel?.()
104
+ }
91
105
  let owner = { cancel }
92
106
 
93
107
  // An unmount mid-drag must not leave a resolved claim behind.
@@ -134,6 +148,10 @@ export function createPan(options: PanOptions) {
134
148
  reset()
135
149
  }
136
150
  },
151
+ onPointerCancel: (e: PointerEvent) => {
152
+ if (active === e.pointerId) cancel()
153
+ else if (armed === e.pointerId) reset()
154
+ },
137
155
  }
138
156
 
139
157
  return { handlers, cancel }
package/src/renderer.ts CHANGED
@@ -86,7 +86,7 @@ function flushDestroy(): void {
86
86
  function removeNode(parent: ProxyNode, node: ProxyNode): void {
87
87
  if (!node || !parent) return
88
88
 
89
- // console.debug("[srt] removeNode", parent.id, node.id)
89
+ // console.debug("[sol] removeNode", parent.id, node.id)
90
90
 
91
91
  // Update JS tree references
92
92
  let index = parent.children.indexOf(node)
@@ -113,7 +113,7 @@ function removeNode(parent: ProxyNode, node: ProxyNode): void {
113
113
  // bookkeeping on the hot create/insert paths, orphans are derived from the
114
114
  // proxy map itself: parentless, not the window root, and not awaiting the
115
115
  // destroy sweep. window.ts runs the scan on a rendered frame every few
116
- // seconds; dev bundles only (srt always defines import.meta.env.DEV, so a
116
+ // seconds; dev bundles only (sol always defines import.meta.env.DEV, so a
117
117
  // production bundle folds the check into a constant early return).
118
118
  const SENTINEL_INTERVAL_MS = 5000
119
119
  let sentinelDue = 0
@@ -208,7 +208,7 @@ function routeFor(name: string): number {
208
208
  function applyProp<T>(node: ProxyNode, name: string, value: T): void {
209
209
  if (!node) return
210
210
 
211
- // console.debug("[srt] applyProp", node.id, name, value)
211
+ // console.debug("[sol] applyProp", node.id, name, value)
212
212
 
213
213
  switch (routeFor(name)) {
214
214
  case ROUTE_EVENT:
@@ -234,7 +234,7 @@ let renderer = createRenderer<ProxyNode>({
234
234
  createElement: (elementType: string, props?: Record<string, any>): ProxyNode => {
235
235
  let proxy = createProxyNode(elementType)
236
236
 
237
- // console.debug("[srt] createElement", proxy.id, elementType)
237
+ // console.debug("[sol] createElement", proxy.id, elementType)
238
238
 
239
239
  if (elementType === "window") tree.createRoot(proxy.id)
240
240
  else tree.createNode(proxy.id, elementType)
@@ -257,20 +257,20 @@ let renderer = createRenderer<ProxyNode>({
257
257
  // no element form of its own (the DOM's "#text" node name).
258
258
  createTextNode: (value: string): ProxyNode => {
259
259
  let proxy = createProxyNode("#text")
260
- // console.debug("[srt] createTextNode", proxy.id, value)
260
+ // console.debug("[sol] createTextNode", proxy.id, value)
261
261
  tree.createNode(proxy.id, "#text")
262
262
  tree.setProperty(proxy.id, "text", "" + value)
263
263
  return proxy
264
264
  },
265
265
 
266
266
  replaceText: (node: ProxyNode, value: string): void => {
267
- // console.debug("[srt] replaceText", node.id, value)
267
+ // console.debug("[sol] replaceText", node.id, value)
268
268
  tree.setProperty(node.id, "text", "" + value)
269
269
  },
270
270
 
271
271
  isTextNode: (node: ProxyNode): boolean => node?.elementType === "#text",
272
272
  setProperty: <T>(node: ProxyNode, name: string, value: T): void => {
273
- // console.debug("[srt] setProperty", node.id, name, value)
273
+ // console.debug("[sol] setProperty", node.id, name, value)
274
274
  applyProp(node, name, value)
275
275
  },
276
276
 
@@ -294,7 +294,7 @@ let renderer = createRenderer<ProxyNode>({
294
294
  pendingDestroy.delete(node.id)
295
295
 
296
296
  if (parent) {
297
- // console.debug("[srt] insertNode", parent.id, node.id, anchor?.id ?? "")
297
+ // console.debug("[sol] insertNode", parent.id, node.id, anchor?.id ?? "")
298
298
 
299
299
  // Native first: the tree refuses a laid-out element under a d-* parent
300
300
  // (it throws, naming both tags), and the mirror must not record a child
@@ -1,4 +1,4 @@
1
- // Lattice runner builtin modules (the "srt:*" surface). These are ambient
1
+ // Lattice runner builtin modules (the "sol:*" surface). These are ambient
2
2
  // module declarations, so they live in this global-script .d.ts (no top-level
3
3
  // import/export) rather than in types.d.ts: an ambient `declare module` only
4
4
  // becomes globally visible to consumers from a non-module declaration file.
@@ -26,7 +26,8 @@ declare module "*.frag" {
26
26
 
27
27
  // Binary asset imports: `import data from "./pic.png" with { type: "binary" }`.
28
28
  // The bundler inlines the file's bytes as a Uint8Array (see packages/cli
29
- // bundler `inlineImport`); feed it straight into createImage/decodeImage.
29
+ // bundler `inlineImport`); feed it straight into createImage/decodeImage, or a
30
+ // `.wasm` into `new Module` from flux:wasm.
30
31
  declare module "*.png" {
31
32
  const bytes: Uint8Array
32
33
  export default bytes
@@ -51,7 +52,11 @@ declare module "*.glb" {
51
52
  const bytes: Uint8Array
52
53
  export default bytes
53
54
  }
54
- declare module "*.srtm" {
55
+ declare module "*.sol3m" {
56
+ const bytes: Uint8Array
57
+ export default bytes
58
+ }
59
+ declare module "*.wasm" {
55
60
  const bytes: Uint8Array
56
61
  export default bytes
57
62
  }
@@ -63,13 +68,13 @@ declare module "*.srtm" {
63
68
  // pointer moves have dispatched, every pointer the same age - multi-pointer
64
69
  // recognizers measure there; its `timeStamp` is theirs), and "render" (the
65
70
  // per-frame signal).
66
- declare module "srt:events" {
71
+ declare module "sol:events" {
67
72
  export function on(event: string, callback: (data: any) => void): () => void
68
73
  export function once(event: string, callback: (data: any) => void): () => void
69
74
  }
70
75
 
71
76
  // The running application's own surface (lattice), present in every build.
72
- declare module "srt:app" {
77
+ declare module "sol:app" {
73
78
  /**
74
79
  * End the current app now, without running the quit hooks: back to the
75
80
  * player in a dev client, quit when standalone or at the player root (on
@@ -100,7 +105,7 @@ declare module "srt:app" {
100
105
 
101
106
  // Dev-server control surface (lattice). Present only in dev/go builds; in other
102
107
  // builds `available` is false and the functions are no-ops.
103
- declare module "srt:dev" {
108
+ declare module "sol:dev" {
104
109
  export const available: boolean
105
110
  export const canDiscover: boolean
106
111
  export const recents: string[]
@@ -139,7 +144,7 @@ declare module "srt:dev" {
139
144
  // Installed-app management (lattice), the player's surface over the client's
140
145
  // version store. Present only in go/dev client builds; elsewhere `available`
141
146
  // is false, `list` returns [], and launch/remove are no-ops.
142
- declare module "srt:apps" {
147
+ declare module "sol:apps" {
143
148
  export const available: boolean
144
149
  /**
145
150
  * An installed app: id, display name (the installed manifest's displayName,
@@ -233,16 +238,16 @@ declare module "srt:apps" {
233
238
  // submits the display list. To schedule a future frame instead, use
234
239
  // requestFrame() from "flux:rendertree". The tree-building surface itself is
235
240
  // "flux:rendertree" (from @solidrt/flux-types).
236
- declare module "srt:render" {
241
+ declare module "sol:render" {
237
242
  export function renderFrame(): void
238
243
  }
239
244
 
240
245
  // The engine verbs of an app test (lattice, dev client only), which
241
246
  // @solidrt/test builds its surface on; tests import that, not this.
242
- // The verbs work only in an engine a test host built (`srt test`) and throw
247
+ // The verbs work only in an engine a test host built (`sol test`) and throw
243
248
  // anywhere else. App time there is frame / frameRate and nothing else: no
244
249
  // frame runs unless one is asked for.
245
- declare module "srt:test" {
250
+ declare module "sol:test" {
246
251
  /**
247
252
  * Runs one frame: timers due by its time fire, then frame callbacks and
248
253
  * the flush, then the draw if anything demanded one. Fulfills once the
package/src/swipe.ts CHANGED
@@ -67,6 +67,10 @@ export interface SwipeOptions {
67
67
  onSwipe?: (direction: SwipeDirection, velocity: Velocity) => void
68
68
  /** Every lift of an active swipe, qualified or not. */
69
69
  onSwipeEnd?: (velocity: Velocity) => void
70
+ /** An active swipe ended without its lift: the system cancelled the
71
+ * pointer (onPointerCancel), or another recognizer took it in the
72
+ * arena. Fired instead of onSwipe and onSwipeEnd. */
73
+ onSwipeCancel?: () => void
70
74
  }
71
75
 
72
76
  export function createSwipe(options: SwipeOptions) {
@@ -97,6 +101,11 @@ export function createSwipe(options: SwipeOptions) {
97
101
  if (direction) options.onSwipe?.(direction, velocity)
98
102
  options.onSwipeEnd?.(velocity)
99
103
  },
104
+ onPanCancel: () => {
105
+ down = null
106
+ lift = null
107
+ options.onSwipeCancel?.()
108
+ },
100
109
  })
101
110
 
102
111
  let handlers = {
@@ -120,6 +129,13 @@ export function createSwipe(options: SwipeOptions) {
120
129
  lift = null
121
130
  }
122
131
  },
132
+ onPointerCancel: (e: PointerEvent) => {
133
+ pan.handlers.onPointerCancel(e)
134
+ if (down && down.id === e.pointerId) {
135
+ down = null
136
+ lift = null
137
+ }
138
+ },
123
139
  }
124
140
  let cancel = () => {
125
141
  pan.cancel()
package/src/textures.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // the runtime: this entry imports nothing the runtime provides, so a bake
3
3
  // script under bun and an extension's pure modules use it as it is
4
4
  // (tests/textures.test.ts imports it on the bare flux binary and fails the
5
- // moment a gui or srt: import creeps in). Any extension that bakes or loads textures
5
+ // moment a gui or sol: import creeps in). Any extension that bakes or loads textures
6
6
  // builds on this, the first-party 3d one included.
7
7
  //
8
8
  // isKtx2 tells a compressed texture file from a PNG or JPEG.
package/src/transform.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { onSettled } from "@solidjs/signals"
2
- import { on } from "srt:events"
2
+ import { on } from "sol:events"
3
3
  import type { PointerEvent } from "./types"
4
4
  import { arena, type ArenaOwner } from "./arena"
5
5
  import { createVelocityTracker, flingVelocity } from "./velocity"
@@ -59,6 +59,10 @@ export interface TransformOptions {
59
59
  * parent-frame pixels per second (velocity.ts; zero after a rest or
60
60
  * under the fling minimum). */
61
61
  onTransformEnd?: (velocity: Velocity) => void
62
+ /** The gesture ended without its result: the system cancelled a finger
63
+ * (onPointerCancel), or another recognizer took the pointers in the
64
+ * arena. Fired instead of onTransformEnd, only after onTransformStart. */
65
+ onTransformCancel?: () => void
62
66
  }
63
67
 
64
68
  // The merged transform recognizer: pan + pinch + rotate as ONE gesture over
@@ -116,7 +120,8 @@ export interface TransformOptions {
116
120
  // configuration, so the change itself never emits a jump delta. The gesture
117
121
  // ends when the last finger lifts. Moves and ups arrive on the frozen down
118
122
  // path, so an active transform survives leaving the node or the window.
119
- // cancel() is the external-cancel hook; it ends an active gesture without
123
+ // cancel() is the external-cancel hook, the arena's; it and a cancelled
124
+ // finger end an active gesture with onTransformCancel instead of
120
125
  // onTransformEnd. Options are read at event time.
121
126
  //
122
127
  // Frames: the slop, the span filter and its rate gates are finger-travel
@@ -206,7 +211,11 @@ export function createTransform(options: TransformOptions) {
206
211
  rebase = false
207
212
  tracker.reset()
208
213
  }
209
- let cancel = reset
214
+ let cancel = () => {
215
+ let started = active
216
+ reset()
217
+ if (started) options.onTransformCancel?.()
218
+ }
210
219
  let owner: ArenaOwner = { cancel }
211
220
 
212
221
  // The per-frame measure point: runs at the pointerFrame terminator, when
@@ -355,6 +364,17 @@ export function createTransform(options: TransformOptions) {
355
364
  pointers.delete(e.pointerId)
356
365
  ref = pointers.size > 0 ? measure() : null
357
366
  },
367
+ onPointerCancel: (e: PointerEvent) => {
368
+ if (!pointers.has(e.pointerId)) return
369
+ // The system cancels the gesture, not a finger: every platform
370
+ // cancels all of them together, so the whole gesture ends.
371
+ if (active) {
372
+ cancel()
373
+ return
374
+ }
375
+ pointers.delete(e.pointerId)
376
+ ref = pointers.size > 0 ? measure() : null
377
+ },
358
378
  }
359
379
 
360
380
  return { handlers, cancel }
package/src/types.d.ts CHANGED
@@ -9,14 +9,14 @@ import type { Element } from "solid-js"
9
9
 
10
10
  export type { TextInputHints }
11
11
 
12
- // The "srt:*" lattice runner modules are declared in ./runtime-modules.d.ts
12
+ // The "sol:*" lattice runner modules are declared in ./runtime-modules.d.ts
13
13
  // (referenced above) - ambient `declare module` only reaches consumers from a
14
14
  // non-module declaration file, and this file is a module.
15
15
 
16
16
  declare global {
17
17
  interface ImportMeta {
18
18
  /**
19
- * Build-mode constants, substituted textually by the srt bundler (Vite
19
+ * Build-mode constants, substituted textually by the sol bundler (Vite
20
20
  * vocabulary). `DEV` is true in dev bundles and false in production
21
21
  * bundles, where the substituted constant lets the minifier fold
22
22
  * dev-only code away entirely.
@@ -278,7 +278,8 @@ export interface TransformProps {
278
278
 
279
279
  // Window-relative pointer coordinates are reported as clientX/clientY (matching
280
280
  // the DOM MouseEvent). pointerType distinguishes mouse from touch; button is the
281
- // pressed button on down/up (0 = primary); the modifier flags mirror the DOM.
281
+ // pressed button on down/up (0 = primary) and absent on a cancel; the modifier
282
+ // flags mirror the DOM.
282
283
  export interface PointerEvent {
283
284
  /**
284
285
  * When the event happened, in milliseconds. Every input event carries one,
@@ -291,7 +292,7 @@ export interface PointerEvent {
291
292
  * pointer, so the real move after it can carry an earlier time. Use it
292
293
  * where you would reach for `performance.now()` in a handler: that one
293
294
  * measures when the handler ran, not when the input happened, and it does
294
- * not follow the dev clock, `srt render` or a test. The origin is
295
+ * not follow the dev clock, `sol render` or a test. The origin is
295
296
  * arbitrary (compare stamps with each other, not with `performance.now()`,
296
297
  * a timer or the `onFrame` tick).
297
298
  */
@@ -408,6 +409,15 @@ export interface NodeProps {
408
409
  export interface PointerProps extends NodeProps {
409
410
  onPointerDown?: (event: PointerEvent) => void
410
411
  onPointerUp?: (event: PointerEvent) => void
412
+ /**
413
+ * The system took the pointer away: a system gesture (the back swipe,
414
+ * the notification shade), palm rejection, the window losing the touch.
415
+ * The last event for that pointer, and no up follows it, so a press
416
+ * ends without firing and a drag ends without a fling. Routed along the
417
+ * pressed path like an up, with the pointer's last known position and
418
+ * no `button`.
419
+ */
420
+ onPointerCancel?: (event: PointerEvent) => void
411
421
  onPointerMove?: (event: PointerEvent) => void
412
422
  onPointerEnter?: (event: PointerEvent) => void
413
423
  onPointerLeave?: (event: PointerEvent) => void
package/src/window.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createSignal, getOwner, onCleanup, onSettled, runWithOwner, flush } from "@solidjs/signals"
2
2
  import { requestFrame, setPointerLock } from "flux:rendertree"
3
- import { renderFrame } from "srt:render"
4
- import { on, once } from "srt:events"
5
- import { exit as nativeExit, background as nativeBackground, registerProtocolHandler as nativeRegisterProtocolHandler } from "srt:app"
3
+ import { renderFrame } from "sol:render"
4
+ import { on, once } from "sol:events"
5
+ import { exit as nativeExit, background as nativeBackground, registerProtocolHandler as nativeRegisterProtocolHandler } from "sol:app"
6
6
  import { platform } from "flux:process"
7
7
  import { getEventHandler, focusedNode, setFocus, activateTextInput, setInterestRoot } from "./core"
8
8
  import { scanForOrphans, getNodePath } from "./renderer"
@@ -269,7 +269,7 @@ export function onFrame(fn: (tick: number, frame: number, rate: number) => void,
269
269
  * between frames (an event handler, a timer) it is the latest frame's. It
270
270
  * is 0 before the first frame. Not reactive, and reading it requests no
271
271
  * frame. Use it to time things against the app's own clock from code that
272
- * has no tick in hand: it follows the dev tools' clock control, `srt
272
+ * has no tick in hand: it follows the dev tools' clock control, `sol
273
273
  * render` and a test, where performance.now() does not.
274
274
  */
275
275
  export function frameTime(): number {
@@ -553,6 +553,7 @@ export function attachWindow(nodeId: number) {
553
553
  let unsubscribe: () => void = null!
554
554
  let unsubDown: () => void = null!
555
555
  let unsubUp: () => void = null!
556
+ let unsubCancel: () => void = null!
556
557
  let unsubMove: () => void = null!
557
558
  let unsubEnter: () => void = null!
558
559
  let unsubLeave: () => void = null!
@@ -664,6 +665,10 @@ export function attachWindow(nodeId: number) {
664
665
  bubble(raw, "onPointerUp")
665
666
  })
666
667
 
668
+ unsubCancel = on("pointerCancel", (raw: RawPointer) => {
669
+ bubble(raw, "onPointerCancel")
670
+ })
671
+
667
672
  unsubMove = on("pointerMove", (raw: RawPointer) => {
668
673
  bubble(raw, "onPointerMove")
669
674
  })
@@ -761,6 +766,7 @@ export function attachWindow(nodeId: number) {
761
766
  if (unsubscribe) unsubscribe()
762
767
  if (unsubDown) unsubDown()
763
768
  if (unsubUp) unsubUp()
769
+ if (unsubCancel) unsubCancel()
764
770
  if (unsubMove) unsubMove()
765
771
  if (unsubEnter) unsubEnter()
766
772
  if (unsubLeave) unsubLeave()