Commit graph

130 commits

Author SHA1 Message Date
2a9f034815 feat(servo-shell): add servo-embed feature gate and embedder contract
Adds src/embedder.rs with the full Servo embedding implementation behind
#[cfg(feature = " servo-embed)]:
2026-03-11 14:52:13 +01:00
6b428e5a47 feat(appd): add compositor IPC client; send AppSurfaceCreated/Destroyed on session lifecycle
Adds crates/weft-appd/src/compositor_client.rs: async Tokio client that connects to the
compositor's Unix socket (/weft/compositor.sock or WEFT_COMPOSITOR_SOCKET),
retrying every 2s on failure and 500ms on write error. Incoming CompositorToAppd frames are
decoded and logged (SurfaceReady, ClientDisconnected).

Wires compositor_tx into SessionRegistry. The supervise task now sends AppSurfaceCreated
(with child PID) immediately after process spawn, and AppSurfaceDestroyed when the process
exits. All three existing supervisor tests updated to pass None for compositor_tx.
2026-03-11 14:40:55 +01:00
69d29ee3a8 feat(protocol): add wl_surface arg to create_window in weft-shell-unstable-v1
Adds an optional (allow-null) wl_surface argument to zweft_shell_manager_v1::create_window.
Shell-owned windows pass null; app-backed windows pass the backing wl_surface so the
compositor can correlate the surface with a session_id from weft-appd. Updates
WeftShellWindowData to store the surface, and updates the CreateWindow handler in state.rs
and the two protocol unit tests.
2026-03-11 14:33:17 +01:00
ca2cc38d4d feat(compositor): add appd IPC server (Unix socket, length-prefixed MessagePack framing)
Adds weft-compositor/src/appd_ipc.rs: WeftAppdIpc state, setup() registers a calloop
UnixListener source. Accepted connections are registered as edge-triggered read sources.
Incoming AppdToCompositor frames are decoded and dispatched; AppSurfaceCreated records
pid->session mapping in pending_pids for later wl_surface association. Wires into both
the DRM and Winit backends. Socket path: /weft/compositor.sock or
WEFT_COMPOSITOR_SOCKET override.
2026-03-11 14:29:22 +01:00
a75c8946fc feat(ipc-types): add weft-ipc-types crate with compositor-appd message types and frame framing 2026-03-11 14:17:48 +01:00
5d7c0bdf79 feat(appd): add version field to AppInfo; surface it in launcher tile tooltip
WappPackage and AppInfo both gain a version field. scan_installed_apps()
reads it from wapp.toml and includes it in InstalledApps responses.
system-ui.html shows it in the title tooltip as 'com.example.app v1.0.0'.
All roundtrip and integration tests updated.
2026-03-11 13:15:09 +01:00
7a2014027a test(pack): add missing-wasm and missing-ui-entry check_package tests 2026-03-11 13:07:45 +01:00
afffe29090 docs: update README to accurately reflect current implementation state 2026-03-11 12:59:24 +01:00
c88c948575 fix(appd): exclude Stopped sessions from running_sessions; add regression test
running_sessions() was returning all sessions regardless of state.
Stopped sessions would reappear in the taskbar on reconnect since
QUERY_RUNNING is sent on every WebSocket open. The filter now matches
the UI expectation: only Starting and Running sessions are returned.
2026-03-11 12:53:07 +01:00
de8939a72e test(appd): add ws_port default and override tests 2026-03-11 12:46:15 +01:00
bded9455f5 test(appd): add appd_socket_path tests; run appd tests single-threaded
Two new tests cover appd_socket_path():
- appd_socket_path_uses_override_env: WEFT_APPD_SOCKET takes precedence
- appd_socket_path_errors_without_xdg_and_no_override: returns error when
  both WEFT_APPD_SOCKET and XDG_RUNTIME_DIR are unset

wsl-test.sh: add --test-threads=1 for weft-appd to prevent WEFT_RUNTIME_BIN
races between the supervisor integration tests.
2026-03-11 12:40:05 +01:00
71597580ba fix(appd): abort TerminateApp during startup phase promptly
Before this fix, TerminateApp sent while a process was waiting for its
READY signal was not acted on until the 30-second timeout fired.
abort_rx is now included in the tokio::select! that wraps wait_for_ready,
so the child is killed and AppState::Stopped broadcast as soon as the
abort is received, regardless of where in the startup sequence it fires.

test: supervisor_abort_during_startup_broadcasts_stopped
2026-03-11 12:30:21 +01:00
488900a5db test(appd): add supervisor spawn-failure test; verifies Stopped broadcast when binary is missing 2026-03-11 12:24:03 +01:00
e80502b184 test(runtime): add resolve_package tests for found and not-found cases 2026-03-11 12:19:17 +01:00
dbcc9965e9 test(appd): add roundtrip tests for TerminateApp, Error, and AppState IPC variants 2026-03-11 12:12:20 +01:00
60256138a9 fix(shell): log IPC ERROR responses to console 2026-03-11 12:06:56 +01:00
d2fa616b00 feat(shell): refresh installed apps list on every launcher open 2026-03-11 12:02:11 +01:00
65a10723b3 fix(shell): clear stale taskbar entries on WebSocket reconnect
When weft-appd restarts, session IDs reset to 0. Without clearing,
RUNNING_APPS repopulation after reconnect would coexist with stale
entries from the previous session. Removing all weft-taskbar-app
elements on the open event ensures the taskbar reflects only the
current appd session registry.
2026-03-11 12:01:33 +01:00
5e7675c043 test(pack): add list_installed_roots tests; run pack tests single-threaded
Two new tests mirror the weft-runtime package_store_roots tests:
- list_installed_roots_uses_weft_app_store_when_set
- list_installed_roots_includes_system_path

wsl-test.sh: add --test-threads=1 for weft-pack to prevent
WEFT_APP_STORE env var races between install, uninstall, and the
new list_roots tests.
2026-03-11 12:00:44 +01:00
cab3a4a956 feat(pack): add list subcommand to show installed packages
list_installed_roots() searches WEFT_APP_STORE, then
~/.local/share/weft/apps, then /usr/share/weft/apps (same
priority order as weft-runtime and weft-appd). list_installed()
deduplicates by app_id (first root wins), sorts alphabetically within
each root, and prints id/name/version per line. Prints 'no packages
installed' when the store is empty or absent.
2026-03-11 11:57:08 +01:00
826f144d9d test(appd): add roundtrip tests for AppInfo and InstalledApps IPC variants 2026-03-11 11:51:04 +01:00
abdefa3388 test(appd): add QueryAppState dispatch test for unknown session returning NotFound 2026-03-11 11:48:22 +01:00
7a07e46c55 fix(appd): broadcast AppState::Stopped on runtime spawn failure
If WEFT_RUNTIME_BIN is set but the binary cannot be spawned (missing,
not executable, etc.), supervise() now transitions the session to
Stopped and broadcasts AppState::Stopped instead of returning an error
that left the session permanently stuck in Starting.
2026-03-11 11:45:17 +01:00
71c6741fae feat(shell): add terminate button to taskbar app entries
ensureTaskbarEntry() now creates a label span and an inline x close
button. The x button is hidden until the taskbar entry is hovered, then
sends TERMINATE_APP via WebSocket on click. CSS for weft-taskbar-app
and .task-close added (flex layout, hover reveal, red tint on x hover).
2026-03-11 11:40:25 +01:00
a3a826058e feat(shell): close launcher on Escape key and click-outside 2026-03-11 11:39:21 +01:00
e5ec05ce2c test(appd): assert AppState::Stopped broadcast in supervisor integration test
supervisor_transitions_through_ready_to_stopped now checks both
broadcast messages: AppReady (on READY signal) and AppState::Stopped
(on process exit), covering the path added in 3315b15.
2026-03-11 11:38:33 +01:00
e83be20798 fix(appd): make appd.wsport write non-fatal when XDG_RUNTIME_DIR is unset
write_ws_port failure is now logged as a warning rather than propagating
an error that would crash the service. Error context strings are added
to create_dir_all and write failures so the warning is actionable.
2026-03-11 11:36:47 +01:00
eef9ecc24a test(appd): add QueryInstalledApps dispatch test; fix weft-runtime test race
main.rs: add dispatch_query_installed_returns_installed_apps to verify
the QueryInstalledApps arm returns Response::InstalledApps.

wsl-test.sh: run weft-runtime tests with --test-threads=1 to prevent
the WEFT_APP_STORE env var race between package_store_roots_includes_
system_path and package_store_roots_uses_weft_app_store_when_set.
2026-03-11 11:32:26 +01:00
0bcb6b1bf6 fix(appd): signal all supervisors to abort on clean shutdown
SessionRegistry::shutdown_all() clears abort_senders, dropping all
oneshot senders. Each supervised process's abort_rx fires, causing
supervise() to kill the child. A 200ms yield after shutdown_all gives
the tokio runtime time to schedule the abort handling before the
process exits and the socket file is removed.
2026-03-11 11:28:29 +01:00
e1c15ea463 feat(appd): add QueryInstalledApps IPC request; wire launcher in system UI 2026-03-11 11:23:46 +01:00
d6de84b4c7 fix(appd): broadcast AppState::Stopped on READY timeout
runtime.rs: the READY-timeout early-return path now broadcasts
AppState::Stopped before returning so WebSocket clients see the
session disappear when a module fails to signal readiness within 30s.
2026-03-11 11:19:09 +01:00
3315b158db feat(appd): broadcast AppState::Stopped when supervised process exits
runtime.rs: after the child exits (natural exit or abort), supervise()
now broadcasts AppState { session_id, state: Stopped } in the same
lock scope as set_state. WebSocket clients receive the notification
without needing to poll QueryAppState or call TerminateApp.
2026-03-11 11:16:28 +01:00
68e1f82ca7 fix(appd): drain module stdout after READY signal to prevent pipe stall
wait_for_ready() now returns the BufReader<ChildStdout> with the READY
line already consumed. supervise() spawns drain_stdout() on that reader
so any subsequent module output is forwarded to the trace log and the
pipe buffer never fills up.

Without this, a long-running Wasm module that writes to stdout after
printing READY would eventually block waiting on a full pipe.
2026-03-11 11:14:18 +01:00
a409b954ab fix(appd): handle SIGTERM for clean shutdown under systemd
run() now registers a SIGTERM handler (unix-only, cfg-gated) alongside
the existing SIGINT handler. Both break the accept loop and allow the
Unix socket to be removed before exit.

On non-Unix targets the SIGTERM arm uses std::future::pending so the
select! shape is unchanged at the type level.
2026-03-11 11:06:01 +01:00
01a4969883 fix(appd): write actual bound WebSocket port to appd.wsport file
ws_listener.local_addr().port() is used instead of the configured
ws_port value. This is correct when WEFT_APPD_WS_PORT=0 lets the OS
assign an ephemeral port; the file reflects the real listening port.
2026-03-11 11:00:13 +01:00
5bd4857a67 fix(servo-shell): show app name in APP_READY notification
system-ui.html: APP_READY handler extracts the last component of
msg.app_id (e.g. 'notes' from 'com.example.notes') and shows
'<name> is ready'. Falls back to the session_id if app_id is absent.
2026-03-11 10:51:16 +01:00
dbe44bd0e0 feat(appd): include app_id in AppReady broadcast
ipc.rs: AppReady { session_id, app_id: String }.

runtime.rs: supervise() passes app_id (already in scope as parameter)
when building the AppReady broadcast message.

main.rs: supervisor integration test updated to use .. to ignore
app_id in the AppReady pattern match.
2026-03-11 10:50:41 +01:00
35db7c2ccc fix(servo-shell): pass app_id from LAUNCH_ACK to taskbar entry
system-ui.html: LAUNCH_ACK handler now passes msg.app_id to
ensureTaskbarEntry so the taskbar label shows the app name
immediately on launch, matching the behaviour of RUNNING_APPS.
2026-03-11 10:46:54 +01:00
b5bf2e538a feat(appd): include app_id in LaunchAck response
ipc.rs: LaunchAck gains app_id: String field so callers receive the
app identifier alongside the session handle in a single response.

main.rs: dispatch::LaunchApp constructs LaunchAck { session_id, app_id }
using the app_id that was already in scope.
Tests updated: dispatch_launch_returns_ack now asserts app_id value;
dispatch_terminate_known_returns_stopped and
dispatch_query_app_state_returns_starting use .. to ignore app_id.
2026-03-11 10:46:28 +01:00
fdeb440766 feat(appd): include app_id in RunningApps response; update system UI
ipc.rs:
- Add SessionInfo { session_id: u64, app_id: String } struct.
- Change RunningApps { session_ids: Vec<u64> } to
  RunningApps { sessions: Vec<SessionInfo> } so callers can display
  meaningful app names without a follow-up QueryAppState round-trip.
- Add session_info_roundtrip test.

main.rs:
- Add SessionEntry { app_id: String, state: AppStateKind } to store
  app_id alongside state in SessionRegistry.
- launch() stores app_id in the entry.
- running_sessions() replaces running_ids(); returns Vec<SessionInfo>.
- state() reads from SessionEntry.state.
- set_state() writes to SessionEntry.state.
- QueryRunning dispatch uses running_sessions().
- Test registry_running_ids_reflects_live_sessions renamed to
  registry_running_sessions_reflects_live_sessions and updated to
  assert both session_id and app_id fields.
- dispatch_query_running test asserts app_id values are present.

system-ui.html:
- RUNNING_APPS handler uses msg.sessions[].{session_id,app_id}.
- ensureTaskbarEntry(sessionId, appId): shows the last component of the
  reverse-domain app ID as the taskbar label; sets data-app-id attribute;
  tooltip shows full app ID and session number.
- LAUNCH_ACK handler passes null for appId (session ID only available
  at launch time; app_id arrives in RUNNING_APPS on reconnect).
2026-03-11 10:42:40 +01:00
d6ede23183 feat(servo-shell): wire appd WebSocket port discovery at startup
appd_ws_port() -> u16:
- Checks WEFT_APPD_WS_PORT env var first.
- Falls back to reading XDG_RUNTIME_DIR/weft/appd.wsport.
- Falls back to hardcoded default 7410.

run() now calls appd_ws_port() and passes the result to embed_servo.
embed_servo signature updated to accept ws_port: u16.
When the Servo embedder is implemented, it injects the port as
window.WEFT_APPD_WS_PORT before loading the system UI HTML.
2026-03-11 10:31:33 +01:00
6d88104f28 feat(runtime): add wasmtime-runtime feature gate for real Wasm execution
Cargo.toml:
- New feature: wasmtime-runtime = [dep:wasmtime, dep:wasmtime-wasi]
- Default is off so the normal build remains lightweight.
- wasmtime 30 and wasmtime-wasi 30 added as optional dependencies.

src/main.rs:
- run_module(wasm_path) replaces the inline stub.
- cfg(not(feature = wasmtime-runtime)): prints READY and returns.
  Preserves all existing test and development behaviour unchanged.
- cfg(feature = wasmtime-runtime): creates a Wasmtime Engine + Module,
  builds a WASI linker with inherited stdout/stderr, prints READY, then
  instantiates the module and calls _start.
  READY is printed before _start so weft-appd can record the session as
  Running before the app enters its event loop.

The production service binary is built with:
  cargo build -p weft-runtime --release --features wasmtime-runtime
2026-03-11 10:26:41 +01:00
5cff1f4412 feat(pack): validate Wasm module magic bytes in check
check_package now reads the first 4 bytes of runtime.module and rejects
files that do not begin with the Wasm magic number (0x00 0x61 0x73 0x6D).
An unreadable or too-short file is treated as invalid.

is_wasm_module(path): opens the file, reads 4 bytes, compares to MAGIC.

Test added: check_package_bad_wasm_magic - writes NOT_WASM to app.wasm,
asserts check fails with a message containing bad magic bytes.
2026-03-11 10:21:43 +01:00
b2bb76125f feat(pack): add uninstall subcommand and full install/uninstall tests
weft-pack:
- uninstall <app_id>: validates app ID, resolves store root, removes
  the installed package directory. Fails with an error if the package
  is not present or the app ID is malformed.
- Extracted install_package_to(dir, root) and
  uninstall_package_from(app_id, root) inner functions so tests can
  drive them directly without touching process env vars (avoids parallel
  test env-var races).
- install_package / uninstall_package remain the CLI-facing wrappers
  that call resolve_install_root().

Tests added (2):
- install_package_copies_to_store: writes a valid temp package, calls
  install_package_to, verifies all files are present, confirms a second
  install fails.
- uninstall_package_removes_directory: installs then uninstalls,
  verifies directory is removed, confirms a second uninstall fails.
Both tests use process-ID-derived paths to avoid cross-test collisions.
2026-03-11 09:54:39 +01:00
265868bf67 feat(pack): add install subcommand; clean up servo-shell stub comment
weft-pack:
- install <dir>: validates the package (runs check), resolves the user
  app store root (WEFT_APP_STORE > ~/.local/share/weft/apps), copies
  the package directory to <root>/<app_id>/. Fails if the destination
  already exists.
- resolve_install_root(): replaces the unused _resolve_store_roots;
  returns a single writable root rather than a search list.
- copy_dir(): recursive directory copy using std::fs only; no new deps.
- Updated usage text to include all three subcommands.

weft-servo-shell: removed stale implementation-note comment from
embed_servo stub.
2026-03-11 09:45:31 +01:00
ab38b96a7f docs: document Wasmtime integration plan for weft-runtime
Specifies the concrete steps needed to replace the current READY stub
with real Wasm module execution:
- Optional cargo feature gate (wasmtime-runtime) to keep default builds
  fast; production service unit uses the feature-enabled binary.
- Engine + Module setup from wasm file path.
- WasiCtxBuilder with inherited stdout/stderr for READY signal passthrough.
- READY signal timing: Option A (print before _start) vs Option B
  (explicit weft_ready() export); Option A is the initial implementation.
- Entry point: _start (standard wasm32-wasi target output).
- Error handling: module load failures, trap handling, missing _start.
- Explicit non-scope: host imports beyond WASI, memory caps, component
  model, Wasm threads, fuel metering.
- Prerequisite: confirmed wasm32-wasi app SDK before integration.
2026-03-11 09:42:09 +01:00
ffae164747 feat(pack): add weft-pack package validator tool
New crate: weft-pack — command-line tool for validating WEFT application
package directories against the app-package-format spec.

src/main.rs:
- check <dir>: loads wapp.toml, validates app ID format, verifies
  package.name is non-empty and <=64 chars, confirms runtime.module and
  ui.entry files exist. Prints 'OK' on success or the list of errors.
- info <dir>: prints all manifest fields to stdout.
- load_manifest(): reads and parses wapp.toml with toml crate.
- is_valid_app_id(): enforces reverse-domain convention (>=3 components,
  each starting with a lowercase letter, digits allowed, no hyphens or
  uppercase).

Tests (5):
- app_id_valid: accepts well-formed reverse-domain IDs.
- app_id_invalid: rejects two-component, uppercase, hyphen, empty IDs.
- check_package_missing_manifest: error when wapp.toml is absent.
- check_package_valid: full happy-path with real temp files.
- check_package_invalid_app_id: error on a hyphenated app ID.

New deps: toml 0.8, serde 1 (derive).
Added weft-pack to workspace Cargo.toml; wsl-test.sh extended.
2026-03-11 09:40:34 +01:00
1e4ced9a39 feat(appd): implement TerminateApp process signaling via abort channel
SessionRegistry now tracks a oneshot abort sender per active session:
- abort_senders: HashMap<u64, oneshot::Sender<()>> field added.
- register_abort(session_id): creates the channel, stores the sender,
  returns the receiver to the supervise task.
- terminate(): removes the session state AND drops the abort sender,
  closing the channel and triggering the receiver in supervise.

runtime::supervise() now accepts abort_rx: oneshot::Receiver<()>:
- After the READY signal is received, the process-wait loop uses
  tokio::select! on child.wait() vs abort_rx.
- On abort: logs intent, calls child.kill(), then sets state Stopped.
- On natural exit: logs exit status, sets state Stopped.

dispatch::LaunchApp: calls register_abort immediately after launch,
passes the receiver to the spawned supervise task.

Integration test updated to pass the abort receiver.
2026-03-11 09:37:09 +01:00
0cb74adc3e infra: wire WEFT_RUNTIME_BIN into weft-appd service unit
Sets WEFT_RUNTIME_BIN to the weft-runtime binary path so that
weft-appd's runtime supervisor can spawn app processes without
additional configuration.
2026-03-11 09:28:37 +01:00
f38f2eef76 feat(runtime): add weft-runtime crate skeleton
New crate: weft-runtime — the child process spawned by weft-appd to
execute WEFT application packages.

src/main.rs:
- Parses CLI arguments: <app_id> <session_id> (as per the supervisor
  contract in runtime.rs).
- resolve_package(): searches user store
  (~/.local/share/weft/apps/<app_id>) then system store
  (/usr/share/weft/apps/<app_id>) for a wapp.toml manifest. Overridden
  by WEFT_APP_STORE env var.
- Verifies app.wasm exists in the resolved package directory.
- Stubs Wasmtime execution with a TODO comment; prints 'READY' to
  stdout and exits cleanly so weft-appd's supervisor can complete the
  session lifecycle during development and integration testing.

Tests (2):
- package_store_roots_includes_system_path: system store path present.
- package_store_roots_uses_weft_app_store_when_set: WEFT_APP_STORE
  override replaces default search list.

Also:
- Added weft-runtime to workspace Cargo.toml members.
- wsl-test.sh: added cargo test -p weft-runtime.
2026-03-11 09:27:30 +01:00