Skip to content

[Web] Publish the browser host runtime as @kandelo/web - #1242

Draft
mho22 wants to merge 3 commits into
mainfrom
explore-npm-packaging
Draft

[Web] Publish the browser host runtime as @kandelo/web#1242
mho22 wants to merge 3 commits into
mainfrom
explore-npm-packaging

Conversation

@mho22

@mho22 mho22 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Why

A downstream browser project that embeds the Kandelo kernel today has to vendor host/src or carry a git submodule. There is no supported way to depend on the browser host runtime as an ordinary npm dependency, so a separate browser IDE cannot pick up kernel, host, or ABI updates through normal package management.

This change adds web-libs/kandelo-web, an npm package that ships the browser host runtime as code-only artifacts. The package ships BrowserKernel, the VFS backends and image helpers, and a binaries loader. It ships no Wasm: the loader fetches ABI-verified kernel/rootfs/program binaries from a Kandelo binaries release at runtime, and every ABI constant derives from the generated host/src/generated/abi.ts, so a kernel ABI bump cannot leave the package stale.

What changed

The branch is three commits.

[Host] Decouple BrowserKernel from Vite-only worker asset imports. The two Vite-only ?worker&url worker-entry imports move from browser-kernel-host.ts into the new browser-kernel-assets.ts. The package build (tsdown/rolldown) swaps that module and the lazy default-artifacts module for package-local replacements; the repository keeps consuming them through Vite unchanged. The BrowserKernelAssets interface lives in browser-kernel-host.ts so type graphs never load the Vite-coupled module. BrowserKernel gains opt-in exposeHostFs: the kernel worker reports the VFS SharedArrayBuffer at boot and BrowserKernel.hostFs gives the main thread a synchronous filesystem view over the same bytes; it stays off by default and the reference is released in destroy(), preserving the WebKit image-switch reclaim behavior. BrowserKernel.spawnFromVfs now defaults stdin to an immediate EOF for non-PTY spawns, matching boot(), spawn(), and NodeKernelHost.spawnProgram — without the default, a VFS-spawned program that reads fd 0 (php does at startup) retried readv(0) forever in the browser.

[Web] Publish the browser host runtime as @kandelo/web. The package build emits each worker entry as a single self-contained file with all dependencies bundled, because consumers copy them as new URL(...) assets where bare imports cannot resolve; deps.onlyImport fails the build if any import other than the lazy Node-only import("net") survives. dist invariants are enforced by web-libs/kandelo-web/test/dist-contract.test.ts, which builds with the package's own tsdown devDependency, so ci-run-test-suite.sh installs the package's node modules for the vitest suite and the suite-runner groups-test fixture provisions the directory. The package loader resolves a relative baseUrl (the same-origin proxy pattern for GitHub's CORS-less release CDN) against the page URL before deriving archive URLs. The root package.json gains pack:web; that manifest is a declared build input of the VFS-shaped packages, so packages/registry/program-packages.json is regenerated.

[Demos] Consume BrowserKernel through @kandelo/web. apps/browser-demos imports BrowserKernel from @kandelo/web through a repository alias in browserRepositoryAliases(), dogfooding the package entry from source.

Validation

  • The full CI matrix is green on this tree: every test suite (vitest shards, libc, POSIX, Sortix, browser Playwright, cargo-kernel, fork-instrument), preflight, test-gate-validation, the seven VFS-package matrix rebuilds under their new cache keys, and the exact-shell proofs.
  • browser-kernel, dist-contract, and fetch-binaries tests pass locally (47/47); the suite-runner groups test passes.
  • The examples/scratch-consumer acceptance harness prints ACCEPTANCE PASSED in cross-origin-isolated headless Chromium: it fetches the binaries-abi-v42 release, boots the kernel, runs /usr/bin/php -v, and round-trips a file through main-thread hostFs.
  • Cross-agent SAB growth observed: a main-thread hostFs write grew the VFS SharedArrayBuffer in place 49 MB -> 154 MB and a kernel-side php process read correct bytes through the grown region.
  • The git-test demo page boots the kernel and runs git.wasm through the packaged import in the Vite dev server.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Phase B-1 matrix build status — pr-1242-staging-run-31606631037-attempt-1

ABI v42. 0 built, 75 failed, 75 total.

Package Arch Status Sha
icu wasm32 failed
libcurl wasm32 failed
libcxx wasm32 failed
libcxx wasm64 failed
libiconv wasm32 failed
libpng wasm32 failed
libxml2 wasm32 failed
libzip wasm32 failed
openssl wasm32 failed
openssl wasm64 failed
sqlite wasm32 failed
sqlite wasm64 failed
zlib wasm32 failed
zlib wasm64 failed
bc wasm32 failed
bzip2 wasm32 failed
coreutils wasm32 failed
cpython wasm32 failed
curl wasm32 failed
dash wasm32 failed
diffutils wasm32 failed
dinit wasm32 failed
erlang wasm32 failed
fbdoom wasm32 failed
file wasm32 failed
findutils wasm32 failed
gawk wasm32 failed
git wasm32 failed
grep wasm32 failed
gzip wasm32 failed
homebrew-bootstrap wasm32 failed
kandelo-sdk wasm32 failed
kernel wasm32 failed
less wasm32 failed
lsof wasm32 failed
m4 wasm32 failed
make wasm32 failed
mariadb wasm32 failed
mariadb wasm64 failed
modeset wasm32 failed
msmtpd wasm32 failed
nano wasm32 failed
ncurses wasm32 failed
netcat wasm32 failed
nginx wasm32 failed
php wasm32 failed
posix-utils-lite wasm32 failed
ruby wasm32 failed
sed wasm32 failed
shell wasm32 failed
spidermonkey wasm32 failed
tar wasm32 failed
unzip wasm32 failed
userspace wasm32 failed
vim wasm32 failed
wget wasm32 failed
xz wasm32 failed
zip wasm32 failed
zstd wasm32 failed
bash wasm32 failed
lamp wasm32 failed
mariadb-test wasm32 failed
mariadb-vfs wasm32 failed
mariadb-vfs wasm64 failed
nethack wasm32 failed
nginx-php-vfs wasm32 failed
nginx-vfs wasm32 failed
node wasm32 failed
redis-vfs wasm32 failed
spidermonkey-node wasm32 failed
vim-browser-bundle wasm32 failed
wordpress wasm32 failed
nethack-browser-bundle wasm32 failed
node-vfs wasm32 failed
rootfs wasm32 failed

Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.

mho22 added 3 commits August 11, 2026 20:41
Move the two `?worker&url` worker-entry imports from
browser-kernel-host.ts into the new browser-kernel-assets.ts so a
package build can swap that module while the repository keeps
consuming it through Vite. The BrowserKernelAssets interface stays
in browser-kernel-host.ts so type graphs never load the
Vite-coupled module.

BrowserKernel gains opt-in exposeHostFs: the kernel worker reports
the VFS SharedArrayBuffer at boot and BrowserKernel.hostFs serves a
synchronous main-thread filesystem view over the same bytes. It
stays off by default and the reference is released in destroy(),
preserving the WebKit image-switch reclaim behavior.

spawnFromVfs now defaults stdin to an immediate EOF for non-PTY
spawns, matching boot(), spawn(), and NodeKernelHost.spawnProgram.
Without the default, a VFS-spawned program that reads fd 0 (php
does at startup) retried readv(0) forever in the browser.
A downstream browser project that embeds the Kandelo kernel today
has to vendor host/src or carry a git submodule. web-libs/kandelo-web
ships BrowserKernel, the VFS backends and image helpers, and a
binaries loader as a code-only npm package, so a separate browser
IDE can depend on the browser host runtime like any other package.

The package ships no Wasm: the loader fetches ABI-verified
kernel/rootfs/program binaries from a Kandelo binaries release at
runtime and resolves a relative baseUrl (the same-origin proxy
pattern for GitHub's CORS-less release CDN) against the page URL.
Every ABI constant derives from the generated
host/src/generated/abi.ts, so a kernel ABI bump cannot leave the
package stale.

The tsdown build swaps the Vite-coupled assets module and the lazy
default-artifacts module for package-local replacements, emits each
worker entry as a single self-contained file, and fails if any
import other than the lazy Node-only import("net") survives. dist
invariants are enforced by test/dist-contract.test.ts, which builds
with the package's own tsdown devDependency, so the vitest suite
installs the package's node modules and the suite-runner fixture
provisions the directory.

The root package.json gains pack:web. That manifest is a declared
build input of the VFS-shaped packages, so
packages/registry/program-packages.json is regenerated.
apps/browser-demos imports BrowserKernel, MemoryFileSystem, and
writeVfsFile from @kandelo/web through a repository alias in
browserRepositoryAliases(), exercising the package entry from
source in every demo page while published consumers get the
bundled dist.
@mho22
mho22 force-pushed the explore-npm-packaging branch from 445c919 to fcee6b1 Compare August 11, 2026 18:59
@mho22
mho22 marked this pull request as ready for review August 12, 2026 14:24
@mho22
mho22 marked this pull request as draft August 12, 2026 14:25
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