diff --git a/content/docs/cli.mdx b/content/docs/cli.mdx index 6bc85fb..9cd84fc 100644 --- a/content/docs/cli.mdx +++ b/content/docs/cli.mdx @@ -76,7 +76,8 @@ These commands require `remote.*` settings in your [config](/docs/configuration) ### `wvb upload [BUNDLE] [VERSION]` -Upload a packed bundle to the remote, optionally computing integrity, signing it, and deploying. +Pack (by default) and upload a bundle to the remote, optionally computing integrity, signing it, and +deploying. ```sh wvb upload # uses config defaults @@ -85,15 +86,16 @@ wvb upload app 1.2.0 --deploy --channel beta wvb upload --file ./dist/app.wvb --force ``` -| Option | Description | -| ------------------- | ------------------------------------------------------------ | -| `BUNDLE`, `VERSION` | Bundle name and version (else from config / `package.json`). | -| `--file, -F` | Path to the `.wvb` to upload. | -| `--force` | Overwrite if the version already exists. | -| `--deploy` | Deploy after upload. Default `true`. | -| `--channel` | Channel to deploy to (with `--deploy`). | -| `--skip-integrity` | Don't compute an integrity hash. | -| `--skip-signature` | Don't sign the bundle. | +| Option | Description | +| ------------------- | --------------------------------------------------------------------------------------------------------- | +| `BUNDLE`, `VERSION` | Bundle name and version (else from config / `package.json`). | +| `--file, -F` | Path to the `.wvb` to upload. | +| `--pack, -P` | Pack from `pack.srcDir` before uploading. Default `true` (pass `--no-pack` to upload an existing `.wvb`). | +| `--force` | Overwrite if the version already exists. | +| `--deploy` | Deploy after upload. Default `true`. | +| `--channel` | Channel to deploy to (with `--deploy`). | +| `--skip-integrity` | Don't compute an integrity hash. | +| `--skip-signature` | Don't sign the bundle. | ### `wvb deploy [BUNDLE] VERSION` @@ -111,7 +113,7 @@ Download a bundle from the remote and (by default) save it to disk. ```sh wvb download app --endpoint https://updates.example.com wvb download app 1.2.0 --out ./bundles/app.wvb --overwrite -wvb download app --skip-write # fetch + print info only +wvb download app --no-write # fetch + print info only ``` | Option | Description | diff --git a/content/docs/concepts.mdx b/content/docs/concepts.mdx index fedffb8..642ac74 100644 --- a/content/docs/concepts.mdx +++ b/content/docs/concepts.mdx @@ -18,8 +18,8 @@ A Webview Bundle is a single file with three parts laid out back to back: one-byte format version, a `u32` index size, and a checksum. - **Index** — a map from each file path (e.g. `/index.html`) to where its bytes live in the data section, plus its content type and any HTTP headers to replay when served. -- **Data** — each file's bytes compressed with [LZ4](https://github.com/lz4/lz4), each block - followed by an [xxHash-32](https://github.com/Cyan4973/xxHash) checksum. +- **Data** — each file's bytes are compressed with [LZ4](https://github.com/lz4/lz4) and followed by + an [xxHash-32](https://github.com/Cyan4973/xxHash) checksum. Every section is checksummed, so a truncated or corrupted archive is detected before its contents are trusted. The full byte-level specification is in the diff --git a/content/docs/configuration.mdx b/content/docs/configuration.mdx index 7f23677..95f4f9b 100644 --- a/content/docs/configuration.mdx +++ b/content/docs/configuration.mdx @@ -50,7 +50,7 @@ export default defineConfig(async () => ({ ```ts pack: { srcDir: './dist', - outFile: 'app', // → app.wvb + outFileName: 'app', // → app.wvb outDir: '.wvb', ignore: ['*.map'], headers: { '*.html': { 'cache-control': 'max-age=3600' } }, @@ -58,14 +58,14 @@ pack: { } ``` -| Field | Type | Description | -| ----------- | ------------------------------------------------------------------------------- | ---------------------------------------------- | -| `srcDir` | `string` | Directory to pack. | -| `outFile` | `string` | Output file name (`.wvb` appended if missing). | -| `outDir` | `string` | Output directory. Default `.wvb`. | -| `ignore` | `Array \| ((file) => boolean)` | Patterns/predicate to exclude files. | -| `headers` | `Record \| [glob, HeadersInit][] \| ((file) => HeadersInit)` | HTTP headers to attach to matching files. | -| `overwrite` | `boolean` | Overwrite an existing output. Default `true`. | +| Field | Type | Description | +| ------------- | ------------------------------------------------------------------------------- | ---------------------------------------------- | +| `srcDir` | `string` | Directory to pack. | +| `outFileName` | `string` | Output file name (`.wvb` appended if missing). | +| `outDir` | `string` | Output directory. Default `.wvb`. | +| `ignore` | `Array \| ((file) => boolean)` | Patterns/predicate to exclude files. | +| `headers` | `Record \| [glob, HeadersInit][] \| ((file) => HeadersInit)` | HTTP headers to attach to matching files. | +| `overwrite` | `boolean` | Overwrite an existing output. Default `true`. | ## `remote` @@ -166,9 +166,10 @@ builtin: { } ``` -| Field | Type | Description | -| ---------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------- | -| `outDir` | `string` | Where to write downloaded builtin bundles. Default `.wvb/builtin/bundles`. | -| `include` / `exclude` | `string \| RegExp \| Array<…> \| ((info) => boolean)` | Match remote bundles by name/version. | -| `clean` | `boolean` | Clear `outDir` before downloading. Default `true`. | -| `download.concurrency` | `number` | Parallel downloads. | +| Field | Type | Description | +| ----------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------- | +| `outDir` | `string` | Where to write downloaded builtin bundles. Default `.wvb/builtin/bundles`. | +| `target` | `BuiltinTarget` | Where to install from. Default `{ type: 'remote' }`. | +| `include` / `exclude` | `string \| RegExp \| Array<…> \| ((info) => boolean)` | Match remote bundles by name/version. | +| `clean` | `boolean` | Clear `outDir` before downloading. Default `true`. | +| `target.download.concurrency` | `number` | Parallel downloads (remote target). | diff --git a/content/docs/guides/android.mdx b/content/docs/guides/android.mdx index 49a8dfe..c32cc2c 100644 --- a/content/docs/guides/android.mdx +++ b/content/docs/guides/android.mdx @@ -100,7 +100,7 @@ val source = BundleSource( // Query what's available: val version = source.loadVersion("app") // current version (remote wins over builtin) -val bundle = source.fetch("app") // load a bundle into memory +val bundle = source.fetchBundle("app") // load a bundle into memory val html = bundle.getData("/index.html") // ByteArray? — file bytes, or null ``` diff --git a/content/docs/guides/electron.mdx b/content/docs/guides/electron.mdx index 2901511..44ecb1f 100644 --- a/content/docs/guides/electron.mdx +++ b/content/docs/guides/electron.mdx @@ -106,7 +106,8 @@ const version = await source.loadVersion('app'); // Is a newer version deployed? const info = await updater.getUpdate('app'); if (info.isAvailable) { - await updater.downloadUpdate('app'); // downloads, verifies, installs + const downloaded = await updater.download('app'); // downloads + verifies, stages into the remote source + await updater.install('app', downloaded.version); // activates the downloaded version // reload the window to pick up the new bundle } ``` @@ -179,7 +180,7 @@ const config: ForgeConfig = { `source` accepts `builtinDir` (shipped, read-only) and `remoteDir` (downloaded updates). A good default is to ship `builtinDir` under `process.resourcesPath` and let `remoteDir` default to a writable app-data location. Downloaded versions always take priority over builtin ones, so an -installed update is served automatically after `updater.downloadUpdate(...)`. +installed update is served automatically after `updater.download(...)` + `updater.install(...)`. ## Troubleshooting diff --git a/content/docs/guides/ios.mdx b/content/docs/guides/ios.mdx index 9219fac..f181a4a 100644 --- a/content/docs/guides/ios.mdx +++ b/content/docs/guides/ios.mdx @@ -86,7 +86,7 @@ let source = BundleSource(config: BundleSourceConfig( )) // Query what's available: -let bundle = try await source.fetch(bundleName: "app") // load into memory +let bundle = try await source.fetchBundle(bundleName: "app") // load into memory let html = try bundle.getData(path: "/index.html") // Data? — bytes or nil ``` diff --git a/content/docs/guides/tauri.mdx b/content/docs/guides/tauri.mdx index 6d417a4..3b6a9a4 100644 --- a/content/docs/guides/tauri.mdx +++ b/content/docs/guides/tauri.mdx @@ -66,8 +66,8 @@ Point your app window at the custom scheme. The simplest approach is to set the window to `bundle://app/` (production) or your dev server (development). Make sure your capabilities allow the plugin's commands if you call them from the frontend. Add the -`wvb` permissions to `src-tauri/capabilities/default.json` as needed (the plugin namespace is -`wvb`). +`wvb-tauri` permissions to `src-tauri/capabilities/default.json` as needed (the plugin namespace is +`wvb-tauri`). ## 3. Pack and ship bundles @@ -94,23 +94,25 @@ Config::new() .remote(Remote::new("https://updates.example.com")); ``` -The plugin then exposes these commands to the frontend via `invoke` (all under the `wvb` plugin +The plugin then exposes these commands to the frontend via `invoke` (all under the `wvb-tauri` plugin namespace): -| Command | Arguments | Returns | -| ------------------------------------- | ------------------------ | ------------------------- | -| `plugin:wvb\|updater_get_update` | `bundleName` | update availability info | -| `plugin:wvb\|updater_download_update` | `bundleName`, `version?` | installed bundle metadata | -| `plugin:wvb\|source_load_version` | `bundleName` | active local version | -| `plugin:wvb\|source_list_bundles` | — | local bundles | -| `plugin:wvb\|remote_get_info` | `bundleName`, `channel?` | current remote metadata | +| Command | Arguments | Returns | +| --------------------------------------- | ------------------------ | ------------------------------ | +| `plugin:wvb-tauri\|updater_get_update` | `bundleName` | update availability info | +| `plugin:wvb-tauri\|updater_download` | `bundleName`, `version?` | downloaded bundle metadata | +| `plugin:wvb-tauri\|updater_install` | `bundleName`, `version` | activates a downloaded version | +| `plugin:wvb-tauri\|source_load_version` | `bundleName` | active local version | +| `plugin:wvb-tauri\|source_list_bundles` | — | local bundles | +| `plugin:wvb-tauri\|remote_get_info` | `bundleName`, `channel?` | current remote metadata | ```ts import { invoke } from '@tauri-apps/api/core'; -const update = await invoke('plugin:wvb|updater_get_update', { bundleName: 'app' }); +const update = await invoke('plugin:wvb-tauri|updater_get_update', { bundleName: 'app' }); if (update.isAvailable) { - await invoke('plugin:wvb|updater_download_update', { bundleName: 'app' }); + const info = await invoke('plugin:wvb-tauri|updater_download', { bundleName: 'app' }); + await invoke('plugin:wvb-tauri|updater_install', { bundleName: 'app', version: info.version }); // reload the webview to pick up the new bundle } ``` @@ -130,7 +132,8 @@ use wvb_tauri::WebviewBundleExtra; let wvb = app.wvb(); let bundles = wvb.source().list_bundles().await?; if let Some(updater) = wvb.updater() { - updater.download_update("app", None).await?; + let info = updater.download("app", None).await?; // download + verify, stage into the remote source + updater.install("app", info.version).await?; // activate the downloaded version } ``` diff --git a/content/docs/index.mdx b/content/docs/index.mdx index 69a6714..521be0e 100644 --- a/content/docs/index.mdx +++ b/content/docs/index.mdx @@ -29,8 +29,8 @@ through a custom URL scheme — and can update over the air without an app-store release required. - **Integrity & authenticity.** Every bundle carries checksums, and downloads can be verified with SHA-3 integrity hashes and digital signatures (ECDSA, Ed25519, RSA). -- **One format, every platform.** The same `.wvb` archive works in Electron, Tauri, Android, and - iOS via a shared Rust core. +- **One format, every platform.** The same `.wvb` archive runs in Electron and Tauri today via a + shared Rust core, with Android and iOS support planned. ## Start here @@ -73,27 +73,27 @@ Then pick your platform guide: ## Packages -| Package | What it is | Where it runs | -| ---------------------------- | --------------------------------------------------------------------------------- | ---------------------- | -| [`wvb`](https://docs.rs/wvb) | Rust core: bundle format, source, remote, updater, protocol, integrity, signature | everywhere (library) | -| `@wvb/cli` (`wvb`) | Command-line tool: pack, serve, upload, deploy, download, local remote | your machine / CI | -| `@wvb/config` | `defineConfig` for `wvb.config.ts` | build tooling | -| `@wvb/node` | N-API bindings to the core | Node.js | -| `@wvb/electron` | Electron integration (protocols, IPC, updater) | Electron main/renderer | -| `wvb-tauri` | Tauri plugin (protocols, commands, updater) | Tauri app | -| `wvb-ffi` | UniFFI bindings (Kotlin/Swift) | Android / iOS | -| `@wvb/remote-*` | Remote server providers (local, AWS, Cloudflare) | your update backend | +| Package | What it is | Where it runs | +| ---------------------------- | --------------------------------------------------------------------------------- | ----------------------- | +| [`wvb`](https://docs.rs/wvb) | Rust core: bundle format, source, remote, updater, protocol, integrity, signature | everywhere (library) | +| `@wvb/cli` (`wvb`) | Command-line tool: pack, serve, upload, deploy, download, local remote | your machine / CI | +| `@wvb/config` | `defineConfig` for `wvb.config.ts` | build tooling | +| `@wvb/node` | N-API bindings to the core | Node.js | +| `@wvb/electron` | Electron integration (protocols, IPC, updater) | Electron main/renderer | +| `wvb-tauri` | Tauri plugin (protocols, commands, updater) | Tauri app | +| `wvb-ffi` | UniFFI bindings (Kotlin/Swift) | Android / iOS (planned) | +| `@wvb/remote-*` | Remote server providers (local, AWS, Cloudflare) | your update backend | ## The bundle format in one line diff --git a/content/docs/remote-updates.mdx b/content/docs/remote-updates.mdx index b3e54bf..526e331 100644 --- a/content/docs/remote-updates.mdx +++ b/content/docs/remote-updates.mdx @@ -43,7 +43,7 @@ import { defineConfig } from '@wvb/config'; export default defineConfig({ pack: { srcDir: './dist', // your build output - outFile: 'app', // → app.wvb (name from package.json if omitted) + outFileName: 'app', // → app.wvb (name from package.json if omitted) }, remote: { endpoint: 'https://updates.example.com', @@ -71,8 +71,9 @@ wvb upload app 1.1.0 # upload only wvb deploy app 1.1.0 # make it the deployed version ``` -`wvb upload` packs nothing by default — it uploads an existing `.wvb` (run `wvb pack` first, or pass -`--file`). With `--deploy` it also deploys; add `--channel beta` to deploy to a channel. See the +`wvb upload` packs before uploading by default — it runs `wvb pack` from `pack.srcDir`. Pass +`--no-pack` to upload an existing `.wvb` as-is, or `--file ` for a specific bundle. With +`--deploy` (also on by default) it deploys; add `--channel beta` to deploy to a channel. See the [CLI reference](/docs/cli) for all flags. ## The remote HTTP contract @@ -152,8 +153,8 @@ let config = UpdaterConfig::new() let updater = Updater::new(source, remote, Some(config)); ``` -The Electron and Tauri updaters expose the same options through their config; the Android/iOS -bindings expose the `IntegrityChecker`/policy types directly. +The Electron and Tauri updaters expose the same options through their config. Android and iOS support +is planned; their UniFFI bindings expose the `IntegrityChecker`/policy types directly. ## How clients install an update @@ -162,13 +163,14 @@ The high-level `Updater` ties a source to a remote and does check → download ```rust let update = updater.get_update("app").await?; if update.is_available { - updater.download_update("app", None).await?; // verifies, then writes into the remote source + let info = updater.download("app", None).await?; // downloads + verifies, stages into the remote source + updater.install("app", info.version).await?; // activates the downloaded version // reload the webview to pick up the new version } ``` -`download_update` writes the verified bundle into the `remote` source directory and records it as the -current version, so it's served on the next load. Each platform guide shows the idiomatic call: +`download` writes the verified bundle into the `remote` source directory; `install` then activates +that version, so it's served on the next load. Each platform guide shows the idiomatic call: [Electron](/docs/guides/electron#3-call-the-api-from-the-renderer-optional), [Tauri](/docs/guides/tauri#4-drive-updates-from-the-frontend-optional), [Android](/docs/guides/android#over-the-air-updates), @@ -194,7 +196,7 @@ import { localRemote } from '@wvb/remote-local'; const local = localRemote({ baseDir: '~/.wvb/local' }); // { uploader, deployer } export default defineConfig({ - pack: { srcDir: './dist', outFile: 'app' }, + pack: { srcDir: './dist', outFileName: 'app' }, remote: { endpoint: 'http://localhost:4313', bundleName: 'app', @@ -220,8 +222,8 @@ wvb remote local # serves ~/.wvb/local on http://localhost: **4. Point your app's updater at it** by setting its endpoint to `http://localhost:4313` (`updater: { remote: { endpoint: 'http://localhost:4313' } }` in Electron, `Remote::new(...)` in -Tauri, the remote URL in your Android/iOS updater). Now `getUpdate` / `downloadUpdate` hit your local -server and install the bundle exactly as production would. +Tauri, the remote URL in your Android/iOS updater). Now `getUpdate` / `download` / `install` hit your +local server and download, verify, and activate the bundle exactly as production would. On the Android emulator, reach your host machine at `http://10.0.2.2:4313` instead of `localhost`. diff --git a/public/showcase/desktop.jpg b/public/showcase/desktop.jpg new file mode 100644 index 0000000..917bdb0 Binary files /dev/null and b/public/showcase/desktop.jpg differ diff --git a/public/showcase/desktop.mp4 b/public/showcase/desktop.mp4 new file mode 100644 index 0000000..ddc6360 Binary files /dev/null and b/public/showcase/desktop.mp4 differ diff --git a/public/showcase/desktop.webm b/public/showcase/desktop.webm new file mode 100644 index 0000000..d4b23d5 Binary files /dev/null and b/public/showcase/desktop.webm differ diff --git a/public/showcase/landscape.jpg b/public/showcase/landscape.jpg new file mode 100644 index 0000000..bd88a76 Binary files /dev/null and b/public/showcase/landscape.jpg differ diff --git a/public/showcase/landscape.mp4 b/public/showcase/landscape.mp4 new file mode 100644 index 0000000..a711b11 Binary files /dev/null and b/public/showcase/landscape.mp4 differ diff --git a/public/showcase/landscape.webm b/public/showcase/landscape.webm new file mode 100644 index 0000000..1d72191 Binary files /dev/null and b/public/showcase/landscape.webm differ diff --git a/public/showcase/vertical.jpg b/public/showcase/vertical.jpg new file mode 100644 index 0000000..a1b2e13 Binary files /dev/null and b/public/showcase/vertical.jpg differ diff --git a/public/showcase/vertical.mp4 b/public/showcase/vertical.mp4 new file mode 100644 index 0000000..c8956b7 Binary files /dev/null and b/public/showcase/vertical.mp4 differ diff --git a/public/showcase/vertical.webm b/public/showcase/vertical.webm new file mode 100644 index 0000000..ca960a4 Binary files /dev/null and b/public/showcase/vertical.webm differ diff --git a/src/layouts/home/Landing.tsx b/src/layouts/home/Landing.tsx index a32b06f..fd431d3 100644 --- a/src/layouts/home/Landing.tsx +++ b/src/layouts/home/Landing.tsx @@ -4,6 +4,7 @@ import { Footer } from './components/Footer'; import { Hero } from './components/Hero'; import { Navbar } from './components/Navbar'; import { Platforms } from './components/Platforms'; +import { Showcase } from './components/Showcase'; export function Landing() { return ( @@ -11,6 +12,7 @@ export function Landing() {
+ diff --git a/src/layouts/home/components/ArchitectureDiagram.tsx b/src/layouts/home/components/ArchitectureDiagram.tsx index cfa9807..8a3a6a0 100644 --- a/src/layouts/home/components/ArchitectureDiagram.tsx +++ b/src/layouts/home/components/ArchitectureDiagram.tsx @@ -68,7 +68,7 @@ export function ArchitectureDiagram() {
- deterministic · signed · offline + versioned · signed · offline
); diff --git a/src/layouts/home/components/CallToAction.tsx b/src/layouts/home/components/CallToAction.tsx index cb2105b..1774fe3 100644 --- a/src/layouts/home/components/CallToAction.tsx +++ b/src/layouts/home/components/CallToAction.tsx @@ -8,7 +8,7 @@ export function CallToAction() { Start now

- Three commands from zero to a mounted bundle. + Three commands from build output to a served bundle.

diff --git a/src/layouts/home/components/Hero.tsx b/src/layouts/home/components/Hero.tsx
index 161b21a..128b750 100644
--- a/src/layouts/home/components/Hero.tsx
+++ b/src/layouts/home/components/Hero.tsx
@@ -21,8 +21,8 @@ export function Hero() {
           inside native.
         
         

- A bundle format and runtime for delivering web resources to any webview-mounted platform — - signed, versioned, and guaranteed to work offline. + A bundle format and runtime for delivering web resources to webview-mounted platforms like + Electron and Tauri — signed, versioned, and offline-first.

diff --git a/src/layouts/home/components/Platforms.tsx b/src/layouts/home/components/Platforms.tsx index e503f39..e8b0092 100644 --- a/src/layouts/home/components/Platforms.tsx +++ b/src/layouts/home/components/Platforms.tsx @@ -6,7 +6,7 @@ export function Platforms() {
- Runs on + Targets
{PLATFORMS.map(platform => ( -
-
platform
-
{platform}
+
+
+ {platform.planned === true ? 'planned' : 'platform'} +
+
{platform.name}
))}
diff --git a/src/layouts/home/components/Showcase.tsx b/src/layouts/home/components/Showcase.tsx new file mode 100644 index 0000000..c9baef3 --- /dev/null +++ b/src/layouts/home/components/Showcase.tsx @@ -0,0 +1,151 @@ +import { useEffect, useRef, useState } from 'react'; +import { cn } from '../../../lib/cn'; + +type View = 'mobile' | 'desktop'; +const VIEWS: { id: View; label: string }[] = [ + { id: 'mobile', label: 'Mobile' }, + { id: 'desktop', label: 'Desktop' }, +]; + +// A dark "spotlight" band: the demo footage is dark-themed, so we frame it on a dark +// surface (in light mode too) to make it an intentional focal moment rather than a clash. +// On lg+ the 2-up cut (large desktop window + phone) always shows. Below lg we default to the +// single-device portrait cut, but a simple Mobile/Desktop tab lets viewers preview either. +// WebM (VP9) is primary with an H.264 mp4 fallback for browsers without VP9 (e.g. older iOS Safari). +export function Showcase() { + const framesRef = useRef(null); + const [view, setView] = useState('mobile'); // only affects the < lg layout + + // Play the currently visible video; pause the hidden one. Honors prefers-reduced-motion + // (everything stays paused on its poster). Re-runs whenever the tab toggles. + useEffect(() => { + const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + framesRef.current?.querySelectorAll('video').forEach(video => { + if (video.offsetParent !== null && !reduce) { + void video.play().catch(() => {}); + } else { + video.pause(); + } + }); + }, [view]); + + return ( +
+ {/* Blue/violet glow echoing the footage's own lighting. */} +
+ {/* Faint vertical grid lines, matching the hero. */} +
+ + {/* Heading column — aligned to the page's content width. */} +
+
+
+ Live demo +
+
one .wvb → every webview
+
+ +

+ One bundle. Every webview. +

+

+ The same signed .wvb — a Hacker News reader — + running unmodified on Electron, Tauri, iOS, and Android. +

+
+ + {/* Video — intentionally wider than the text column so the UI stays legible. */} +
+ {/* Mobile/Desktop toggle — only below lg, where a single cut is shown at a time. */} +
+
+ {VIEWS.map(v => ( + + ))} +
+
+ +
+ {/* 2-up (desktop window + phone): wide screens only (lg+). */} + + {/* Single desktop window: below lg, on the "Desktop" tab. */} + + {/* Single phone (portrait): below lg, on the "Mobile" tab (default). */} + +
+
+
+ ); +} diff --git a/src/layouts/home/data.ts b/src/layouts/home/data.ts index 884776f..66efc5d 100644 --- a/src/layouts/home/data.ts +++ b/src/layouts/home/data.ts @@ -8,6 +8,7 @@ export interface NavItem { export const NAV_ITEMS: NavItem[] = [ { label: 'Docs', href: DOCS_URL }, + { label: 'Demo', href: '#demo' }, { label: 'How it works', href: '#how-it-works' }, { label: 'Platforms', href: '#platforms' }, { label: 'Reference', href: '/docs/cli' }, @@ -23,10 +24,9 @@ export interface WebviewHost { /** Hosts shown on the right ("webview host") of the architecture diagram. */ export const WEBVIEW_HOSTS: WebviewHost[] = [ + { platform: 'Desktop', runtime: 'Electron / Tauri' }, { platform: 'iOS', runtime: 'WKWebView' }, { platform: 'Android', runtime: 'WebView' }, - { platform: 'Desktop', runtime: 'Tauri / Electron' }, - { platform: 'RN', runtime: 'react-native-webview' }, ]; export interface Feature { @@ -41,36 +41,47 @@ export const FEATURES: Feature[] = [ number: '01', title: 'Offline-first, by default.', description: - "Every bundle carries its full dependency graph. Mount it once and the app keeps working — subway, plane, dead network, doesn't matter.", - code: 'await mount("app.wvb", { offline: true });', + "Every bundle carries its full asset graph and is served through a custom protocol — no network round-trip. Subway, plane, dead network, doesn't matter.", + code: "webviewBundle({ protocols: [bundleProtocol('app')] });", }, { number: '02', title: 'Written in web code.', description: - 'Author with React, Vue, Svelte, or vanilla HTML. The build step emits a single .wvb artifact from any bundler output — no custom toolchain to learn.', - code: 'wvb build ./dist --sign ed25519', + 'Author with React, Vue, Svelte, or vanilla HTML. wvb pack turns any bundler output into a single .wvb artifact — no custom toolchain to learn.', + code: 'wvb pack ./dist --outfile app.wvb', }, { number: '03', title: 'Cross-platform contract.', description: - 'One bundle format, identical runtime behavior on iOS WKWebView, Android WebView, Tauri, and Electron. No per-host packaging branches.', - code: '// Same bundle. Every host.', + 'One bundle format over one shared Rust core. Electron and Tauri run it today; iOS WKWebView and Android WebView are on the way. No per-host packaging branches.', + code: '// One .wvb — Electron & Tauri today.', }, { number: '04', title: 'Native where it matters.', description: - 'A typed bridge exposes host capabilities to your web code. Streaming IPC with cancellation and backpressure, adapters for Swift, Kotlin, and Rust.', - code: 'const result = await native.fs.read(path);', + 'A typed IPC layer lets web code load bundles and pull over-the-air updates from the host. Native bindings for Swift and Kotlin are generated from the Rust core via UniFFI.', + code: 'const info = await updater.getUpdate("app");', }, ]; -export const PLATFORMS = ['iOS', 'Android', 'macOS', 'Windows', 'Linux']; +export interface Platform { + name: string; + planned?: boolean; +} + +export const PLATFORMS: Platform[] = [ + { name: 'macOS' }, + { name: 'Windows' }, + { name: 'Linux' }, + { name: 'iOS', planned: true }, + { name: 'Android', planned: true }, +]; export const INSTALL_COMMANDS = [ - 'npm install -g webview-bundle', - 'wvb init my-app', - 'wvb build ./my-app --out app.wvb', + 'npm install -D @wvb/cli', + 'wvb pack ./dist --outfile app.wvb', + 'wvb serve .wvb/app.wvb', ]; diff --git a/src/styles.css b/src/styles.css index aa3707c..f451a4b 100644 --- a/src/styles.css +++ b/src/styles.css @@ -21,6 +21,76 @@ --color-brand-hover: #60a5fa; } +/* --------------------------------------------------------------------------- + * Docs theme — align fumadocs with the main page. + * + * Only fumadocs components read the `--color-fd-*` tokens and they only render + * on /docs, so overriding them here re-skins the guides (plus the search + * dialog, popovers, etc.) without touching the home page. Neutrals follow the + * Tailwind `zinc` scale and the accent reuses the brand blue, mirroring + * Landing.tsx (`bg-white text-zinc-900 dark:bg-[#09090a] dark:text-zinc-100`). + * ------------------------------------------------------------------------- */ +:root { + --color-fd-background: #ffffff; + --color-fd-foreground: #18181b; /* zinc-900 */ + --color-fd-muted: #fafafa; /* zinc-50 */ + --color-fd-muted-foreground: #71717a; /* zinc-500 */ + --color-fd-popover: #ffffff; + --color-fd-popover-foreground: #18181b; /* zinc-900 */ + --color-fd-card: #fafafa; /* zinc-50 */ + --color-fd-card-foreground: #18181b; /* zinc-900 */ + --color-fd-border: #e4e4e7; /* zinc-200 */ + --color-fd-primary: var(--color-brand); + --color-fd-primary-foreground: #ffffff; + --color-fd-secondary: #f4f4f5; /* zinc-100 */ + --color-fd-secondary-foreground: #18181b; /* zinc-900 */ + --color-fd-accent: #f4f4f5; /* zinc-100 */ + --color-fd-accent-foreground: #18181b; /* zinc-900 */ + --color-fd-ring: var(--color-brand); +} + +:root.dark { + --color-fd-background: #09090a; /* matches Landing's dark base */ + --color-fd-foreground: #f4f4f5; /* zinc-100 */ + --color-fd-muted: #18181b; /* zinc-900 */ + --color-fd-muted-foreground: #a1a1aa; /* zinc-400 */ + --color-fd-popover: #18181b; /* zinc-900 */ + --color-fd-popover-foreground: #f4f4f5; /* zinc-100 */ + --color-fd-card: #18181b; /* zinc-900 */ + --color-fd-card-foreground: #f4f4f5; /* zinc-100 */ + --color-fd-border: #27272a; /* zinc-800 */ + --color-fd-primary: var(--color-brand); + --color-fd-primary-foreground: #ffffff; + --color-fd-secondary: #18181b; /* zinc-900 */ + --color-fd-secondary-foreground: #f4f4f5; /* zinc-100 */ + --color-fd-accent: #27272a; /* zinc-800 */ + --color-fd-accent-foreground: #f4f4f5; /* zinc-100 */ + --color-fd-ring: var(--color-brand); +} + +/* fumadocs re-tints the sidebar with a higher-specificity rule of its own; + * keep it in step with the dark palette above. */ +:root.dark #nd-sidebar { + --color-fd-muted: #18181b; /* zinc-900 */ + --color-fd-secondary: #27272a; /* zinc-800 */ + --color-fd-muted-foreground: #a1a1aa; /* zinc-400 */ +} + +/* --------------------------------------------------------------------------- + * Sharper, more angular corners for the docs. CSS variables inherit, so + * redefining the radius scale on the docs root flows into every `rounded-*` + * utility inside it — the home page keeps its softer radii, and `rounded-full` + * (dots, pills, avatars) ignores these tokens and stays circular. + * ------------------------------------------------------------------------- */ +#nd-docs-layout { + --radius-xs: 1px; + --radius-sm: 2px; + --radius-md: 3px; + --radius-lg: 4px; + --radius-xl: 6px; + --radius-2xl: 8px; +} + body { font-synthesis: style; text-rendering: optimizeLegibility;