WAOS is a deployment-ready, local-first browser OS. It is a static React + TypeScript PWA: there is no application server, account service, or proxy. Files, settings, notes, and installed .wa apps stay in IndexedDB on the current device.
- Friendly iOS-inspired interaction design with a rounded, bubbly GNOME-like desktop
- Responsive desktop and mobile shells
- Movable, resizable, minimizable, and maximizable app windows
- Desktop shortcuts, dock, searchable app launcher, clock, connectivity status, toasts, and notification center
- IndexedDB virtual filesystem with folders, editing, import, export, and persistent notes
- Built-in Welcome, Files, Notes, Browser, App Store, Settings, and Calculator apps
- Configurable GitHub-backed app catalog with an offline starter catalog
- Plain-JavaScript
.wapackages with a parsed manifest block - Sandboxed
.waruntime using an iframe without same-origin access, plus a small message-based API - PWA manifest, service worker, offline caching, version checks, and an update prompt
- Cloudflare-compatible security headers and a static production build
- Sample
.waapps and a sample GitHub app-store index
Requirements: Node.js 22.13 or newer.
npm ci
npm run devOpen the local address printed by Vite. Production build:
npm run buildThe static site is written to dist/static.
WAOS is configured as a static-assets Cloudflare Worker. It does not run application code on a server; Cloudflare only serves the generated files.
- Push this folder to a GitHub repository.
- In Cloudflare, open Workers & Pages, create or select your Worker, and connect the GitHub repository under Settings → Builds.
- Set the production branch to
main. - Set the build command to
npm run build. - Keep the deploy command as
npx wrangler deploy. - Deploy once, then open Settings → Domains & Routes → Add → Custom Domain
and enter your chosen hostname, such as
os.example.com. - If the parent domain uses Cloudflare DNS, Cloudflare creates the DNS record and certificate automatically.
The committed wrangler.jsonc points Cloudflare to dist/static and supplies
the single-page-app fallback. Do not add a catch-all _redirects rule; it
duplicates this fallback and Cloudflare rejects it as an infinite loop.
Bump the version before a release, commit, and push:
npm version patch --no-git-tag-version
npm run buildThe build synchronizes version.json, the application version, and the service-worker cache. Once Cloudflare deploys the new static files, open WAOS sessions detect the newer version and offer an update. IndexedDB data is not replaced by a site update.
WAOS reads a configurable public GitHub repository. Change the owner, repository, and branch in Settings → GitHub app store. The client reads:
https://raw.githubusercontent.com/{owner}/{repo}/{branch}/store/index.json
Copy examples/store/index.json into the store repository. Package URLs may be relative to the repository root or absolute HTTPS URLs.
{
"formatVersion": 1,
"name": "My WAOS App Store",
"updatedAt": "2026-07-26T00:00:00Z",
"apps": [
{
"id": "com.example.my-app",
"name": "My App",
"description": "A short description.",
"version": "1.0.0",
"icon": "package",
"color": "#6d5dfc",
"author": "Example Developer",
"packageUrl": "apps/my-app.wa",
"featured": true,
"permissions": ["storage", "notifications"]
}
]
}If the configured catalog is offline, private, missing, or invalid, the App Store safely falls back to the bundled sample apps.
A .wa package is a JavaScript file with a machine-readable comment at the top. The main OS parses only the comment; the JavaScript itself runs inside a sandboxed iframe.
/* WA_MANIFEST
{
"formatVersion": 1,
"id": "com.example.hello",
"name": "Hello",
"version": "1.0.0",
"description": "My first WAOS app.",
"icon": "sparkles",
"color": "#6d5dfc",
"author": "Example Developer",
"permissions": ["storage", "notifications"],
"window": { "width": 600, "height": 480, "resizable": true }
}
WA_MANIFEST */
WA.mount(({ root, notify, storage, openExternal }) => {
root.innerHTML = `<h1>Hello from WAOS</h1><button id="hello">Say hello</button>`;
root.querySelector("#hello").addEventListener("click", () => {
notify("Hello", "This came from a sandboxed app.");
});
});Available runtime methods:
root: the app's root elementnotify(title, message): requiresnotificationsstorage.get(key)/storage.set(key, value): requiresstorage; data is namespaced per appopenExternal(url): requiresexternal-links; only HTTP(S) URLs are accepted
The sandbox does not get DOM access to WAOS, direct IndexedDB access, filesystem access, or same-origin privileges. For a public store, add package hashes and signature verification before accepting untrusted third-party publishers.
The built-in Browser uses a standards-compliant sandboxed iframe. Websites that allow embedding can open inside WAOS. Websites that send X-Frame-Options or restrictive CSP headers may refuse to display; WAOS presents an Open externally action for those sites. The project intentionally does not attempt to bypass host-browser cookies, extensions, CORS, CSP, device policy, or other browser security boundaries.
Browser storage belongs to the site origin and can be cleared by the browser or under storage pressure. Important files can be exported from Files. Hosting WAOS permanently at the same HTTPS origin preserves the same IndexedDB namespace between releases. Changing domains creates a separate empty workspace.
src/
apps/ Built-in apps and sandbox host
components/ Desktop shell, dock, launcher, windows, notifications
lib/db.ts IndexedDB filesystem, settings, and installed apps
lib/wa.ts .wa manifest parser, installer, and sandbox document
public/
apps/ Bundled sample .wa packages
store/ Offline starter catalog
sw.js Offline and update service worker
version.json Release manifest
examples/store/ GitHub store example
build/ Version and Cloudflare build helpers
- Keep the provided CSP and Permissions Policy unless a reviewed app feature needs a narrow exception.
- Do not add
allow-same-originto the.waruntime iframe. - Do not expose generic code execution, operating-system commands, secrets, or unrestricted file APIs through the message bridge.
- A browser PWA cannot provide a truly independent browser engine. Use a native shell if that requirement changes.