@solidrt/cli 0.0.49 → 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 +31 -5
- package/agents/assets.md +32 -0
- package/agents/debugging.md +142 -0
- package/package.json +9 -6
- package/scaffold/AGENTS.md +102 -541
- package/scaffold/package.json +5 -4
- package/server/control.ts +15 -5
- package/server/main.ts +8 -8
- package/server/rebuild.ts +10 -2
- package/server/remap.ts +46 -33
- package/server/state.ts +6 -5
- package/src/args.ts +5 -7
- package/src/bundler.ts +112 -37
- package/src/commands/bundle.ts +106 -21
- package/src/commands/check.ts +7 -0
- package/src/commands/init.ts +61 -77
- package/src/commands/mcp.ts +63 -12
- package/src/commands/pack.ts +18 -8
- package/src/commands/render.ts +28 -5
- package/src/commands/server.ts +5 -5
- package/src/dev-server.ts +7 -7
- package/src/packer.ts +16 -12
- package/src/prompt.ts +46 -72
- package/src/repl.ts +27 -11
- package/src/util.ts +4 -3
- package/src/watcher.ts +7 -3
- package/scaffold/templates/components/template.json +0 -4
- package/scaffold/templates/default/template.json +0 -4
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,16 +24,24 @@ 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` -
|
|
19
|
-
`--
|
|
20
|
-
|
|
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`
|
|
24
36
|
separately (server distributes code; clients on other devices connect to it).
|
|
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
|
-
disconnect), for replaying later with `render --script`.
|
|
39
|
+
disconnect), for replaying later with `render --script`. The file is JSON
|
|
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.
|
|
28
45
|
|
|
29
46
|
## Verifying without a display (headless / CI / agent box)
|
|
30
47
|
|
|
@@ -35,7 +52,8 @@ Two reliable checks that need no GUI:
|
|
|
35
52
|
renders offscreen via EGL and writes `frame-NNNNNN.png`. This actually
|
|
36
53
|
proves the app renders. Combine with `--fps`/`--duration` (defaults
|
|
37
54
|
1280x720, 60fps, 1s). No display needed: rendering uses SDL's offscreen
|
|
38
|
-
driver
|
|
55
|
+
driver, or alloy's own EGL pbuffer where that driver cannot go headless
|
|
56
|
+
(see the ANGLE gotcha below).
|
|
39
57
|
|
|
40
58
|
Also headless: the bundled flux runtime runs a plain `.js` file directly -
|
|
41
59
|
`node_modules/@solidrt/<platform>/flux script.js` (e.g.
|
|
@@ -49,6 +67,14 @@ behavior in isolation.
|
|
|
49
67
|
- `--size` is physical output pixels: layout runs at exactly that size
|
|
50
68
|
(display scale is pinned to 1), so frames are identical on every machine.
|
|
51
69
|
- Run from the project directory. There is no `bunx --cwd` flag.
|
|
70
|
+
- On ANGLE stacks (Windows, macOS) SDL's offscreen driver cannot go
|
|
71
|
+
headless (no EGL device enumeration), so `render` there builds its own
|
|
72
|
+
EGL pbuffer context behind SDL's dummy video driver instead; the log says
|
|
73
|
+
"using a headless EGL context". If that also fails it renders into a
|
|
74
|
+
hidden window, which needs an interactive desktop session (fails under a
|
|
75
|
+
service, in Session 0, or over SSH-only). Verified headless on Linux
|
|
76
|
+
(Wayland) and the pbuffer path on Windows from a desktop session; a
|
|
77
|
+
non-interactive Windows session and macOS are unverified.
|
|
52
78
|
|
|
53
79
|
## Sessions (parallel dev servers on one machine)
|
|
54
80
|
|
package/agents/assets.md
ADDED
|
@@ -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.
|
|
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,12 +14,14 @@
|
|
|
13
14
|
"server/",
|
|
14
15
|
"scaffold/",
|
|
15
16
|
"fonts/",
|
|
17
|
+
"agents/",
|
|
16
18
|
"AGENTS.md"
|
|
17
19
|
],
|
|
18
20
|
"dependencies": {
|
|
19
21
|
"@babel/core": "^7.0.0",
|
|
20
22
|
"@babel/plugin-syntax-jsx": "^7.0.0",
|
|
21
23
|
"@babel/preset-typescript": "^7.0.0",
|
|
24
|
+
"@clack/prompts": "^1.7.0",
|
|
22
25
|
"@jridgewell/remapping": "^2.3.0",
|
|
23
26
|
"@jridgewell/trace-mapping": "^0.3.25",
|
|
24
27
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -28,16 +31,16 @@
|
|
|
28
31
|
"zod": "^4.4.3"
|
|
29
32
|
},
|
|
30
33
|
"optionalDependencies": {
|
|
31
|
-
"@solidrt/darwin-arm64": "0.0.
|
|
32
|
-
"@solidrt/linux-arm64-gnu": "0.0.
|
|
33
|
-
"@solidrt/linux-x64-gnu": "0.0.
|
|
34
|
-
"@solidrt/win32-x64-msvc": "0.0.
|
|
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"
|
|
35
38
|
},
|
|
36
39
|
"peerDependencies": {
|
|
37
40
|
"typescript": "^7"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
|
-
"@solidrt/flux-types": "0.0.
|
|
43
|
+
"@solidrt/flux-types": "0.0.51",
|
|
41
44
|
"@types/babel__core": "^7.20.5",
|
|
42
45
|
"@types/bun": "latest"
|
|
43
46
|
}
|