React UI
├─ validated catalogue — @lumiboot/catalog + @lumiboot/contracts
└─ SessionLifecycle / VMRegistry — @lumiboot/vm-core
└─ lazy adapter → V86Provider, dynamic import
├─ assets and manifests — @lumiboot/vm-assets
└─ network profile — @lumiboot/vm-network
├─ v86 Fetch
├─ WISP local proxy
└─ user WISP relay
VM, CPU and RAM run in the browser; the local proxy and the WISP relay carry network traffic.
| Path | Responsibility |
|---|---|
apps/web |
React/Vite, catalogue, system detail, session, dynamic provider import |
apps/network-proxy |
Go WISP V1 binary on loopback |
packages/contracts |
shared types and Zod schemas |
packages/catalog |
families, images, profiles, variants and availability |
packages/vm-core |
engine contracts, registry, compatibility and lifecycle |
packages/provider-v86 |
v86 integration, console, readiness and network |
packages/provider-v64 |
v64 x86-64 JIT integration, Workers, WebGPU and OPFS disks |
packages/vm-assets |
manifests, download, integrity and local disks |
packages/vm-storage |
versioned storage, quotas, backends; implemented, not wired to the sessions yet |
packages/vm-network |
network modes, profiles, capabilities and diagnostics |
images |
build scripts and metadata; large binaries ignored |
infra |
Nginx, Docker dev service, WISP relay |
tests/architecture.test.ts runs these rules from tests/arch/dependency-graph.ts, with no external dependency. A violation fails pnpm test, locally and in CI. The declared graph in package.json and the real import graph are acyclic; findCycles runs Tarjan's algorithm, reports a cycle and the suite fails.
ALLOWED_INTERNAL_DEPS pins the dependency direction:
apps/web → {catalog, contracts, provider-v86, provider-v64, vm-assets, vm-core, vm-network}
contracts → ∅
catalog → contracts
vm-network → contracts
vm-assets → contracts
vm-storage → ∅
vm-core → {contracts, vm-network}
provider-v86 → {contracts, vm-assets, vm-core, vm-network}
provider-v64 → {contracts, provider-v86 (shared console/lifecycle adapters), vm-assets, vm-core, vm-network}
@lumiboot/contractsis a leaf and imports no@lumiboot/*package.- An undeclared
@lumiboot/*dependency is a phantom dependency and fails the test. - No package depends on
@lumiboot/web, React or Zustand. - Deep imports
@lumiboot/<pkg>/…and relative paths crossing a package are forbidden. The public surface issrc/index.ts. - A new package declares its boundary; the test "each workspace package declares its boundaries" enforces it.
- The profile
engineIdexists in the catalogue (packages/catalog/src/engines.ts). - The profile engine serves the system architecture, per the
ENGINE_ARCHITECTUREStest table. - A
validatedprofile relies on anintegratedengine. checkEngineCompatibility, inpackages/vm-core/src/compatibility/, checkscanRun, the architecture when the engine declares one, and WebAssembly for everybrowserprofile.
SessionLifecycle and VMRegistry run against a fake engine, without disk:
- two concurrent
start()calls create a single instance; the start is single-flight; - two concurrent sessions stay isolated, with no shared state;
dispose()during an in-flightstart()releases the instance, without orphan;register()is idempotent for the same engine.
- The catalogue builds and validates the
OSDefinitionobjects, an image plus a profile. - The UI creates a session from the system or variant id.
VMRegistryresolves the engine; the web adapter imports@lumiboot/provider-v86or@lumiboot/provider-v64dynamically.V86Providerchecks the manifest and builds the emulator for the profile.runningfollows a proof: Linux serial marker or first disk read (disk-boot).- Stop releases the network controller, emulator, listeners and session.
The session view uses displayMode to describe the guest surface (text for CLI, graphical or absent for VGA/framebuffer), while serial logs always start collapsed. The user opens or closes them explicitly with the "Logs" button.
States: idle → preparing → downloading → booting → running → stopping → stopped, error on failure (packages/vm-core/src/lifecycle/SessionState.ts).
The engine comes from the profile. getAggregatedAvailability computes the state:
imageRef present
+ compatible browser profile
+ integrated engine
+ validated = true
= ready
Each image × profile candidate is evaluated on its own. Required user media gives resource-required; an unproven ready falls back to the non-ready state of packages/catalog/src/availability.ts. Only the generic custom-image entry declares mediaStrategy: 'user-import', so it is the single resource-required system; every other non-ready system stays planned. Variants: systems.md.
| Route | View |
|---|---|
/ |
catalogue |
/systems/:slug |
detail and variant selection |
/session/:id |
local session |
/settings has no route and returns page not found. Global configuration: apps/web/public/lumiboot.config.json.
- Integrated engines: v86 for i386 and v64 for x86-64.
- Debian 12: CLI, X11/Openbox and XFCE validated.
- Ubuntu 18.04 LTS: CLI, X11/Openbox and XFCE built locally, published and validated by a real boot.
- Windows 7: Lite and Professional SP1, local RAW disks over HTTP Range.
- Windows 10 x86: boot proven on an operator RAW disk over HTTP Range, with
virtio-netand the VirtIO NetKVM driver; guest connectivity untested, seeimages/windows-10-x86/README.md. - Windows 11, Fedora, Mint and macOS: catalogue only, not developed, engines or images missing.
- Custom image: generic entry point booting a local
.iso,.imgor.rawimported by the visitor, through the integrated v86 engine. - Network modes
offline,browser-fetch,local-proxyanduser-relayare implemented. - Boot proof: the profile
readiness.timeoutMsis a diagnostic, not a failure. On expiry the provider emits theboot-timeoutprogress phase only: the VM is not stopped, the console/screen stays live, the session staysbooting, and a late guest marker still reachesrunning(never simulated by a timer). The loading veil is bounded to 5 s from mount; past it the real console or screen is shown whatever the boot state.
- No remote VM, VNC/RDP or orchestration API.
- No large disk or proprietary media in Git.
- No secret in a
VITE_*variable or a public JSON file. - No engine logic in React components.
- No
ready,runningorconnectedstate without matching proof. - No dependency outside the allowed boundaries and no cycle (
tests/architecture.test.ts). - A launch profile never pairs an architecture with an engine that does not serve it (
ENGINE_ARCHITECTURES).