Skip to content

Extract TypeScript compiler to companion @secure-exec/typescript package#5

Merged
NathanFlurry merged 2 commits into
mainfrom
node-ts-typecheck
Mar 11, 2026
Merged

Extract TypeScript compiler to companion @secure-exec/typescript package#5
NathanFlurry merged 2 commits into
mainfrom
node-ts-typecheck

Conversation

@NathanFlurry

@NathanFlurry NathanFlurry commented Mar 11, 2026

Copy link
Copy Markdown
Member

Summary

Extracts TypeScript compilation and type-checking from the core secure-exec runtime into a new companion package @secure-exec/typescript. The core runtime now executes JavaScript only, with compiler work sandboxed in a dedicated isolate to prevent untrusted type graphs from consuming host resources.

  • New createTypeScriptTools() factory with typecheckProject, compileProject, typecheckSource, compileSource methods
  • Core runtime cleanup: fixes variable consistency in browser worker transforms and adds regression tests for TypeScript-only syntax failures
  • Comprehensive documentation and OpenSpec updates reflecting the JavaScript-only core contract
  • Restored typescript devDependency to secure-exec package.json for its own build scripts

@pkg-pr-new

pkg-pr-new Bot commented Mar 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/rivet-dev/secure-exec@5

commit: 6306654

@NathanFlurry NathanFlurry merged commit 3e4dfec into main Mar 11, 2026
1 of 2 checks passed
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…#5, commit to openbox

Per direction: (1) reorder so the out-of-band debugging toolchain is the first M8 milestone
(M8.1) before any DE component, since the spike cost multiple sessions for lack of native
observability; (2) add constraint #5 — DE/X/GTK components build+run from UNMODIFIED upstream
source, fixes go in the native/platform layer (runtime/sidecar/VFS/X-server + toolchain
sysroot/shim), never in the component source (flag the M8.0 per-lib X edits as a deviation to
repay in the libc pthread layer); (3) commit firmly to real LXDE with real openbox, no
JWM/twm substitute. Renumbered: M8.1 tools -> M8.2 openbox -> M8.3 menu-cache/libfm ->
M8.4 lxpanel -> M8.5 pcmanfm -> M8.6 session = acceptance. Reframed dbus/dlopen/volume/SVG
items as native-or-config rather than source/build edits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…TK window, all wasm

openbox 3.6.1 builds from UNMODIFIED upstream and runs as the window manager on the wasm X
server, decorating a real GTK 3 window (Clearlooks titlebar + min/max/close + border + title).
Proof: ~/tmp/gui-progress/proof-m8.2-openbox-decorates-gtk.png. Per constraint #5 every wasi gap
is fixed in the platform/toolchain layer, never in openbox:
- crates/execution wasi fd_renumber (Node WASI omits it; openbox imports it via libSM/dup) —
  reuses the host_process fd_dup2 handle model + closes the source (move semantics).
- toolchain/openbox-compat.c: getpwuid/getpwuid_r return a valid stub identity (sandbox has no
  /etc/passwd; openbox find_uid_gid derefs pw->pw_name -> null-deref trap), pthread_exit (omitted
  by threaded wasi libc), alarm/gethostbyaddr stubs.
- toolchain/compat-include/grp.h: declares the group-enum API (setgrent/getgrent/endgrent) wasi-libc
  omits, so callers don't get implicit-int signatures that mismatch the stubs at the wasm ABI and trap.
- scripts/build-openbox.sh: reproducible recipe (libxml2 + pangoxft deps, libhostcompat.a archive,
  config.sub/.la fixes, setjmp+libc as trailing LIBS, --whole-archive, fpcast-emu).
- scripts/prepare-openbox-fixtures.sh: stage rc.xml + Clearlooks theme as a VFS tree (data, not source).

fd_renumber is the only crates/ (TCB) change: it operates on the guest's own fd table via the
existing fd_dup2 model, no new sidecar<->executor boundary surface — surfaced for confirmation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…he kernel VFS, all wasm

libfm-extra -> menu-cache -> libfm-gtk3 all cross-compile from UNMODIFIED upstream and libfm
enumerates a kernel-VFS directory headless (19 entries listed, clean exit). Proof:
~/tmp/gui-progress/proof-m8.3-libfm-lists-vfs.txt. Per constraint #5 every gap is platform/
toolchain-layer:
- toolchain/openbox-compat.c: execv/execve stubs (no process-image replacement in the sandbox) +
  ns_get16/ns_get32 (gio resolver DNS field readers, implemented).
- toolchain/wasi-compat.c: strsignal made weak (libwasi-emulated-signal owns the real one; strong
  caused a duplicate-symbol link error when libfm pulls both).
- toolchain/host-bin/: fake intltool tools (intltool unavailable; only needed for translations,
  which we --disable-nls) + a stub perl XML::Parser to satisfy intltool's configure check.
- scripts/build-libfm.sh + test-m8-libfm.sh: reproducible build (libfm-extra --with-extra-only,
  menu-cache lib-only since menu-cache-gen needs fork/exec, libfm --with-gtk=3 --disable-old-actions
  to skip the Vala component) + the headless dir-listing acceptance test.
The menu-cache freedesktop enumeration (menu-cache-gen via fork/exec) is a separate wasi concern;
the libfm folder/listing path is proven.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…es; strip-debug insight

lxpanel 0.10.1 cross-compiles from UNMODIFIED upstream (scripts/build-lxpanel.sh), building its
extra deps libXres -> libwnck-3.0 (taskbar) + keybinder-3.0 (hotkeys), and instantiates + runs in
the V8 sidecar. Per constraint #5 the one new wasi gap (tmpfile, omitted by wasi-libc) is filled
in the platform layer (openbox-compat.c, via the VFS: create+open+unlink a /tmp file).

KEY INSIGHT: the linked X/glib/gtk libs carry massive DWARF that bloats modules ~3-7x — lxpanel
was 44MB (38MB of it .debug_*), which OOMed the V8 isolate during compile. wasm-opt
--strip-debug --strip-dwarf drops it to 5MB; openbox 21MB -> 6MB. Added to the build scripts.
This also reduces V8 JIT pressure, which should ease the M8.2 multi-guest X-latency starvation.

Remaining for M8.4: lxpanel traps early in init (before any RPC — a static ctor / very-early
main from libwnck/keybinder), needing the same -g-bisection openbox required; then a panel
config + run on openbox to show the live panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ull window; 3 platform fixes

pcmanfm 1.3.2 (the LXDE file manager) cross-compiles from UNMODIFIED upstream to
wasm32-wasip1-threads and runs in secure-exec: reaches main, runs gtk_init fully
(cross-VM X round-trips work), passes single-instance init, and builds its entire
main window (gtk_widget_show_all over the menubar/toolbar/notebook/statusbar tree
completes with no trap, 272 X requests exchanged).

Three real platform-layer fixes (constraint #5 — fix the platform, not the component):

1. WASI sockets (crates/execution/src/node_import_cache.rs): net_setsockopt accepts
   the benign boolean SOL_SOCKET options SO_REUSEADDR/SO_REUSEPORT/SO_KEEPALIVE/
   SO_BROADCAST as a no-op instead of EINVAL. pcmanfm sets SO_REUSEADDR before bind
   on its single-instance socket and treats a setsockopt failure as fatal; the kernel
   socket table owns real bind/reuse, so accepting these is correct sandbox semantics
   and benefits every guest. (Asset version 84 -> 85.)

2. Toolchain 8MB wasm stack (-Wl,-z,stack-size=8388608 in build-pcmanfm.sh): pcmanfm's
   GtkUIManager menu build + recursive gtk_widget_show_all over the deep window tree
   overflow the small default wasm stack, surfacing as 'memory access out of bounds'
   inside the show_all vfunc recursion.

3. Demo harness (experiments/wasm-gui/host/src/main.rs): a wasi worker thread is named
   '<client>~thread~<id>' which still starts_with('xclient'); the host counted such a
   thread exit (pcmanfm's glib/GIO pool worker) as the client completing and tore down
   the VM before the main thread rendered. Now excludes '~thread~'.

Remaining (M8-gating, ties to WASM-THREADS-SPEC): pcmanfm never reaches gtk_main; it
blocks in fm_main_win_add_win inside libfm's async job system (confirmed at both the
side-pane FmPlacesModel/GVolumeMonitor load and the FmDirListJob folder enumeration).
A glib/GIO worker thread exits while the main thread waits on it without re-entering the
GMainContext. Needs real wasm-threads completion/wakeup semantics, not a source change.

New: scripts/build-pcmanfm.sh, scripts/test-m8-pcmanfm.sh, pcmanfm.wasm.
Proof: ~/tmp/gui-progress/proof-m8.5-pcmanfm-progress.txt. SPEC.md M8.5 -> 🟡.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…blocks glib async-job dispatch)

wasi-threads workers share the parent kernel_pid + fd table, but the runner's
synthetic JS pipe state is per-isolate, so a synthetic pipe could not wake a
poll() blocked in another thread. glib's GWakeup (g_main_context_invoke from a
worker waking the GTK main loop) therefore never fired, and was stubbed inert
under #ifdef __wasi__ on the false premise the sandbox is single-threaded. That
is the libfm/pcmanfm async-job (FmDirListJob/FmPlacesModel) render blocker.

Fix: back the guest pipe() with a REAL kernel pipe (shared via the per-kernel_pid
fd table; its write notifies the kernel poll notifier, so a cross-thread write
wakes a blocked __kernel_poll). The two kernel fds are range-encoded
(KERNEL_PIPE_FD_BASE + kernelFd) so every worker isolate resolves the same shared
pipe deterministically with no per-isolate registration.

- sidecar: __kernel_pipe / __kernel_fd_read|write|close sync-RPCs (guest operates
  only on its OWN process fd table; pipe alloc bounded by check_pipe_allocation).
- runner (node_import_cache): fd_pipe creates a kernel pipe; fd_read/fd_write/
  fd_close/poll_oneoff/fd_fdstat_get/set_flags route range-encoded kernel-pipe fds.
- V8 bridge: register the 4 methods (map_bridge_method, SYNC_BRIDGE_FNS,
  bridge-contract.json, the wasm runner sync-RPC switch).
- toolchain: pipe()/pipe2() shim over host_process.fd_pipe; glib gwakeup.c
  reverted to pristine upstream (no __wasi__ carve-out) per constraint #5.

Verified: GLIB-INVOKE (cross-thread g_main_context_invoke) PASS timed_out=0;
POLL-PIPE-WAKE PASS 298ms; THREADS-ALL suite PASS (no regression). pcmanfm now
runs its full main loop and builds its entire widget tree (122 CreateWindow),
past the old fm_main_win_add_win block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…dow, all wasm

Builds on the committed wasm-threads GWakeup kernel-pipe fix. Reaching a rendered
pcmanfm window (menu bar, toolbar, /usr/share path bar, Places side pane with Home
Folder) required clearing a chain of cross-thread/GIO deadlocks that only surfaced
once the threading blocker was gone:

- build-libfm.sh: patch fm_run_in_default_main_context's GLIB>=2.32 path to run
  inline when the caller owns/can-acquire the default context (it otherwise does
  g_main_context_invoke(NULL)+g_cond_wait and deadlocks on the main thread before
  gtk_main is entered).
- scripts/prepare-icons.sh (new) + test-m8-pcmanfm.sh: stage a curated Adwaita PNG
  icon set into the VFS via --vm-tree (GTK's default themes were absent; gdk-pixbuf
  already builds -Dbuiltin_loaders=png so PNG decodes statically). Also bump the
  xtrace cap.
- build-glib-stack.sh: gwakeup.c stays PRISTINE now (the old inert-wakeup patch is
  removed — it broke cross-thread g_main_context_invoke); add a GUnixVolumeMonitor
  is_supported()=FALSE-on-__wasi__ patch so the union volume monitor uses the null
  monitor instead of constructing the native unix monitor, whose
  g_unix_mount_monitor_get() -> g_context_specific_group worker-context wait
  deadlocks (the sandbox has no mounts).
- host/src/main.rs: set GIO_USE_VOLUME_MONITOR=null in the guest env (defense in
  depth for the loadable volume monitors).

Proof: ~/tmp/gui-progress/proof-m8.5-pcmanfm-WINDOW-RENDERS.png (framebuffer
307009/307200 px non-black). Remaining (not blocking the window render): folder-view
file entries not yet populated; GDK input device/seat; and the deeper constraint-#5
fix = the runtime GIO worker-context wakeup (main->worker), which would let unmodified
glib work and let the is_supported workaround be reverted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…der the WM; concurrent-guest scheduling is the remaining blocker

M8.6 (full LXDE: openbox+lxpanel+pcmanfm together) substantial progress, not yet green.

Works (all wasm; openbox = the real LXDE WM):
- openbox MANAGES + DECORATES real GTK app windows (pcmanfm in a Clearlooks-decorated
  window: title bar + folder icon + min/max/close; folder loaded).
- lxpanel renders the bottom panel + a live clock under openbox.
- openbox + a light X client (xclock) fully renders.

Platform fixes (constraint #5 clean, no DE source patched):
- lxpanel wasm 8MB stack (build-lxpanel.sh): the default stack overflows under the WM's
  deeper event call chain -> "RuntimeError: memory access out of bounds" (same fix pcmanfm had).
- GDK_CORE_DEVICE_EVENTS=1 (host guest-env): Xvfb XI2 enumerates NULL master/slave devices;
  once a WM sends focus/crossing events GDK derefs the NULL device -> wasm trap. Force GDK's
  core seat (the honest single-XTEST-seat model), like GIO_USE_VOLUME_MONITOR=null.
- Host settle-gated launch (host/src/main.rs): gate each client on the previous having truly
  gone quiet/idle, not a fixed/short delay. A heavyweight WM inits slowly under contention and
  must select SubstructureRedirect before the app maps. Env-tunable WM_SETTLE_QUIET_MS/APP_SETTLE_MS.
- Removed debug instrumentation from vendored openbox/gtk3/lxpanel (constraint #5) and fixed an
  instrumentation-introduced bug in openbox obt/paths.c find_uid_gid (a debug getgrent() consumed
  the first /etc/group entry). [vendored, gitignored — restored to pristine upstream]

Harness scripts/test-m8-lxde.sh (WM=/APPS=/APP_SETTLE_MS=/WM_SETTLE_QUIET_MS=) + analyzer
scripts/xwd-analyze.py (XWD framebuffer -> PNG + coverage).

Remaining blocker (= this spec's own M8.6 platform item, "concurrent-guest net.poll scheduling"):
- 4 concurrent heavy V8 guests (Xvfb+openbox+lxpanel+pcmanfm): the 4th starves at init; ceiling
  is 3 heavy guests (Xvfb + WM + one GTK app).
- Under the WM's hot event stream, pcmanfm's file-VIEW interior doesn't repaint (M8.5 idle
  starvation, exacerbated by openbox's focus/property events) — window+decoration render, interior
  black.
Closing M8.6 needs the runtime concurrent-guest scheduling work (a core/TCB change, validate
against the M7.5 thread suite).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ols + correlatable breadcrumbs

SPEC M8.1: add tools #4 (sync-RPC tracer), #5 (lock-holder/silent-guest watchdog = the M8.1
thread/lock-holder dump), #6 (host-stamped guest "BC:" breadcrumbs on the watchdog timeline), the
SECURE_EXEC_ASYNC_POLL A/B switch, and the still-TODO DWARF-symbolizer-for-parked-wasm. Records the
first payoff (the openbox+pcmanfm interior stall is in-guest, not a sidecar deadlock) and the scoped
NEXT STEPS for the M8.6 interior-paint fix.

host: stamp guest stderr with a relative-ms timestamp so fprintf "BC:" breadcrumbs line up with the
sidecar rpc-watchdog dumps in the run log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…openbox+ANY-client, not pcmanfm

WM-matrix proof: lxpanel/xclock render standalone+twm; openbox + any client (even
trivial xclock) → one sidecar 100% CPU + black; openbox-alone fine. strace: the
spinner is Xvfb (97% futex, secure-exec-v8- threads = its own pthreads, main-dispatch
wasm busy-loop ⇄ input-thread net_poll via V8 FutexEmulation). Ruled out (each tested):
HarfBuzz, fpcast/-Oz, fonts, dir-content, guest net_poll throttle (deployed), poll-waiter
pool (ASYNC_POLL=0/POLL_WAITERS=1), input thread (-dumbSched). Fix target: crates/v8-runtime
wasm-threads / setitimer shim, not the X server source (constraint #5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ready() perpetually true

Named-Xvfb + net_poll instrumentation: the X server polls with timeout=0 (ready=0, ~43/s)
because WaitFor.c:196-199 sets timeout=0 when clients_are_ready() (dix/dispatch.c:275 =
ready_clients list non-empty). A client is stuck on that list (os/io.c mark_client_ready:
buffered-request 545/590 or output_pending 666). So WaitForSomething never blocks and each
iteration vfbBlockHandler pwrites the full 1.2MB framebuffer via sync-RPC = the 100% CPU /
futex storm. openbox separately does a POLLOUT-only net_poll spin (~13/s, unconditional
POLLOUT). Writes are non-partial, so output_pending is not from short writes. Next: named
openbox stackdump + identify the ready-list path. Fix stays runtime-side (constraint #5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ist; plan X-server-side probe

Named openbox confirms the consistent net_poll spinner is the X SERVER's
WaitForSomething (openbox's thread is parked). Read os/io.c: WriteToClient buffers +
output_pending_mark unless (oco->count==0 && who->local); FlushClient registers POLLOUT
only on a short write (EAGAIN), which our net.write never returns. So a client stays on
the ready list (→ clients_are_ready → timeout=0 → spin writing the framebuffer) either
because who->local is FALSE (every write buffers), output is re-generated each cycle, or a
buffered request never completes. Next iteration adds temporary (reverted) ErrorF
instrumentation in the X server to pinpoint the client + path, per constraint #5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…hroughput is the spin

It RENDERS at -screen 160x120 (98% nonblack, CPU 210j->44j settled); black-spins at 640x480.
So the M8.6 blocker is framebuffer-write THROUGHPUT, not openbox/HarfBuzz/XKB/RandR/events.
Feedback loop: Xvfb's vfbBlockHandler pwrites the WHOLE 1.2MB framebuffer to the host shadow
file every block (no damage tracking); each fd_pwrite -> fsModule.writeSync -> callSync
base64-encodes the 1.2MB (encodeSyncRpcValue:10968/10981) guest-side + decodes sidecar-side,
~40x/s. That starves the X server's main thread (blocked in sync-RPC Atomics.wait = the 97%
futex), so openbox's finite setup never completes, so clients_are_ready() stays true, so
WaitForSomething uses timeout=0, so the block handler (and fb write) runs every cycle. Tiny fb
breaks the loop. FIX (runtime-side, constraint #5): pass large binary sync-RPC payloads through
the existing 4MB dataBuffer SharedArrayBuffer (memcpy) instead of base64. Next: implement the
SAB binary fast-path for fs.writeSync and verify 640x480 + full LXDE render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…throughput spin; 2-client desktops render

The wasm Xvfb pwrites its ENTIRE framebuffer to the host shadow file every block handler;
each 1.2MB write is base64'd through the sync-RPC and blocks the X server's main thread
~24ms, starving request processing so clients never finish setup (the timeout=0 feedback
loop = the 97% futex storm). Fix in fd_pwrite (node_import_cache.rs, runtime-side, constraint
#5): for large guest-file writes, diff against the previous write at the same offset and send
ONLY the changed byte range (written at off+firstDiff); identical frames are skipped. Always
correct (the file already holds the bytes we don't send); cost now scales with changed pixels,
not frame size. Result @640x480 (were black-spinning): openbox+xclock RENDERS; openbox+pcmanfm
RENDERS at 99% (pcmanfm's file-manager window fully drawn, 209817px). Full LXDE (3 clients)
still panel-only — that is the separate 4-heavy-guest host-scheduling ceiling, not fb throughput.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ncy is inherent render throughput

Measured the remaining listing-populate time after the batched-drain fix: Xvfb is the busy
sidecar, pcmanfm is near-idle/waiting (20 futex/3s) — so it is NOT a blocked GIO worker and NOT
pcmanfm spinning. With the batched net_poll drain + POLL_WAITERS=4 the X server is healthy (1552
futex/4s, mostly long blocking waits). The remaining ~46s (2 clients) / ~140s (3 clients) before
the listing fully populates is bounded by the single-threaded wasm X server's THROUGHPUT
rendering GTK's 21 icon-heavy folder entries (many CreatePixmap/RENDER/PutImage per icon) plus
pcmanfm's libfm round-trips — inherent, not a storm/bug, and well under 5 minutes. So the
X-server side is optimized as far as is worthwhile (lazy readReadyGen + batched drain killed the
futex storm). Remaining open items are diminishing-returns polish: more listing speed (would need
re-enabling the X input thread or cutting GTK render volume) and the constraint-#5 repayment of
the M8.0 per-lib X / vfbBlockHandler patches into the sysroot/runtime layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…moves to the platform layer (constraint #5)

Restore Xvfb hw/vfb/InitOutput.c to unmodified-upstream mmap(MAP_SHARED)+msync
framebuffer export (was a secure-exec malloc + per-block full-buffer pwrite patch,
plus stray /data/vfb_reached + ErrorF debug). The wasi-specific glue now lives in
the platform layer instead of Xvfb source:

- toolchain/wasi-compat.c: a file-backed MAP_SHARED writeback registry behind
  __wrap_mmap/__wrap_munmap (wasi-emulated-mman gives no write-back) plus a real
  msync() that pwrites tracked regions. The runner delta-encodes the pwrite, so only
  changed byte-runs cross the wire. MAP_PRIVATE caches (fontconfig/freetype) pass
  straight through to __real_mmap untracked.
- scripts/link-xvfb.sh: link Xvfb with -Wl,--wrap=mmap,--wrap=munmap.

Verified: full LXDE@800x600 renders green and identical (openbox-decorated pcmanfm,
populated 21-item "/" listing, lxpanel clock, fonts+icons intact); the stock code
path is confirmed live (debug ErrorFs gone). The vendored xserver tree and the
generated Xvfb.wasm are untracked/gitignored build inputs, so the InitOutput.c
restoration lives on-disk as the prior patches did.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ptance met

New scripts/test-m8-openbox.sh runs openbox + a decorated xclock, all wasm, and
drives the two remaining M8.2 interactions via injected XTEST:
- move: a titlebar drag relocates the decorated window (318,206)->(148,418).
- menu: a right-click of the root opens the openbox root menu, fully rendered
  (Applications -> Accessories/Editors/Graphics/Internet/Office/Multimedia/
  Terminals/File Managers, System, Log Out).

Surfaced and fixed a platform-layer fixture gap (constraint #5, not an openbox
source patch): the openbox guest needs the Xft/fontconfig fixture
(prepare-xftfonts.sh: config + writable cache) staged, else fontconfig fails
("Cannot load default config file" / "No writable cache directories") and
menu/Xft text does not render (menu text pixels 11 -> 1596 once staged).

SPEC.md M8.2 marked DONE. Proofs: gui-progress/2026-06-24T16/
proof-m8.2-openbox-move.png, proof-m8.2-openbox-menu.png.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
Upstream LXDE builds the application menu by fork/exec'ing menu-cached ->
menu-cache-gen, which the sandbox lacks. Solved by running the one-shot
generator directly as a wasm guest (no daemon, no fork/exec): menu-cache-gen
is cross-compiled and run headless against a staged freedesktop fixture tree,
where it composes the full categorized application menu in-sandbox
(Applications -> Accessories/Internet/Office/System, each populated from the
.desktop files). Automated via scripts/test-m8-menucache.sh (PASS = 4
categories composed) + scripts/prepare-menucache-fixtures.sh.

Build-layer glue only (constraint #5, upstream .c untouched): a guarded unity
TU #includes the unmodified sources, working around clang's -fno-common default
plus an LLVM -fcommon codegen ICE on wasm that otherwise duplicate menu-tags.h's
tentative-def globals (objcopy can't weaken wasm symbols; wasm-ld has no
--allow-multiple-definition).

Host --exec harness extended (this workspace) to stage --vm-tree fixtures, set
--guest-env, and read back guest output files on clean exit.

Also fixes O_EXCL handling found here (correctness): the runner's path_open
precreate must skip O_EXCL (else the exclusive create hits EEXIST), and wasm.rs's
numeric open must map O_CREAT|O_EXCL to create_new. menu-cache-gen's on-disk
cache serialization (save_menu_cache's mkstemp/rename atomic save) still has a
further host-backed-fs write-path gap; the enumeration (the M8.3 acceptance)
does not depend on the persisted cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
An exclusive-create open without O_TRUNC (exactly what mkstemp() uses) was
silently broken: fsOpenFlagForPathOpen fell through to Node's 'r+' (no create)
because it only special-cased the truncating case, and the path_open precreate
is deliberately skipped for O_EXCL — so nothing created the temp file and every
safe-create (mkstemp, atomic temp+rename) failed ENOENT.

Fix in the runtime (constraint #5): fsOpenFlagForPathOpen now returns 'wx'/'wx+'
for any exclusive create (O_TRUNC is a harmless no-op on the brand-new file),
ahead of the truncate branch. Together with the existing precreate-skip-for-EXCL
and wasm.rs's O_CREAT|O_EXCL -> create_new, mkstemp works for ALL guests.

Completes M8.3 end-to-end: menu-cache-gen now serializes its on-disk cache (the
843-byte categorized menu with all 5 records), not just the in-memory
enumeration. scripts/test-m8-menucache.sh strengthened to read back the cache and
assert >=4 menu records. Proof: gui-progress/2026-06-24T17/
proof-m8.3-menucache-cachefile.txt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ayer (constraint #5)

libxcb src/xcb_conn.c set_fd_flags() is restored to UNMODIFIED upstream. It was
a secure-exec block that called the net_set_nonblock host import directly because
fcntl() can't reach host_net socket fds (they aren't in the WASI fd table). That
workaround now lives in the platform libc fcntl override
(registry/native/patches/wasi-libc-overrides/fcntl.c, committed separately): for
host_net fds (>=0x40000000) F_GETFL/F_SETFL/F_GETFD/F_SETFD are handled directly,
routing O_NONBLOCK to net_set_nonblock; normal-fd behavior is unchanged. Rebuilt
the threaded override_fcntl.o so the clients link it.

Also harden build-openbox.sh: regenerate the gitignored wasi-compat-threads.o
right before the libhostcompat.a `ar`. A concurrent session in the shared
workspace deletes that object, which made `ar` abort mid-archive and silently
leave a stale libhostcompat.a missing the host-import shims — so the relinked
clients couldn't open the X display. Regenerating it makes the archive build
deterministic.

Verified: full LXDE@800x600 renders green with all three clients (openbox,
lxpanel, pcmanfm) connecting via stock libxcb (0 "Failed to open display").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…rride (constraint #5)

libX11-threads/src/XlibInt.c SocketBytesReadable is restored to UNMODIFIED
upstream ioctl(ConnectionNumber, FIONREAD, &bytes). It was a poll()-based
replacement because WASI has no working ioctl. The platform now provides ioctl
(new registry/native/patches/wasi-libc-overrides/ioctl.c, committed separately):
FIONREAD on host_net fds (>=0x40000000) reports a readable estimate via the
host_net-backed poll(), 0 when not ready (the opportunistic "bytes pending"
semantics libX11 wants); ENOTTY otherwise.

Wire override_ioctl.o into libhostcompat.a (build-openbox.sh + build-libfm.sh),
regenerated from source alongside override_fcntl.o so the archive build is
deterministic under the shared-workspace cleaner.

Verified: full LXDE@800x600 renders green with all three clients on stock libX11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ride (constraint #5)

libxcb-threads/src/xcb_conn.c is restored to STOCK upstream writev() (+ the
errno==EAGAIN would-block check). It was a hand-rolled per-iovec send() loop
because wasi-libc's writev() fails on host_net socket fds. The platform now
provides it (new registry/native/patches/wasi-libc-overrides/writev_hostnet.c):
__wrap_writev emits each iovec with send() for host_net fds (>=0x40000000) and
surfaces would-block as EAGAIN (host_net returns -1 only for would-block and
errno is otherwise unreliable in the threaded build); non-host_net fds delegate
straight to __real_writev. Linked with -Wl,--wrap=writev on the three clients;
override_writev.o wired into libhostcompat.a.

Verified: full LXDE@800x600 renders green, all clients on stock libxcb writev.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…unmodified upstream

Start of the Xubuntu/Xfce milestone (XUBUNTU-SPEC.md). XU0 is the D-Bus session
bus. dbus-daemon, dbus-send and dbus-monitor cross-compile to wasm32-wasip1-threads
from UNMODIFIED upstream (scripts/build-dbus.sh) and run as guests; the daemon uses
the host_net AF_UNIX socket layer (net_socket/bind/listen/accept/poll), the same
kernel brokering the X clients use.

Platform fixes (constraint #5, nothing patched in dbus):
- Configure WITHOUT --allow-undefined so autotools feature-detection is accurate.
  With it, every link-test passed and configure mis-detected Solaris getpeerucred,
  pulling a non-existent <ucred.h>. Linking -lhostcompat lets the real host_net
  socket funcs resolve so socket features detect correctly; --allow-undefined is
  added only at the final daemon link.
- setgroups() no-op stub in wasi-compat.c (dbus drops privileges at startup; the
  sandbox has a single identity).

The daemon now instantiates and starts, reaching the point where it needs the
kernel-VFS device nodes (/dev/null, /dev/urandom — provided by device_layer.rs for
kernel-VFS/X-mode guests, absent from the host-backed --exec fs). XU0 acceptance
(the method-call + signal round-trip) needs a multi-guest kernel-VFS harness, the
next step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…cked guest fs

Threaded wasm guests use the host-backed fs (wasm.rs open_wasm_guest_file), which
bypasses the kernel device_layer, so opening /dev/null, /dev/urandom, etc. failed
with ENOENT. The GTK/X clients never noticed (they use getrandom()), but dbus-daemon
opens these device FILES at startup ("setting up standard fds" + UUID generation).

Fix (constraint #5, runtime layer):
- wasm.rs: a WasmCharDevice table (Null/Zero/Full/Random) parallel to open_files;
  open synthesizes a device fd, read produces EOF (null) / zeros (zero,full) /
  getrandom bytes (random,urandom), write is discarded (ENOSPC for full).
- node_import_cache.rs: the path_open override now routes the /dev device paths
  through openGuestFileForPathOpen -> fsModule.openSync (the device handler)
  regardless of O_CREAT; the default WASI path_open over the sandbox preopens
  would return ENOENT. Asset version bumped.

dbus-daemon now starts past /dev/null + /dev/urandom. No regression: the LXDE
openbox client still renders. General fix: any Linux guest needing /dev devices.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
With a minimal session.conf fixture (scripts/prepare-dbus-fixtures.sh) the
unmodified dbus-daemon binds /tmp/.dbus/session, prints its address, and stays
alive in its main loop — a working D-Bus session bus, all wasm.

Platform fixes (constraint #5, dbus untouched):
- getrlimit() fills the rlimit struct with RLIM_INFINITY. The old stub returned 0
  but left the struct uninitialized, so dbus read garbage limits and fataled with
  "cannot change fd limit".
- socketpair() is now a pipe()-based emulation (was a weak stub returning -1, so
  dbus's reload self-pipe failed "_dbus_socketpair not implemented"). The reload
  pipe is unidirectional (write-to-wake, read-to-drain), so a pipe suffices.
- build-dbus.sh forces ac_cv_func_{getrlimit,setrlimit,socketpair}=yes: their
  autotools link-tests false-negative here because the test object pulls
  libhostcompat/wasi-compat symbols (net_*, __real_mmap) that only resolve at the
  final --allow-undefined link, so configure wrongly concluded the functions are
  missing and compiled out dbus's rlimit/socketpair paths.

XU0 acceptance (the client round-trip) still needs a multi-guest harness; the
daemon side is done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
XU0 acceptance met: unmodified dbus 1.14.10 (dbus-daemon + dbus-send + dbus-monitor) runs as
wasm guests over the host_net AF_UNIX socket table, and test-xu0-dbus.sh drives a full
method-call + signal round-trip — guest dbus-send ListNames gets a method_return while guest
dbus-monitor observes NameAcquired/NameLost/NameOwnerChanged on the live session bus:
PASS (method_return=3 signals=4). Proof ~/tmp/gui-progress/2026-06-24T20/.

The EXTERNAL-auth handshake needed four platform fixes (constraint #5, dbus untouched). Three
live in the main repo's dbus_creds.c (host_net recvmsg/sendmsg/getsockopt(SO_PEERCRED)/read);
this commit carries the rest:
- build-dbus.sh: -DSO_PEERCRED=17 -D_GNU_SOURCE so dbus's #ifdef SO_PEERCRED credential path
  (its only EXTERNAL mechanism) compiles in; force ac_cv_func_poll=yes so dbus uses the
  host_net-aware poll() instead of select() (whose fd_set can't hold fd 0x40000000); archive
  dbus_creds.o as libdbuscreds.a + link --wrap=read.
- node_import_cache.rs net_poll: POLLOUT was the wrong 0x002 (POLLPRI); corrected to the real
  wasi <poll.h> 0x004, keeping 0x002 as a compat bit (asset 100->101). The X libs never parked
  on POLLOUT so this latent bug only surfaced with dbus, which waits to flush its auth OK/reply.
- host --bus-test forwards DBUS_* env to the guests.

No X regression: test-m5-twm still decorates a window. XU0 gated everything; XU1 next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…works

GLib's poll() routes to net_poll (the host_net poll, via the wasi-libc sockets patch), which only
handled host_net sockets -- for any other fd, including a kernel-pipe fd, revents stayed 0. A
GMainContext GWakeup is a kernel pipe, and GDBus's g_bus_get_sync blocks the main thread in
g_main_loop_run on a context whose only source is that wakeup pipe; the worker writes the pipe on the
Hello reply to wake it, but net_poll never reported the pipe readable -> the main thread spun (readiness
generation frozen, since pipe writes don't touch socket_readiness). M8's GTK never hit this: it polls
the X socket, not a wakeup-only context. GDBus is the first guest that does.

Fix (constraint #5, runtime):
- New __kernel_fd_poll sync-RPC: service_javascript_kernel_fd_poll_sync_rpc -> kernel.poll_fds
  (non-consuming readiness; kernel POLLIN=0x1 / POLLHUP=0x10 match the wasi <poll.h> values).
- Registered in the WASM bridge allowlist (_kernelFdPollRaw): wasm.rs switch, v8_runtime.rs
  map_bridge_method, session.rs global list, bridge-contract.json.
- net_poll batches the poll-set's kernel-pipe fds (decoded via kernelPipeKernelFd) through it and
  reports POLLIN/POLLHUP; it also caps the blocking net.poll_wait to ~10ms when the set contains pipe
  fds (net.poll_wait wakes only on socket readiness), keeping cross-thread pipe wakeups prompt.

Result: the gdbus-probe passes -- g_bus_get_sync(SESSION) connects and ListNames returns 2 names, all
wasm. M8 (test-m5-twm) and XU0 (dbus round-trip 3/4) stay green. This completes the XU1 foundation
(xfconf/xfsettingsd reach the bus via GDBus); next is building xfconf + xfsettingsd on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…Bus, all wasm

Built UNMODIFIED libxfce4util 4.18.2 + xfconf 4.18.3 (xfconfd + xfconf-query) to wasm on the working
GDBus-over-host_net path. test-xu1-xfconf.sh PASSES: dbus-daemon + xfconfd (registers org.xfce.Xfconf)
+ xfconf-query --set hello-xu1, then xfconf-query GET returns hello-xu1 -- a real xfconfd method-call
round-trip over the session bus. M8 (test-m5-twm) and XU0 stay green.

Platform fixes (constraint #5, the reusable Xfce 4.18 autotools recipe for XU2+):
- Build tooling: gettext stubs (msgfmt/xgettext/... in toolchain/host-bin; host gettext is unavailable,
  brew needs GLIBC_2.38) and --as-needed/--export-dynamic stripping in clang-wasi-wrap.sh (libtool
  injects --as-needed, wasm-ld rejects it).
- toolchain/glib-compat.c shim (archived libglibcompat.a): g_variant_builder_init_static ->
  g_variant_builder_init -- the host gdbus-codegen (glib >= 2.84) emits the static API in the generated
  D-Bus bindings but we link glib 2.78 (host-codegen/target-lib version skew). Plus BSD err.h
  (errx/warnx/err/warn) + daemon() stubs (wasi gaps; fork/sigaction already in wasi-compat.c).
- Runtime: fd_datasync WASI import (node_import_cache.rs + wasm.rs, both -> fd_sync); xfconfd
  fdatasync()s its channel XML and the kernel VFS owns durability.

New: scripts/build-libxfce4util.sh, build-xfconf.sh, test-xu1-xfconf.sh, toolchain/glib-compat.c.
Remaining for XU1 acceptance: xfsettingsd XSETTINGS push to a GTK client in Greybird.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
… all wasm

Built UNMODIFIED libxfce4ui 4.18.6 + exo 4.18.0 + garcon 4.18.2 + xfce4-settings 4.18.4 ->
xfsettingsd.wasm (15.8MB) via the proven Xfce autotools recipe. xfsettingsd instantiates and runs
past ALL platform gaps, reaching 'Unable to open display' (the expected failure in a bus-only smoke
with no X server) -- the binary works and just needs an X server to publish XSETTINGS.

New GTK-linking platform fixes (constraint #5, apply to all upcoming GTK components):
- libsetjmp.a appended last via LIBS: the -wasm-enable-sjlj intrinsics __wasm_setjmp/__wasm_longjmp
  (pulled by GTK's gdk-pixbuf/libpng); with --allow-undefined an earlier position turns them into
  unresolved imports, so it must come after the objects.
- GTK transitive libs that non-static pkg-config drops (Requires.private): the stub -latk-bridge-2.0
  (atk_bridge_adaptor_init; a11y is off in the sandbox) + epoxy + the X ext libs, appended via LIBS.
- Build only the xfsettingsd target (make -C common then make -C xfsettingsd) -- the GUI dialog
  binaries we don't need have huge transitive links that hit 'argument list too long'.

Scripts: build-libxfce4ui.sh, build-exo.sh, build-garcon.sh, build-xfce4-settings.sh. M8 + XU0 +
XU1-xfconf stay green. Remaining for XU1 acceptance: a combined X+D-Bus harness + Greybird fixture ->
screenshot of a GTK window themed by xfsettingsd's XSETTINGS push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…l wasm (decoration paint WIP)

Built UNMODIFIED libXinerama 1.1.5 + libwnck 3.24.1 + xfwm4 4.18.0 to
wasm32-wasip1-threads (xfwm4.wasm, 15.9 MB). xfwm4 takes over the root,
connects to xfconf over D-Bus, and places the gtk-hello window centered
(exact WM placement) — it runs as a real window manager.

- libwnck 3.24.1 (scripts/build-libwnck.sh): the staged libwnck was an ancient
  3.4.9 < the >= 3.14 xfwm4 requires; 3.24.1 is the last autotools series
  (3.32+ is meson). config.sub must go in build-aux/ too.
- libXinerama 1.1.5: xfwm4 configure requires xinerama, which the staged X-ext
  set lacked. Built via build-xlib.sh.
- xfwm4 4.18.0 (scripts/build-xfwm4.sh): Xfce autotools + the two mandatory GTK
  flags; compositor/startup-notification/xpresent off (no GPU); make -C common
  then make -C src.

Two platform fixes (constraint #5, both apply to XU3+ file-loading components):
- --datadir=/usr/share at configure: xfwm4 loads its 'defaults' file from the
  compile-time PACKAGE_DATADIR (not XDG); the wasm-prefix path is absent in the
  VM -> "Missing defaults file" -> exit 1 (fatal). scripts/prepare-xfwm4.sh
  stages the defaults file + the bundled Default fallback theme.
- prepare-themes.sh transcodes XPM-only decoration images -> PNG (our gdk-pixbuf
  is PNG-only; several Greybird borders ship only .xpm). Also stages the xfwm4/
  deco theme dir alongside gtk-3.0/.

OPEN BLOCKER: xfwm4 draws no visible decoration (titlebar paints black). Ruled
out theme-not-found, the defaults-file exit, XPM-only borders, and a missing
PNG loader (gdk_pixbuf__png_image is present in xfwm4.wasm; gdk-pixbuf built
builtin_loaders=png; xfwmPixmapLoad reaches the PNG path via xfwmPixmapCompose).
So it's a frame-draw / Expose / reparent-render runtime issue, not image loading.
Next: XTRACE the xfwm4<->Xvfb frame draw vs M8's working openbox. Side finding:
xfwm4's xpm_image_load fread(1024)+fseek(0,SEEK_SET)+getc hits a wasi-libc
fseek-after-fread stdio-buffer bug ("Cannot read Pixmap header"); PNG fallback
covers it. Harness scripts/test-xu2-xfwm4.sh. M8 + XU0 + XU1 stay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…iro A1 shape masks)

Constraint #4 observability: XTRACE + a new bare-X-server repro
(guest-xclient/bgpixmap-test.c) narrow the xfwm4 decoration blank to a precise,
proven root cause.

Findings:
- xfwm4 runs as a WM: reparents (3 ReparentNotify), creates the 326x32 titlebar,
  uploads REAL decoration pixel data (18 PutImage, 17-62% nonzero), ~30 CopyArea.
  Images load fine.
- The wasm X server (real X.Org Xvfb) passes EVERY primitive xfwm4 uses, proven
  by bgpixmap-test: solid bg color, background pixmap, TILED bg pixmap (16x16 on
  200x150), direct draw, and the SHAPE ext XShapeCombineMask (left-half mask ->
  left visible, right shaped away). openbox decorates fine in the same workspace.
- Root cause: xfwm4 shapes its title/side/corner decoration windows with a mask
  via XShapeCombineMask (frame.c:512-544); the mask is a depth-1 bitmap drawn
  with CAIRO (cairo_xlib_surface_create_for_bitmap). An all-zero mask shapes a
  window to nothing (proven). Since even fully-opaque transcoded decoration PNGs
  render invisible, cairo->depth-1(A1)-bitmap rendering produces EMPTY masks in
  the wasm cairo build -> XShapeCombineMask shapes all decorations away. openbox/
  twm draw rectangular decorations directly and never shape, so they work.

Next (constraint #5, platform): a cairo-to-A1 test to confirm, then fix the wasm
cairo A1/depth-1 xlib-surface (pixman A1) rendering. xfwm4 stays unmodified.
Side finding: xfwm4's xpm_image_load fread+fseek+getc hits a wasi-libc
fseek-after-fread stdio-buffer bug (PNG fallback covers it). M8+XU0+XU1 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
… the fork-migration blocker

The xfce4-panel crash was a libxfce4ui GResource that was never registered: the
dialog UI bundle's register-constructor lives in libxfce4ui-resources.o inside
libxfce4ui-2.a, but archive-pull drops it (unreferenced), so the ctor never runs
-> "resource /org/xfce/libxfce4ui/libxfce4ui-dialog-ui.ui does not exist" ->
Gtk-ERROR -> unreachable.

Fix (constraint #5, pure link composition; libxfce4ui untouched): extract
libxfce4ui_2_la-libxfce4ui-resources.o from the archive and link it as a plain
object, so it's always included and its constructor runs. (--undefined and
--whole-archive via LIBS didn't pull it -- automake dedups the repeated -l; and
make won't relink a stale binary, so rm it first.) Baked into build-xfce4-panel.sh.

Verified: the libxfce4ui error dialog now renders correctly (was crashing) and
the panel BAR renders (~10% non-black). This GResource pattern also helps XU4/XU5
(they use libxfce4ui dialogs).

Revealed the next blocker: with no existing config the panel fork()s
xfce4-panel-migrate -> "Failed to fork" (no fork in wasm). Next: pre-stage a
panel xfconf config so migration is skipped; then the gmodule static-plugin work
for whisker/clock/tasklist/systray.

M8 + XU0 + XU1 green; XU2 decoration done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
… the wiring plan

xfce4-panel loads every plugin via g_module_open(<.so path>) + g_module_symbol;
the sandbox has no dlopen, and the plugins can't all be static-linked under
their real names (every plugin exports the same entry symbol). Approach
(constraint #5; panel + plugins UNMODIFIED -- a toolchain shim + a compile-time
-D symbol rename):

toolchain/gmodule-shim.c (compiles clean; exports __wrap_g_module_open/open_full/
symbol/close/make_resident/error/supported) intercepts the panel's g_module
calls via -Wl,--wrap=g_module_*, parses the plugin name from the .so path, and
resolves the entry from a generated static table panel_static_plugin_lookup(
name, symbol). It handles both plugin styles: xfce_panel_module_init (GObject/
TypeModule plugins, e.g. separator) and xfce_panel_module_construct (simple).

Reverse-engineered the remaining per-plugin wiring from panel-module.c: compile
each plugin .c with its entry symbol renamed, link it in, add a gen-table entry,
stage a STUB .so at the compile-time PANEL_PLUGINS_LIB_DIR (the panel g_file_tests
it exists), stage the plugin .desktop with X-XFCE-Internal=true (INTERNAL mode,
no wrapper fork), and add it to the xfconf config. Documented in the spec.

Next: prove the path with one plugin (separator -> clock), then tasklist/systray/
clock + build xfce4-whiskermenu-plugin. M8+XU0+XU1 green; XU2 done; panel bar renders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…, no dlopen)

The xfce4-panel plugins load without dlopen: toolchain/gmodule-shim.c (linked +
-Wl,--wrap=g_module_open/open_full/symbol/close/make_resident/error/supported)
intercepts the panel's g_module_open(<.so>)+g_module_symbol, parses the plugin
name from the path, and resolves the entry from the generated
gmodule-plugins.gen.c table. Each plugin .o is rebuilt with a compile-time
entry-symbol rename (-D<entry>=<name>_module_entry, appended to the plugin's
CPPFLAGS so the sysroot + -include wasi-compat.h survive) since all plugins
export the same entry symbol. (llvm-objcopy here can't --redefine-sym, so the
rename must be compile-time.)

Confirmed end-to-end via a GMODSHIM_DEBUG build: "GMODSHIM open separator" then
"GMODSHIM resolved xfce_panel_module_init" -> the panel loads + constructs the
separator with no dlopen, no "no module found"; the bar renders.

Baked into build-xfce4-panel.sh (STATIC_PLUGINS="name:entry" loop) and
prepare-xfce4-panel.sh (stub .so at the compile-time PANEL_PLUGINS_LIB_DIR so the
panel's g_file_test passes + the X-XFCE-Internal=TRUE .desktop + the config
entry). test-xu3-panel.sh -> "BAR renders" with separator.

Next: add clock/tasklist/systray to STATIC_PLUGINS + build+static-link
xfce4-whiskermenu-plugin = XU3 DoD. The panel + plugins stay UNMODIFIED upstream
(constraint #5: a toolchain shim + a compile-time -D rename). M8+XU0+XU1 green;
XU2 decoration done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…er-context (no sign-off needed)

Tested option (a): set DBUS_SYSTEM_BUS_ADDRESS to the existing session bus so the
GIO system-bus probe connects instead of failing ENOENT. The probe DID redirect
(the /run/dbus/system_bus_socket error is gone), YET the populated applicationsmenu
STILL deadlocks. So the system-bus probe + worker-exit was a RED HERRING.

Combined with last iteration ruling out GNetworkMonitor, the deadlock is
definitively the deeper GIO worker-context / GMainContext cross-thread wakeup on a
worker thread (the XU1 GDBus-worker wakeup fix didn't cover garcon's menu-init GIO
async path). Per constraint #5 this is a native/platform-layer runtime compat fix,
NOT a TCB/network expansion, so it does NOT need the human sign-off I'd been
holding for -- the work is unblocked. Reverted the option-(a) test.

Next: deep-dive with a symbolic stack to find the exact cond/futex + the GSource
that isn't woken, then fix the runtime wakeup. Taskbar/clock/systray/separator +
xfwm4 decoration all work. M8+XU0+XU1 green; XU2 done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…/passwd libc stubs)

Started XU5. scripts/build-thunar.sh clones the xfdesktop recipe: fetch Thunar
4.18.10, configure --disable-gio-unix --disable-notifications, build thunarx/ then
thunar/ with the libxfce4ui+libwnck GResource force-links + -lthunarx-3
-lxfce4kbd-private-3 + the GTK transitive link.

Platform fixes (constraint #5, in the libc/compat layer, not Thunar): Thunar
references getgroups() + setpwent/endpwent/getpwent (passwd/group db for the file
owner/group columns), which wasi-libc lacks -> undefined env imports -> LinkError
at instantiation. Added stubs to toolchain/wasi-compat.c (getgroups -> 0 groups;
setpwent/endpwent no-op; getpwent -> NULL) + rebuilt libhostcompat.a. thunar.wasm
now instantiates clean (zero undefined imports), runs, parked=0 (no deadlock).
54MB -> fpcast -> 16.3MB.

Remaining: Thunar registers as a GtkApplication but opens no window without a
folder arg -- needs argv like "thunar /root" (per-client argv from the harness).
Like xfdesktop, not deadlocked; just needs the right invocation. NOTE:
build-thunar.sh isn't idempotent on re-run (re-extract the tarball first). The
passwd/group stubs also unblock any future consumer.

M8+XU0+XU1 green; XU2 done; XU3 core done; XU4 wallpaper done; XU5 builds+runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…block in worker-isolate file I/O

Instrumented g_task_return + g_task_return_now with GLocalFile counters (reverted).
BOTH are ZERO: not a single GLocalFile task ever calls g_task_return. So the prior
"completions stranded on context Z" framing was wrong -- the 46 GLocalFile workers
are dispatched but NEVER complete; they block inside the worker's blocking file
read and never reach g_task_return. The X/Y-vs-Z context spin is a symptom (the
menu waits for results that never arrive), not the cause.

Decisive contrast: the gtask-probe (trivial worker, g_usleep, no file I/O) DID
call g_task_return -> workers CAN complete; only workers doing FILE I/O hang. So
the bug is the WORKER-ISOLATE file-I/O path: a wasi-thread worker isolate doing a
blocking file read (path_open/fd_read sync-RPC) never gets serviced -> hangs. The
main isolate's file I/O works (pcmanfm); worker isolates' does not.

Actionable runtime fix (constraint #5 in-scope): find why a worker-isolate
blocking file-read sync-RPC isn't serviced by the sidecar. Reverted GLib.

M8+XU0+XU1 green; XU2 done; XU3 core done; XU4 wallpaper done; XU5 builds+runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…allback) async query_info

Traced the 46 hanging GLocalFile async tasks to their origin: garcon calls
g_file_monitor on the menu files; wasm has NO inotify (sys/inotify.h absent from
the wasi-sysroot, so GLib's inotify backend isn't compiled); g_local_file_monitor
returns NULL; g_file_monitor falls back to GPollFileMonitor, which does
g_file_query_info_async per watched file (gpollfilemonitor.c:159,208). Those ~46
async query_info workers are exactly the hanging GLocalFile tasks (same call-site
tag, query_info not load_contents, 46 = watched files).

Fix (platform-layer, constraint #5 in-scope): add sys/inotify.h to the
wasi-sysroot + stub inotify_init1/add_watch/rm_watch in the runtime (dummy fd, no
events -- a static menu needs none), then rebuild GLib gio so its UNMODIFIED
inotify backend compiles + is_supported() returns true. GIO uses the no-op inotify
monitor instead of the hanging poll monitor -> no query_info_async -> menu loads.
Cleanest, most actionable fix of the whole arc; sysroot/runtime, not a component
patch.

M8+XU0-XU2 green; XU3 core done; XU4 wallpaper done; XU5 builds+runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…s the hanging poll monitor

Root-cause fix (built, validation pending on infra recovery): garcon's
g_file_monitor fell back to GPollFileMonitor (no inotify in wasm) which does
g_file_query_info_async per watched file -> 46 hanging workers -> black panel.

Fix (constraint #5: sysroot/runtime shim, GLib inotify backend UNMODIFIED):
- toolchain/compat-include/sys/inotify.h: the header wasi lacks, so meson now
  compiles GLib's inotify backend (HAVE_SYS_INOTIFY_H + inotify_init1 both YES
  after meson --reconfigure --clearcache).
- toolchain/wasi-compat.c: no-op inotify stubs. inotify_init1 returns a real but
  NEVER-READY fd (read end of a kernel pipe, write end held + never written), so
  the inotify GSource polls but gets no events; add_watch/rm_watch dummy. A static
  menu needs no live change events.
- Rebuilt libgio (28 inotify symbols, _ik_startup, g_inotify_file_monitor_get_type)
  + relinked the panel (rc=0). GIO now picks the no-op inotify monitor instead of
  the hanging poll monitor -> no query_info_async -> no hang.

VALIDATION PENDING: concurrent session wiped target/ + root node_modules (esbuild
gone), so wasm-gui-host/sidecar can't rebuild to run the menu-draws screenshot
yet. Next: restore node_modules, rebuild host, confirm the bar draws.

M8+XU0-XU2 green; XU3 core done; XU4 wallpaper done; XU5 builds+runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…nd); applicationsmenu has a separate remaining block

Rebuilt host+sidecar (restored node_modules via pnpm install -- concurrent churn
wiped target/+esbuild) and validated the inotify fix:

(1) The infinite hang is GONE: the applicationsmenu panel now PROGRESSES (reaches
the WM check at 45s + the system-bus connect) and its worker threads EXIT CODE 0,
where they previously parked forever never reaching g_task_return. GIO no longer
falls back to the hanging GPollFileMonitor.
(2) NO REGRESSION: panel-core (clock+separator) still draws (23200 px) with the
inotify libgio; it shows the same non-blocking WM warning yet draws.
(3) REMAINING (separate, smaller): the applicationsmenu plugin still blocks the bar
(clock+separator draws; +applicationsmenu -> black). Now that the file-monitor
workers complete, the block is elsewhere in applicationsmenu/garcon init (missing
button icon, or garcon eager-load blocking the plugin-add loop).

task#11 root cause (GPollFileMonitor async query_info, no inotify) is FIXED +
validated; the applicationsmenu-draws-the-bar goal is one step away. Fix is
constraint-#5 clean.

M8+XU0-XU2 green; XU3 core done; XU4 wallpaper done; XU5 builds+runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
… (clean upstream GLib)

End-to-end validated. Instrumented g_local_file_monitor_new (diagnosis-only,
reverted): with the inotify backend enabled GIO now picks GInotifyFileMonitor (47
selections = the watched menu files) instead of the hanging GPollFileMonitor, and
the busy-spin is GONE (net_poll BLOCK 0, was 4176).

FINAL CLEAN BUILD (GLib fully unmodified): the applicationsmenu+clock+separator
panel BAR DRAWS -- 23200 px, 0 spins, FB 1923232 bytes. Proof under
~/tmp/gui-progress/2026-06-25T12/.

Root cause: no inotify in wasi -> GLib's inotify backend uncompiled -> g_file_monitor
falls back to GPollFileMonitor -> g_file_query_info_async per file -> ~46 hung
workers -> black panel. Fix (constraint #5, sysroot+runtime only): sys/inotify.h +
no-op inotify stubs (never-ready fd) so GLib's UNMODIFIED inotify backend compiles
+ is selected (no events, fine for a static menu). Earlier black runs were a
stale-libgio panel + a 95s readback-timing fluke.

XU3 applicationsmenu = DONE. Same fix unblocks all g_file_monitor users
(Thunar/xfdesktop). M8+XU0-XU2 green; XU3 panel+applicationsmenu DONE; XU4
wallpaper done; XU5 builds+runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…e-deferred construct shim

XU3 complete: panel + applicationsmenu + whiskermenu all draw, all wasm.

The whiskermenu construct memory-OOB is solved. Root cause: xfce_panel_plugin_lookup_rc_file
read a garbage plugin name because mapping whiskermenu's RAW construct directly into the gmodule
table called it before the panel set the plugin name/unique-id. Pinpointed via temporary printf
instrumentation (now reverted -- plugin.cpp re-extracted UNMODIFIED).

Fix (platform layer, constraint #5 honored): toolchain/whiskermenu-register.c uses
XFCE_PANEL_PLUGIN_REGISTER, whose generated xfce_panel_module_construct DEFERS whiskermenu_construct
to a realize handler (run after the panel sets name/unique-id) -- the wrapper whiskermenu's own .so
would provide. Compiled with -Dxfce_panel_module_construct=whiskermenu_module_entry; the table maps
(whiskermenu, xfce_panel_module_construct) -> whiskermenu_module_entry.

Construct now runs to completion; bar DRAWS 23200 px on a clean unmodified build. The earlier
integration fixes stand (EXTERNAL_PLUGINS path, -lc++ -lc++abi -lxfconf-0, PANEL_STACK_SIZE env).
Proof: gui-progress/2026-06-25T15/xu3-whiskermenu-panel-draws.png. whiskermenu loads garcon lazily,
avoiding the binaryen fpcast file-view gate.

XU0/XU1/XU2/XU3 DONE; next XU4 xfdesktop (wallpaper done; icons gated on the fpcast fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ocal; query_info works, XU4/XU5/XU6 unblocked

The multi-day file-view gate is fixed. After narrowing it to a trapping built-in GIO
type-registration inside _g_io_modules_ensure_loaded (reached only by g_vfs_get_default;
g_vfs_get_local + g_vfs_get_file_for_path already proven to work), the clean fix is
toolchain/gio-vfs-local-shim.c + -Wl,--wrap=g_vfs_get_default (build-gtk-app.sh
SECURE_EXEC_GIO_VFS_LOCAL=1): wrap g_vfs_get_default -> g_vfs_get_local.

Rationale: the sandbox has no dlopen, so no gvfs daemon backend modules can load -> the default
GVfs is ALWAYS the local vfs. Returning it directly is the semantically correct value for a
module-less sandbox AND bypasses the trapping ensure_loaded module-init machinery.

Validated on fileview-probe (host --exec): g_vfs_get_default / g_file_new_for_path /
g_file_get_path / g_file_query_info all PASS (err=none). A toolchain --wrap shim (constraint #5,
like gmodule-shim.c + the writev wrap) -- no component patched, no host-fd/GPU/network expansion.

Next: apply the wrap to xfdesktop/Thunar/file-dialog builds and render the desktop icons (XU4) +
Thunar folder view (XU5). This was THE blocker for XU4/XU5/XU6 visual completeness. XU0-XU3 DONE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…empty-path hang + deterministic build fix

TWDBG window-init trace: ... -> e menus -> e2 "before sidepane gtk_image_new_from_file" = LAST
LINE, no e3. So thunar-window.c `gtk_image_new_from_file ("")` (an empty placeholder image) HANGS
(~75s, never returns). Platform/runtime edge case, not Thunars fault: gtk_image_new_from_file("")
-> gdk_pixbuf_new_from_file("") -> the runtime opens path "" to sniff the format, and the
empty-path VFS open apparently hangs instead of returning ENOENT. Likely fix = the kernel VFS
returns ENOENT immediately for an empty path (platform layer, constraint #5).

Also fixed the flaky Thunar build for good: replaced `make distclean` with a direct clean (no
`make`), because the prior Makefile has AM_MAINTAINER_MODE on -> `make` tries to regenerate
Makefile.in via the wrong-version automake and wipes them. Direct clean + unconditional tarball
restore = deterministic build.

XU0-XU4 DONE; XU5 block pinpointed to the empty-path open hang. Next: minimal repro + VFS fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…13.4s for ONE GtkLabel), NOT scheduling

Drove popup-repro to the root. X round-trips ~5ms (xsync-bench); async events MapNotify 21ms/Expose
28ms (map-bench) -> the wasm X server is FAST both ways. gtk_widget_realize ~42ms. The ~13.4s is inside
gtk_widget_show_all after realize. DECISIVE: empty window -> show_all ~44ms; add ONE gtk_label ->
show_all ~13.4s. So the slowness is rendering a SINGLE LABEL = the text/font path (pango + harfbuzz +
freetype + cairo software raster), pathologically slow in wasm.

This CORRECTS the prior "X-server starved / concurrent-guest scheduling" hypothesis -- the X server is
fast; the slowness is guest-internal TEXT rendering. XU5 Thunar (ThunarPathEntry text) + the notifyd
popup (summary/body text) hang because their first text render is this slow (notifyds synchronous Notify
blocks during it). PLATFORM-LAYER perf issue (constraint #5), NOT a TCB decision. Likely a one-time
font/glyph-cache setup. Next: pin which stage (fontconfig/FT_New_Face/harfbuzz/cairo) + a platform fix
(prebuilt fontconfig cache / faster freetype). Repros: popup-repro/xsync-bench/map-bench.c.

XU5 + notifyd unblock once text render is fast; XU7 ceiling is separate. XU0-XU4 DONE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ion cost is GTK CSS/style (one-time first widget)

pango-bench (direct pango, no X): cairo setup 0ms, create+set 220ms, SHAPE (fontconfig+FT+harfbuzz)
1284ms, rasterize 4ms; SECOND label 0ms (warm). So the font/glyph setup is ~1.3s one-time -- NOT the
13.4s. A GtkLabel is 13.4s vs raw pango 1.3s -> GTK adds ~12s = the GtkWidget CSS/style path (theme CSS
parse + style cascade + repeated style/font lookups), one-time on the first text widget.

So the construction hang = GTKs one-time CSS/style setup (~12s) + font setup (~1.3s); first text widget
pays it, rest instant (notifyds synchronous Notify blocks during it). Platform-layer perf (constraint
#5), not TCB, not the X server. Next: a gtk get_preferred_size micro-bench to confirm + narrow within
the CSS/style cascade -> platform fix. Repro: guest-xclient/pango-bench.c.

XU5 + notifyd unblock once the GTK first-widget cost is cut; XU7 ceiling separate. XU0-XU4 DONE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…ing fix = fpcast-emu overhead on GObject/CSS

8-label window -> show_all ~14.6s, basically same as 1 label (~13.4s) -> the ~12s GTK CSS/style cost is
ONE-TIME (theme CSS parse + first cascade); each extra widget ~0.15s. So multi-widget windows pay ~13s
once then render. But notifyds popup took >134s -> an EXTRA cost beyond the one-time CSS (its custom
window CSS + Pango markup + icon scaling).

Leading fix hypothesis: the ~12s is fpcast-emu overhead -- the fpcast (wasm-opt --fpcast-emu) emulates
function-pointer casts because GObject/GTK function pointers have mismatched wasm table signatures, and
GObjects type system + CSS machinery are extremely indirect-call-heavy (pango is 1.3s = far fewer
indirect calls). A faster fpcast path would be a toolchain-layer fix (constraint #5) helping all GTK
guests. Next: native GtkLabel bench to confirm ms-native vs 13s-wasm, then profile the hot path.

XU5/notifyd are slow not deadlocked; XU7 ceiling separate. XU0-XU4 DONE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…n.build:442); process-spawn item

Re-ran build-vte.sh: VTE 0.70.6 configures past every check (wchar.h, TIOCGWINSZ shim, libm, fribidi) but
HARD-ASSERTS at meson.build:442 "fork not found" -- wasi-libc has no fork() (no process duplication in
the wasm model). Constraint #5 forbids patching VTE, so the fix is platform-layer: declare fork() so the
compile check passes, then intercept the fork+exec PATTERN and map it to the EXISTING wasi-spawn bridge
(spawn a sandboxed shell guest via the wasi-pty seam). A deep fork/spawn-sequence intercept = the
process-spawn architecture decision. Does NOT unblock 100% (XU7 does). Parked behind that decision.

Updated HANDOFF + status with the precise terminal blocker. XU0-XU5 DONE; XU6 = 4 components render +
terminal blocked at VTE-fork; XU7 gated on sidecar threading + perf root. All remaining sign-off-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…k->posix_spawn+pty-seam shim, not a TCB gap

Reconsidered the terminal blocker. KEY: posix_spawn WORKS in the sandbox (posix_spawn_compat.h + wasi-spawn
bridge + syscall_coverage.c uses posix_spawnp w/ file_actions). So the spawn capability is already present
-- the terminal is a fork->posix_spawn emulation + pty-seam integration (platform layer, allowed by #5),
NOT a TCB decision. VTE 0.70 uses fork() directly + needs grantpt/posix_openpt/ptsname/unlockpt/tcgetattr.

Progress: added platform-layer compat decls (compat-include/{unistd,stdlib,string,termios}.h) -> VTE meson
now passes fork/grantpt/posix_openpt/ptsname. Next: tcgetattr needs struct termios (wasi-libc lacks it ->
define a full termios.h), then the IMPLEMENTATIONS: fork->deferred-posix_spawn shim, pty funcs + termios on
the wasi-pty seam, waitpid for the reaper. Deep but concrete multi-turn effort completing XU6 (not 100%).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…n_pty via __pty_open sync-RPC)

Replaced the FAULT stub with a real implementation modeled on the existing kernel-pipe path. 3 edits in the
WASMGUI workspace: (1) node_import_cache.rs pty_open import -> callSyncRpc("__pty_open") -> {masterFd,slaveFd}
range-encoded KERNEL_PIPE_FD_BASE+kernelFd (fd_read/write/poll route to the kernel pty discipline); (2)
execution.rs dispatch "__pty_open" => service_javascript_pty_open_sync_rpc; (3) the handler calls
kernel.open_pty(EXECUTION_DRIVER_NAME, process.kernel_pid) -> (master,slave,path). Bumped
NODE_IMPORT_CACHE_ASSET_VERSION 111->112.

In-scope (constraint #5): a kernel pty is kernel-virtual like the kernel pipe (used by brush/pty-shell), not
a host-fd expansion -- wiring existing kernel.open_pty to a FAULT-stub import. Rebuild + re-run in progress.
Next: shell spawn via proc_spawn with the pty slave as stdio. VTE already renders (02b). XU6; not 100% (XU7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…p (XfceTitledDialog type unregistered)

Major constraint-#4 win. The silent-trap class was "deep" only for lack of a symbolizer (KEEP_NAMES bloats big GTK guests
past the frame cap). KEY: the runtime prints trap frames as wasm-function[N], so an ADDRESS-based symbol map works without
running a named binary -- re-fpcast KEEP_NAMES to extract --symbolmap, then strip ONLY the name section (indices preserved)
to get a runnable index-matched binary, re-render, map the frames. Keyboard stack resolved to g_log_writer_default->abort =
a GLib fatal log: "Failed to load the UI file: Invalid object type XfceTitledDialog". Root cause: the 3 rendering dialogs
force-register the type (if xfce_titled_dialog_get_type()==0) before their builder; the keyboard does not, and the static
no-dlopen build does not auto-register it. Fix shape (constraint #5): a build-linked constructor that ensures the type,
keyboard source untouched. Symbolizer is reusable for the whole silent class (grid, theme enum). proof-kbd.symbolmap saved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…symbolizer->root-cause->platform-fix

The full constraint-#4 chain, end to end:
- Built an address-based trap symbolizer (the runtime prints wasm-function[N] frames): re-fpcast KEEP_NAMES to extract
  --symbolmap, strip ONLY the name section (indices preserved) for an aligned runnable binary, re-render, map frames.
- Root-caused the keyboard silent trap to a GLib fatal log: Invalid object type XfceTitledDialog (NOT XKB as long guessed,
  NOT tree-view as a misaligned first pass suggested). The 3 rendering dialogs force-register the type after
  gtk_init_with_args; the keyboard does not, and the static no-dlopen build does not auto-register it.
- Fixed in the platform layer (constraint #5): toolchain/xfce-type-ensure.c wraps gtk_init_with_args (-Wl,--wrap) to
  ensure the type right after the real init -- keyboard SOURCE unmodified. (A pre-main constructor ran too early:
  g_type_register_static asserts before GObject init.)
- Result: keyboard-settings renders fully (Behavior/App-Shortcuts/Layout tabs, repeat sliders, Test area), exit 0.

The symbolizer is reusable for the remaining silent traps (settings grid, theme enumeration). Records updated
(progress.html, XUBUNTU-SPEC, M8-STATUS-LOG); proof-kbd.symbolmap + xu6-keyboard.png saved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…tory fixtures, not a deep code path

Read the population code instead of guessing again. xfce-settings-manager-dialog.c:1251-1253:
  directory = garcon_menu_get_directory(li->data); if (directory == NULL) continue;
-- the manager skips any top-level sub-menu without a .directory file. My 4 prior fixture variants (T37-T42) had
sub-menus but no .directory files, so every category was skipped -> empty grid. Fix is a pure fixture (constraint #5):
real menu structure (Personal/Hardware/System sub-menus with <Directory> refs + X-XFCE-*Settings includes) + absolute
AppDir/DirectoryDir + the 3 created .directory files + real .desktop. Grid now renders POPULATED -- Hardware: Display,
Keyboard, Mouse and Touchpad; Personal: Appearance.

Honest: I wrongly concluded "deep code path" across ~6 commits by doing blind fixture variations instead of reading the
3-line skip condition. Third silent item this session (after keyboard XfceTitledDialog, theme xfconf-timeout) that
dissolved on observation. Minor follow-ups: item icons are placeholders (icon theme), accessibility .desktop not globbed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NathanFlurry added a commit that referenced this pull request Jul 1, 2026
…est scope correction

The thunar-sbr renamer plugin now loads all-wasm: the bulk-rename dialog shows live rule controls
(Insert/Overwrite, Name only, Insert/Text/At-position) instead of the "No renamer modules" infobar.
Proof: ~/tmp/gui-progress/2026-06-26T17/xu3-thunar-sbr-static.png.

Mechanism (toolchain/, components untouched -- constraint #5): keep-shim constructor defeats wasm-ld
--gc-sections; --export the entry points; --wrap=g_module_open returns a non-NULL sentinel handle (the runtime
has no self/main-module handle so g_module_open(NULL)=0); --wrap=g_module_symbol resolves the 3 thunarx entry
points by name; a marker .so triggers the factory dir-scan. The real bug was found by adding SHIMLOG logging to
the wraps (constraint #4) -- correcting an earlier WRONG "libtool strips the flags" root cause.

HONEST CORRECTION: I initially framed this as the "XU3 core blocker" -- wrong. XU3 (panel+plugins) was already
DONE 2026-06-25 via the EXISTING toolchain/gmodule-shim.c (named-handle + generated table); the cron seed is
stale. This is an XU5 Thunar enhancement (different plugin family). My gmodule-static-shim.c (sentinel) +
thunar-sbr-symwrap.c (hardcoded names) DUPLICATE that existing shim inferiorly -- it already returns a
name-carrying handle (never hits g_module_open(NULL)=0, the thing I spent ~12 fires re-discovering) and resolves
via a table. FOLLOW-UP (GMODULE-STATIC-PLUGINS.md): consolidate thunar onto the existing shim, drop the
duplicates, re-verify panel+thunar. Lesson: observe existing solutions + re-read the spec before a long build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant