Repository navigation
Buttons in two layers: down/up at once, press/doublePress/longPress once clear; 30 ms debounce; a sound per event - #557
Conversation
…edges, doublePress On the ESP32 the button task queues edges and the render task drains the queue only between passes, so a press and its release made during a 30 s E1004 refresh were both timed at the drain: a tap arrived as a 30 s hold with a longPress it never had, and two taps could not be told from two slow presses. Every `button` edge now carries `atMs`, when the driver saw it, in ms on the frame's monotonic clock (docs/events-contract.json; esp_timer in fos_buttons.c, the same clock Nim's getMonoTime reads under freertos; getMonoTime in the Linux gpioButton driver). The queue item grows from 8 to 12 bytes (64 items, 768 bytes) and the stamp is widened back on drain by fos_wake_edge_at_ms, wrap-safe. input_state.nim measures the hold between the edges, advances the holds to an edge's time before applying it, delivers the longPress that was due right before a late release, and makes `doublePress` from a second short press within buttons.doublePressMs (350) of the previous one's release; the third press starts over. The ESP32/wasm host no longer ticks to now on an event's way in, which is what turned a queued tap into a 30 s long press. The buzzer's hold ticks count from the same instant as heldSeconds. The corpus gains an `inputState` section (test_input_state.nim runs it); fos_wake's host test covers the stamp arithmetic; docs/events.md, event-system-analysis.md, the ESP32 README and the manual test boxes say the new rule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ESP32 firmware sizeBuilt from d62c597 by this run; compared with the latest release v2026.10.15.
Breakdown by subsystem —
|
| Subsystem | This PR | v2026.10.15 | Δ |
|---|---|---|---|
| FrameOS core (Nim) | 479,284 | 477,042 | +2,242 (+0.5%) |
| QuickJS | 360,381 | 360,373 | +8 (+0.0%) |
| pixie | 304,390 | 304,354 | +36 (+0.0%) |
| Wi-Fi stack | 295,304 | 295,304 | ±0 |
| Nim stdlib | 251,515 | 251,436 | +79 (+0.0%) |
| FrameOS apps (Nim) | 190,404 | 190,356 | +48 (+0.0%) |
| ESP-IDF misc | 181,367 | 181,564 | -197 (-0.1%) |
| Embedded font | 146,362 | 146,362 | ±0 |
| String pool (attributed to efuse) | 136,028 | 136,134 | -106 (-0.1%) |
| fos_* firmware shell (C) | 133,310 | 132,233 | +1,077 (+0.8%) |
| mbedTLS + certificates | 132,439 | 132,439 | ±0 |
| lwIP / HTTP / WebSocket | 126,788 | 126,800 | -12 (-0.0%) |
| libc / libm / newlib | 116,263 | 116,263 | ±0 |
| Storage (SPIFFS / FatFS / SD / NVS) | 94,076 | 94,080 | -4 (-0.0%) |
| Nim packages (chrono, zippy, chroma, qrgen, ...) | 71,845 | 71,849 | -4 (-0.0%) |
| Display drivers (C) | 64,958 | 64,946 | +12 (+0.0%) |
| Crypto (monocypher) | 35,141 | 35,141 | ±0 |
| Total mapped flash | 3,119,855 | 3,116,676 | +3,179 (+0.1%) |
Inside each subsystem
FrameOS core (Nim) — 479,284
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
frameos/js_runtime |
116,426 | 115,444 | +982 (+0.9%) |
frameos/utils |
112,365 | 112,401 | -36 (-0.0%) |
frameos/interpreter.nim |
100,316 | 100,264 | +52 (+0.1%) |
embedded_main.nim |
19,252 | 19,252 | ±0 |
embedded_runtime.nim |
17,830 | 17,834 | -4 (-0.0%) |
lib/tz.nim |
16,722 | 16,722 | ±0 |
frameos_nim_glue.c.obj |
10,679 | 10,635 | +44 (+0.4%) |
frameos/planner.nim |
9,681 | 9,681 | ±0 |
frameos/event_loop.nim |
8,412 | 8,412 | ±0 |
frameos/types.nim |
7,909 | 7,909 | ±0 |
frameos/values.nim |
7,699 | 7,699 | ±0 |
frameos/single_scene_host.nim |
6,687 | 6,712 | -25 (-0.4%) |
frameos/apps.nim |
6,585 | 6,585 | ±0 |
frameos/spool.nim |
5,787 | 5,787 | ±0 |
frameos/input_state.nim |
4,947 | 3,766 | +1,181 (+31.4%) |
embedded_scene.nim |
4,945 | 4,945 | ±0 |
frameos/events_gen.nim |
3,948 | 3,948 | ±0 |
frameos/app_config.nim |
2,995 | 2,935 | +60 (+2.0%) |
frameos/reboot_reason.nim |
2,648 | 2,648 | ±0 |
frameos/node_config.nim |
2,618 | 2,618 | ±0 |
frameos/app_capabilities.nim |
2,489 | 2,489 | ±0 |
frameos/events.nim |
2,410 | 2,410 | ±0 |
fos_netguard.c.obj |
2,175 | 2,179 | -4 (-0.2%) |
frameos/refresh_interval.nim |
1,635 | 1,639 | -4 (-0.2%) |
frameos/runtime_diagnostics.nim |
767 | 767 | ±0 |
frameos/event_log.nim |
427 | 431 | -4 (-0.9%) |
fos_version.c.obj |
328 | 328 | ±0 |
frameos/cloud |
255 | 255 | ±0 |
frameos/channels.nim |
152 | 152 | ±0 |
frameos/ids.nim |
137 | 137 | ±0 |
frameos/hal |
44 | 44 | ±0 |
frameos/retired_apps.nim |
14 | 14 | ±0 |
QuickJS — 360,381
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
quickjs.c.obj |
287,854 | 287,846 | +8 (+0.0%) |
libunicode.c.obj |
50,178 | 50,178 | ±0 |
libregexp.c.obj |
13,999 | 13,999 | ±0 |
dtoa.c.obj |
5,685 | 5,685 | ±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 |
pixie — 304,390
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
pixie/fontformats/opentype.nim |
55,460 | 55,460 | ±0 |
pixie/fileformats/webp.nim |
37,493 | 37,493 | ±0 |
pixie/fileformats/svg.nim |
36,983 | 36,983 | ±0 |
pixie/paths.nim |
32,875 | 32,875 | ±0 |
pixie/fileformats/jpeg.nim |
29,436 | 29,436 | ±0 |
pixie/fileformats/png.nim |
22,101 | 22,101 | ±0 |
pixie/images.nim |
15,779 | 15,779 | ±0 |
pixie/fonts.nim |
10,995 | 10,995 | ±0 |
pixie/paints.nim |
9,869 | 9,869 | ±0 |
pixie/fileformats/bmp.nim |
8,278 | 8,258 | +20 (+0.2%) |
pixie/common.nim |
7,413 | 7,413 | ±0 |
pixie/blends.nim |
6,516 | 6,516 | ±0 |
pixie/fileformats/gif.nim |
5,957 | 5,965 | -8 (-0.1%) |
pixie/inflatestream.nim |
5,233 | 5,233 | ±0 |
pixie.nim |
5,224 | 5,224 | ±0 |
pixie/fileformats/webp_vp8_tables.nim |
3,881 | 3,881 | ±0 |
pixie/fileformats/ppm.nim |
3,717 | 3,713 | +4 (+0.1%) |
pixie/contexts.nim |
3,054 | 3,054 | ±0 |
pixie/fileformats/qoi.nim |
2,722 | 2,702 | +20 (+0.7%) |
pixie/internal.nim |
754 | 754 | ±0 |
pixie/rgb565.nim |
385 | 385 | ±0 |
pixie/fontformats/svgfont.nim |
215 | 215 | ±0 |
pixie/decodebudget.nim |
50 | 50 | ±0 |
Wi-Fi stack — 295,304
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libnet80211.a |
136,168 | 136,168 | ±0 |
libpp.a |
62,154 | 62,154 | ±0 |
libwpa_supplicant.a |
54,904 | 54,904 | ±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 — 251,515
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
pure/collections/tables.nim |
72,312 | 72,312 | ±0 |
system.nim |
48,465 | 48,457 | +8 (+0.0%) |
pure/unicode.nim |
17,405 | 17,405 | ±0 |
pure/times.nim |
16,888 | 16,888 | ±0 |
pure/json.nim |
13,722 | 13,722 | ±0 |
std/private/dragonbox.nim |
10,351 | 10,351 | ±0 |
pure/strutils.nim |
8,047 | 8,047 | ±0 |
pure/parsexml.nim |
6,003 | 6,011 | -8 (-0.1%) |
pure/algorithm.nim |
5,372 | 5,372 | ±0 |
pure/base64.nim |
5,290 | 5,290 | ±0 |
pure/hashes.nim |
4,181 | 4,181 | ±0 |
pure/xmlparser.nim |
3,555 | 3,555 | ±0 |
pure/parsejson.nim |
3,553 | 3,553 | ±0 |
std/formatfloat.nim |
3,504 | 3,504 | ±0 |
pure/uri.nim |
3,225 | 3,225 | ±0 |
pure/streams.nim |
2,820 | 2,820 | ±0 |
pure/strformat.nim |
2,546 | 2,546 | ±0 |
std/syncio.nim |
2,339 | 2,355 | -16 (-0.7%) |
pure/collections/sets.nim |
2,075 | 2,075 | ±0 |
pure/xmltree.nim |
2,047 | 2,047 | ±0 |
pure/lexbase.nim |
1,801 | 1,801 | ±0 |
pure/parseutils.nim |
1,750 | 1,750 | ±0 |
system/exceptions.nim |
1,598 | 1,598 | ±0 |
pure/strtabs.nim |
1,572 | 1,572 | ±0 |
pure/pathnorm.nim |
1,336 | 1,336 | ±0 |
pure/options.nim |
1,016 | 1,016 | ±0 |
std/private/ospaths2.nim |
994 | 994 | ±0 |
pure/collections/lists.nim |
979 | 979 | ±0 |
pure/math.nim |
841 | 841 | ±0 |
pure/collections/deques.nim |
822 | 822 | ±0 |
pure/random.nim |
696 | 696 | ±0 |
std/private/digitsutils.nim |
651 | 651 | ±0 |
std/oserrors.nim |
633 | 633 | ±0 |
std/private/osdirs.nim |
572 | 572 | ±0 |
system/dollars.nim |
529 | 529 | ±0 |
std/monotimes.nim |
501 | 406 | +95 (+23.4%) |
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 |
65 | 65 | ±0 |
system/ctypes.nim |
9 | 9 | ±0 |
FrameOS apps (Nim) — 190,404
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
apps/data/icalJson |
50,737 | 50,733 | +4 (+0.0%) |
apps/render/calendar |
30,886 | 30,874 | +12 (+0.0%) |
apps/data/localImage |
10,432 | 10,432 | ±0 |
apps/render/split |
9,732 | 9,772 | -40 (-0.4%) |
apps/data/eventsToAgenda |
9,064 | 9,068 | -4 (-0.0%) |
apps/render/zoomPan |
8,668 | 8,648 | +20 (+0.2%) |
apps/apps.nim |
7,251 | 7,247 | +4 (+0.1%) |
apps/render/text |
5,483 | 5,455 | +28 (+0.5%) |
apps/data/xmlToJson |
5,428 | 5,428 | ±0 |
apps/render/svg |
5,208 | 5,192 | +16 (+0.3%) |
apps/render/image |
4,708 | 4,708 | ±0 |
apps/data/rotateImage |
3,465 | 3,465 | ±0 |
apps/logic/setAsState |
3,453 | 3,453 | ±0 |
apps/data/downloadImage |
3,321 | 3,325 | -4 (-0.1%) |
apps/render/gradient |
3,213 | 3,229 | -16 (-0.5%) |
apps/data/qr |
3,118 | 3,122 | -4 (-0.1%) |
apps/data/downloadUrl |
3,053 | 3,053 | ±0 |
apps/render/color |
2,844 | 2,824 | +20 (+0.7%) |
apps/data/newImage |
2,668 | 2,668 | ±0 |
apps/data/resizeImage |
2,558 | 2,558 | ±0 |
apps/render/opacity |
2,320 | 2,296 | +24 (+1.0%) |
apps/data/log |
2,299 | 2,299 | ±0 |
apps/data/prettyJson |
2,225 | 2,225 | ±0 |
apps/data/clock |
1,871 | 1,875 | -4 (-0.2%) |
apps/data/parseJson |
1,848 | 1,856 | -8 (-0.4%) |
apps/logic/nextSleepDuration |
1,663 | 1,663 | ±0 |
apps/logic/ifElse |
1,494 | 1,494 | ±0 |
apps/logic/breakIfRendering |
1,394 | 1,394 | ±0 |
ESP-IDF misc — 181,367
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libesp_hw_support.a |
37,636 | 37,632 | +4 (+0.0%) |
libhal.a |
21,865 | 21,865 | ±0 |
libfreertos.a |
17,240 | 17,240 | ±0 |
libesp_system.a |
13,736 | 13,780 | -44 (-0.3%) |
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,878 | 3,878 | ±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,253 | 3,253 | ±0 |
libesp_driver_ledc.a |
3,054 | 3,054 | ±0 |
libesp_mm.a |
2,451 | 2,451 | ±0 |
libapp_update.a |
2,414 | 2,414 | ±0 |
libesp_timer.a |
2,337 | 2,337 | ±0 |
libesp_psram.a |
2,172 | 2,329 | -157 (-6.7%) |
libsoc.a |
2,316 | 2,316 | ±0 |
libesp_adc.a |
2,285 | 2,285 | ±0 |
libpthread.a |
1,397 | 1,397 | ±0 |
liblog.a |
1,375 | 1,375 | ±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,362
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
assets/fonts.nim |
146,362 | 146,362 | ±0 |
String pool (attributed to efuse) — 136,028
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
esp_efuse_utility.c.obj |
134,982 | 135,088 | -106 (-0.1%) |
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 |
fos_ firmware shell (C)* — 133,310
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
fos_http.c.obj |
22,185 | 22,185 | ±0 |
fos_cloud.c.obj |
19,258 | 19,258 | ±0 |
fos_console.c.obj |
13,950 | 13,950 | ±0 |
fos_client.c.obj |
12,350 | 12,266 | +84 (+0.7%) |
fos_settings.c.obj |
7,618 | 7,618 | ±0 |
fos_scenes.c.obj |
7,578 | 7,578 | ±0 |
fos_ota.c.obj |
5,467 | 5,467 | ±0 |
fos_cloud_contract.c.obj |
4,744 | 4,744 | ±0 |
fos_config.c.obj |
4,619 | 4,619 | ±0 |
fos_wifi.c.obj |
3,189 | 3,189 | ±0 |
fos_assets.c.obj |
3,124 | 3,124 | ±0 |
fos_buttons.c.obj |
2,772 | 2,493 | +279 (+11.2%) |
fos_sd_probe.c.obj |
2,753 | 2,753 | ±0 |
fos_schedule.c.obj |
2,602 | 2,602 | ±0 |
fos_assets_sd.c.obj |
2,484 | 2,484 | ±0 |
fos_events.c.obj |
2,320 | 2,320 | ±0 |
fos_status_screen.c.obj |
2,049 | 2,049 | ±0 |
main.c.obj |
1,703 | 1,699 | +4 (+0.2%) |
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,294 | 1,294 | ±0 |
fos_buzzer.c.obj |
1,162 | 901 | +261 (+29.0%) |
fos_battery.c.obj |
1,140 | 1,140 | ±0 |
fos_minisig.c.obj |
1,140 | 1,140 | ±0 |
fos_backend_http.c.obj |
1,072 | 1,072 | ±0 |
fos_commands.c.obj |
940 | 940 | ±0 |
fos_scene_state.c.obj |
650 | 650 | ±0 |
fos_framebuffer.c.obj |
483 | 483 | ±0 |
fos_button_timing.c.obj |
434 | — | +434 |
fos_board.c.obj |
334 | 334 | ±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 |
199 | 184 | +15 (+8.2%) |
fos_json_guard.c.obj |
119 | 119 | ±0 |
mbedTLS + certificates — 132,439
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libmbedcrypto.a |
71,295 | 71,291 | +4 (+0.0%) |
libmbedtls.a |
45,799 | 45,799 | ±0 |
libmbedx509.a |
7,088 | 7,092 | -4 (-0.1%) |
libesp-tls.a |
6,705 | 6,705 | ±0 |
libesp_https_server.a |
1,552 | 1,552 | ±0 |
lwIP / HTTP / WebSocket — 126,788
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
liblwip.a |
78,433 | 78,445 | -12 (-0.0%) |
libhttp_parser.a |
10,350 | 10,350 | ±0 |
libesp_http_server.a |
10,020 | 10,020 | ±0 |
libtcp_transport.a |
8,245 | 8,245 | ±0 |
libespressif__esp_websocket_client.a |
6,822 | 6,822 | ±0 |
libesp_http_client.a |
6,666 | 6,666 | ±0 |
libesp_netif.a |
6,252 | 6,252 | ±0 |
libc / libm / newlib — 116,263
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
libc.a |
76,471 | 76,471 | ±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,076
| Part | This PR | v2026.10.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,681 | 13,685 | -4 (-0.0%) |
libsdmmc.a |
13,013 | 13,013 | ±0 |
libvfs.a |
6,196 | 6,196 | ±0 |
libesp_driver_sdspi.a |
3,866 | 3,866 | ±0 |
libesp_partition.a |
1,956 | 1,956 | ±0 |
libesp_vfs_console.a |
678 | 678 | ±0 |
libnvs_sec_provider.a |
5 | 5 | ±0 |
Nim packages (chrono, zippy, chroma, qrgen, ...) — 71,845
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
QRgen 3.1.0 |
22,056 | 22,060 | -4 (-0.0%) |
chrono 0.3.1 |
15,586 | 15,586 | ±0 |
zippy 0.10.19 |
15,200 | 15,200 | ±0 |
chroma 1.0.0 |
14,714 | 14,714 | ±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 |
113 | 113 | ±0 |
Display drivers (C) — 64,958
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
(everything else) |
21,278 | 21,282 | -4 (-0.0%) |
frameos_panel_table.c.obj |
6,685 | 6,685 | ±0 |
EPD_13in3e.c.obj |
1,796 | 1,796 | ±0 |
EPD_3in7.c.obj |
1,564 | 1,564 | ±0 |
EPD_5in79.c.obj |
1,481 | 1,481 | ±0 |
DEV_Debug.c.obj |
1,325 | 1,317 | +8 (+0.6%) |
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,580 | 23,572 | +8 (+0.0%) |
Crypto (monocypher) — 35,141
| Part | This PR | v2026.10.15 | Δ |
|---|---|---|---|
monocypher.c.obj |
31,916 | 31,916 | ±0 |
monocypher-ed25519.c.obj |
3,225 | 3,225 | ±0 |
Biggest movers (per object)
| Object | Δ | This PR | Baseline |
|---|---|---|---|
frameos/input_state.nim |
+4,947 | 4,947 | — |
pkgs/QRgen-3.1.0/QRgen/renderer.nim |
-3,780 | — | 3,780 |
frameos/js_runtime/app_runtime.nim |
+938 | 73,573 | 72,635 |
esp_efuse_utility.c.obj |
-106 | 134,982 | 135,088 |
fos_client.c.obj |
+84 | 12,350 | 12,266 |
frameos/interpreter.nim |
+52 | 100,316 | 100,264 |
frameos_nim_glue.c.obj |
+44 | 10,679 | 10,635 |
frameos/js_runtime/runtime.nim |
+36 | 31,722 | 31,686 |
apps/render/split/app_loader.nim |
-36 | 4,666 | 4,702 |
frameos/single_scene_host.nim |
-25 | 6,687 | 6,712 |
pkgs/pixie-6.1.0/pixie/fileformats/bmp.nim |
+20 | 8,278 | 8,258 |
apps/render/calendar/app_loader.nim |
+12 | 7,069 | 7,057 |
tcp.c.obj |
-12 | 5,068 | 5,080 |
quickjs.c.obj |
+8 | 287,854 | 287,846 |
nim/lib/system.nim |
+8 | 48,465 | 48,457 |
Largest 30 objects
| Object | Subsystem | This PR | v2026.10.15 | Δ |
|---|---|---|---|---|
quickjs.c.obj |
QuickJS | 287,854 | 287,846 | +8 (+0.0%) |
assets/fonts.nim |
Embedded font | 146,362 | 146,362 | ±0 |
esp_efuse_utility.c.obj |
String pool (attributed to efuse) | 134,982 | 135,088 | -106 (-0.1%) |
frameos/interpreter.nim |
FrameOS core (Nim) | 100,316 | 100,264 | +52 (+0.1%) |
frameos/js_runtime/app_runtime.nim |
FrameOS core (Nim) | 73,573 | 72,635 | +938 (+1.3%) |
nim/lib/pure/collections/tables.nim |
Nim stdlib | 72,312 | 72,312 | ±0 |
pkgs/pixie-6.1.0/pixie/fontformats/opentype.nim |
pixie | 55,460 | 55,460 | ±0 |
libunicode.c.obj |
QuickJS | 50,178 | 50,178 | ±0 |
nim/lib/system.nim |
Nim stdlib | 48,465 | 48,457 | +8 (+0.0%) |
apps/data/icalJson/ical.nim |
FrameOS apps (Nim) | 45,020 | 45,016 | +4 (+0.0%) |
pkgs/pixie-6.1.0/pixie/fileformats/webp.nim |
pixie | 37,493 | 37,493 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/svg.nim |
pixie | 36,983 | 36,983 | ±0 |
frameos/utils/image.nim |
FrameOS core (Nim) | 35,942 | 35,942 | ±0 |
pkgs/pixie-6.1.0/pixie/paths.nim |
pixie | 32,875 | 32,875 | ±0 |
monocypher.c.obj |
Crypto (monocypher) | 31,916 | 31,916 | ±0 |
frameos/js_runtime/runtime.nim |
FrameOS core (Nim) | 31,722 | 31,686 | +36 (+0.1%) |
pkgs/pixie-6.1.0/pixie/fileformats/jpeg.nim |
pixie | 29,436 | 29,436 | ±0 |
apps/render/calendar/app.nim |
FrameOS apps (Nim) | 23,817 | 23,817 | ±0 |
fos_http.c.obj |
fos_* firmware shell (C) | 22,185 | 22,185 | ±0 |
pkgs/pixie-6.1.0/pixie/fileformats/png.nim |
pixie | 22,101 | 22,101 | ±0 |
fos_cloud.c.obj |
fos_* firmware shell (C) | 19,258 | 19,258 | ±0 |
embedded_main.nim |
FrameOS core (Nim) | 19,252 | 19,252 | ±0 |
frameos/utils/status_screen.nim |
FrameOS core (Nim) | 17,939 | 17,943 | -4 (-0.0%) |
x509_crt_bundle.S.obj |
mbedTLS + certificates | 17,928 | 17,928 | ±0 |
embedded_runtime.nim |
FrameOS core (Nim) | 17,830 | 17,834 | -4 (-0.0%) |
nim/lib/pure/unicode.nim |
Nim stdlib | 17,405 | 17,405 | ±0 |
frameos/utils/asset_colors.nim |
FrameOS core (Nim) | 17,333 | 17,333 | ±0 |
nim/lib/pure/times.nim |
Nim stdlib | 16,888 | 16,888 | ±0 |
lib/tz.nim |
FrameOS core (Nim) | 16,722 | 16,722 | ±0 |
pkgs/pixie-6.1.0/pixie/images.nim |
pixie | 15,779 | 15,779 | ±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.
…press kind The ESP32 button task needed the level settled for 80 ms before it took an edge, which lost any tap shorter than that and the second tap of a quick double. It now takes the first change as the edge and locks bounce out for 30 ms (fos_button_timing.c, pure and host tested). A Pi's lgpio debounce had the same flaw at 100 ms; it is 30 ms now. `buttons.repeatMs` is 1000, counted in whole seconds from the press, so a `repeat` is the buzzer's per-second tick and one more `heldSeconds`; a late tick catches up with one repeat, not a burst. The contract's timing numbers are emitted into fos_events_gen.h so the C side mirrors them. The buzzer sounds each press kind when the button task's timing (the dispatcher's rules on the same stamps) says it happened: the press pair is unchanged, a double press plays two rising notes on its second tap, a long press a short note falling an octave, each repeat the 7 ms tick. The buzzer gains a small note-sequence player for that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce clear The hardware layer is what the line did, delivered the moment the driver reports it: `down`, and `up` with `durationMs`. The button layer is what the person meant, and each press is exactly one of: `press` (a short press, 350 ms after its up when no second down came), `doublePress` (the second down inside that window; no press, no long press, a third down starts over) or `longPress` (at every whole `longPressMs` held, now 1000: 1 s, 2 s, ..., `heldSeconds` counting them). `repeat` and `release` are gone; `longPressMs` 500 and `repeatMs` 1000 become one `longPressMs` of 1000, so a hold no longer chirps at half a second. A listener with no action filter still hears `press` only. Drivers send `down` / `up` (ESP32, Pi gpioButton, the preview); the `level` decides, so an older driver's press / release still works. A `button` that is not an edge (HTTP, cloud, schedule, scene) is delivered as said. Every event sounds the instant it happens on the ESP32: click on down, lower click on up, two rising notes for a double press, the falling pair at each long press; a press adds none. The per-second tick is gone. A press is decided after a window and a long press each second, and only a tick delivers them, so hosts tick while a key is busy: the ESP32 waits in 50 ms slices and waits (up to 10 s) before deep sleep, the preview ticks every 50 ms while a button is busy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press held at least `longPressMs` (1000) is a `longPress` delivered once, right after its `up`, with the same `durationMs` and `heldSeconds`. Nothing is delivered while a key is held: no long press at 1 s, none each second after. The tick now only turns a tap whose double-press window closed into a `press`. On the ESP32 the long press's sound plays in place of the up click, as the double press's notes take the down click's place; a held key is silent. fos_button_timing drops its per-second hold logic: its release says whether the up is a long press. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A double press's second down plays two rising notes (1.5x, 2x the tone), and its release now plays one more above them (2.5x, 12 ms) instead of the press's lower up click. A double press rises to its end; a tap falls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a key is held the buzzer sounds the long-press pair at every whole longPressMs (1 s, 2 s, ...), and its release is the regular up click. The scene's `longPress` still comes once, with the `up`: the sounds count the hold, they are not events. A double press is the plain down click twice; only its second release differs, a 14 ms click at 1.5x the press pitch instead of 0.7x, so the pair ends on a rise where a tap falls. The two-note double sound is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_single_scene_host sent a raw `level: 0` edge and expected the scene's press listener to run; with the two layers that edge is a `down`, and the `press` waits for the release. The case is the battery ESP32's wake press, so it now sends one (`wake: true`, a press at once); the abort case sends a press as said. The status screen's "Last button" line now follows the hardware `down` rather than the button layer's `press`: it answers "I pressed it and nothing happened", so every touch counts at once, a double or a long press too. ESP32 size against main went from +4,208 to +1,936 bytes: - one `addButton` builds every button payload, where seven call sites each built their own (input_state +2,435 -> +942); - a waiting tap is the same `ButtonKey` record as a held key, marked not down, instead of a second type with its own seq and string hooks (system.nim +647 -> +8). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A double tap now sounds 2000, 1400, 3000, 1400 Hz at the default tone: the second down clicks at 1.5x the press pitch instead of 1x, and every up is the same lower click (0.7x), a double press's included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Until now the wake press was replayed as a plain `press` with no hold, so holding the key that woke the frame could not be a long press, and it could not start a double press. Now the button task's first look queues its edges like any other press's: a key still down began at the reset (atMs 0), so its hold counts from there, it duns each second and its up is a `longPress`; a key already up was a tap, its down and up stamped when first seen, so a second tap within doublePressMs pairs into a `doublePress`. The button-layer event a wake starts carries `wake: true`. The queue item drops its duration field (12 -> 8 bytes), which only the wake release used. The PSRAM memory test is off at boot: it took 0.41 s of every boot on the E1002, deep-sleep wakes included. The button task now starts 0.75 s after the reset instead of 1.18 s; the rest is the bootloader checking the app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
On the ESP32 the button task (
fos_buttons.c) queues edges, and the render task drains the queue only between passes. An E1004 refresh is about 30 s. The Nim dispatcher (input_state.nim) timed every edge when it was delivered, so a tap made during a refresh became a 30 s hold, and two taps could not be told from two slow presses.Review found more:
presswas the down edge itself, so a double tap waspress,press,doublePress, and a scene binding press to next and double to previous went next, next, previous.Two layers, one event (
docs/events-contract.json)Still one event,
button;actionsays which. No new event names.downupdurationMspressdoublePressMs(350) after itsupwhen no second down camedoublePresslongPressupof a press held at leastlongPressMs(now 1000), once, withdurationMsandheldSeconds; nothing while heldEach press is exactly one of
press,doublePressorlongPress. A tap isdown,up,press; a double tapdown,up,down,doublePress,up; a 2.2 s holddown, thenupandlongPresstogether. A listener with noactionfilter still hearspressonly. A scene that must count every tap listens fordown.repeatandrelease, and therepeatMskey.longPressMsgoes 500 → 1000.down/up. The dispatcher takes an edge by itslevel, so a driver that still sayspress/releaseworks.buttonfrom HTTP, the cloud, a schedule or a scene moved no line, so it is delivered as said, with noactionmeaningpress.atMs, never from delivery. A tap during a refresh is a tap, a double tap adoublePress, a hold through it alongPress.fos_events_gen.h, so the C side mirrors the contract.Sounds (ESP32)
Every event sounds the instant it happens, played by the button task from
fos_button_timing.c: the dispatcher's rules on the same stamps.buzzer_tone_hz, ms)downupdoublePresspressA double tap is 2000, 1400, 3000, 1400 Hz at the default tone. A 2.5 s hold is "click, dun, dun, clack": the duns count the hold, and the scene hears its one
longPresswith theup. The old per-second tick and the two-note double sound are gone. The playground below has an editor for every note's pitch and length, with a copy-out in the firmware's terms.Hosts tick while a button is busy
A
pressis decided 350 ms after itsup, and only a tick delivers it.fos_buttons_busy()is true while a key is down or a tap is in its window. The idle wait then runs in 50 ms slices instead of 1 s. A frame about to deep-sleep waits for the decision, up to 10 s, and renders if the scene asked, so a tap just before sleep is not lost.frameos_wasm_pointers_downcounts busy buttons too.From deep sleep
The press that wakes an ESP32 is a press like any other. Holding it is a long press, with the per-second sounds; tapping it and tapping again is a double press. Until now it was replayed as a plain
presswith no hold, so it could be neither.atMs0), so the hold counts from there and itsupis alongPresswhen it should be.downandupare stamped then, so a second tap within 350 ms of that pairs into adoublePress. A second tap made and let go entirely during the boot is not seen.press,doublePressorlongPressa wake starts carrieswake: true.Debounce
Tests
docs/event-fixtures.jsoninputState: 17 cases, run bytest_input_state.nim. They cover a tap and its window, a fast double, the window's edge and the third tap, slow taps, a tap, a double and two slow taps during a render, a hold as one long press on its up, a hold through a render, a key held when the render ends, the long-press line (999 vs 1000 ms), a held double, the wake press, no stamp, a future stamp, someone else's press, and an older driver's edges. Direct tests were rewritten too.Three
dispatcherfixture cases used a rawlevel: 0edge to mean "a press". They now sendaction: press, since the case is about queued dispatch, not timing.test_input_state,test_event_loop,test_event_fixtures, evdevtest_translate: pass.nim checkpasses onsrc/frameos.nim, the wasm entry with the preview's defines, and the gpioButton driver.main/tests/test_button_timing.c: 48 checks, 0 failures, run in CI bybackend/app/tasks/tests/test_esp32_button_timing.py. The contract and scene-guide checks and the backend ESP32 tests pass, 22 in all.ESP32 firmware built in the
frameos-esp32-ci-checkcontainer (EPD_7in3e, 8 MB layout), no warnings in the touched files. Size against main (9857fca), measured withfirmware_size.py: +1,936 bytes. The CI comment compares with the v2026.10.15 release, so its +6 KB also counts Photos: shapes, album links, Select all; waiting deploys; JS app console and tests #553 and ESP32 buzzer: wake press clicks like any press, a tick per second held, heldSeconds #554. Of the 1.9 KB:input_state.nim(the dispatcher's button layer)fos_button_timing.cfos_buzzer.c(the note sequencer for the long-press pair)std/monotimes(timing fromatMs)fos_client.c(short slices, sleep settle)Every button payload is built by one helper, and a waiting tap reuses the held-key record instead of a second type. That cut the first measurement of +4.2 KB to this.
pnpm verifyfromcloud/(FRAMEOS_WASM_SOURCE=local): 28 of 28.Needs a bench
The bench E1002 runs this branch (written into its live slot as 2026.10.15, backups kept) and boots, renders and sleeps normally; the gestures themselves, the wake ones included, are still to be pressed.
docs/manual-testing-todo.mdhas the boxes. On the E1004, check each item:down,up,press, and the fastest double tap onedoublePresswith nopress.longPress; a double tap sounds 2000, 1400, 3000, 1400 Hz.press.On a Pi, the 30 ms debounce must not let a slow press bounce into two.
A browser playground with the same rules (button task, dispatcher, refresh, sounds): https://claude.ai/artifact/HkqaESr9iSoHyqDXz61936
🤖 Generated with Claude Code