The browser preview moves into the scene editor's Preview drawer, and frames serve the runtime - #489
mariusandra wants to merge 4 commits into
Conversation
… frames serve the runtime The per-frame scene editor's preview was a split button with three options and a modal, while scenes.frameos.net has had the preview inline for a while. The drawer now carries the picture itself, in three pinned pieces. - The Preview drawer — the scene page's utility drawer and the frame dashboard's scene control drawer arrange the same three parts — pins the picture above the scroll area and the buttons below it, so the actions are always in the fold. - Two buttons, plus Reset. "Preview in browser" swaps the picture for the live wasm canvas in place, and editing a state field re-renders it as you stop typing; "Stop preview" gives the picture back. "Deploy to frame" decides on its own what that means: send the whole edited scene when there are pending edits, activate by id and carry the state fields when there are none. The mechanism only ever shows up in the tooltip (docs/ui-vocabulary.md). utils/scenePreviewDeploy.ts is the rule, in one pure module the button, its tooltip and the tests all read. - On the cloud that push is the durable set_scenes one, carrying the frame's whole scene list: the shim REPLACES what the device holds, so an ad-hoc subset would silently drop the frame's other scenes. - The surface shows the scene's stored snapshot when the frame has one. A deploy dims it under a spinner until the frame reports the render — over whichever signal that control plane has — and then shows the frame's own picture. A frame that says nothing gives up after 90 s with a notice rather than spinning forever. - The scene page's left sidebar no longer carries a frame picture: two pictures of the same frame competed for the same glance. The live badge and refresh button it had moved onto the drawer's surface. - LivePreviewModal is now only the template preview. Its pieces live in LivePreviewParts.tsx, shared with the drawer, and the diagram toolbar offers the same two actions (a stored "Preview on frame" preference folds into "Deploy to frame"). On the device the preview had nothing to run: the wasm runtime was never served, so the button failed into a message telling the user to run a build script. web_routes.nim now answers GET and HEAD /frameos-wasm/@asset out of the embedded frame_web table under the same access rules and gzip passthrough as /static/@asset, with application/wasm and text/javascript content types. The release chain and the Docker image build the runtime into frontend/public/frameos-wasm before the frame assets are baked, so a frame previews with exactly the runtime version it runs; PR builds stay wasm-less, where the route 404s and the SPA's HEAD probe disables the button and says why. The embedded bytes cost about 1.25 MiB gzipped per binary, looked up per request and never decompressed at startup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0189Yr2ywLDSYr6UvmRYS4w1
ESP32 firmware sizeBuilt from d62421d by this run; compared with the latest release v2026.9.15.
Breakdown by subsystem —
|
| Subsystem | This PR | v2026.9.15 | Δ |
|---|---|---|---|
| FrameOS core (Nim) | 404,620 | 404,620 | ±0 |
| QuickJS | 356,328 | 356,328 | ±0 |
| FrameOS apps (Nim) | 316,786 | 316,786 | ±0 |
| pixie | 300,949 | 300,949 | ±0 |
| Wi-Fi stack | 295,300 | 295,300 | ±0 |
| Nim stdlib | 249,026 | 249,026 | ±0 |
| ESP-IDF misc | 178,065 | 178,065 | ±0 |
| Embedded font | 146,358 | 146,358 | ±0 |
| String pool (attributed to efuse) | 132,857 | 132,857 | ±0 |
| mbedTLS + certificates | 132,539 | 132,539 | ±0 |
| lwIP / HTTP / WebSocket | 126,644 | 126,644 | ±0 |
| fos_* firmware shell (C) | 116,897 | 116,897 | ±0 |
| libc / libm / newlib | 116,259 | 116,259 | ±0 |
| Storage (SPIFFS / FatFS / SD / NVS) | 94,132 | 94,132 | ±0 |
| Nim packages (chrono, zippy, chroma, qrgen, ...) | 72,043 | 72,043 | ±0 |
| Display drivers (C) | 64,934 | 64,934 | ±0 |
| Crypto (monocypher) | 35,149 | 35,149 | ±0 |
| Total mapped flash | 3,138,886 | 3,138,886 | ±0 |
Inside each subsystem
FrameOS core (Nim) — 404,620
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
frameos/js_runtime |
102,097 | 102,097 | ±0 |
frameos/interpreter.nim |
97,276 | 97,276 | ±0 |
frameos/utils |
89,928 | 89,928 | ±0 |
embedded_main.nim |
18,319 | 18,319 | ±0 |
lib/tz.nim |
16,751 | 16,751 | ±0 |
embedded_runtime.nim |
15,056 | 15,056 | ±0 |
frameos_nim_glue.c.obj |
9,814 | 9,814 | ±0 |
frameos/planner.nim |
9,605 | 9,605 | ±0 |
frameos/types.nim |
7,913 | 7,913 | ±0 |
frameos/values.nim |
6,873 | 6,873 | ±0 |
frameos/apps.nim |
6,054 | 6,054 | ±0 |
frameos/spool.nim |
5,678 | 5,678 | ±0 |
embedded_scene.nim |
4,945 | 4,945 | ±0 |
frameos/app_config.nim |
2,871 | 2,871 | ±0 |
frameos/reboot_reason.nim |
2,628 | 2,628 | ±0 |
frameos/node_config.nim |
2,590 | 2,590 | ±0 |
frameos/app_capabilities.nim |
2,489 | 2,489 | ±0 |
fos_netguard.c.obj |
2,092 | 2,092 | ±0 |
frameos/runtime_diagnostics.nim |
763 | 763 | ±0 |
fos_version.c.obj |
324 | 324 | ±0 |
frameos/cloud |
255 | 255 | ±0 |
frameos/ids.nim |
137 | 137 | ±0 |
frameos/channels.nim |
122 | 122 | ±0 |
frameos/hal |
40 | 40 | ±0 |
QuickJS — 356,328
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
quickjs.c.obj |
283,813 | 283,813 | ±0 |
libunicode.c.obj |
50,174 | 50,174 | ±0 |
libregexp.c.obj |
13,995 | 13,995 | ±0 |
dtoa.c.obj |
5,681 | 5,681 | ±0 |
cutils.c.obj |
2,141 | 2,141 | ±0 |
fos_qjs_glue.c.obj |
449 | 449 | ±0 |
fos_quickjs_tz.c.obj |
75 | 75 | ±0 |
FrameOS apps (Nim) — 316,786
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
apps/data/icalJson |
50,265 | 50,265 | ±0 |
apps/render/calendar |
30,707 | 30,707 | ±0 |
apps/render/chart |
21,541 | 21,541 | ±0 |
apps/data/immich |
17,695 | 17,695 | ±0 |
apps/data/wikicommons |
17,110 | 17,110 | ±0 |
apps/data/openaiImage |
13,970 | 13,970 | ±0 |
apps/data/unsplash |
11,084 | 11,084 | ±0 |
apps/data/weather |
10,193 | 10,193 | ±0 |
apps/data/beRecycle |
9,918 | 9,918 | ±0 |
apps/render/split |
9,648 | 9,648 | ±0 |
apps/data/localImage |
9,202 | 9,202 | ±0 |
apps/data/eventsToAgenda |
9,060 | 9,060 | ±0 |
apps/data/googlePhotos |
9,044 | 9,044 | ±0 |
apps/apps.nim |
8,636 | 8,636 | ±0 |
apps/render/zoomPan |
8,544 | 8,544 | ±0 |
apps/data/haSensor |
7,287 | 7,287 | ±0 |
apps/data/openaiText |
6,762 | 6,762 | ±0 |
apps/data/xmlToJson |
5,420 | 5,420 | ±0 |
apps/render/text |
5,419 | 5,419 | ±0 |
apps/render/svg |
5,176 | 5,176 | ±0 |
apps/render/image |
4,510 | 4,510 | ±0 |
apps/data/rotateImage |
3,457 | 3,457 | ±0 |
apps/logic/setAsState |
3,441 | 3,441 | ±0 |
apps/data/downloadImage |
3,345 | 3,345 | ±0 |
apps/render/gradient |
3,173 | 3,173 | ±0 |
apps/data/qr |
3,118 | 3,118 | ±0 |
apps/data/frameOSGallery |
3,046 | 3,046 | ±0 |
apps/data/downloadUrl |
3,045 | 3,045 | ±0 |
apps/render/color |
2,804 | 2,804 | ±0 |
apps/data/newImage |
2,676 | 2,676 | ±0 |
apps/data/resizeImage |
2,550 | 2,550 | ±0 |
apps/data/log |
2,283 | 2,283 | ±0 |
apps/render/opacity |
2,272 | 2,272 | ±0 |
apps/data/prettyJson |
2,213 | 2,213 | ±0 |
apps/data/parseJson |
1,844 | 1,844 | ±0 |
apps/data/clock |
1,835 | 1,835 | ±0 |
apps/logic/nextSleepDuration |
1,639 | 1,639 | ±0 |
apps/logic/ifElse |
1,476 | 1,476 | ±0 |
apps/logic/breakIfRendering |
1,378 | 1,378 | ±0 |
pixie — 300,949
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
pixie/fontformats/opentype.nim |
55,507 | 55,507 | ±0 |
pixie/fileformats/webp.nim |
37,525 | 37,525 | ±0 |
pixie/fileformats/svg.nim |
37,143 | 37,143 | ±0 |
pixie/paths.nim |
32,570 | 32,570 | ±0 |
pixie/fileformats/jpeg.nim |
25,338 | 25,338 | ±0 |
pixie/fileformats/png.nim |
22,849 | 22,849 | ±0 |
pixie/images.nim |
15,783 | 15,783 | ±0 |
pixie/fonts.nim |
11,019 | 11,019 | ±0 |
pixie/paints.nim |
9,889 | 9,889 | ±0 |
pixie/fileformats/bmp.nim |
8,234 | 8,234 | ±0 |
pixie/common.nim |
7,416 | 7,416 | ±0 |
pixie/blends.nim |
6,520 | 6,520 | ±0 |
pixie/fileformats/gif.nim |
5,925 | 5,925 | ±0 |
pixie/inflatestream.nim |
5,325 | 5,325 | ±0 |
pixie.nim |
5,224 | 5,224 | ±0 |
pixie/fileformats/webp_vp8_tables.nim |
3,881 | 3,881 | ±0 |
pixie/fileformats/ppm.nim |
3,681 | 3,681 | ±0 |
pixie/contexts.nim |
3,038 | 3,038 | ±0 |
pixie/fileformats/qoi.nim |
2,682 | 2,682 | ±0 |
pixie/internal.nim |
754 | 754 | ±0 |
pixie/rgb565.nim |
381 | 381 | ±0 |
pixie/fontformats/svgfont.nim |
215 | 215 | ±0 |
pixie/decodebudget.nim |
50 | 50 | ±0 |
Wi-Fi stack — 295,300
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
libnet80211.a |
136,168 | 136,168 | ±0 |
libpp.a |
62,154 | 62,154 | ±0 |
libwpa_supplicant.a |
54,900 | 54,900 | ±0 |
libphy.a |
34,176 | 34,176 | ±0 |
libesp_wifi.a |
4,780 | 4,780 | ±0 |
libesp_phy.a |
2,541 | 2,541 | ±0 |
libesp_coex.a |
291 | 291 | ±0 |
libcore.a |
287 | 287 | ±0 |
libespnow.a |
3 | 3 | ±0 |
Nim stdlib — 249,026
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
pure/collections/tables.nim |
70,556 | 70,556 | ±0 |
system.nim |
46,659 | 46,659 | ±0 |
pure/unicode.nim |
17,405 | 17,405 | ±0 |
pure/times.nim |
16,984 | 16,984 | ±0 |
pure/json.nim |
13,414 | 13,414 | ±0 |
std/private/dragonbox.nim |
10,351 | 10,351 | ±0 |
pure/strutils.nim |
8,520 | 8,520 | ±0 |
pure/parsexml.nim |
5,999 | 5,999 | ±0 |
pure/algorithm.nim |
5,368 | 5,368 | ±0 |
pure/base64.nim |
5,302 | 5,302 | ±0 |
pure/hashes.nim |
4,114 | 4,114 | ±0 |
pure/xmlparser.nim |
3,567 | 3,567 | ±0 |
pure/parsejson.nim |
3,553 | 3,553 | ±0 |
std/formatfloat.nim |
3,508 | 3,508 | ±0 |
pure/uri.nim |
3,225 | 3,225 | ±0 |
pure/collections/sets.nim |
3,211 | 3,211 | ±0 |
pure/streams.nim |
2,820 | 2,820 | ±0 |
pure/strformat.nim |
2,641 | 2,641 | ±0 |
std/syncio.nim |
2,355 | 2,355 | ±0 |
pure/xmltree.nim |
2,051 | 2,051 | ±0 |
pure/lexbase.nim |
1,805 | 1,805 | ±0 |
pure/parseutils.nim |
1,757 | 1,757 | ±0 |
system/exceptions.nim |
1,598 | 1,598 | ±0 |
pure/strtabs.nim |
1,568 | 1,568 | ±0 |
pure/pathnorm.nim |
1,356 | 1,356 | ±0 |
pure/options.nim |
1,118 | 1,118 | ±0 |
std/private/ospaths2.nim |
1,032 | 1,032 | ±0 |
pure/collections/lists.nim |
983 | 983 | ±0 |
system/dollars.nim |
891 | 891 | ±0 |
pure/random.nim |
835 | 835 | ±0 |
std/private/digitsutils.nim |
651 | 651 | ±0 |
pure/math.nim |
650 | 650 | ±0 |
std/oserrors.nim |
637 | 637 | ±0 |
std/private/osdirs.nim |
580 | 580 | ±0 |
std/monotimes.nim |
401 | 401 | ±0 |
std/private/oscommon.nim |
329 | 329 | ±0 |
std/envvars.nim |
250 | 250 | ±0 |
std/private/decode_helpers.nim |
240 | 240 | ±0 |
std/private/ossymlinks.nim |
187 | 187 | ±0 |
pure/bitops.nim |
162 | 162 | ±0 |
pure/os.nim |
99 | 99 | ±0 |
std/private/osfiles.nim |
96 | 96 | ±0 |
std/assertions.nim |
87 | 87 | ±0 |
core/macros.nim |
69 | 69 | ±0 |
system/iterators.nim |
33 | 33 | ±0 |
system/ctypes.nim |
9 | 9 | ±0 |
ESP-IDF misc — 178,065
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
libesp_hw_support.a |
37,650 | 37,650 | ±0 |
libhal.a |
21,464 | 21,464 | ±0 |
libfreertos.a |
17,194 | 17,194 | ±0 |
libesp_system.a |
13,780 | 13,780 | ±0 |
libesp_driver_uart.a |
11,627 | 11,627 | ±0 |
libheap.a |
8,660 | 8,660 | ±0 |
libesp_driver_i2c.a |
8,062 | 8,062 | ±0 |
libesp_driver_spi.a |
7,756 | 7,756 | ±0 |
libconsole.a |
5,733 | 5,733 | ±0 |
libjson.a |
4,940 | 4,940 | ±0 |
libesp_driver_gpio.a |
3,890 | 3,890 | ±0 |
libbootloader_support.a |
3,813 | 3,813 | ±0 |
libxtensa.a |
3,691 | 3,691 | ±0 |
libesp_driver_usb_serial_jtag.a |
3,408 | 3,408 | ±0 |
libesp_ringbuf.a |
3,322 | 3,322 | ±0 |
libesp_event.a |
3,257 | 3,257 | ±0 |
libesp_mm.a |
2,451 | 2,451 | ±0 |
libapp_update.a |
2,422 | 2,422 | ±0 |
libesp_psram.a |
2,329 | 2,329 | ±0 |
libesp_timer.a |
2,326 | 2,326 | ±0 |
libsoc.a |
2,315 | 2,315 | ±0 |
libesp_adc.a |
2,289 | 2,289 | ±0 |
liblog.a |
1,375 | 1,375 | ±0 |
libpthread.a |
1,365 | 1,365 | ±0 |
libesp_security.a |
1,050 | 1,050 | ±0 |
libesp_rom.a |
902 | 902 | ±0 |
libesp_app_format.a |
507 | 507 | ±0 |
libxt_hal.a |
437 | 437 | ±0 |
libesp_common.a |
26 | 26 | ±0 |
libesp_pm.a |
24 | 24 | ±0 |
Embedded font — 146,358
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
assets/fonts.nim |
146,358 | 146,358 | ±0 |
String pool (attributed to efuse) — 132,857
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
esp_efuse_utility.c.obj |
131,811 | 131,811 | ±0 |
esp_efuse_rtc_calib.c.obj |
566 | 566 | ±0 |
esp_efuse_table.c.obj |
268 | 268 | ±0 |
esp_efuse_api.c.obj |
151 | 151 | ±0 |
esp_efuse_startup.c.obj |
61 | 61 | ±0 |
mbedTLS + certificates — 132,539
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
libmbedcrypto.a |
71,391 | 71,391 | ±0 |
libmbedtls.a |
45,803 | 45,803 | ±0 |
libmbedx509.a |
7,084 | 7,084 | ±0 |
libesp-tls.a |
6,709 | 6,709 | ±0 |
libesp_https_server.a |
1,552 | 1,552 | ±0 |
lwIP / HTTP / WebSocket — 126,644
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
liblwip.a |
78,305 | 78,305 | ±0 |
libhttp_parser.a |
10,350 | 10,350 | ±0 |
libesp_http_server.a |
10,016 | 10,016 | ±0 |
libtcp_transport.a |
8,237 | 8,237 | ±0 |
libespressif__esp_websocket_client.a |
6,826 | 6,826 | ±0 |
libesp_http_client.a |
6,666 | 6,666 | ±0 |
libesp_netif.a |
6,244 | 6,244 | ±0 |
fos_ firmware shell (C)* — 116,897
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
fos_http.c.obj |
22,202 | 22,202 | ±0 |
fos_cloud.c.obj |
16,975 | 16,975 | ±0 |
fos_console.c.obj |
13,601 | 13,601 | ±0 |
fos_client.c.obj |
10,499 | 10,499 | ±0 |
fos_scenes.c.obj |
7,551 | 7,551 | ±0 |
fos_settings.c.obj |
6,511 | 6,511 | ±0 |
fos_ota.c.obj |
5,039 | 5,039 | ±0 |
fos_config.c.obj |
4,298 | 4,298 | ±0 |
fos_wifi.c.obj |
3,201 | 3,201 | ±0 |
fos_assets.c.obj |
3,124 | 3,124 | ±0 |
fos_cloud_contract.c.obj |
2,896 | 2,896 | ±0 |
fos_sd_probe.c.obj |
2,753 | 2,753 | ±0 |
fos_assets_sd.c.obj |
2,480 | 2,480 | ±0 |
fos_schedule.c.obj |
2,351 | 2,351 | ±0 |
fos_buttons.c.obj |
2,139 | 2,139 | ±0 |
fos_status_screen.c.obj |
2,049 | 2,049 | ±0 |
main.c.obj |
1,667 | 1,667 | ±0 |
fos_config_parse.c.obj |
1,467 | 1,467 | ±0 |
fos_url_guard.c.obj |
1,397 | 1,397 | ±0 |
fos_tz.c.obj |
1,381 | 1,381 | ±0 |
fos_battery.c.obj |
1,140 | 1,140 | ±0 |
fos_minisig.c.obj |
497 | 497 | ±0 |
fos_framebuffer.c.obj |
483 | 483 | ±0 |
fos_assets_path.c.obj |
259 | 259 | ±0 |
fos_power.c.obj |
241 | 241 | ±0 |
fos_battery_filter.c.obj |
214 | 214 | ±0 |
fos_wake.c.obj |
184 | 184 | ±0 |
fos_board.c.obj |
179 | 179 | ±0 |
fos_json_guard.c.obj |
119 | 119 | ±0 |
libc / libm / newlib — 116,259
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
libc.a |
76,467 | 76,467 | ±0 |
libm.a |
31,563 | 31,563 | ±0 |
libnewlib.a |
6,609 | 6,609 | ±0 |
libstdc++.a |
1,476 | 1,476 | ±0 |
libgcc.a |
98 | 98 | ±0 |
libcxx.a |
46 | 46 | ±0 |
Storage (SPIFFS / FatFS / SD / NVS) — 94,132
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
libspiffs.a |
20,380 | 20,380 | ±0 |
libfatfs.a |
20,133 | 20,133 | ±0 |
libspi_flash.a |
14,168 | 14,168 | ±0 |
libnvs_flash.a |
13,741 | 13,741 | ±0 |
libsdmmc.a |
13,013 | 13,013 | ±0 |
libvfs.a |
6,196 | 6,196 | ±0 |
libesp_driver_sdspi.a |
3,858 | 3,858 | ±0 |
libesp_partition.a |
1,960 | 1,960 | ±0 |
libesp_vfs_console.a |
678 | 678 | ±0 |
libnvs_sec_provider.a |
5 | 5 | ±0 |
Nim packages (chrono, zippy, chroma, qrgen, ...) — 72,043
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
QRgen 3.1.0 |
22,036 | 22,036 | ±0 |
chrono 0.3.1 |
15,510 | 15,510 | ±0 |
zippy 0.10.19 |
15,280 | 15,280 | ±0 |
chroma 1.0.0 |
14,924 | 14,924 | ±0 |
checksums 0.2.1 |
3,371 | 3,371 | ±0 |
flatty 0.3.4 |
533 | 533 | ±0 |
jsony 1.1.5 |
272 | 272 | ±0 |
bumpy 1.1.3 |
117 | 117 | ±0 |
Display drivers (C) — 64,934
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
(everything else) |
21,290 | 21,290 | ±0 |
frameos_panel_table.c.obj |
6,685 | 6,685 | ±0 |
EPD_13in3e.c.obj |
1,792 | 1,792 | ±0 |
EPD_3in7.c.obj |
1,564 | 1,564 | ±0 |
EPD_5in79.c.obj |
1,481 | 1,481 | ±0 |
DEV_Debug.c.obj |
1,309 | 1,309 | ±0 |
EPD_2in7.c.obj |
1,292 | 1,292 | ±0 |
EPD_4in2.c.obj |
1,262 | 1,262 | ±0 |
EPD_7in3e.c.obj |
1,208 | 1,208 | ±0 |
EPD_4in2_V2.c.obj |
1,184 | 1,184 | ±0 |
DEV_Config_esp.c.obj |
1,180 | 1,180 | ±0 |
EPD_4in26.c.obj |
1,123 | 1,123 | ±0 |
| 29 smaller rows | 23,564 | 23,564 | ±0 |
Crypto (monocypher) — 35,149
| Part | This PR | v2026.9.15 | Δ |
|---|---|---|---|
monocypher.c.obj |
31,916 | 31,916 | ±0 |
monocypher-ed25519.c.obj |
3,233 | 3,233 | ±0 |
Largest 30 objects
| Object | Subsystem | This PR | v2026.9.15 | Δ |
|---|---|---|---|---|
quickjs.c.obj |
QuickJS | 283,813 | 283,813 | ±0 |
assets/fonts.nim |
Embedded font | 146,358 | 146,358 | ±0 |
esp_efuse_utility.c.obj |
String pool (attributed to efuse) | 131,811 | 131,811 | ±0 |
frameos/interpreter.nim |
FrameOS core (Nim) | 97,276 | 97,276 | ±0 |
nim/lib/pure/collections/tables.nim |
Nim stdlib | 70,556 | 70,556 | ±0 |
frameos/js_runtime/app_runtime.nim |
FrameOS core (Nim) | 61,493 | 61,493 | ±0 |
pkgs/pixie-6.1.0/pixie/fontformats/opentype.nim |
pixie | 55,507 | 55,507 | ±0 |
libunicode.c.obj |
QuickJS | 50,174 | 50,174 | ±0 |
nim/lib/system.nim |
Nim stdlib | 46,659 | 46,659 | ±0 |
apps/data/icalJson/ical.nim |
FrameOS apps (Nim) | 44,568 | 44,568 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/webp.nim |
pixie | 37,525 | 37,525 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/svg.nim |
pixie | 37,143 | 37,143 | ±0 |
frameos/utils/image.nim |
FrameOS core (Nim) | 34,490 | 34,490 | ±0 |
pkgs/pixie-6.1.0/pixie/paths.nim |
pixie | 32,570 | 32,570 | ±0 |
monocypher.c.obj |
Crypto (monocypher) | 31,916 | 31,916 | ±0 |
frameos/js_runtime/runtime.nim |
FrameOS core (Nim) | 30,086 | 30,086 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/jpeg.nim |
pixie | 25,338 | 25,338 | ±0 |
apps/render/calendar/app.nim |
FrameOS apps (Nim) | 23,659 | 23,659 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/png.nim |
pixie | 22,849 | 22,849 | ±0 |
fos_http.c.obj |
fos_* firmware shell (C) | 22,202 | 22,202 | ±0 |
apps/render/chart/app.nim |
FrameOS apps (Nim) | 18,940 | 18,940 | ±0 |
embedded_main.nim |
FrameOS core (Nim) | 18,319 | 18,319 | ±0 |
frameos/utils/status_screen.nim |
FrameOS core (Nim) | 18,111 | 18,111 | ±0 |
x509_crt_bundle.S.obj |
mbedTLS + certificates | 17,928 | 17,928 | ±0 |
nim/lib/pure/unicode.nim |
Nim stdlib | 17,405 | 17,405 | ±0 |
nim/lib/pure/times.nim |
Nim stdlib | 16,984 | 16,984 | ±0 |
fos_cloud.c.obj |
fos_* firmware shell (C) | 16,975 | 16,975 | ±0 |
lib/tz.nim |
FrameOS core (Nim) | 16,751 | 16,751 | ±0 |
apps/data/immich/app.nim |
FrameOS apps (Nim) | 16,148 | 16,148 | ±0 |
pkgs/pixie-6.1.0/pixie/images.nim |
pixie | 15,783 | 15,783 | ±0 |
Flash = .text + .rodata from the linker map via esp_idf_size; "Inside each subsystem" splits every bucket one level down (the Nim core by directory, apps by app, packages by package, ESP-IDF by archive) over all objects, not just the largest. "String pool (attributed to efuse)" is the linker's merged string-literal pool for the whole image, not efuse code — see docs/esp32-image-size.md.
Conflicts: - docs/manual-testing-todo.md — main (#490) closed the whole non-ESP32 side and left only the ESP32 bench; this branch had added a Preview drawer box main never saw. Took main's closures and carried the Preview drawer box over under its Backend (self-hosted) bench heading, header line reworded so it no longer claims the non-ESP32 side is empty. - four frame-scenes--expanded-scene snapshots — took main's baseline (#490's real UI change). CI re-renders these with --update-snapshots and the bot job commits the result, so this branch's own diffs come back from CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tywARkZTUQNozKsXMHEq9
Under the frame pages the scene editor's preview was a split button with three
options and a modal, while scenes.frameos.net has had the preview inline for a
while. The Preview drawer now carries the picture itself.
The drawer
Three pinned pieces, the same in both hosts (the scene page's utility drawer
and the frame dashboard's scene control drawer): the picture on top, the
scene's state fields and the preview's own controls scrolling in the middle,
the buttons below — always in the fold.
Two buttons, plus Reset:
Editing a state field re-renders the preview once you stop typing; Stop
preview gives the picture back.
edited scene when there are pending edits, activate by id and carry the
state fields when there are none. The mechanism only ever shows up in the
tooltip (
docs/ui-vocabulary.md).frontend/src/utils/scenePreviewDeploy.tsis that rule as one pure module, which the button, its tooltip and the tests
all read.
On the cloud the deploy is the durable
set_scenespush carrying the frame'swhole scene list: that shim REPLACES what the device holds, so an ad-hoc
subset would silently drop the frame's other scenes. It is the same action the
drawer's change notice offers under the same name.
The surface shows the scene's stored snapshot when the frame has one. A deploy
dims it under a spinner until the frame reports the render — over whichever
signal that control plane has (a websocket render event, a new scene image,
the frame image bumping, the activation settling) — and then shows the frame's
own picture. A frame that says nothing gives up after 90 s with a notice
rather than spinning forever.
The scene page's left sidebar no longer carries a frame picture: two pictures
of the same frame competed for the same glance. The live badge and refresh
button it had moved onto the drawer's surface.
LivePreviewModalis now onlythe template preview; its pieces live in
LivePreviewParts.tsx, shared withthe drawer, and the diagram toolbar offers the same two actions (a stored
"Preview on frame" preference folds into "Deploy to frame").
Frames serve the runtime
On the device the preview had nothing to run: the wasm runtime was never
served, so the button failed into a message telling the user to run a build
script.
web_routes.nimnow answersGETandHEAD /frameos-wasm/@assetoutof the embedded
frame_webtable under the same access rules and gzippassthrough as
/static/@asset, withapplication/wasmandtext/javascriptcontent types. The release chain and the Docker image build the runtime into
frontend/public/frameos-wasmbefore the frame assets are baked, so a framepreviews with exactly the runtime version it runs — and the status row shows
which one. Pull-request builds stay wasm-less: there the route 404s, the SPA's
HEADprobe disables the button and says why. The embedded bytes cost about1.25 MiB gzipped per binary, looked up per request and never decompressed at
startup.
The device also has no
scene_preview_settingsroute, so the preview readsthe admin API's
/api/settingsthere, through the same consent gate as everyother mode.
Tests
frameos/src/frameos/server/tests/test_web_routes_behavior.nim: the accessrules, the 404s (including traversal-looking segments), the content types,
gzip passthrough and the HEAD. The positive assertions skip themselves on a
build without the runtime, so the suite passes in both worlds — verified by
running it with the entries stripped. 18 tests pass.
cloud/apps/auth-web/src/test/shared-spa/scene-preview-deploy.test.ts: alltwelve mode × changes × active combinations of the deploy rule as one exact
table, the status line for every surface, and the split-button key
normalisation. 19 tests.
e2e/frontend-visual/tests/frontend-e2e.spec.ts: the drawer's footer holdsexactly the two buttons and Reset, with the old three-option labels absent.
pnpm verifyfromcloud/is green (28/28). Thescene-workspaceandframe-scenesvisual snapshots will change; CI regenerates them.Needs a bench
docs/manual-testing-todo.mdhas the box: all three control planes, thesnapshot → live → deploy sequence, and on the cloud a check that the frame's
other scenes survive a deploy from the drawer.
🤖 Generated with Claude Code
https://claude.ai/code/session_0189Yr2ywLDSYr6UvmRYS4w1