From 7cec74e18b2c719c63de781dd62756e6b4b15f7e Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 12 Oct 2026 22:17:15 +0200 Subject: [PATCH] fix(wasm): finalize config without desktop shell lookup The Node smoke test expected shaper info logs after the default logger became errors-only. Its failure also exposed config finalization calling an unsupported passwd lookup on wasm and returning Unexpected before normalization completed. Skip shell defaults on wasm, matching upstream Zig. Assert successful config finalization and window-width clamping in the Node test. Check shaper output directly while verifying that info logs stay suppressed. Document these behaviors in the wasm port notes. Test Plan: - cargo test -p ghostty-wasm --test node (passed) - cargo test -p ghostty-config --lib (291 passed) - cargo clippy --target wasm32-unknown-unknown -p ghostty-wasm -- -D warnings - cargo clippy; cargo clippy --benches; cargo clippy --tests - cargo +nightly fmt, followed by the native Clippy checks again - git diff --check Related-commit: 4fb9faa6675883ccf8609ca9e26ca219e7bcef60 --- crates/ghostty-config/src/config.rs | 4 +++- crates/ghostty-wasm/tests/node_test.js | 12 +++++++++--- docs/rewrite/notes/wasm-W1.md | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/ghostty-config/src/config.rs b/crates/ghostty-config/src/config.rs index 3cefeda..7bf42c5 100644 --- a/crates/ghostty-config/src/config.rs +++ b/crates/ghostty-config/src/config.rs @@ -4594,7 +4594,9 @@ impl Config { // If we are missing either a command or home directory, we need // to look up defaults which is kind of expensive. We only do this // on desktop (and not in tests, like the Zig `builtin.is_test`). - if !cfg!(test) && (self.command.is_none() || wd == WorkingDirectory::Home) { + if !cfg!(any(test, target_arch = "wasm32")) + && (self.command.is_none() || wd == WorkingDirectory::Home) + { self.lookup_shell_defaults(probable_cli, &mut wd)?; } wd.finalize(); diff --git a/crates/ghostty-wasm/tests/node_test.js b/crates/ghostty-wasm/tests/node_test.js index eee29b9..275b0c6 100644 --- a/crates/ghostty-wasm/tests/node_test.js +++ b/crates/ghostty-wasm/tests/node_test.js @@ -127,11 +127,12 @@ const liveValues = (zigjs) => zigjs.values.slice(7).filter((v) => v !== null).le { const cfg = x.config_new(); assert.ok(cfg); - const s = host.writeString('font-size = 18\nwindow-padding-x = 7\nnot-a-key = 1\n'); + const s = host.writeString('font-size = 18\nwindow-padding-x = 7\nwindow-width = 1\nnot-a-key = 1\n'); x.config_load_string(cfg, s.ptr, s.len); s.free(); assert.strictEqual(x.config_diagnostics_count(cfg), 1); x.config_finalize(cfg); + assert.deepStrictEqual(logs, [], 'config finalization must succeed without desktop shell lookup'); const cap = 1 << 16; const out = x.wasm_alloc(cap); const n = x.config_format(cfg, out, cap); @@ -140,6 +141,8 @@ const liveValues = (zigjs) => zigjs.values.slice(7).filter((v) => v !== null).le x.wasm_free(out, cap); assert.match(text, /font-size = 18/); assert.match(text, /window-padding-x = 7/); + // Width clamping happens after shell defaults: finalization must reach it. + assert.match(text, /window-width = 10\n/); x.config_free(cfg); } @@ -202,10 +205,13 @@ const liveValues = (zigjs) => zigjs.values.slice(7).filter((v) => v !== null).le x.glyph_free(gg); assert.strictEqual(canvases[0].lastText.t, 'é'); - // shaper_test prints through a terminal and logs the shaped runs + // shaper_test prints through a terminal and shapes its rows. The default + // errors-only logger suppresses the info records for those shaped runs. const st = host.writeString('hello é'); x.shaper_test(sh, face, st.ptr, st.len); - assert.ok(logs.some((l) => l.startsWith('info(')), logs.join('\n')); + assert.deepStrictEqual(logs, [], 'shaper info logs must be suppressed'); + assert.ok(x.shaper_shape(sh, face, outp, 16) > 0, 'shaper_test prepared a run'); + assert.strictEqual(new Uint32Array(instance.exports.memory.buffer, outp, 2)[1], 0x68); // ---- deferred face ---- const d = x.deferred_face_new(f.ptr, f.len, 0); diff --git a/docs/rewrite/notes/wasm-W1.md b/docs/rewrite/notes/wasm-W1.md index be040a2..0763d14 100644 --- a/docs/rewrite/notes/wasm-W1.md +++ b/docs/rewrite/notes/wasm-W1.md @@ -84,6 +84,9 @@ Differences/extras: `region_free`/`glyph_free` (Zig: "global free"), `config_diagnostics_count`, `ghostty_wasm_init`, `wasm_alloc/free`. `shaper_test` takes a `Face` (Zig takes a stale `GroupCache`) and prints into a `ghostty-terminal` Terminal. +Config finalization skips desktop shell and home-directory lookup on wasm, +matching Zig, but still applies normalization such as minimum window dimensions. +The wasm logger forwards errors only, so `shaper_test` info records are suppressed. ## Deviations from the (stale) Zig web_canvas code - Zig refers to `Atlas.Format.rgba` and a 7-field `Metrics` that no longer @@ -104,6 +107,8 @@ Differences/extras: `region_free`/`glyph_free` (Zig: "global free"), `target/wasm-test`) and runs `tests/node_test.js` under node with a fake canvas: config parse/finalize/format, atlas, face metrics/glyph_index/ render, emoji presentation, shaper clustering, deferred face, debug canvas. + It checks that config finalization completes without errors and clamps window + dimensions, and that `shaper_test` prepares a run without emitting info logs. Skipped if node or the wasm32 target is missing. - Not ported: Zig tests in Atlas.Wasm (`toHostOwned`, stale).