@solidrt/cli 0.0.50 → 0.0.51

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
@@ -7,6 +7,15 @@ reactivity), see @solidrt/core (its AGENTS.md).
7
7
  `srt` is the dev tool. Bun is a dev prerequisite only; SolidRT apps run on the
8
8
  bundled `flux` runtime, not on Bun. Invoke via `bunx srt <command>`.
9
9
 
10
+ Two companion files carry the depth this one leaves out; read the one that
11
+ matches the work before starting it:
12
+ - agents/debugging.md - driving a running app over MCP, and the debugging
13
+ lessons that cost real time. Read before investigating a bug or verifying
14
+ a change against the live app.
15
+ - agents/assets.md - the assets/ folder, inlined imports, fonts, and the
16
+ `solidrt` package.json key. Read before adding an asset or font, or
17
+ building for distribution.
18
+
10
19
  ## Commands
11
20
 
12
21
  - `bunx srt init <dir>` - scaffold a new SolidRT project into a new (empty)
@@ -15,9 +24,12 @@ bundled `flux` runtime, not on Bun. Invoke via `bunx srt <command>`.
15
24
  `bun create solidrt <dir>`.
16
25
  - `bunx srt run src/index.tsx` - dev server + a local client window, watches and
17
26
  hot-reloads. NEEDS A DISPLAY (opens a GUI window). Not usable headless.
18
- - `bunx srt bundle src/index.tsx` - transpile to `<file>.srt.js`. With
19
- `--compile`, emits `.srt.bin` bytecode. `--minify`, `--dev`, `--stdout`,
20
- `--output` also available.
27
+ - `bunx srt bundle src/index.tsx` - bundle into `dist/bundle/` (or
28
+ `--output <dir>`): `<name>.srt.js` plus the app's isolate modules as
29
+ `isolates/<id>.js`. With `--compile`, bytecode (`.srt.bin`/`.bin`)
30
+ instead. Move the dir, not the bare file - a bundle loaded without its
31
+ isolates/ dir loses them (`--stdout` cannot carry them at all).
32
+ `--minify`, `--dev` also available.
21
33
  - `bunx srt render src/index.tsx [flags]` - render OFFSCREEN to PNG frames,
22
34
  optionally replaying a `--script` file recorded via `--capture`.
23
35
  - `bunx srt server [file]` / `bunx srt client` - the two halves of `run`
@@ -25,10 +37,11 @@ bundled `flux` runtime, not on Bun. Invoke via `bunx srt <command>`.
25
37
  - `bunx srt run src/index.tsx --capture out.script.json` - records keydown/keyup
26
38
  from every connected client into one script file (written on client
27
39
  disconnect), for replaying later with `render --script`. The file is JSON
28
- Lines and hand-authorable - the exact shape (`after` ms, `type`, `key`,
29
- `device`) is in `docs/cli.md` under `--capture`. For probing app state
30
- without a display, `-- <args...>` reaches the app as `flux:process` argv
31
- (also `docs/cli.md`), which is often simpler than scripting input.
40
+ Lines and hand-authorable: one object per line,
41
+ `{"after": <ms since previous event>, "type": "keydown" | "keyup",
42
+ "key": <W3C KeyboardEvent.key>, "device": <client id>}`. For probing app
43
+ state without a display, `-- <args...>` reaches the app as `flux:process`
44
+ argv, which is often simpler than scripting input.
32
45
 
33
46
  ## Verifying without a display (headless / CI / agent box)
34
47
 
@@ -0,0 +1,32 @@
1
+ # Assets and app identity
2
+
3
+ Read this before adding an asset, a font, or preparing a build for
4
+ distribution.
5
+
6
+ - Everything under `assets/` ships with the app: the folder is collected
7
+ wholesale into each build's version manifest (no bundler analysis, no
8
+ registration step). Reference assets by path - `file("assets/sounds/x.ogg")`
9
+ from `flux:fs` - and treat them as read-only at runtime; writes belong in
10
+ plain relative paths, which land in the app's private data dir.
11
+ - Small text-like assets (SVG documents, shaders) can instead be inlined via
12
+ imports. An import attribute picks the form and works on any extension:
13
+ `import src from "./effect.glsl" with { type: "text" }` yields the file's
14
+ contents as a string, `with { type: "binary" }` yields a Uint8Array. `.svg`
15
+ is text-loaded with no attribute needed. Shader sources (`.glsl`/`.vert`/
16
+ `.frag`) are declared as text modules out of the box, so they typecheck
17
+ without setup. Inlining trades update granularity for zero I/O - keep big or
18
+ streamable files (audio, images) in `assets/`.
19
+ - Custom fonts go in `assets/fonts/` and are declared in the `solidrt.fonts`
20
+ map in package.json (alias -> file path; role aliases `sans`/`serif`/`mono`
21
+ replace the built-in defaults, `false` drops one, other keys add fonts
22
+ selectable via fontFamily). A newly added font shows after restarting the
23
+ client.
24
+ - The `solidrt` key in package.json is the app's identity: set a stable
25
+ reverse-DNS `appId` before distributing - it keys the app's storage
26
+ folder, defaults from the package name in dev, and `srt pack` warns
27
+ while defaulted. `org` and `displayName` are optional display metadata
28
+ (future launcher/window naming) with no storage meaning.
29
+ - `bunx srt pack src/index.tsx` builds a single-file executable;
30
+ `bunx srt pack --folder src/index.tsx` writes the flat app folder
31
+ (runner + manifest.json + bundle + assets/, plus the runner's GL
32
+ libraries on Windows and macOS) to `dist/pack/`.
@@ -0,0 +1,142 @@
1
+ # Debugging a running app
2
+
3
+ Read this before investigating a bug in a running app, or before driving the
4
+ app over MCP to verify a change.
5
+
6
+ ## Driving the app over MCP
7
+
8
+ The project ships an MCP server (`.mcp.json`, `srt mcp`) that talks to the dev
9
+ server `bunx srt run` starts. Each tool documents itself in full - read the
10
+ tool description rather than guessing at its arguments. What the individual
11
+ descriptions cannot tell you:
12
+
13
+ - If `list_clients` is empty, no app is running: ask the user to start
14
+ `bunx srt run src/index.tsx` rather than starting a second one yourself.
15
+ The bridge dials the dev server's default port (34884), so if the user
16
+ started it with `--port N`, `.mcp.json` needs the same flag:
17
+ `"args": [..., "mcp", "--port", "N"]`.
18
+ - Several clients may be attached at once (desktop window, phone, tablet)
19
+ with different sizes, display scales and safe areas. `reload` pushes to all
20
+ of them, but `call_debug` / `send_input` / `get_snapshot` / log cursors are
21
+ per client, and interactive state does NOT sync - a flow driven on one
22
+ client leaves the others sitting on the initial screen, which reads as a
23
+ crash to a human holding that device. So: when driving state via
24
+ `call_debug`, send the same call to every client (or say which client you
25
+ are using); and before calling a visual change done, snapshot each distinct
26
+ form factor at least once - a layout that fits one window can clip or
27
+ overflow another.
28
+ - A `shader` on `<window>` runs on the finished frame past the point every
29
+ capture reads: `get_snapshot` returns the UNSHADED content (window node
30
+ included), `get_texture` has no id for the shaded layer, and
31
+ `get_gpu_resources` reports only that the pass exists. `bunx srt render` is
32
+ the only programmatic view of what a window shader produces.
33
+ - Permission prompts: agents typically ask approval per MCP tool. All of
34
+ these tools only talk to the local dev server the user started with
35
+ `bunx srt run` - nothing leaves the machine - so approving the server as a
36
+ whole is a reasonable default. If repeated prompts get in the way, do not
37
+ work around them; tell the user they can pre-approve the server in their
38
+ agent's settings (most agents have a per-server trust or allowlist setting
39
+ - in Claude Code, add "mcp__solidrt" to `permissions.allow` in
40
+ ~/.claude/settings.json to cover every solidrt project). This is the user's
41
+ call to make, once, in their own tooling.
42
+
43
+ ## Lessons that cost real time
44
+
45
+ - console.log + get_logs is your primary probe into runtime state. For state
46
+ you will want repeatedly (a pose, a mode, a counter), bind a debug key that
47
+ logs it and read it back via get_logs.
48
+ - Better than debug keys when driving the app over MCP: register debug
49
+ COMMANDS - `registerDebug(name, fn)` from `srt:dev`, invoked via the
50
+ list_debug/call_debug tools. Use them to SET UP state (jump to a level,
51
+ force a mode, seed a scenario); then the runtime-level tools take over -
52
+ set_time_scale 0 freezes the result for as many snapshots as you need,
53
+ and step_frames walks it forward deterministically. Set state, pause,
54
+ snapshot. Registrations reset on hot reload, so register at module init;
55
+ sync return values only - and note a signal you just wrote flushes on a
56
+ microtask, so returning a signal read straight after setting it returns
57
+ the OLD value.
58
+ - call_debug sets state directly, skipping focus, key routing, and
59
+ TextInput - fine for SETUP, but "the interaction works" is only shown by
60
+ the real pipeline: verify clicks, typing, and drags with send_input,
61
+ which enters events where SDL input does.
62
+ - Key events start at the focused node and bubble to the window root; with
63
+ nothing focused they go to the window root alone. So a debug key bound via
64
+ `<window onKeyDown>` always fires (unless a focused component consumes the
65
+ key with stopPropagation, as TextInput does for editing keys). `key` and
66
+ `code` are W3C KeyboardEvent values, so arrow keys arrive as "ArrowLeft"/
67
+ "ArrowRight"/"ArrowUp"/"ArrowDown" (not "Left"), alongside "Enter",
68
+ "Escape", "a".
69
+ - Idle frames skip work: shaders/pipelines only re-render when an input
70
+ changes - their own params/geometry, or a sampled texture (a data upload,
71
+ or a sampled target re-rendering; chains propagate automatically). Measure
72
+ performance while inputs are actually changing. get_snapshot works on an
73
+ idle client (it requests its own frame); a timeout means the JS thread is
74
+ busy or wedged. get_texture on a pipeline's render target reads the
75
+ current output, pending writes included, without needing a new frame.
76
+ - When a human reports a visual bug: capture a snapshot and SAY WHAT YOU SEE
77
+ in it before investigating, so you agree on the symptom. If you cannot see
78
+ the problem in the capture, say that instead of guessing.
79
+ - Snapshots are downscaled by the time you see them, so a full-window capture
80
+ cannot show you a defect a few pixels across. Whenever you hand-author
81
+ geometry - a `d-path` from raw path math, a `radius` where two shapes meet,
82
+ a stroke join - inspect it MAGNIFIED once, when you write it: get_snapshot
83
+ with a tight crop at scale 4-8 shows the actual rendered pixels enlarged,
84
+ in one call, on the real app. Verifying that a shape is in the right place
85
+ is not the same check as verifying it is drawn right - and get_render_tree
86
+ props answers the third question, whether the value you set is the value
87
+ the renderer holds.
88
+ - GPU/geometry bugs: inspect the actual GPU data FIRST - get_gpu_resources
89
+ for draw counts/uniforms/sizes, get_texture for atlas or data-texture
90
+ contents ("is this tile blank?" is a ten-second question), get_buffer for
91
+ vertex data. The pixels only tell you THAT something is wrong; the
92
+ resources tell you WHERE the data stops being right. In a one-big-pipeline
93
+ app the render tree is a single <texture> leaf and tells you nothing -
94
+ these tools are the visibility layer behind it. Only when the GPU data is
95
+ all correct (so the bug is in producing it, or in the shader), reproduce
96
+ the math CPU-side in a scratch bun script against the app's real data and
97
+ print values.
98
+ - Validate assets at load time and log anomalies (missing lumps/files,
99
+ fully-transparent composites, zero-sized images). Silent fallbacks hide
100
+ bugs for days; a one-line warning surfaces them the first run.
101
+ - After every reload the app restarts from its initial state. If reaching
102
+ the bug site takes navigation, add a dev shortcut (teleport key, noclip,
103
+ initial-state override) before iterating - the round trips add up fast.
104
+ - Clamp onFrame time deltas to [0, cap], not just capped: across a hot
105
+ reload the runtime's tick counter resets AFTER the new instance's first
106
+ frame, so the second frame computes a hugely NEGATIVE delta.
107
+ Math.min(dt, cap) lets it through, and one bad frame can corrupt anything
108
+ integrated from dt (positions fly off, accumulators go so negative they
109
+ never recover). Math.max(0, Math.min(dt, cap)) costs nothing.
110
+ - A registered onFrame is a standing request, not demand-gated: it re-requests
111
+ the next frame every time it runs, so the runtime keeps calling it - and
112
+ presents - every frame at the refresh rate until you deregister it (fps
113
+ stays at the refresh rate on an idle screen; that is the 60fps burn called
114
+ out in @solidrt/core agents/performance.md, rule 4). The upside is that a
115
+ self-running loop - a game clock, a shader driver, a stepped VM doing silent
116
+ CPU work with no console output - keeps advancing on its own: it does NOT
117
+ stall when the body changes nothing and needs no startup "prime" write.
118
+ Deregister onFrame (return its cleanup, or let onCleanup fire) whenever
119
+ there is nothing left to advance.
120
+ - Layout is incremental: a change re-solves only the dirty path, and clean
121
+ subtrees answer from a per-node cache, so long lists no longer cap layout
122
+ (a thousand-node tree relays out in well under a millisecond). If layoutMs
123
+ still grows with tree size, read the get_stats counters - a low
124
+ cacheHits/cacheGets ratio means the layout cache is being defeated, high
125
+ paraShapes means text is actually reshaping. Paint is viewport-culled:
126
+ under an `overflow="hidden"` scroller only the subtrees that can reach the
127
+ visible box are painted, so paintMs tracks what is on screen, not what is
128
+ mounted (nodesPainted in get_stats shows the count). Very long lists still
129
+ pay for the initial mount and for memory, so windowing stays sensible at
130
+ the thousands-of-rows scale.
131
+ - Remote images: createImage (and Image) dedupes repeated URLs, caches the
132
+ bytes on disk, and the runtime rate-limits concurrent asset fetches per
133
+ host - do not build your own promise cache around it. Images are fetched
134
+ with no freshness check (an already-cached URL is never re-checked), so
135
+ use versioned URLs for content that changes. Use Image's `fallback` prop
136
+ (an image source) for the broken-image case instead of catching errors
137
+ yourself.
138
+ - fetch() never caches by default and ignores server cache headers. Caching
139
+ is explicit and per call: `fetch(url, { cache: "force-cache" })` for
140
+ assets (serve from disk or fetch-and-store, no freshness),
141
+ `{ cache: "reload" }` to refresh an entry. Image/createImage already do
142
+ this for you.
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@solidrt/cli",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "license": "MIT",
5
+ "funding": "https://github.com/sponsors/wellawaretech",
5
6
  "author": "Antoine van Wel",
6
7
  "type": "module",
7
8
  "bin": {
@@ -13,6 +14,7 @@
13
14
  "server/",
14
15
  "scaffold/",
15
16
  "fonts/",
17
+ "agents/",
16
18
  "AGENTS.md"
17
19
  ],
18
20
  "dependencies": {
@@ -29,16 +31,16 @@
29
31
  "zod": "^4.4.3"
30
32
  },
31
33
  "optionalDependencies": {
32
- "@solidrt/darwin-arm64": "0.0.50",
33
- "@solidrt/linux-arm64-gnu": "0.0.50",
34
- "@solidrt/linux-x64-gnu": "0.0.50",
35
- "@solidrt/win32-x64-msvc": "0.0.50"
34
+ "@solidrt/darwin-arm64": "0.0.51",
35
+ "@solidrt/linux-arm64-gnu": "0.0.51",
36
+ "@solidrt/linux-x64-gnu": "0.0.51",
37
+ "@solidrt/win32-x64-msvc": "0.0.51"
36
38
  },
37
39
  "peerDependencies": {
38
40
  "typescript": "^7"
39
41
  },
40
42
  "devDependencies": {
41
- "@solidrt/flux-types": "0.0.50",
43
+ "@solidrt/flux-types": "0.0.51",
42
44
  "@types/babel__core": "^7.20.5",
43
45
  "@types/bun": "latest"
44
46
  }