diff --git a/.github/workflows/references.yaml b/.github/workflows/references.yaml
new file mode 100644
index 0000000..ee66152
--- /dev/null
+++ b/.github/workflows/references.yaml
@@ -0,0 +1,51 @@
+name: references
+on:
+ repository_dispatch:
+ types: [generate-references]
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: 'webview-bundle ref (tag/branch/SHA) to generate from'
+ required: false
+ default: 'main'
+permissions:
+ contents: write
+ pull-requests: write
+concurrency:
+ group: references
+ cancel-in-progress: true
+jobs:
+ generate:
+ runs-on: ubuntu-latest
+ env:
+ WVB_REF: ${{ github.event.client_payload.ref || inputs.ref || 'main' }}
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1
+ with:
+ repository: webview-bundle/webview-bundle
+ ref: ${{ env.WVB_REF }}
+ path: .webview-bundle
+ - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # 4.0.1
+ with:
+ install_args: 'node'
+ - run: corepack enable
+ - run: yarn install
+ - run: yarn install --mode=skip-build
+ working-directory: .webview-bundle
+ - run: yarn gen:references --repo .webview-bundle
+ - run: yarn gen:fumadocs
+ - run: yarn format content/docs/references/api
+ - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # 8.1.1
+ with:
+ branch: docs/regenerate-references
+ delete-branch: true
+ add-paths: content/docs/references/api
+ commit-message: 'docs(references): regenerate API references from webview-bundle@${{ env.WVB_REF }}'
+ title: 'docs(references): regenerate API references'
+ body: |
+ Auto-generated JavaScript API references from
+ [`webview-bundle@${{ env.WVB_REF }}`](https://github.com/webview-bundle/webview-bundle/tree/${{ env.WVB_REF }}).
+
+ Generated by `yarn gen:references` (TypeScript + JSDoc via TypeDoc) —
+ see `scripts/generate-references.ts` and `.github/workflows/references.yaml`.
diff --git a/.gitignore b/.gitignore
index af13e13..2ddc373 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,4 @@ dist/
# AI
.claude/
.env
+.webview-bundle/
diff --git a/WORKS.md b/WORKS.md
new file mode 100644
index 0000000..df04874
--- /dev/null
+++ b/WORKS.md
@@ -0,0 +1,111 @@
+# 26.07.04 문서 작업 : 1단계
+
+docs/guide/getting-started
+docs/guide/core-concepts
+
+위 두 경로에 있는 문서들이 아직 미완성 상태입니다.
+`(설명)`으로 감싼 부분은 들어가야 하는 내용에 대한 간단한 설명입니다.
+
+당신의 역할은 이미 작성된 문서를 리뷰하고 내용을 채워넣는 것입니다.
+
+- 이미 잡아둔 헤드라인 내에서만 문서를 채워주세요.
+- 명확하고 정확하고, 간결한 내용만 작성해주세요. 불필요한 첨언은 생략해주세요.
+- https://technical-writing.dev/tutorial/review-prompt.html 테크니컬 라이팅 기술을 참고하여 작성해주세요.
+- `.mdx`에는 영문을, `.ko.mdx`에는 한글로 작성합니다.
+- 이미 한글로 작성이 완료된 문서가 있다면, 영문 문서에 번역해서 기입해주세요.
+- OTA는 "원격"으로 번역해주세요.
+
+# 26.07.05 문서 작업 : 2단계
+
+content/docs/guide/frontend/**
+content/docs/guide/native/**
+
+이제 웹뷰 번들을 개발하는 단계에서 실질적인 가이드를 적을 예정입니다.
+위 두 경로의 문서들은 아래 내용을 포함해야 합니다.
+(파일명 - 대주제 - 섹션별 세부항목)
+
+- 네이티브
+ - index - 웹뷰 번들을 설치하기 위한 설정 : 패키지 설치, 요구사항, 시작하는 법(wvb 인스턴스 생성), Bundle Protocol / Source / Remote / Updater 설정법, 브릿지 노출방법, 이후 웹뷰를 띄우는 동작까지
+ - local-development - 로컬 개발환경 설정 : Local Protocol 설정으로 로컬 개발 서버를 웹뷰에 띄워서 개발하는 방법
+ - builtin - 빌트인 번들 설치 : 앱을 패키징할 때 빌트인 번들을 설치하는 방법 설명 / 리모트에서 다운받아 설치 / 로컬 파일을 설치
+- 프론트엔드
+ - packing - 번들 패키징 : 번들 패키징을 위한 설정 방법
+ - development - 개발 서버 띄워서 네이티브에서 개발 : Local Protocol 설정으로 로컬 개발 서버를 웹뷰에 띄우는 방법
+ - upload - 웹뷰 번들 업로드 하기 : 배포할 버전의 웹뷰 번들을 리모트에 업로드 하기
+ - deploy - 웹뷰 번들 배포하기 : 업르도된 웹뷰 번들을 현재 버전을 배포하기
+ - bridges
+ - index - 브릿지 설명 : 브릿지 패키지 설명 / 브릿지의 역할 및 제공하는 기능
+ - checkout-sources - Source 브릿지 사용법 : 번들 소스 유즈케이스 설명
+ - update-remote-bundles - Updater 브릿지 사용법 : 브릿지를 사용하여 리모트에서 원격으로 새 웹뷰 번들을 다운받고 설치하는 방법 설명
+ - testing - 테스트 방법 : 브릿지를 테스트하는 방법 설명 / mockInvoke 사용법
+
+문장의 어투는 기존 content/docs/getting-started, content/docs/core-concepts 에 있는 문서를 참고해주시고,
+간결하고 정확하게, 그리고 불필요한 내용이 거의 없게 (no verbose) 작성해주세요. 살은 제가 붙이겠습니다.
+
+API에 대한 자세한 스펙은 소스코드를 참고해주세요.
+
+- webview-bundle : /Users/seokju.me/workspaces/webview-bundle
+- android : /Users/seokju.me/workspaces/webview-bundle-android
+- ios : /Users/seokju.me/workspaces/webview-bundle-ios
+
+다른 참고할 레퍼런스 문서
+
+- Tauri : https://tauri.app/develop/
+- Capacitor : https://capacitorjs.com/docs
+- Expo : https://docs.expo.dev/
+
+# 26.07.05 문서 작업 : 3단계
+
+위치 : content/docs/guide/remote/\*\*
+
+이제 리모트 구성 및 리모트로부터 신규 웹뷰 번들을 안전하게 다운받아 설치하는 Over-The-Air를 사용하는 방법에 대한 가이드 문서를 작성하겠습니다.
+
+- remote
+ - index.mdx : 리모트가 왜 필요한지랑 목적에 대해서 간단하게 설명합니다. CLI로 리모트를 사용하는 방법, Providers를 사용하여 리모트 구성하는 방법에 대해 설명합니다.
+ - using-cli.mdx : CLI를 사용하여 리모트를 구성하고 신규 웹뷰 번들을 다운받아 설치하는 방법에 대한 가이드 문서입니다.
+ - providers
+ - aws.mdx : AWS에 리모트 서버를 구축하는 방법에 대한 가이드 (`@wvb/remote-aws-provider`) / 클라이언트에서 리모트 사용을 위한 가이드 (`@wvb/remote-aws`)
+ - cloudflare.mdx : Cloudflare에 리모트 서버를 구축하는 방법에 대한 가이드 (`@wvb/remote-cloudflare-provider`) / 클라이언트에서 리모트 사용을 위한 가이드 (`@wvb/remote-cloudflare`)
+ - local.mdx : 로컬에 리모트 서버를 구축하는 방법에 대한 가이드 (`@wvb/remote-local-provider`) / 클라이언트에서 리모트 사용을 위한 가이드 (`@wvb/remote-local`)
+ - http-spec
+ - index.mdx : 직접 리모트 서버를 구축할 때 HTTP 스펙에 대한 가이드입니다.
+ - endpoints
+ - (각 엔트리 포인트 별로 문서가 생성되고, OpenAPI 스펙과 유사한 디자인으로 문서를 만들어주세요.)
+ - errors.mdx : 리모트 서버에서 발생하는 에러에 대한 가이드입니다.
+
+문장의 어투는 기존 content/docs/getting-started, content/docs/core-concepts 에 있는 문서를 참고해주시고,
+간결하고 정확하게, 그리고 불필요한 내용이 거의 없게 (no verbose) 작성해주세요. 살은 제가 붙이겠습니다.
+
+---
+
+API에 대한 자세한 스펙은 소스코드를 참고해주세요.
+
+- webview-bundle : /Users/seokju.me/workspaces/webview-bundle
+- android : /Users/seokju.me/workspaces/webview-bundle-android
+- ios : /Users/seokju.me/workspaces/webview-bundle-ios
+
+다른 참고할 레퍼런스 문서
+
+- Tauri : https://tauri.app/develop/
+- Capacitor : https://capacitorjs.com/docs
+- Expo : https://docs.expo.dev/
+
+# 26.07.05 문서 작업 : 4단계
+
+References 문서 자동화를 구축합니다. 각 언어별 패키지들의 레퍼런스 문서화 전략은 다음과 같습니다.
+
+- JavaScript
+ - 리포지토리 https://github.com/webview-bundle/webview-bundle 에서 각 패키지 TypeScript + JSDoc 기반으로 레퍼런스 자동 생성
+- Rust : docs.rs 링크 external 처리
+- Android : javadoc.io 링크 external 처리
+ - 리포지토리 : https://github.com/webview-bundle/webview-bundle-android
+ - 소스 코드 : /Users/seokju.me/workspaces/webview-bundle-android
+- iOS : swift package index 링크 external 처리
+ - 리포지토리 : https://github.com/webview-bundle/webview-bundle-ios
+ - 소스 코드 : /Users/seokju.me/workspaces/webview-bundle-ios
+
+JavaScript 패키지들의 레퍼런스 문서 자동화는 다음과 같이 동작을 설계합니다.
+
+1. https://github.com/webview-bundle/webview-bundle 리포지토리에서 release 워크플로가 실행되고 이후 결과가 이 리포지토리(webview-bundle/website)로 dispatch event
+2. 이벤트를 받은 이후, latest 패키지들 기준으로 TypeScript + JSDoc 기반으로 레퍼런스 자동 생성
+3. 이후, 변경된 레퍼런스 정보를 신규 브랜치에서 커밋하여 PR을 자동 생성합니다.
diff --git a/content/docs/changelog/index.ko.mdx b/content/docs/changelog/index.ko.mdx
deleted file mode 100644
index 208453c..0000000
--- a/content/docs/changelog/index.ko.mdx
+++ /dev/null
@@ -1,41 +0,0 @@
----
-title: 변경 이력
-description: Webview Bundle 릴리스와 지금까지 배포된 버전을 추적합니다.
----
-
-릴리스는 패키지별로 발행됩니다. 따라서 공식 릴리스 노트는 각 레지스트리와 GitHub 릴리스 페이지에 있습니다.
-
-
-
-
-
-
-## 배포된 버전
-
-패키지별 현재 배포 버전은 다음과 같습니다.
-
-| Package | Registry | Version |
-| ------------------------ | ----------------------------------------------------------- | ------- |
-| `wvb` (Rust 코어) | [crates.io](https://crates.io/crates/wvb) | 0.2.0 |
-| `wvb-tauri` | [crates.io](https://crates.io/crates/wvb-tauri) | 0.1.0 |
-| `@wvb/cli` | [npm](https://www.npmjs.com/package/@wvb/cli) | 0.1.0 |
-| `@wvb/config` | [npm](https://www.npmjs.com/package/@wvb/config) | 0.1.0 |
-| `@wvb/node` | [npm](https://www.npmjs.com/package/@wvb/node) | 0.1.0 |
-| `@wvb/bridge` | [npm](https://www.npmjs.com/package/@wvb/bridge) | 0.1.0 |
-| `@wvb/electron` | [npm](https://www.npmjs.com/package/@wvb/electron) | 0.1.0 |
-| `@wvb/remote-aws` | [npm](https://www.npmjs.com/package/@wvb/remote-aws) | 0.1.0 |
-| `@wvb/remote-cloudflare` | [npm](https://www.npmjs.com/package/@wvb/remote-cloudflare) | 0.1.0 |
-
-## 버전 관리
-
-패키지는 [유의적 버전](https://semver.org)을 따릅니다. 재현 가능한 빌드를 위해 정확한 버전을 고정하세요. `.wvb`
-번들 포맷은 패키지 버전과 별개로 자체 포맷 버전(`v1`)을 가집니다 —
-[번들 포맷](/docs/guide/bundle-format)을 참고하세요.
diff --git a/content/docs/changelog/index.mdx b/content/docs/changelog/index.mdx
deleted file mode 100644
index 0ac4339..0000000
--- a/content/docs/changelog/index.mdx
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: Changelog
-description: Track Webview Bundle releases and the versions published so far.
----
-
-Releases are cut per package, so the authoritative release notes live with each registry and the
-GitHub release pages.
-
-
-
-
-
-
-## Published versions
-
-The current published versions, by package:
-
-| Package | Registry | Version |
-| ------------------------ | ----------------------------------------------------------- | ------- |
-| `wvb` (Rust core) | [crates.io](https://crates.io/crates/wvb) | 0.2.0 |
-| `wvb-tauri` | [crates.io](https://crates.io/crates/wvb-tauri) | 0.1.0 |
-| `@wvb/cli` | [npm](https://www.npmjs.com/package/@wvb/cli) | 0.1.0 |
-| `@wvb/config` | [npm](https://www.npmjs.com/package/@wvb/config) | 0.1.0 |
-| `@wvb/node` | [npm](https://www.npmjs.com/package/@wvb/node) | 0.1.0 |
-| `@wvb/bridge` | [npm](https://www.npmjs.com/package/@wvb/bridge) | 0.1.0 |
-| `@wvb/electron` | [npm](https://www.npmjs.com/package/@wvb/electron) | 0.1.0 |
-| `@wvb/remote-aws` | [npm](https://www.npmjs.com/package/@wvb/remote-aws) | 0.1.0 |
-| `@wvb/remote-cloudflare` | [npm](https://www.npmjs.com/package/@wvb/remote-cloudflare) | 0.1.0 |
-
-## Versioning
-
-Packages follow [semantic versioning](https://semver.org). Pin exact versions for reproducible
-builds. The `.wvb`
-bundle format carries its own format version (`v1`) independent of package versions — see the
-[bundle format](/docs/guide/bundle-format).
diff --git a/content/docs/changelog/meta.json b/content/docs/changelog/meta.json
deleted file mode 100644
index 48c9822..0000000
--- a/content/docs/changelog/meta.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "root": true,
- "title": "Changelog",
- "pages": ["index"]
-}
diff --git a/content/docs/changelog/meta.ko.json b/content/docs/changelog/meta.ko.json
deleted file mode 100644
index 0cca8eb..0000000
--- a/content/docs/changelog/meta.ko.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "root": true,
- "title": "변경 이력",
- "pages": ["index"]
-}
diff --git a/content/docs/config/index.ko.mdx b/content/docs/config/index.ko.mdx
deleted file mode 100644
index de045d9..0000000
--- a/content/docs/config/index.ko.mdx
+++ /dev/null
@@ -1,307 +0,0 @@
----
-title: 설정
-description: CLI와 프로그래밍 API가 pack, remote, serve, builtin 옵션을 읽어 오는 wvb.config 파일입니다.
----
-
-단 하나의 `wvb.config` 파일이 모든 Webview Bundle 워크플로를 이끕니다. `wvb` CLI와
-프로그래밍 API(`@wvb/cli/api`)가 모두 이 파일을 불러옵니다. 따라서 설정 하나로
-패킹, 제공, 원격 배포, 빌트인 설치를 일관되게 유지합니다. `@wvb/config`의
-`defineConfig`로 작성하면 완전한 타입 체크와 에디터 자동 완성을 얻습니다.
-
-이 페이지에서는 설정 파일 자체와 최상위 `pack`, `serve`, `builtin` 섹션을 다룹니다.
-원격 배포, 무결성, 서명 옵션은 별도 페이지에서 설명합니다.
-
-
-
-
-
-
-
-## 설정 파일
-
-설정 파일을 프로젝트 루트에 두세요. CLI는 작업 디렉터리에서 다음 순서로 처음 일치하는
-파일을 자동으로 찾습니다.
-
-```text
-wvb.config.js
-wvb.config.cjs
-wvb.config.mjs
-wvb.config.ts
-wvb.config.cts
-wvb.config.mts
-webview-bundle.config.js
-webview-bundle.config.cjs
-webview-bundle.config.mjs
-webview-bundle.config.ts
-webview-bundle.config.cts
-webview-bundle.config.mts
-wvb.config.json
-wvb.config.jsonc
-```
-
-`wvb.config.*`와 `webview-bundle.config.*` 두 기본 이름 모두 `.js`, `.cjs`, `.mjs`,
-`.ts`, `.cts`, `.mts` 확장자를 지원합니다. `.json`과 `.jsonc` 형식은 `wvb.config`
-기본 이름에서만 인식됩니다. 자동 탐색에 의존하는 대신 특정 파일을 지정하려면 어떤
-명령어에든 `--config`(별칭 `-C`)를 전달하세요.
-
-```sh
-wvb pack --config ./configs/wvb.prod.ts
-```
-
-### defineConfig
-
-설정을 `@wvb/config`의 `defineConfig`로 감싸세요. 이 함수는 런타임에서는 항등 함수이며,
-타입을 붙이는 것이 유일한 역할입니다. 객체, 설정으로 이행되는 프로미스, 또는 설정을
-반환하는 동기 혹은 비동기 함수를 받습니다. 따라서 불러오는 시점에 값을 계산할 수 있습니다.
-
-
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- root: process.cwd(),
- pack: {
- srcDir: './dist',
- },
- serve: {
- port: 4312,
- },
-});
-```
-
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig(async () => {
- const endpoint = process.env.WVB_ENDPOINT;
- return {
- remote: {
- endpoint,
- },
- };
-});
-```
-
-
-
-이 패키지를 개발 의존성으로 설치하세요: `npm install -D @wvb/config`.
-
-## 최상위 필드
-
-설정 객체는 다섯 개의 선택 필드를 가집니다. 최상위에서는 그 외에 아무것도 읽지 않습니다.
-
-| Field | Type | Default |
-| --------- | --------------- | --------------- |
-| `root` | `string` | `process.cwd()` |
-| `pack` | `PackConfig` | — |
-| `remote` | `RemoteConfig` | — |
-| `serve` | `ServeConfig` | — |
-| `builtin` | `BuiltinConfig` | — |
-
-`root`는 상대 경로를 해석하는 데 사용하는 프로젝트 루트를 설정합니다. 절대 경로일 수도
-있고 설정 파일 기준 상대 경로일 수도 있습니다. `remote` 필드는
-[원격, 무결성 및 서명](/docs/config/remote) 페이지에서 설명합니다.
-
-## pack
-
-`pack`(`PackConfig`)은 `wvb pack`의 기본값을 설정합니다. CLI 플래그는 호출할 때마다
-이 값을 재정의합니다.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- pack: {
- srcDir: './dist',
- outFile: '.wvb/app',
- overwrite: true,
- ignore: ['*.map', /\.DS_Store$/],
- headers: {
- '*.html': { 'cache-control': 'max-age=0' },
- '*.js': { 'cache-control': 'max-age=31536000' },
- },
- },
-});
-```
-
-| Field | Type | Default |
-| ----------- | ------------------------------------------------------------------------------------ | ------------- |
-| `srcDir` | `string` | `./dist` |
-| `outFile` | `string` | `.wvb/` |
-| `overwrite` | `boolean` | `true` |
-| `ignore` | `Array \| ((file: string) => boolean \| Promise)` | — |
-| `headers` | `Record \| Array<[glob, HeadersInit]> \| ((file) => HeadersInit)` | — |
-
-`outFile`은 단일 출력 경로입니다. 확장자를 생략하면 `.wvb` 확장자가 자동으로 붙습니다.
-경로가 절대 경로가 아니면 `root` 기준 상대 경로로 해석됩니다. 기본값 `.wvb/`은
-`package.json`의 name에서 스코프 접두사를 제거하여 ``을 도출합니다.
-
-
- 이 필드는 완전한 경로인 `outFile`입니다. `pack`에는 `outFileName`이나 `outDir`가 없습니다. 하위
- 디렉터리를 포함한 전체 출력 경로를 `outFile`에 작성하세요.
-
-
-`ignore`는 글로브와 정규식의 배열, 또는 불리언을 반환하는 프레디킷(선택적으로 비동기)을
-받습니다. `headers`는 일치하는 파일에 HTTP 헤더를 붙이며 세 가지 형태를 받습니다.
-
-```ts
-// 1. 글로브를 키로 갖는 Record
-headers: {
- '*.html': { 'cache-control': 'max-age=3600' },
-}
-
-// 2. [glob, HeadersInit] 튜플의 배열
-headers: [
- ['*.html', { 'cache-control': 'max-age=3600' }],
- ['*.png', [['cache-control', 'max-age=0']]], // HeadersInit는 [name, value] 쌍도 받습니다
-]
-
-// 3. 파일별로 헤더를 반환하는 함수
-headers: (file) => (file.endsWith('.html') ? { 'cache-control': 'max-age=0' } : undefined)
-```
-
-## serve
-
-`serve`(`ServeConfig`)는 `wvb serve`의 기본값을 설정합니다. `wvb serve`는 개발용으로
-패킹된 번들을 제공하는 로컬 서버입니다.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- serve: {
- file: './.wvb/app.wvb',
- port: 4312,
- silent: false,
- },
-});
-```
-
-| Field | Type | Default |
-| -------- | --------- | ----------------------- |
-| `file` | `string` | the `pack.outFile` path |
-| `port` | `number` | `4312` |
-| `silent` | `boolean` | — |
-
-`file`을 생략하면 해석된 `pack.outFile` 경로로 대체됩니다. 요청 로그 출력을 끄려면
-`silent`를 설정하세요.
-
-
- `serve`에는 `hostname` 필드가 없습니다. 다른 호스트에 바인딩하려면 명령줄에서 `wvb serve`에
- `--hostname`(별칭 `-H`)을 전달하세요.
-
-
-## builtin
-
-`builtin`(`BuiltinConfig`)은 `wvb builtin`의 기본값을 설정합니다. `wvb builtin`은 앱
-안에 함께 배포되는 번들을 설치합니다. 번들은 원격에서 다운로드하거나 로컬 워크스페이스에서
-수집합니다.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- builtin: {
- outDir: '.wvb/builtin/bundles',
- target: { type: 'remote' },
- include: ['app*'],
- exclude: [/^internal-/],
- clean: true,
- },
-});
-```
-
-| Field | Type | Default |
-| --------- | -------------------------------------------------------------------- | ---------------------- |
-| `outDir` | `string` | `.wvb/builtin/bundles` |
-| `target` | `BuiltinTarget` | `{ type: 'remote' }` |
-| `include` | `string \| RegExp \| Array \| ((info) => boolean)` | — |
-| `exclude` | `string \| RegExp \| Array \| ((info) => boolean)` | — |
-| `clean` | `boolean` | `true` |
-
-`include`와 `exclude`는 후보 번들을 필터링합니다. 각 필드는 글로브 문자열, 정규식, 둘 중
-하나의 배열, 또는 프레디킷 `(info: { name: string; version: string }) => boolean`(선택적으로
-비동기)을 받습니다. `clean`을 활성화하면 설치 전에 출력 디렉터리를 비웁니다.
-
-### target
-
-`target`은 `type`을 기준으로 하는 판별 유니온입니다. 서버에서 번들을 다운로드하려면
-`remote`를, 저장소의 워크스페이스에서 번들을 수집하려면 `local`을 선택하세요.
-
-```ts
-// remote 타겟 — 서버에서 다운로드
-target: {
- type: 'remote',
- endpoint: 'https://updates.example.com',
- download: {
- concurrency: 4,
- // http: { /* @wvb/node의 HttpOptions */ },
- },
-}
-
-// local 타겟 — 워크스페이스에서 수집
-target: {
- type: 'local',
- workspaces: ['packages/*'],
- bundleName: { from: 'package.json' },
- version: { from: 'package.json' },
- packBeforeInstall: true,
-}
-```
-
-`remote` 타겟에서 `endpoint`와 `download`는 선택 사항입니다. 유일한 동시성 조절 값은
-`download.concurrency`입니다. `download.http`는 [`@wvb/node`](/docs/references/node)의
-`HttpOptions` 타입을 받습니다.
-
-| `remote` field | Type | Default |
-| ---------------------- | ------------- | ------- |
-| `endpoint` | `string` | — |
-| `download.concurrency` | `number` | — |
-| `download.http` | `HttpOptions` | — |
-
-`local` 타겟에서 `workspaces`는 필수입니다. 이는 설정 전체에서 유일하게 필수인 필드입니다.
-`integrity`와 `signature` 옵션은 remote 섹션과 동일한 형태를 공유합니다.
-[원격, 무결성 및 서명](/docs/config/remote)을 참고하세요.
-
-| `local` field | Type | Default |
-| ------------------- | --------------------------------------------------- | -------- |
-| `workspaces` | `string[] \| (() => string[] \| Promise)` | required |
-| `bundleName` | `BundleNameResolver` | — |
-| `version` | `VersionResolver` | — |
-| `integrity` | `boolean \| IntegrityMakeConfig` | — |
-| `signature` | `SignatureSignConfig` | — |
-| `packBeforeInstall` | `boolean` | `true` |
-
-## 다음 단계
-
-
-
-
-
-
diff --git a/content/docs/config/index.mdx b/content/docs/config/index.mdx
deleted file mode 100644
index 2985ebc..0000000
--- a/content/docs/config/index.mdx
+++ /dev/null
@@ -1,314 +0,0 @@
----
-title: Configuration
-description: The wvb.config file that the CLI and the programmatic API read for pack, remote, serve, and builtin options.
----
-
-A single `wvb.config` file drives every Webview Bundle workflow. Both the `wvb` CLI
-and the programmatic API (`@wvb/cli/api`) load it, so one config keeps packing,
-serving, remote publishing, and builtin installs consistent. Author it with
-`defineConfig` from `@wvb/config` to get full type-checking and editor autocompletion.
-
-This page covers the config file itself and the top-level `pack`, `serve`, and
-`builtin` sections. Remote publishing, integrity, and signature options live on a
-dedicated page.
-
-
-
-
-
-
-
-## The config file
-
-Place the config in your project root. The CLI auto-discovers the first matching
-file in the working directory, in this order:
-
-```text
-wvb.config.js
-wvb.config.cjs
-wvb.config.mjs
-wvb.config.ts
-wvb.config.cts
-wvb.config.mts
-webview-bundle.config.js
-webview-bundle.config.cjs
-webview-bundle.config.mjs
-webview-bundle.config.ts
-webview-bundle.config.cts
-webview-bundle.config.mts
-wvb.config.json
-wvb.config.jsonc
-```
-
-Both base names `wvb.config.*` and `webview-bundle.config.*` are supported with the
-`.js`, `.cjs`, `.mjs`, `.ts`, `.cts`, and `.mts` extensions. The `.json` and `.jsonc`
-forms are only recognized for the `wvb.config` base name. Pass `--config` (alias
-`-C`) to any command to point at a specific file instead of relying on discovery.
-
-```sh
-wvb pack --config ./configs/wvb.prod.ts
-```
-
-### defineConfig
-
-Wrap your config with `defineConfig` from `@wvb/config`. It is an identity function
-at runtime — its only job is to attach types. It accepts an object, a promise that
-resolves to a config, or a synchronous or asynchronous function that returns one, so
-you can compute values at load time.
-
-
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- root: process.cwd(),
- pack: {
- srcDir: './dist',
- },
- serve: {
- port: 4312,
- },
-});
-```
-
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig(async () => {
- const endpoint = process.env.WVB_ENDPOINT;
- return {
- remote: {
- endpoint,
- },
- };
-});
-```
-
-
-
-
- Install the package as a dev dependency: `npm install -D @wvb/config`.
-
-
-## Top-level fields
-
-The config object has five optional fields. Nothing else is read at the top level.
-
-| Field | Type | Default |
-| --------- | --------------- | --------------- |
-| `root` | `string` | `process.cwd()` |
-| `pack` | `PackConfig` | — |
-| `remote` | `RemoteConfig` | — |
-| `serve` | `ServeConfig` | — |
-| `builtin` | `BuiltinConfig` | — |
-
-`root` sets the project root used to resolve relative paths. It may be absolute or
-relative to the config file. The `remote` field is documented on the
-[Remote, integrity & signature](/docs/config/remote) page.
-
-## pack
-
-`pack` (`PackConfig`) sets the defaults for `wvb pack`. CLI flags override these per
-invocation.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- pack: {
- srcDir: './dist',
- outFile: '.wvb/app',
- overwrite: true,
- ignore: ['*.map', /\.DS_Store$/],
- headers: {
- '*.html': { 'cache-control': 'max-age=0' },
- '*.js': { 'cache-control': 'max-age=31536000' },
- },
- },
-});
-```
-
-| Field | Type | Default |
-| ----------- | ------------------------------------------------------------------------------------ | ------------- |
-| `srcDir` | `string` | `./dist` |
-| `outFile` | `string` | `.wvb/` |
-| `overwrite` | `boolean` | `true` |
-| `ignore` | `Array \| ((file: string) => boolean \| Promise)` | — |
-| `headers` | `Record \| Array<[glob, HeadersInit]> \| ((file) => HeadersInit)` | — |
-
-`outFile` is a single output path. The `.wvb` extension is appended automatically
-when you omit it, and the path resolves relative to `root` unless it is absolute. The
-default `.wvb/` derives `` from your `package.json` name with any scope
-prefix stripped.
-
-
- The field is `outFile`, a complete path. There is no `outFileName` or `outDir` on `pack` — write
- the full output path, including any subdirectory, in `outFile`.
-
-
-`ignore` accepts an array of globs and regular expressions, or a predicate that
-returns a boolean (optionally async). `headers` attaches HTTP headers to matching
-files and accepts three shapes:
-
-```ts
-// 1. Record keyed by glob
-headers: {
- '*.html': { 'cache-control': 'max-age=3600' },
-}
-
-// 2. Array of [glob, HeadersInit] tuples
-headers: [
- ['*.html', { 'cache-control': 'max-age=3600' }],
- ['*.png', [['cache-control', 'max-age=0']]], // HeadersInit also accepts [name, value] pairs
-]
-
-// 3. Function returning headers per file
-headers: (file) => (file.endsWith('.html') ? { 'cache-control': 'max-age=0' } : undefined)
-```
-
-## serve
-
-`serve` (`ServeConfig`) sets the defaults for `wvb serve`, the local server that
-serves a packed bundle for development.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- serve: {
- file: './.wvb/app.wvb',
- port: 4312,
- silent: false,
- },
-});
-```
-
-| Field | Type | Default |
-| -------- | --------- | ----------------------- |
-| `file` | `string` | the `pack.outFile` path |
-| `port` | `number` | `4312` |
-| `silent` | `boolean` | — |
-
-`file` falls back to the resolved `pack.outFile` path when omitted. Set `silent` to
-disable request-log output.
-
-
- `serve` has no `hostname` field. To bind a different host, pass `--hostname` (alias `-H`) to `wvb
- serve` on the command line.
-
-
-## builtin
-
-`builtin` (`BuiltinConfig`) sets the defaults for `wvb builtin`, which installs
-bundles that ship inside your app — either downloaded from a remote or collected from
-local workspaces.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- builtin: {
- outDir: '.wvb/builtin/bundles',
- target: { type: 'remote' },
- include: ['app*'],
- exclude: [/^internal-/],
- clean: true,
- },
-});
-```
-
-| Field | Type | Default |
-| --------- | -------------------------------------------------------------------- | ---------------------- |
-| `outDir` | `string` | `.wvb/builtin/bundles` |
-| `target` | `BuiltinTarget` | `{ type: 'remote' }` |
-| `include` | `string \| RegExp \| Array \| ((info) => boolean)` | — |
-| `exclude` | `string \| RegExp \| Array \| ((info) => boolean)` | — |
-| `clean` | `boolean` | `true` |
-
-`include` and `exclude` filter the candidate bundles. Each accepts a glob string, a
-regular expression, an array of either, or a predicate
-`(info: { name: string; version: string }) => boolean` (optionally async). With
-`clean` enabled, the output directory is cleared before install.
-
-### target
-
-`target` is a discriminated union on `type`. Choose `remote` to download bundles from
-a server, or `local` to collect them from workspaces in your repository.
-
-```ts
-// Remote target — download from a server
-target: {
- type: 'remote',
- endpoint: 'https://updates.example.com',
- download: {
- concurrency: 4,
- // http: { /* HttpOptions from @wvb/node */ },
- },
-}
-
-// Local target — collect from workspaces
-target: {
- type: 'local',
- workspaces: ['packages/*'],
- bundleName: { from: 'package.json' },
- version: { from: 'package.json' },
- packBeforeInstall: true,
-}
-```
-
-For the `remote` target, `endpoint` and `download` are optional. The only concurrency
-knob is `download.concurrency`; `download.http` accepts the `HttpOptions` type from
-[`@wvb/node`](/docs/references/node).
-
-| `remote` field | Type | Default |
-| ---------------------- | ------------- | ------- |
-| `endpoint` | `string` | — |
-| `download.concurrency` | `number` | — |
-| `download.http` | `HttpOptions` | — |
-
-For the `local` target, `workspaces` is required — it is the only required field
-anywhere in the config. The `integrity` and `signature` options share the same shapes
-as the remote section; see [Remote, integrity & signature](/docs/config/remote).
-
-| `local` field | Type | Default |
-| ------------------- | --------------------------------------------------- | -------- |
-| `workspaces` | `string[] \| (() => string[] \| Promise)` | required |
-| `bundleName` | `BundleNameResolver` | — |
-| `version` | `VersionResolver` | — |
-| `integrity` | `boolean \| IntegrityMakeConfig` | — |
-| `signature` | `SignatureSignConfig` | — |
-| `packBeforeInstall` | `boolean` | `true` |
-
-## Next steps
-
-
-
-
-
-
diff --git a/content/docs/config/meta.json b/content/docs/config/meta.json
deleted file mode 100644
index 578a090..0000000
--- a/content/docs/config/meta.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "root": true,
- "title": "Config",
- "pages": ["index", "remote"]
-}
diff --git a/content/docs/config/meta.ko.json b/content/docs/config/meta.ko.json
deleted file mode 100644
index 71280a9..0000000
--- a/content/docs/config/meta.ko.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "root": true,
- "title": "설정",
- "pages": ["index", "remote"]
-}
diff --git a/content/docs/guide/bundle-format.ko.mdx b/content/docs/guide/bundle-format.ko.mdx
deleted file mode 100644
index 5bbe29f..0000000
--- a/content/docs/guide/bundle-format.ko.mdx
+++ /dev/null
@@ -1,165 +0,0 @@
----
-title: 번들 포맷
-description: .wvb 아카이브가 디스크에 섹션별로 어떻게 배치되는지, 그리고 체크섬이 손상으로부터 아카이브를 어떻게 보호하는지 설명합니다.
----
-
-Webview Bundle은 빌드된 웹 자산을 압축하고 무결성을 검증한 하나의 아카이브로 묶은 단일 `.wvb` 파일입니다. 세 개의 섹션이 연속으로 기록됩니다. 고정 길이 17바이트 헤더 뒤에 가변 길이의 인덱스와 데이터 영역이 이어집니다.
-
-| Offset | Section | Size | 담는 내용 |
-| ----------- | ------- | ---------- | ----------------------------------------- |
-| `0` | Header | `17 bytes` | 매직 넘버, 포맷 버전, 인덱스 크기, 체크섬 |
-| `17` | Index | variable | 경로 → 오프셋/길이/헤더 엔트리 맵 |
-| after index | Data | variable | 경로순으로 정렬된 LZ4 압축 파일 내용 |
-
-각 섹션은 자체 체크섬을 가지므로, 잘리거나 손상된 아카이브는 내용을 제공하기 전에 걸러집니다. 번들을 만들려면 [CLI](/docs/guide/cli) 또는 [Node API](/docs/references/node)를 참고하세요.
-
-```ts title="build.ts"
-import { readFile } from 'node:fs/promises';
-import { BundleBuilder, writeBundle } from '@wvb/node';
-
-const builder = new BundleBuilder();
-builder.insertEntry('/index.html', await readFile('dist/index.html'), 'text/html');
-builder.insertEntry('/app.js', await readFile('dist/app.js'), 'application/javascript');
-
-const bundle = builder.build();
-await writeBundle(bundle, '.wvb/app_1.0.0.wvb');
-```
-
-관례상 패킹된 파일은 `_.wvb` 형식으로 이름을 짓습니다. 예를 들어 `app_1.0.0.wvb`입니다. 번들 이름과 버전은 소스 수준의 개념으로, 파일명과 매니페스트에 존재할 뿐 아카이브 내부에는 들어가지 않습니다. [번들 소스](/docs/guide/bundle-sources)를 참고하세요.
-
-## 헤더
-
-헤더는 정확히 17바이트이며 고정 레이아웃을 가집니다. 직렬화기를 거치지 않고 직접 기록하므로, 모든 필드가 알려진 오프셋에 자리합니다.
-
-| Field | Offset | Length | Encoding |
-| ------------ | ------ | ------ | ------------------------------ |
-| Magic number | 0 | 8 | raw bytes `0xF09F8C90F09F8E81` |
-| Version | 8 | 1 | single byte (포맷 버전) |
-| Index size | 9 | 4 | `u32`, big-endian |
-| Checksum | 13 | 4 | `u32`, big-endian xxHash-32 |
-
-인덱스 크기가 `1234`인 `V1` 번들의 경우, 17바이트는 다음과 같습니다.
-
-```text
-F0 9F 8C 90 F0 9F 8E 81 magic (🌐🎁)
-01 version (V1)
-00 00 04 D2 index size = 1234, big-endian
-31 38 03 10 xxHash-32 of the first 13 bytes
-```
-
-- 매직 넘버 `0xF09F8C90F09F8E81`은 🌐🎁(지구본 + 포장된 선물)의 UTF-8 인코딩입니다. 이 바이트를 찾지 못한 리더는 해당 파일을 번들이 아닌 것으로 판단해 거부합니다.
-- 버전 바이트는 번들의 **포맷 버전**을 담습니다. 이 enum은 현재 `V1`이라는 단일 값만 가집니다. 이는 `1.0.0` 같은 번들의 릴리스 버전과는 별개이며, 릴리스 버전은 아카이브 내부에 절대 나타나지 않습니다.
-- 인덱스 크기는 뒤따르는 인덱스의 바이트 길이입니다. 인덱스 자체의 후행 체크섬은 여기에 **포함되지 않습니다**.
-- 체크섬은 처음 13바이트(체크섬 앞의 모든 바이트)에 대한 xxHash-32이며, big-endian으로 저장됩니다.
-
-디스크립터를 통해 헤더 필드를 다시 읽어올 수 있습니다.
-
-```ts
-import { readBundle } from '@wvb/node';
-
-const header = (await readBundle('app_1.0.0.wvb')).descriptor().header();
-header.version(); // 'v1'
-header.indexSize(); // 인덱스의 바이트 길이
-header.indexEndOffset(); // 17 + indexSize + 4 -> 데이터 섹션이 시작되는 위치
-```
-
-## 인덱스
-
-인덱스는 헤더 바로 뒤에 위치하며, 각 파일 경로(예: `/index.html`)를 해당 파일의 바이트 위치와 제공 방식을 설명하는 엔트리로 매핑합니다. 각 엔트리는 다음을 담습니다.
-
-| Field | Type | 의미 |
-| ---------------- | ------ | ------------------------------------------------- |
-| `offset` | `u64` | 데이터 섹션 내에서 이 파일 데이터의 바이트 오프셋 |
-| `len` | `u64` | **압축된** 바이트의 길이 |
-| `content_type` | string | MIME 타입. 제공 시 `Content-Type` 헤더에 사용됨 |
-| `content_length` | `u64` | 원본 **비압축** 크기. `Content-Length`에 사용됨 |
-| `headers` | map | 파일 제공 시 재생할 HTTP 헤더 이름/값 쌍 |
-
-```ts
-const index = (await readBundle('app_1.0.0.wvb')).descriptor().index();
-
-index.containsPath('/index.html'); // true
-index.getEntry('/index.html');
-// {
-// offset: 0,
-// len: 184, // 압축됨
-// contentType: 'text/html',
-// contentLength: 412, // 비압축
-// headers: { 'cache-control': 'no-cache' },
-// isEmpty: false,
-// }
-```
-
-직렬화 세부 사항:
-
-- big-endian 가변 길이 정수 구성으로 [bincode](https://github.com/bincode-org/bincode)를 사용해 인코딩합니다. 숫자 필드는 고정 폭이 아니라 varint로 인코딩됩니다.
-- 맵은 인코딩 전에 경로순으로 정렬됩니다. 따라서 출력 바이트는 결정적이며 삽입 순서와 무관합니다.
-- 인덱스 바이트에 대한 4바이트 big-endian xxHash-32 체크섬이 인덱스 바로 뒤에 기록됩니다.
-
-프로토콜 핸들러가 파일을 제공할 때는 저장된 `headers`를 재생한 다음, `content_type`으로 `Content-Type`을, `content_length`(비압축 크기)로 `Content-Length`를 설정합니다. URL이 엔트리로 어떻게 매핑되는지는 [프로토콜 처리](/docs/guide/protocol-handling)를 참고하세요.
-
-## 데이터
-
-데이터 섹션은 인덱스와 동일한 순서로, 경로순으로 정렬된 파일 내용을 담습니다. 각 파일은 LZ4로 압축된 바이트와 그 압축 바이트에 대한 4바이트 big-endian xxHash-32 체크섬으로 구성됩니다.
-
-```text
-[ LZ4 bytes for file A ][ xxHash-32 ][ LZ4 bytes for file B ][ xxHash-32 ] ...
-```
-
-파일은 크기를 앞에 붙이는 블록 포맷으로 [LZ4](https://github.com/lz4/lz4)를 사용해 압축됩니다. 따라서 비압축 길이가 압축 데이터와 함께 전달됩니다. 엔트리의 `offset`은 압축 바이트의 시작을 가리키고, `len`은 그 바이트의 길이이며, 파일별 체크섬은 `offset + len` 위치에 자리합니다.
-
-`getData`는 압축 해제된 바이트를 반환하고, `getDataChecksum`은 저장된 xxHash-32를 반환합니다. 호출자는 이를 사용해 정지 상태의 파일을 검증할 수 있습니다.
-
-```ts
-const bundle = await readBundle('app_1.0.0.wvb');
-
-bundle.getData('/index.html'); // Buffer (압축 해제됨) | null
-bundle.getDataChecksum('/index.html'); // number (xxHash-32) | null
-```
-
-## 체크섬과 무결성
-
-이 포맷은 서로 다른 두 가지 작업을 위해 두 가지 해싱 방식을 사용합니다.
-
-헤더, 인덱스, 파일별 데이터에 들어가는 **내부 체크섬**은 모두 **xxHash-32**입니다. 이들은 저장 무결성을 지킵니다. 파일이 잘리거나, 일부만 기록되거나, 디스크에서 손상되는 경우를 막습니다. xxHash는 빠르고 비암호학적이며, 이것이 바로 손상 검사에 필요한 특성입니다.
-
-번들의 **무결성** 값은 이와 다릅니다. 다운로드된 번들의 직렬화된 바이트에 대한 **SHA-2** 해시로, 클라이언트가 서버가 게시한 것을 정확히 받았는지 확인하는 데 쓰입니다. 알고리즘은 `sha256`(기본값), `sha384`, `sha512`이며, `:` 형식으로 직렬화됩니다.
-
-```text
-sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-sha384:Ws2q...
-```
-
-```ts
-import type { IntegrityAlgorithm, IntegrityPolicy } from '@wvb/node';
-// IntegrityAlgorithm = 'sha256' | 'sha384' | 'sha512'
-// IntegrityPolicy = 'strict' | 'optional' | 'none' (기본값: 'optional')
-```
-
-
- 이 포맷은 무결성에 SHA-3가 아니라 **SHA-2**를 사용합니다. 두 작업은 서로 구별됩니다. xxHash-32는
- 정지 상태의 바이트를 보호하고, SHA-2 무결성은 다운로드한 내용을 검증합니다. 업데이트 중에 무결성과
- 서명이 어떻게 검사되는지는 [원격 번들](/docs/guide/remote-bundles)을 참고하세요.
-
-
-## 다음으로 볼 내용
-
-정확한 상수와 필드 레이아웃을 포함한 바이트 수준 명세는 [docs.rs/wvb](https://docs.rs/wvb)의 Rust 크레이트 문서에 있습니다.
-
-
-
-
-
-
diff --git a/content/docs/guide/bundle-format.mdx b/content/docs/guide/bundle-format.mdx
deleted file mode 100644
index 4d6ebf0..0000000
--- a/content/docs/guide/bundle-format.mdx
+++ /dev/null
@@ -1,189 +0,0 @@
----
-title: Bundle format
-description: How a .wvb archive is laid out on disk, section by section, and how its checksums protect against corruption.
----
-
-A Webview Bundle is a single `.wvb` file that packs your built web assets into one compressed,
-integrity-checked archive. It is three sections written back to back — a fixed 17-byte header followed
-by a variable-length index and data region:
-
-| Offset | Section | Size | Holds |
-| ----------- | ------- | ---------- | -------------------------------------------------- |
-| `0` | Header | `17 bytes` | magic number, format version, index size, checksum |
-| `17` | Index | variable | path → offset/length/headers entry map |
-| after index | Data | variable | LZ4-compressed file contents, sorted by path |
-
-Every section carries its own checksum, so a truncated or corrupted archive is caught before any content
-is served. To produce one, see the [CLI](/docs/guide/cli) or the [Node API](/docs/references/node):
-
-```ts title="build.ts"
-import { readFile } from 'node:fs/promises';
-import { BundleBuilder, writeBundle } from '@wvb/node';
-
-const builder = new BundleBuilder();
-builder.insertEntry('/index.html', await readFile('dist/index.html'), 'text/html');
-builder.insertEntry('/app.js', await readFile('dist/app.js'), 'application/javascript');
-
-const bundle = builder.build();
-await writeBundle(bundle, '.wvb/app_1.0.0.wvb');
-```
-
-By convention a packed file is named `_.wvb`, e.g. `app_1.0.0.wvb`. The bundle name and
-version are source-level concepts that live in the filename and the manifest, never inside the archive.
-See [Bundle sources](/docs/guide/bundle-sources).
-
-## Header
-
-The header is exactly 17 bytes with a fixed layout, written by hand rather than through a serializer, so
-every field sits at a known offset.
-
-| Field | Offset | Length | Encoding |
-| ------------ | ------ | ------ | ------------------------------ |
-| Magic number | 0 | 8 | raw bytes `0xF09F8C90F09F8E81` |
-| Version | 8 | 1 | single byte (format version) |
-| Index size | 9 | 4 | `u32`, big-endian |
-| Checksum | 13 | 4 | `u32`, big-endian xxHash-32 |
-
-For a `V1` bundle with an index size of `1234`, the 17 bytes are:
-
-```text
-F0 9F 8C 90 F0 9F 8E 81 magic (🌐🎁)
-01 version (V1)
-00 00 04 D2 index size = 1234, big-endian
-31 38 03 10 xxHash-32 of the first 13 bytes
-```
-
-- The magic number `0xF09F8C90F09F8E81` is the UTF-8 encoding of 🌐🎁 (globe + wrapped gift). A reader
- that does not find these bytes rejects the file as not a bundle.
-- The version byte holds the bundle **format version**. The enum currently has a single value, `V1`.
- This is distinct from a bundle's release version such as `1.0.0`, which never appears inside the archive.
-- The index size is the byte length of the index that follows, **not** counting the index's own trailing
- checksum.
-- The checksum is an xxHash-32 over the first 13 bytes (everything before the checksum), stored big-endian.
-
-Read header fields back through the descriptor:
-
-```ts
-import { readBundle } from '@wvb/node';
-
-const header = (await readBundle('app_1.0.0.wvb')).descriptor().header();
-header.version(); // 'v1'
-header.indexSize(); // byte length of the index
-header.indexEndOffset(); // 17 + indexSize + 4 -> where the data section starts
-```
-
-## Index
-
-The index sits immediately after the header and maps each file path, e.g. `/index.html`, to an entry
-describing where its bytes are and how to serve them. Each entry holds:
-
-| Field | Type | Meaning |
-| ---------------- | ------ | -------------------------------------------------------------- |
-| `offset` | `u64` | byte offset of this file's data within the data section |
-| `len` | `u64` | length of the **compressed** bytes |
-| `content_type` | string | MIME type, used for the `Content-Type` header when served |
-| `content_length` | `u64` | original **uncompressed** size, used for `Content-Length` |
-| `headers` | map | HTTP header name/value pairs to replay when the file is served |
-
-```ts
-const index = (await readBundle('app_1.0.0.wvb')).descriptor().index();
-
-index.containsPath('/index.html'); // true
-index.getEntry('/index.html');
-// {
-// offset: 0,
-// len: 184, // compressed
-// contentType: 'text/html',
-// contentLength: 412, // uncompressed
-// headers: { 'cache-control': 'no-cache' },
-// isEmpty: false,
-// }
-```
-
-Serialization details:
-
-- Encoded with [bincode](https://github.com/bincode-org/bincode) in a big-endian, variable-length integer
- configuration. Numeric fields are varint-encoded, not fixed width.
-- The map is sorted by path before encoding, so the output bytes are deterministic and independent of
- insertion order.
-- A 4-byte big-endian xxHash-32 checksum of the index bytes is written right after the index.
-
-When the protocol handler serves a file, it replays the stored `headers`, then sets `Content-Type` from
-`content_type` and `Content-Length` from `content_length` (the uncompressed size). See
-[Protocol handling](/docs/guide/protocol-handling) for how a URL maps to an entry.
-
-## Data
-
-The data section holds file contents sorted by path, in the same order as the index. Each file is its
-LZ4-compressed bytes followed by a 4-byte big-endian xxHash-32 checksum of those compressed bytes:
-
-```text
-[ LZ4 bytes for file A ][ xxHash-32 ][ LZ4 bytes for file B ][ xxHash-32 ] ...
-```
-
-Files are compressed with [LZ4](https://github.com/lz4/lz4) in its size-prepended block format, so the
-uncompressed length travels with the compressed data. An entry's `offset` points at the start of its
-compressed bytes, `len` is the length of those bytes, and the per-file checksum sits at `offset + len`.
-
-`getData` returns the decompressed bytes; `getDataChecksum` returns the stored xxHash-32 so callers can
-verify a file at rest:
-
-```ts
-const bundle = await readBundle('app_1.0.0.wvb');
-
-bundle.getData('/index.html'); // Buffer (decompressed) | null
-bundle.getDataChecksum('/index.html'); // number (xxHash-32) | null
-```
-
-## Checksums versus integrity
-
-The format uses two different hashing schemes for two different jobs.
-
-**Internal checksums** in the header, index, and per-file data are all **xxHash-32**. They guard storage
-integrity: a file truncated, partially written, or corrupted on disk. xxHash is fast and
-non-cryptographic, which is exactly what a corruption check needs.
-
-A bundle's **integrity** value is something else: a **SHA-2** hash over the serialized bytes of a
-downloaded bundle, so a client can confirm it received exactly what the server published. Algorithms are
-`sha256` (default), `sha384`, and `sha512`, serialized as `:`:
-
-```text
-sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-sha384:Ws2q...
-```
-
-```ts
-import type { IntegrityAlgorithm, IntegrityPolicy } from '@wvb/node';
-// IntegrityAlgorithm = 'sha256' | 'sha384' | 'sha512'
-// IntegrityPolicy = 'strict' | 'optional' | 'none' (default: 'optional')
-```
-
-
- The format uses **SHA-2** for integrity, not SHA-3. The two jobs are distinct: xxHash-32 protects
- bytes at rest, while SHA-2 integrity verifies what was downloaded. See [Remote
- bundles](/docs/guide/remote-bundles) for how integrity and signatures are checked during an
- update.
-
-
-## Where to go next
-
-The byte-level specification, including exact constants and field layouts, lives in the Rust crate
-documentation at [docs.rs/wvb](https://docs.rs/wvb).
-
-
-
-
-
-
diff --git a/content/docs/guide/bundle-sources.ko.mdx b/content/docs/guide/bundle-sources.ko.mdx
deleted file mode 100644
index 4e62ef8..0000000
--- a/content/docs/guide/bundle-sources.ko.mdx
+++ /dev/null
@@ -1,141 +0,0 @@
----
-title: 번들 소스
-description: Webview Bundle이 builtin 및 remote 소스에 걸쳐 번들을 디스크에 저장하고, 매니페스트로 추적하며, 채널별로 버전을 선택하는 방식을 설명합니다.
----
-
-**소스**는 번들이 저장되는 기기 내의 위치입니다. 모든 앱은 두 개의 소스를 가집니다. 하나는 앱 패키지 안에 포함되어 배포되는 읽기 전용 `builtin` 소스이고, 다른 하나는 다운로드된 업데이트를 위한 쓰기 가능한 `remote` 소스입니다. 이 페이지에서는 소스가 어떻게 구성되는지, `manifest.json`이 버전을 어떻게 추적하는지, 그리고 채널이 클라이언트에게 전달할 버전을 어떻게 선택하는지 설명합니다.
-
-## 번들, 이름, 버전
-
-**번들**은 하나의 논리적 웹 앱이며, **번들 이름**(예: `app`)으로 식별됩니다. 번들은 여러 **버전**(`1.0.0`, `1.1.0`, …)을 가지며, 각 버전은 별도의 `.wvb` 파일입니다. 어느 시점이든 하나의 버전이 **현재(current)** 버전으로, 웹뷰에 제공됩니다.
-
-번들 이름과 버전은 _소스 수준_ 식별자입니다. 이 값들은 디스크에 저장된 파일명과 매니페스트에 나타나지만, `.wvb` 아카이브 자체에는 저장되지 **않습니다**. 아카이브는 바이너리 파일 포맷 버전(`v1`)만 담습니다. 바이트 레이아웃은 [번들 포맷](/docs/guide/bundle-format)을 참고하세요.
-
-## builtin 소스와 remote 소스
-
-앱은 동일한 번들 이름을 서로 다른 저장소에서 제공하는 두 개의 소스로 실행됩니다.
-
-- **`builtin`** — 앱 패키지 _안에_ 포함되어 배포되는 번들입니다. 읽기 전용이며, 앱을 처음 실행할 때 아직 아무것도 다운로드되지 않은 상태에서 폴백으로 사용됩니다.
-- **`remote`** — 업데이트 서버에서 다운로드한 번들입니다. 쓰기 가능하며, 앱스토어 릴리스 없이 무선(OTA)으로 업데이트됩니다.
-
-번들이 두 소스에 모두 존재하면 **remote가 우선합니다**. 소스는 remote 매니페스트의 현재 버전을 먼저 확인하여 버전을 결정하며, remote에 아무것도 없으면 builtin으로 폴백합니다.
-
-
- 두 소스는 서로 독립적인 디렉터리입니다. 소스 객체는 별도의 `builtinDir`과 `remoteDir`을 가지며, 각
- 디렉터리는 자체 `manifest.json`을 가집니다. 버전을 결정할 때는 remote 매니페스트를 먼저 읽고
- builtin 매니페스트를 나중에 읽습니다.
-
-
-이러한 분리 덕분에 업데이트가 안전해집니다. 폴백할 수 있는 배포된 builtin 버전이 항상 존재하며, 다운로드된 remote 버전은 검증과 설치를 마치면 자연스럽게 builtin 버전을 가립니다. 다운로드 및 검증 흐름은 [원격 번들](/docs/guide/remote-bundles)을 참고하세요.
-
-## 디스크 레이아웃
-
-소스 디렉터리 안에서 각 번들은 자체 폴더를 가지며, 매니페스트는 루트에 위치합니다.
-
-```text
-{source_dir}/
-├── {name}/
-│ └── {name}_{version}.wvb
-└── manifest.json
-```
-
-따라서 `app` 번들의 두 버전을 담고 있는 remote 소스는 다음과 같습니다.
-
-```text
-{remote_dir}/
-├── app/
-│ ├── app_1.0.0.wvb
-│ └── app_1.1.0.wvb
-└── manifest.json
-```
-
-파일 경로는 항상 `{source_dir}/{name}/{name}_{version}.wvb`입니다. builtin 소스와 remote 소스는 서로 다른 디렉터리이므로, 동일한 `app_1.1.0.wvb` 파일명이 양쪽에 모두 존재할 수 있으며 각각 별도의 매니페스트로 추적됩니다.
-
-
- 번들 이름과 버전은 경로 구성 요소가 되므로 ASCII `[A-Za-z0-9._-]`로 제한됩니다. 빈 값일 수 없고,
- `.`이나 `..`일 수 없으며, `.`으로 끝날 수 없고, Windows 예약 이름(`CON`, `PRN`, `AUX`, `NUL`,
- `COM1`–`COM9`, `LPT1`–`LPT9`)일 수 없습니다. 그 외의 값은 파일 시스템에 닿기 전에 거부됩니다.
-
-
-## 매니페스트
-
-`manifest.json`은 소스 디렉터리의 루트에 위치합니다. 각 번들에 대해 존재하는 버전, 해당 메타데이터, 그리고 어느 버전이 현재 버전인지를 기록합니다.
-
-```json title="manifest.json"
-{
- "manifestVersion": 1,
- "entries": {
- "app": {
- "versions": {
- "1.0.0": {},
- "1.1.0": {}
- },
- "currentVersion": "1.0.0"
- }
- }
-}
-```
-
-각 필드는 다음과 같습니다.
-
-| Field | Type | Description |
-| -------------------------------- | ----------------- | ------------------------------------------- |
-| `manifestVersion` | integer | 매니페스트 스키마 버전. 항상 `1`입니다. |
-| `entries` | object | 번들 이름 → 엔트리의 맵입니다. |
-| `entries.{name}.versions` | object | 버전 문자열 → 버전별 메타데이터의 맵입니다. |
-| `entries.{name}.currentVersion` | string (optional) | 이 번들에 대해 제공되는 활성 버전입니다. |
-| `entries.{name}.previousVersion` | string (optional) | 현재 버전 직전에 활성이었던 버전입니다. |
-
-각 버전의 메타데이터는 모든 필드가 선택 사항인 객체입니다. 빈 `{}`도 유효합니다. 메타데이터는 번들을 다운로드할 때 remote 서버의 응답 헤더에서만 채워지기 때문입니다.
-
-| Field | Type | Description |
-| -------------- | ------ | ------------------------------------------------------------- |
-| `etag` | string | 다운로드한 번들에 대한 서버의 `ETag` 값입니다. |
-| `integrity` | string | 무결성 해시. `:` 형식입니다(예: `sha256:n4bQ…`). |
-| `signature` | string | 무결성 문자열에 대한 Base64 서명입니다. |
-| `lastModified` | string | 서버의 `Last-Modified` 값입니다. |
-
-### 현재 버전과 이전 버전
-
-`currentVersion`은 소스가 지금 제공하는 버전입니다. 여러 버전이 스테이징되었지만 아직 활성화된 버전이 없을 때는 이 값이 없을 수 있습니다. 버전을 스테이징하면 `currentVersion`을 바꾸지 않은 채 `versions` 아래에 해당 버전이 기록됩니다.
-
-`previousVersion`은 가장 최근 활성화 직전에 현재 버전이었던 버전입니다. 소스는 두 가지 이유로 이 버전을 보관합니다. 첫째, 정상 동작이 확인된 빌드로 롤백할 수 있게 해줍니다. 둘째, 이미 해당 파일을 연 진행 중인 요청이 새 버전이 현재 버전이 되는 동안에도 읽기를 깔끔하게 마칠 수 있도록 이전 파일을 디스크에 유지합니다. 현재 버전과 이전 버전만 보관되며, 더 오래된 버전은 정리됩니다.
-
-## 채널
-
-**채널**을 사용하면 동일한 서버에서 서로 다른 대상에게 서로 다른 버전을 전달할 수 있습니다. `stable`, `beta`, `canary` 등이 그 예입니다. 채널은 단계적 롤아웃과 사전 배포 테스트를 가능하게 합니다.
-
-채널은 순수하게 **remote**와 **updater**의 개념입니다. `.wvb` 포맷의 일부가 _아니며_ `manifest.json`에도 저장되지 _않습니다_. 클라이언트는 remote의 HTTP 호출에 `channel` 쿼리 파라미터를 실어 요청마다 채널을 선택합니다.
-
-```text
-GET /bundles?channel=beta
-HEAD /bundles/app?channel=beta
-GET /bundles/app?channel=beta
-```
-
-채널은 선택 사항입니다. **하드코딩된 기본 채널 이름은 없습니다.** 채널이 설정되지 않으면 쿼리 파라미터가 그냥 생략되고, 서버가 무엇을 반환할지 결정합니다. 전체 HTTP 규약과 업데이터 흐름은 [원격 번들](/docs/guide/remote-bundles)을, 업데이터의 채널 설정은 [원격 설정](/docs/config/remote)을 참고하세요.
-
-## 소스가 제공되는 방식
-
-소스는 웹뷰 요청에 응답하는 프로토콜 계층에 데이터를 공급합니다. 프로토콜은 들어오는 URI를 번들 이름과 파일 경로로 매핑하고, 소스에 현재 버전을 결정하도록 요청한 뒤, 일치하는 `.wvb` 안에서 파일을 제공합니다. URI가 번들 조회로 변환되는 방식은 [프로토콜 처리](/docs/guide/protocol-handling)를, 런타임이 builtin 및 remote 디렉터리를 가리키도록 설정하는 방법은 [wvb.config 파일](/docs/config)을 참고하세요.
-
-## 관련 페이지
-
-
-
-
-
-
-
diff --git a/content/docs/guide/bundle-sources.mdx b/content/docs/guide/bundle-sources.mdx
deleted file mode 100644
index 4b5fb36..0000000
--- a/content/docs/guide/bundle-sources.mdx
+++ /dev/null
@@ -1,178 +0,0 @@
----
-title: Bundle sources
-description: How Webview Bundle stores bundles on disk across builtin and remote sources, tracks them in a manifest, and selects versions per channel.
----
-
-A **source** is a place on the device where bundles live. Each app has two of them: a read-only
-`builtin` source shipped inside the app package, and a writable `remote` source for downloaded
-updates. This page explains how a source is laid out, how the `manifest.json` tracks versions, and
-how channels select which version a client receives.
-
-## Bundles, names, and versions
-
-A **bundle** is one logical web app, identified by a **bundle name** (for example `app`). A bundle
-has many **versions** (`1.0.0`, `1.1.0`, …), and each version is a separate `.wvb` file. At any
-moment one version is the **current** version — the one served to the webview.
-
-The bundle name and version are _source-level_ identifiers. They appear in the on-disk filename and
-in the manifest, but they are **not** stored inside the `.wvb` archive itself. The archive only
-carries the binary file-format version (`v1`). See [Bundle format](/docs/guide/bundle-format) for
-the byte layout.
-
-## Builtin vs remote sources
-
-Apps run with two sources that serve the same bundle name from different storage:
-
-- **`builtin`** — bundles shipped _inside_ the app package. Read-only, and used as the fallback the
- first time the app runs, before anything has been downloaded.
-- **`remote`** — bundles downloaded from your update server. Writable, and updated over the air
- (OTA) without an app-store release.
-
-When a bundle exists in both sources, **remote wins**. The source resolves a version by checking the
-remote manifest's current version first, then falling back to builtin if the remote has nothing.
-
-
- The two sources are independent directories. A source object holds a separate `builtinDir` and
- `remoteDir`, and each directory has its own `manifest.json`. Resolving a version reads the remote
- manifest first and the builtin manifest second.
-
-
-This split is what makes updates safe. You always have the shipped builtin version to fall back to,
-and downloaded remote versions transparently shadow it once they are verified and installed. See
-[Remote bundles](/docs/guide/remote-bundles) for the download and verification flow.
-
-## On-disk layout
-
-Within a source directory, each bundle gets its own folder and the manifest sits at the root:
-
-```text
-{source_dir}/
-├── {name}/
-│ └── {name}_{version}.wvb
-└── manifest.json
-```
-
-So a remote source holding two versions of the `app` bundle looks like this:
-
-```text
-{remote_dir}/
-├── app/
-│ ├── app_1.0.0.wvb
-│ └── app_1.1.0.wvb
-└── manifest.json
-```
-
-The file path is always `{source_dir}/{name}/{name}_{version}.wvb`. Because the builtin and remote
-sources are separate directories, the same `app_1.1.0.wvb` filename can exist in both, tracked by
-two separate manifests.
-
-
- Bundle names and versions become path components, so they are restricted to ASCII
- `[A-Za-z0-9._-]`. They cannot be empty, cannot be `.` or `..`, cannot end with `.`, and cannot be
- a Windows reserved name (`CON`, `PRN`, `AUX`, `NUL`, `COM1`–`COM9`, `LPT1`–`LPT9`). Anything else
- is rejected before it touches the filesystem.
-
-
-## The manifest
-
-`manifest.json` lives at the root of a source directory. For each bundle it records the versions
-present, their metadata, and which version is current:
-
-```json title="manifest.json"
-{
- "manifestVersion": 1,
- "entries": {
- "app": {
- "versions": {
- "1.0.0": {},
- "1.1.0": {}
- },
- "currentVersion": "1.0.0"
- }
- }
-}
-```
-
-The fields are:
-
-| Field | Type | Description |
-| -------------------------------- | ----------------- | ---------------------------------------------- |
-| `manifestVersion` | integer | Manifest schema version. Always `1`. |
-| `entries` | object | Map of bundle name → entry. |
-| `entries.{name}.versions` | object | Map of version string → per-version metadata. |
-| `entries.{name}.currentVersion` | string (optional) | The active version served for this bundle. |
-| `entries.{name}.previousVersion` | string (optional) | The version active immediately before current. |
-
-Each version's metadata is an object whose fields are all optional. Empty `{}` is valid, since the
-metadata is only filled in from the remote server's response headers when a bundle is downloaded:
-
-| Field | Type | Description |
-| -------------- | ------ | ------------------------------------------------------------------------ |
-| `etag` | string | The server's `ETag` for the downloaded bundle. |
-| `integrity` | string | Integrity hash, formatted `:` (for example `sha256:n4bQ…`). |
-| `signature` | string | Base64 signature over the integrity string. |
-| `lastModified` | string | The server's `Last-Modified` value. |
-
-### Current and previous versions
-
-`currentVersion` is the version a source serves right now. It may be absent when versions have been
-staged but none has been activated yet — staging a version records it under `versions` without
-changing `currentVersion`.
-
-`previousVersion` is the version that was current immediately before the latest activation. The
-source retains it for two reasons: it lets you roll back to a known-good build, and it keeps the old
-file on disk so in-flight requests that already opened it finish reading cleanly while a new version
-becomes current. Only the current and previous versions are retained; older versions are pruned.
-
-## Channels
-
-A **channel** lets you deliver different versions to different audiences from the same server —
-`stable`, `beta`, `canary`, and so on. Channels power staged rollouts and pre-release testing.
-
-A channel is purely a **remote** and **updater** concept. It is _not_ part of the `.wvb` format and
-_not_ stored in `manifest.json`. The client selects a channel per request by sending it as a
-`channel` query parameter on the remote's HTTP calls:
-
-```text
-GET /bundles?channel=beta
-HEAD /bundles/app?channel=beta
-GET /bundles/app?channel=beta
-```
-
-The channel is optional. There is **no hardcoded default channel name** — when no channel is set,
-the query parameter is simply omitted and the server decides what to return. See
-[Remote bundles](/docs/guide/remote-bundles) for the full HTTP contract and the updater flow, and
-[Remote config](/docs/config/remote) for configuring the updater's channel.
-
-## How sources are served
-
-A source feeds the protocol layer that answers webview requests. The protocol maps an incoming URI
-to a bundle name and file path, asks the source to resolve the current version, and serves the file
-from inside the matching `.wvb`. See [Protocol handling](/docs/guide/protocol-handling) for how URIs
-become bundle lookups, and [The wvb.config file](/docs/config) for pointing the runtime at your
-builtin and remote directories.
-
-## Related pages
-
-
-
-
-
-
-
diff --git a/content/docs/guide/commands/meta.json b/content/docs/guide/commands/meta.json
deleted file mode 100644
index 75ac8fb..0000000
--- a/content/docs/guide/commands/meta.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "Commands",
- "pages": ["pack", "extract", "serve", "upload", "deploy", "download", "builtin", "remote"]
-}
diff --git a/content/docs/guide/commands/meta.ko.json b/content/docs/guide/commands/meta.ko.json
deleted file mode 100644
index d40b31d..0000000
--- a/content/docs/guide/commands/meta.ko.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "명령어",
- "pages": ["pack", "extract", "serve", "upload", "deploy", "download", "builtin", "remote"]
-}
diff --git a/content/docs/guide/core-concepts/bundle-source.ko.mdx b/content/docs/guide/core-concepts/bundle-source.ko.mdx
new file mode 100644
index 0000000..275ba20
--- /dev/null
+++ b/content/docs/guide/core-concepts/bundle-source.ko.mdx
@@ -0,0 +1,89 @@
+---
+title: 번들 소스
+---
+
+**소스**는 번들이 저장되는 기기 내의 위치입니다. 두 가지 유형의 소스가 있습니다. 하나는 앱 패키지 안에 포함되어 배포되는 읽기 전용 빌트인(`builtin`) 소스이고, 다른 하나는 리모트로부터 원격으로 업데이트하는 리모트(`remote`) 소스입니다.
+
+## 빌트인 vs 리모트
+
+웹뷰 번들은 빌트인(`builtin`)과 리모트(`remote`) 번들 소스를 나눕니다.
+
+- **`builtin`** — 앱 패키지 _안에_ 포함되어 배포되는 번들입니다. 읽기 전용이며, 앱을 처음 실행할 때 아직 아무것도 다운로드되지 않은 상태에서 폴백으로 사용됩니다.
+- **`remote`** — 리모트 서버에서 다운로드한 번들입니다. 쓰기 가능하며, 앱스토어 릴리스 없이 원격으로 업데이트됩니다.
+
+번들이 두 소스에 모두 존재하면 **리모트가 우선합니다**. 소스는 리모트 매니페스트의 현재 버전을 먼저 확인하여 버전을 결정하며, 리모트에 없으면 빌트인으로 폴백합니다.
+
+---
+
+이러한 분리 덕분에 업데이트가 안전해집니다. 폴백할 수 있는 배포된 빌트인 버전이 항상 존재하며, 다운로드된 리모트 버전은 검증과 설치를 마치면 자연스럽게 빌트인 버전을 대체합니다. 다운로드 및 검증 흐름은 [Over-The-Air](/docs/guide/core-concepts/over-the-air)를 참고하세요.
+
+## 디스크 레이아웃
+
+소스 디렉터리 안에서 각 번들은 자체 폴더를 가지며, 매니페스트는 루트에 위치합니다.
+
+```text
+{source_dir}/
+├── {name}/
+│ └── {name}_{version}.wvb
+└── manifest.json
+```
+
+예를들어, `app` 번들이 `1.0.0`, `1.1.0` 두 버전이 설치되어 있을 때 디스크 레이아웃은 다음과 같습니다.
+
+```text
+{source_dir}/
+├── app/
+│ ├── app_1.0.0.wvb
+│ └── app_1.1.0.wvb
+└── manifest.json
+```
+
+파일 경로는 항상 `{source_dir}/{name}/{name}_{version}.wvb`입니다. 빌트인 번들과 리모트 번들은 서로 다른 소스이므로, 각각 별도의 매니페스트로 추적됩니다.
+
+
+ 번들 이름과 버전은 경로 구성 요소가 되므로 ASCII `[A-Za-z0-9._-]`로 제한됩니다.
+
+
+## 매니페스트
+
+`manifest.json`은 소스 디렉터리의 루트에 위치합니다. 각 번들에 대해 존재하는 버전, 해당 메타데이터, 그리고 어느 버전이 현재 버전인지를 기록합니다.
+
+```json title="manifest.json"
+{
+ "manifestVersion": 1,
+ "entries": {
+ "app": {
+ "versions": {
+ "1.0.0": {},
+ "1.1.0": {}
+ },
+ "currentVersion": "1.0.0"
+ }
+ }
+}
+```
+
+각 필드는 다음과 같습니다.
+
+| Field | Type | Description |
+| -------------------------------- | ----------------- | ---------------------------------------- |
+| `manifestVersion` | integer | 매니페스트 스키마 버전. 항상 `1`입니다. |
+| `entries` | object | 번들 엔트리의 맵 |
+| `entries.{name}.versions` | object | 버전별 메타데이터의 맵 |
+| `entries.{name}.currentVersion` | string (optional) | 이 번들에 대해 제공되는 활성 버전입니다. |
+| `entries.{name}.previousVersion` | string (optional) | 현재 버전 직전에 활성이었던 버전입니다. |
+
+각 버전의 메타데이터는 모든 필드가 선택 사항인 객체입니다. 일반적으로 메타데이터는 번들을 다운로드할 때 리모트 서버의 응답 헤더에서 채워집니다.
+
+| Field | Type | Description |
+| -------------- | ------ | ------------------------------------------------------------- |
+| `etag` | string | 다운로드한 번들에 대한 서버의 `ETag` 값입니다. |
+| `integrity` | string | 무결성 해시. `:` 형식입니다(예: `sha256:n4bQ…`). |
+| `signature` | string | 무결성 문자열에 대한 Base64 서명입니다. |
+| `lastModified` | string | 서버의 `Last-Modified` 값입니다. |
+
+### 현재 버전과 이전 버전
+
+`currentVersion`은 소스가 지금 제공하는 버전입니다. 여러 버전이 스테이징되었지만 아직 활성화된 버전이 없을 때는 이 값이 없을 수 있습니다. 버전을 스테이징하면 `currentVersion`을 바꾸지 않은 채 `versions` 아래에 해당 버전이 기록됩니다.
+
+`previousVersion`은 가장 최근 활성화 직전에 현재 버전이었던 버전입니다. 소스는 두 가지 이유로 이 버전을 보관합니다. 첫째, 정상 동작이 확인된 빌드로 롤백할 수 있게 해줍니다. 둘째, 이미 해당 파일을 연 진행 중인 요청이 새 버전이 현재 버전이 되는 동안에도 읽기를 깔끔하게 마칠 수 있도록 이전 파일을 디스크에 유지합니다. 현재 버전과 이전 버전만 보관되며, 더 오래된 버전은 정리됩니다.
diff --git a/content/docs/guide/core-concepts/bundle-source.mdx b/content/docs/guide/core-concepts/bundle-source.mdx
new file mode 100644
index 0000000..8960c35
--- /dev/null
+++ b/content/docs/guide/core-concepts/bundle-source.mdx
@@ -0,0 +1,90 @@
+---
+title: Bundle source
+---
+
+A **source** is the location on the device where bundles are stored. There are two types of source. One is the read-only builtin (`builtin`) source shipped inside the app package, and the other is the remote (`remote`) source that updates over the air from a remote server.
+
+## Builtin vs remote
+
+Webview Bundle splits bundle sources into builtin (`builtin`) and remote (`remote`).
+
+- **`builtin`** — a bundle shipped _inside_ the app package. It is read-only and serves as the fallback on first launch, before anything has been downloaded.
+- **`remote`** — a bundle downloaded from a remote server. It is writable and updates over the air without an app store release.
+
+When a bundle exists in both sources, **remote wins**. A source determines the version by checking the current version in the remote manifest first, and falls back to builtin when the bundle is not in remote.
+
+---
+
+This separation makes updates safe. A shipped builtin version is always available to fall back to, and a downloaded remote version naturally replaces the builtin version once it passes verification and installs. See [Over-The-Air](/docs/guide/core-concepts/over-the-air) for the download and verification flow.
+
+## Disk layout
+
+Inside the source directory, each bundle has its own folder, and the manifest lives at the root.
+
+```text
+{source_dir}/
+├── {name}/
+│ └── {name}_{version}.wvb
+└── manifest.json
+```
+
+For example, when the `app` bundle has two versions installed, `1.0.0` and `1.1.0`, the disk layout looks like this.
+
+```text
+{source_dir}/
+├── app/
+│ ├── app_1.0.0.wvb
+│ └── app_1.1.0.wvb
+└── manifest.json
+```
+
+The file path is always `{source_dir}/{name}/{name}_{version}.wvb`. The builtin bundle and the remote bundle are separate sources, so each is tracked by its own manifest.
+
+
+ Bundle names and versions become path components, so they are restricted to ASCII
+ `[A-Za-z0-9._-]`.
+
+
+## Manifest
+
+`manifest.json` lives at the root of the source directory. For each bundle, it records the versions that exist, their metadata, and which version is current.
+
+```json title="manifest.json"
+{
+ "manifestVersion": 1,
+ "entries": {
+ "app": {
+ "versions": {
+ "1.0.0": {},
+ "1.1.0": {}
+ },
+ "currentVersion": "1.0.0"
+ }
+ }
+}
+```
+
+The fields are as follows.
+
+| Field | Type | Description |
+| -------------------------------- | ----------------- | -------------------------------------------------------- |
+| `manifestVersion` | integer | Manifest schema version. Always `1`. |
+| `entries` | object | Map of bundle entries |
+| `entries.{name}.versions` | object | Map of per-version metadata |
+| `entries.{name}.currentVersion` | string (optional) | The active version served for this bundle. |
+| `entries.{name}.previousVersion` | string (optional) | The version that was active just before the current one. |
+
+Each version's metadata is an object whose fields are all optional. The metadata is usually populated from the remote server's response headers when the bundle is downloaded.
+
+| Field | Type | Description |
+| -------------- | ------ | --------------------------------------------------------------- |
+| `etag` | string | The server's `ETag` value for the downloaded bundle. |
+| `integrity` | string | Integrity hash, in `:` form (e.g. `sha256:n4bQ…`). |
+| `signature` | string | Base64 signature over the integrity string. |
+| `lastModified` | string | The server's `Last-Modified` value. |
+
+### Current version and previous version
+
+`currentVersion` is the version the source serves right now. It may be absent when several versions are staged but none is active yet. Staging a version records it under `versions` without changing `currentVersion`.
+
+`previousVersion` is the version that was current just before the most recent activation. A source keeps it for two reasons. First, it lets you roll back to a build confirmed to work. Second, it keeps the previous file on disk so an in-flight request that already opened that file can finish reading cleanly while the new version becomes current. Only the current and previous versions are kept; older versions are cleaned up.
diff --git a/content/docs/guide/core-concepts/communicate-between-native-and-webview.ko.mdx b/content/docs/guide/core-concepts/communicate-between-native-and-webview.ko.mdx
new file mode 100644
index 0000000..d994696
--- /dev/null
+++ b/content/docs/guide/core-concepts/communicate-between-native-and-webview.ko.mdx
@@ -0,0 +1,18 @@
+---
+title: 네이티브와 웹뷰 간 통신
+---
+
+웹뷰의 웹 코드에서 네이티브 기능을 사용하려면 네이티브와 통신해야 합니다. 웹뷰 번들은 이를 위해 브릿지를 제공합니다.
+
+## 브릿지 호출하기
+
+웹뷰에서는 통합 브릿지 패키지 `@wvb/bridge`의 `invoke(name, params)`로 네이티브 기능을 호출합니다.
+
+호출은 네이티브 단에 등록된 핸들러가 처리하고 결과를 돌려줍니다. 플랫폼/프레임워크별 전달 방식은 다음과 같습니다.
+
+- **Electron** — Electron IPC
+- **Tauri** — Tauri 커맨드
+- **Android** — WebView 메시지 리스너(`WebViewCompat.addWebMessageListener`)
+- **iOS** — `WKScriptMessageHandler`
+
+브릿지 API를 사용하는 자세한 방법은 [브릿지](/docs/guide/frontend/bridges) 가이드를 참고하세요.
diff --git a/content/docs/guide/core-concepts/communicate-between-native-and-webview.mdx b/content/docs/guide/core-concepts/communicate-between-native-and-webview.mdx
new file mode 100644
index 0000000..641e69b
--- /dev/null
+++ b/content/docs/guide/core-concepts/communicate-between-native-and-webview.mdx
@@ -0,0 +1,18 @@
+---
+title: Communicate between native and webview
+---
+
+To use native features from web code in the webview, you communicate with the native side. Webview Bundle provides a bridge for this.
+
+## Call the bridge
+
+In the webview, call native features with `invoke(name, params)` from the unified bridge package `@wvb/bridge`.
+
+A handler registered on the native side processes the call and returns a result. The delivery mechanism per platform and framework is as follows.
+
+- **Electron** — Electron IPC
+- **Tauri** — Tauri command
+- **Android** — WebView message listener (`WebViewCompat.addWebMessageListener`)
+- **iOS** — `WKScriptMessageHandler`
+
+For details on using the bridge API, see the [Bridges](/docs/guide/frontend/bridges) guide.
diff --git a/content/docs/guide/core-concepts/glue-to-native.ko.mdx b/content/docs/guide/core-concepts/glue-to-native.ko.mdx
new file mode 100644
index 0000000..56f3888
--- /dev/null
+++ b/content/docs/guide/core-concepts/glue-to-native.ko.mdx
@@ -0,0 +1,51 @@
+---
+title: 네이티브 연동
+---
+
+웹뷰 번들은 특정 네이티브 플랫폼이나 프레임워크에 종속되지 않도록 설계되었습니다. 익숙한 웹 애플리케이션 코드를 한 번만 작성하면, 하나의 코어를 통해 여러 플랫폼과 프레임워크에 동일하게 연동할 수 있습니다.
+
+## Rust로 작성된 코어
+
+코어 크레이트(`wvb`)는 Rust로 작성되어 멀티 플랫폼 환경에서 동작하게 설계하였습니다.
+
+코어 크레이트에서 제공되는 기능은 다음과 같습니다.
+
+- `.wvb` 번들 포맷 읽기·쓰기
+- 번들 소스 관리(빌트인·리모트)
+- 리모트에서 번들 다운로드
+- 원격 업데이터
+- 프로토콜 핸들링(번들을 커스텀 스킴으로 제공하며 `GET`·`HEAD`와 범위 요청 지원)
+- 무결성·서명 검증
+
+## 멀티 플랫폼/환경 지원
+
+여러 플랫폼 및 환경을 지원하기 위해 `wvb` 크레이트는 바인딩되어 제공됩니다. 바인딩은 코어 크레이트인 `wvb`의 API를 그대로 노출하여 서로 다른 플랫폼/환경에서도 동일한 API를 사용할 수 있습니다.
+
+- **Node.js**
+- [napi.rs](https://napi.rs/)를 이용해 Rust 코드를 [N-API](https://nodejs.org/api/n-api.html)를 바인딩한 네이티브 에드온을 사용합니다.
+- 사용하는 N-API 버전은 8으로 호환가능한 Node.js 버전은 [Node.js 문서](https://nodejs.org/api/n-api.html#node-api-version-matrix)를 참고해주세요.
+- **Android/iOS**
+- [uniffi](https://github.com/mozilla/uniffi-rs)를 이용해 Rust 코드를 Kotlin/Swift 바인딩을 만듭니다.
+- 만들어진 바인딩은 각각 [Android](https://github.com/webview-bundle/webview-bundle-android), [iOS](https://github.com/webview-bundle/webview-bundle-ios) 라이브러리에 포함돼 퍼블리싱 됩니다.
+- **Deno**
+- [Deno FFI](https://docs.deno.com/runtime/fundamentals/ffi/) API를 이용해 동적으로 라이브러리를 불러와 사용합니다.
+
+자세한 API 레퍼런스는 [여기서](/docs/references) 확인해주세요.
+
+## 네이티브에 붙이기
+
+코어는 웹뷰의 네트워크 요청을 가로채(intercept) 번들에서 리소스를 읽어 응답합니다. 예를 들어 Android에서는 `WebViewClient.shouldInterceptRequest`를 재정의해 요청을 코어의 프로토콜 핸들러로 넘깁니다.
+
+```kotlin
+class WebViewBundleClient(
+ private val owner: WebViewBundle,
+) : WebViewClient() {
+ override fun shouldInterceptRequest(
+ view: WebView,
+ request: WebResourceRequest,
+ ): WebResourceResponse? =
+ owner.handleRequest(request)
+}
+```
+
+각 플랫폼/프레임워크의 통합 패키지가 이 연동을 대신 처리하므로, 대부분 직접 인터셉트 코드를 작성할 필요는 없습니다. 자세한 방법은 **네이티브** 섹션을 참고하세요.
diff --git a/content/docs/guide/core-concepts/glue-to-native.mdx b/content/docs/guide/core-concepts/glue-to-native.mdx
new file mode 100644
index 0000000..14261cc
--- /dev/null
+++ b/content/docs/guide/core-concepts/glue-to-native.mdx
@@ -0,0 +1,51 @@
+---
+title: Glue to native
+---
+
+Webview Bundle is designed to avoid being tied to any specific native platform or framework. Write your familiar web application code once, and a single core lets you integrate it the same way across multiple platforms and frameworks.
+
+## A core written in Rust
+
+The core crate (`wvb`) is written in Rust and designed to run across multiple platforms.
+
+The core crate provides the following:
+
+- Read and write the `.wvb` bundle format
+- Manage bundle sources (builtin and remote)
+- Download bundles from a remote
+- Remote updater
+- Protocol handling (serve bundles over a custom scheme, with `GET`, `HEAD`, and range request support)
+- Integrity and signature verification
+
+## Multi-platform and multi-environment support
+
+To support multiple platforms and environments, the `wvb` crate ships with bindings. Each binding exposes the API of the `wvb` core crate as-is, so you use the same API across different platforms and environments.
+
+- **Node.js**
+ - Uses a native addon built with [napi.rs](https://napi.rs/) that binds the Rust code to [N-API](https://nodejs.org/api/n-api.html).
+ - The N-API version is 8; for compatible Node.js versions, see the [Node.js docs](https://nodejs.org/api/n-api.html#node-api-version-matrix).
+- **Android/iOS**
+ - Uses [uniffi](https://github.com/mozilla/uniffi-rs) to generate Kotlin/Swift bindings from the Rust code.
+ - The generated bindings are published as part of the [Android](https://github.com/webview-bundle/webview-bundle-android) and [iOS](https://github.com/webview-bundle/webview-bundle-ios) libraries, respectively.
+- **Deno**
+ - Uses the [Deno FFI](https://docs.deno.com/runtime/fundamentals/ffi/) API to load the library dynamically.
+
+See the full API reference [here](/docs/references).
+
+## Glue to native
+
+The core intercepts the webview's network requests, reads resources from the bundle, and responds. On Android, for example, you override `WebViewClient.shouldInterceptRequest` to hand the request to the core's protocol handler.
+
+```kotlin
+class WebViewBundleClient(
+ private val owner: WebViewBundle,
+) : WebViewClient() {
+ override fun shouldInterceptRequest(
+ view: WebView,
+ request: WebResourceRequest,
+ ): WebResourceResponse? =
+ owner.handleRequest(request)
+}
+```
+
+The integration package for each platform and framework handles this glue for you, so in most cases you never write intercept code yourself. See the **Native** section for details.
diff --git a/content/docs/guide/core-concepts/over-the-air.ko.mdx b/content/docs/guide/core-concepts/over-the-air.ko.mdx
new file mode 100644
index 0000000..5ace0eb
--- /dev/null
+++ b/content/docs/guide/core-concepts/over-the-air.ko.mdx
@@ -0,0 +1,109 @@
+---
+title: Over-The-Air
+---
+
+신규 웹뷰 번들을 원격(Over-The-Air)에서 다운로드할 수 있습니다. 네이티브 앱 배포없이도 업데이트된 앱 코드를 웹뷰 번들 리모트 서버를 통해 배포할 수 있습니다.
+
+## 업데이트 라이프사이클
+
+앱 코드를 빌드하여 웹뷰 번들 포맷으로 패키징한 이후 기기에 다운로드되기까지 다섯 단계를 거칩니다.
+
+1. 빌드 결과물을 `.wvb` 포맷으로 패킹합니다.
+2. 패킹된 웹뷰 번들 파일을 리모트 서버로 업로드합니다. 무결성 해시와 서명을 추가할 수 있습니다.
+3. 업로드한 버전을 배포하면 클라이언트가 업데이트 정보를 불러올 수 있습니다.
+4. 클라이언트에서 새로운 버전을 확인한 뒤, 다운로드 합니다.
+5. 다운로드된 데이터를 검증하고, 이를 리모트 소스에 설치합니다.
+
+1~3 단계는 CLI 명령어로 실행할 수 있습니다.
+
+```sh
+wvb pack # `.wvb` 파일 패키징
+wvb upload --version 1.2.0 # 리모트 서버로 1.2.0 버전 업로드
+wvb deploy --version 1.2.0 # 1.2.0을 현재 버전으로 배포
+```
+
+또는, 이용중인 플랫폼/프레임워크에서 더 편한 방식으로 연동할 수 있습니다. 이는 **네이티브** 섹션에서 가이드를 확인해주세요.
+
+## 클라이언트에서 업데이트
+
+클라이언트에서 리모트 번들을 다운받아 리모트 [번들 소스](/docs/guide/core-concepts/bundle-source)에 설치하는 과정을 제어할 수 있습니다. 사용할 수 있는 API는 다음과 같습니다.
+
+- **updater.getUpdate** : 리모트의 현재 버전 정보를 조회(HEAD 요청)해 설치된 버전과 비교합니다. 다운로드는 하지 않으며, 업데이트 가능 여부와 버전 정보를 반환합니다.
+- **updater.download** : 새 버전의 번들을 내려받아 검증한 뒤 디스크에 준비합니다. 이 단계에서는 아직 활성화되지 않습니다.
+- **updater.install** : 내려받아 둔 버전을 현재 버전으로 활성화합니다. 설치한 뒤부터 해당 버전이 제공됩니다.
+
+다운로드와 설치 과정이 분리되어 있어서, 앱의 비즈니스 요구사항에 맞춰 원하는 타이밍에 신규 번들을 반영할 수 있습니다.
+
+---
+
+Rust 코어 크레이트에서 API를 활용하는 방법입니다.
+
+```rust
+use std::sync::Arc;
+use wvb::updater::Updater;
+
+let updater = Updater::new(source, remote, None);
+
+let update = updater.get_update("app").await?;
+if update.is_available {
+ let info = updater.download("app", None).await?;
+ updater.install("app", info.version).await?;
+ // 이후 새 버전으로 번들 사용 가능
+}
+```
+
+각 플랫폼/프레임워크에서 업데이트 API를 사용하는 방법은 가이드 문서에서 확인해주세요.
+
+### 무결성
+
+무결성 해시를 사용하면 클라이언트가 다운로드한 번들이 리모트에서 배포된 번들과 데이터가 일치하는지 확인할 수 있습니다. 지원하는 알고리즘(SHA-2)은 `sha256`(기본값), `sha384`, `sha512`이며, `":"` 형식으로 직렬화됩니다.
+
+```text
+sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
+```
+
+클라이언트는 다운로드한 번들 데이터로 해시를 다시 계산해 리모트가 알려 준 무결성 해시와 일치하는지 확인합니다. 검증은 설치 전에 수행하며, 일치하지 않으면 설치가 실패합니다. 검증 정책은 세 가지입니다.
+
+- `strict` : 무결성 해시가 반드시 있어야 하며, 없거나 일치하지 않으면 실패합니다.
+- `optional`(기본값) : 해시가 있으면 검증하고, 없으면 건너뜁니다.
+- `none` : 무결성을 검증하지 않습니다.
+
+### 서명
+
+서명은 번들이 개인 키 소유자가 배포한 것임을 증명합니다. 무결성 해시는 데이터가 특정 해시와 일치하는지만 보장하지만, 서명은 배포자의 진위까지 확인합니다. 서명은 무결성 문자열에 대해 생성되므로, 서명을 사용하려면 무결성을 함께 활성화해야 합니다.
+
+지원하는 알고리즘은 다음과 같습니다.
+
+- ECDSA (P-256, P-384)
+- Ed25519
+- RSA (PKCS#1 v1.5, PSS)
+
+서명 검증은 선택 사항입니다. 클라이언트에 공개 키로 서명 검증기를 설정하면, 다운로드한 번들의 서명을 무결성 문자열과 대조해 확인하고, 검증에 실패하면 설치를 중단합니다.
+
+## 리모트
+
+리모트는 클라이언트가 번들과 메타데이터를 내려받는 HTTP 서버입니다. 엔드포인트 URL로 구성하며, 번들 목록 조회, 현재 버전 메타데이터 조회(HEAD), 현재 버전 및 특정 버전 다운로드를 제공합니다. 버전·무결성·서명 값은 응답 헤더로 전달됩니다.
+
+### 제공자
+
+번들 서버를 직접 운영하지 않아도 되도록 기본 제공자를 제공합니다.
+
+- **AWS** — `@wvb/remote-aws`
+- **Cloudflare** — `@wvb/remote-cloudflare`
+
+리모트 HTTP 규약을 구현하면 직접 제공자를 만들 수도 있습니다. 자세한 내용은 [리모트](/docs/guide/remote) 문서를 참고하세요.
+
+### 채널
+
+채널은 배포를 구분하는 이름표입니다(예: `stable`, `beta`). 클라이언트는 지정한 채널의 현재 버전을 받으며, 채널을 지정하지 않으면 기본 배포를 받습니다.
+
+Rust에서는 `UpdaterConfig`에 채널을 지정합니다.
+
+```rust
+use wvb::updater::{Updater, UpdaterConfig};
+
+let config = UpdaterConfig::new().channel("beta");
+let updater = Updater::new(source, remote, Some(config));
+```
+
+채널은 리모트 요청에 `?channel=beta` 쿼리 파라미터로 전달됩니다.
diff --git a/content/docs/guide/core-concepts/over-the-air.mdx b/content/docs/guide/core-concepts/over-the-air.mdx
new file mode 100644
index 0000000..7a1b4e0
--- /dev/null
+++ b/content/docs/guide/core-concepts/over-the-air.mdx
@@ -0,0 +1,109 @@
+---
+title: Over-the-air
+---
+
+Download new webview bundles remotely. Ship updated app code through the Webview Bundle remote server without redeploying the native app.
+
+## Update lifecycle
+
+After you build your app code and package it into the Webview Bundle format, it goes through five steps before it reaches the device.
+
+1. Pack the build output into the `.wvb` format.
+2. Upload the packed webview bundle file to the remote server. You can attach an integrity hash and a signature.
+3. Deploy the uploaded version so clients can fetch its update info.
+4. The client checks for a new version, then downloads it.
+5. Verify the downloaded data, then install it into the remote source.
+
+Steps 1 through 3 run as CLI commands.
+
+```sh
+wvb pack # Package the `.wvb` file
+wvb upload --version 1.2.0 # Upload version 1.2.0 to the remote server
+wvb deploy --version 1.2.0 # Deploy 1.2.0 as the current version
+```
+
+Or integrate it in a more convenient way for the platform/framework you use. See the guide in the **Native** section.
+
+## Updating on the client
+
+Control how the client downloads the remote bundle and installs it into the remote [bundle source](/docs/guide/core-concepts/bundle-source). The available APIs are:
+
+- **updater.getUpdate** : Queries the remote's current version info (a HEAD request) and compares it against the installed version. It does not download anything; it returns whether an update is available along with the version info.
+- **updater.download** : Downloads the new version's bundle, verifies it, then stages it on disk. It is not activated at this step yet.
+- **updater.install** : Activates the downloaded version as the current version. That version is served from installation onward.
+
+Because download and install are separate, you can apply a new bundle at whatever timing fits your app's business requirements.
+
+---
+
+Here is how to use the API from the Rust core crate.
+
+```rust
+use std::sync::Arc;
+use wvb::updater::Updater;
+
+let updater = Updater::new(source, remote, None);
+
+let update = updater.get_update("app").await?;
+if update.is_available {
+ let info = updater.download("app", None).await?;
+ updater.install("app", info.version).await?;
+ // The new version bundle is available from here on
+}
+```
+
+See the guide docs for how to use the update API on each platform/framework.
+
+### Integrity
+
+An integrity hash lets the client confirm that the bundle it downloaded matches the data of the bundle deployed on the remote. The supported algorithms (SHA-2) are `sha256` (default), `sha384`, and `sha512`, serialized in the `":"` format.
+
+```text
+sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
+```
+
+The client recomputes the hash from the downloaded bundle data and checks that it matches the integrity hash the remote reported. Verification runs before installation, and installation fails if they do not match. There are three verification policies.
+
+- `strict` : An integrity hash must be present; it fails if the hash is missing or does not match.
+- `optional` (default) : Verifies the hash if present, and skips it if absent.
+- `none` : Does not verify integrity.
+
+### Signature
+
+A signature proves that the bundle was deployed by the holder of the private key. An integrity hash only guarantees that the data matches a specific hash, but a signature also confirms the authenticity of the deployer. The signature is generated over the integrity string, so you must enable integrity together to use signatures.
+
+The supported algorithms are:
+
+- ECDSA (P-256, P-384)
+- Ed25519
+- RSA (PKCS#1 v1.5, PSS)
+
+Signature verification is optional. Configure a signature verifier with a public key on the client, and it checks the downloaded bundle's signature against the integrity string, aborting installation if verification fails.
+
+## Remote
+
+The remote is an HTTP server from which clients download bundles and metadata. Configure it with an endpoint URL. It provides listing bundles, querying current version metadata (HEAD), and downloading the current version and specific versions. Version, integrity, and signature values are passed in the response headers.
+
+### Providers
+
+Builtin providers let you skip running a bundle server yourself.
+
+- **AWS** — `@wvb/remote-aws`
+- **Cloudflare** — `@wvb/remote-cloudflare`
+
+You can also build your own provider by implementing the remote HTTP protocol. See the [Remote](/docs/guide/remote) docs for details.
+
+### Channels
+
+A channel is a label that distinguishes deployments (for example, `stable`, `beta`). The client receives the current version of the channel it specifies, and receives the default deployment when it specifies no channel.
+
+In Rust, you specify the channel on `UpdaterConfig`.
+
+```rust
+use wvb::updater::{Updater, UpdaterConfig};
+
+let config = UpdaterConfig::new().channel("beta");
+let updater = Updater::new(source, remote, Some(config));
+```
+
+The channel is passed to the remote request as the `?channel=beta` query parameter.
diff --git a/content/docs/guide/core-concepts/protocol-handling.ko.mdx b/content/docs/guide/core-concepts/protocol-handling.ko.mdx
new file mode 100644
index 0000000..c9c7b98
--- /dev/null
+++ b/content/docs/guide/core-concepts/protocol-handling.ko.mdx
@@ -0,0 +1,93 @@
+---
+title: 프로토콜 핸들링
+---
+
+웹뷰 번들은 HTTP 요청에 대해 번들 내 적절한 리소스를 응답하는 프로토콜 핸들러 기능을 제공합니다. 각 플랫폼/프레임워크에서 웹뷰 요청에 대한 응답을, 이 프로토콜 핸들러를 이용하면 웹뷰 번들 내 리소스를 표준 HTTP 응답으로 반환할 수 있습니다.
+
+## 번들 프로토콜
+
+번들 프로토콜은 요청 URI에 매핑되는 웹뷰 번들로부터 요청 경로에 해당하는 파일을 불러와 HTTP 응답합니다.
+
+예를들어, `app://app.wvb/index.html` 요청 URI에 대해서 찾을 번들명은 `app`, 번들 내에서 불러올 파일경로는 `/index.html`로 해석하여 HTTP 응답을 프로토콜 핸들러에서 반환하게 됩니다.
+
+```text
+app://app.wvb/index.html -> bundle "app", file "/index.html"
+app://myapp.wvb/assets/logo.png -> bundle "myapp", file "/assets/logo.png"
+```
+
+경로 해석은 정적 파일 서버처럼 `index.html`을 채워 넣습니다. 끝에 슬래시가 있거나, 경로의 마지막 세그먼트에 `.`이 없는 경우 `index.html`을 채워 넣어 번들에서 파일을 찾습니다.
+
+```text
+/ -> /index.html # 끝에 슬래시가 있는 경우
+/about -> /about/index.html # 마지막 세그먼트에 "."이 없는 경우
+/a.js -> /a.js # 마지막 세그먼트에 "."이 있으면 그대로 제공
+```
+
+번들을 못찾은 경우에는 `BundleNotFound` 오류로 핸들러가 실패하고, 500(Internal Server Error)을 응답하게 됩니다.
+
+만약, 번들은 존재하나 번들 내에서 파일을 못찾은 경우 404(Not Found)를 응답합니다.
+
+### 메서드, 헤더, 응답
+
+`GET`과 `HEAD`만 제공하며, 그 외의 메서드는 405(Method Not Allowed)를 반환합니다. `HEAD`는 본문이 비어 있는 헤더를 반환합니다.
+
+파일을 제공할 때 핸들러는 인덱스 엔트리에 기록된 `Content-Type`과 `Content-Length` 헤더값을 기본으로 추가합니다. 그 외, 사용자가 임의로 추가한 헤더값도 응답에 포함됩니다.
+
+```text
+GET app://app.wvb/assets/app.js
+-> 200 OK
+ cache-control: public, max-age=31536000 # 인덱스 엔트리에 사용자 임의로 지정한 값
+ content-type: application/javascript # 인덱스 엔트리에 기록된 값
+ content-length: 84213 # 압축 해제된 크기
+```
+
+### 범위 요청
+
+웹뷰는 미디어 탐색과 대용량 다운로드 재개를 위해 범위 요청에 의존합니다. `Range` 헤더가 있으면 `Accept-Ranges: bytes`와 `Content-Range`를 담아 206(Partial Content)를 반환합니다.
+
+```text
+GET app://app.wvb/media/clip.mp4
+Range: bytes=0-1023
+-> 206 Partial Content
+ accept-ranges: bytes
+ content-range: bytes 0-1023/5242880
+ content-length: 1024
+```
+
+- 반환되는 각 범위는 `1000 * 1024` 바이트(약 1 MB)로 제한되며, 이보다 큰 요청 범위는 잘립니다.
+- 여러 범위는 `multipart/byteranges` 응답으로 반환됩니다.
+- 충족할 수 없는 범위는 `Content-Range: bytes */`와 함께 416(Range Not Satisfiable)를 반환합니다.
+
+```text
+Range: bytes=99999999-
+-> 416 Range Not Satisfiable
+ content-range: bytes */5242880
+```
+
+범위는 압축 해제된 콘텐츠를 기준으로 계산됩니다. 따라서 오프셋은 빌드가 생성한 파일과 일치합니다.
+
+## 로컬 프로토콜
+
+웹뷰 번들 파일(`.wvb`)에서 리소스를 가져오는 대신, 로컬 프로토콜을 사용하면 localhost로부터 프록시로 리소스를 불러올 수 있습니다.
+
+이는 개발 단계에서 유용합니다. 예를들어, [Vite](https://vite.dev/) 개발 서버 (예: `http://localhost:5173`)로부터 핫 리로드를 동작시키고 싶다면 Electron에서 아래와 같이 설정할 수 있습니다.
+
+```ts title="main.ts"
+import { localProtocol, wvb } from '@wvb/electron';
+
+const instance = wvb({
+ protocols: [
+ localProtocol('myscheme', {
+ hosts: {
+ 'myapp.wvb': 'http://localhost:5173',
+ },
+ }),
+ ],
+});
+```
+
+이후, `myscheme://myapp.wvb` URL로 웹뷰를 로드하여 사용할 수 있습니다.
+
+---
+
+각 플랫폼/프레임워크에서 프로토콜을 사용하는 자세한 방법은 "네이티브" 섹션에서 확인해주세요.
diff --git a/content/docs/guide/core-concepts/protocol-handling.mdx b/content/docs/guide/core-concepts/protocol-handling.mdx
new file mode 100644
index 0000000..f7022ab
--- /dev/null
+++ b/content/docs/guide/core-concepts/protocol-handling.mdx
@@ -0,0 +1,93 @@
+---
+title: Handling protocols
+---
+
+Webview Bundle provides a protocol handler that responds to HTTP requests with the matching resource inside a bundle. On each platform and framework, this protocol handler serves webview requests by returning resources from a Webview Bundle as standard HTTP responses.
+
+## Bundle protocol
+
+The bundle protocol loads the file at the requested path from the Webview Bundle mapped to the request URI, then returns it as an HTTP response.
+
+For example, given the request URI `app://app.wvb/index.html`, the handler resolves the bundle name to `app` and the file path within the bundle to `/index.html`, then returns the HTTP response.
+
+```text
+app://app.wvb/index.html -> bundle "app", file "/index.html"
+app://myapp.wvb/assets/logo.png -> bundle "myapp", file "/assets/logo.png"
+```
+
+Path resolution fills in `index.html` like a static file server. When the path ends with a slash, or the last segment has no `.`, the handler fills in `index.html` and looks up that file in the bundle.
+
+```text
+/ -> /index.html # path ends with a slash
+/about -> /about/index.html # last segment has no "."
+/a.js -> /a.js # last segment has a ".", so it is served as-is
+```
+
+When the bundle is not found, the handler fails with a `BundleNotFound` error and responds with 500 (Internal Server Error).
+
+When the bundle exists but the file is not found inside it, the handler responds with 404 (Not Found).
+
+### Methods, headers, responses
+
+Only `GET` and `HEAD` are served; any other method returns 405 (Method Not Allowed). `HEAD` returns the headers with an empty body.
+
+When serving a file, the handler adds the `Content-Type` and `Content-Length` header values recorded in the index entry by default. Any other headers you added are also included in the response.
+
+```text
+GET app://app.wvb/assets/app.js
+-> 200 OK
+ cache-control: public, max-age=31536000 # value you set on the index entry
+ content-type: application/javascript # value recorded in the index entry
+ content-length: 84213 # decompressed size
+```
+
+### Range requests
+
+Webviews rely on range requests to seek media and resume large downloads. When the `Range` header is present, the handler returns 206 (Partial Content) with `Accept-Ranges: bytes` and `Content-Range`.
+
+```text
+GET app://app.wvb/media/clip.mp4
+Range: bytes=0-1023
+-> 206 Partial Content
+ accept-ranges: bytes
+ content-range: bytes 0-1023/5242880
+ content-length: 1024
+```
+
+- Each returned range is capped at `1000 * 1024` bytes (about 1 MB); a requested range larger than this is truncated.
+- Multiple ranges are returned as a `multipart/byteranges` response.
+- An unsatisfiable range returns 416 (Range Not Satisfiable) with `Content-Range: bytes */`.
+
+```text
+Range: bytes=99999999-
+-> 416 Range Not Satisfiable
+ content-range: bytes */5242880
+```
+
+Ranges are computed against the decompressed content. Offsets therefore match the files the build produced.
+
+## Local protocol
+
+Instead of fetching resources from a Webview Bundle file (`.wvb`), the local protocol lets you proxy resources from localhost.
+
+This is useful during development. For example, to run hot reload from a [Vite](https://vite.dev/) dev server (such as `http://localhost:5173`), configure Electron as follows.
+
+```ts title="main.ts"
+import { localProtocol, wvb } from '@wvb/electron';
+
+const instance = wvb({
+ protocols: [
+ localProtocol('myscheme', {
+ hosts: {
+ 'myapp.wvb': 'http://localhost:5173',
+ },
+ }),
+ ],
+});
+```
+
+You can then load the webview from the `myscheme://myapp.wvb` URL.
+
+---
+
+For details on using protocols on each platform and framework, see the "Native" section.
diff --git a/content/docs/guide/core-concepts/webview-bundle.ko.mdx b/content/docs/guide/core-concepts/webview-bundle.ko.mdx
new file mode 100644
index 0000000..1da3cb1
--- /dev/null
+++ b/content/docs/guide/core-concepts/webview-bundle.ko.mdx
@@ -0,0 +1,66 @@
+---
+title: 웹뷰 번들 (.wvb)
+---
+
+웹뷰 번들은 여러 웹 리소스를 `.wvb` 파일 하나로 묶는 아카이브 포맷입니다. 웹 애플리케이션을 패킹하면 HTML·JS·CSS·이미지 같은 자산이 이 파일 하나에 담깁니다. 네이티브 호스트는 웹뷰의 요청을 가로채 이 파일에서 자산을 읽어 응답합니다. 각 자산은 체크섬과 함께 저장되므로, 네트워크 없이도 무결성이 검증된 자산을 제공할 수 있습니다.
+
+## 데이터 구조
+
+
+
+`.wvb` 파일은 헤더, 인덱스, 데이터 세 부분으로 구성됩니다. 세 부분은 각각 xxHash-32 체크섬으로 끝나며, 이 값으로 무결성을 검증합니다.
+
+### 헤더
+
+헤더는 파일의 첫 17바이트로, 포맷 메타데이터를 담습니다.
+
+- **매직 넘버 (8바이트)** — 웹뷰 번들 포맷을 식별하는 고정값입니다. `0xF09F8C90F09F8E81`을 사용합니다. 이는 UTF-8로 "🌐🎁"이며 빅엔디언으로 기록됩니다.
+- **버전 (1바이트)** — 포맷 버전입니다. 현재 값은 v1(`0x01`)입니다.
+- **인덱스 크기 (4바이트)** — 인덱스 부분의 바이트 크기입니다. u32 값을 빅엔디언으로 저장합니다.
+- **체크섬 (4바이트)** — 앞의 13바이트가 온전한지 검증하는 xxHash-32 해시입니다.
+
+### 인덱스
+
+인덱스는 요청 경로를 데이터 위치에 1:1로 대응시키는 맵입니다. 호스트는 요청 경로로 인덱스를 조회해 해당 자산의 데이터 범위를 찾고, 그 범위를 읽어 응답합니다.
+
+
+
+- 인덱스는 헤더에 명시된 크기만큼 차지하며, 빅엔디언으로 인코딩됩니다.
+- 경로(key)를 항목(value)에 대응시키는 맵이며, 결과가 항상 같도록 key를 정렬한 순서로 직렬화됩니다.
+- 각 항목은 다음 값을 담습니다.
+ - **오프셋, 길이** — 데이터에서 파일 바이트를 읽을 범위를 지정합니다.
+ - **콘텐츠 타입, 콘텐츠 길이** — 자산의 MIME 타입과 압축 전 원본 크기입니다.
+ - **HTTP 헤더** — 프로토콜로 응답할 때 함께 내보낼 헤더입니다. (선택)
+- 마지막 4바이트는 인덱스가 온전한지 검증하는 xxHash-32 체크섬입니다.
+
+### 데이터
+
+데이터는 각 항목의 파일 바이트를 순서대로 저장합니다. 호스트는 인덱스에서 얻은 오프셋과 길이로 읽을 범위를 지정해 해당 자산만 읽습니다.
+
+
+
+예를 들어 `/index.js` 항목이 오프셋 `0`, 길이 `768`로 기록되어 있으면, 호스트는 데이터의 `0`부터 `768`바이트까지를 읽습니다.
+
+- 각 항목은 [LZ4 블록 포맷](https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md)으로 압축됩니다.
+- 마지막 4바이트는 데이터가 온전한지 검증하는 xxHash-32 체크섬입니다.
+
+## 설계 결정
+
+### `.zip` 대신 새 포맷을 만든 이유
+
+`.zip`은 파일 목록(중앙 디렉터리)을 파일 끝에 두므로, 자산을 찾으려면 먼저 끝을 읽어야 합니다. 또한 자산을 HTTP로 응답하는 데 필요한 콘텐츠 타입이나 헤더 같은 정보를 담지 않습니다.
+
+웹뷰 번들은 이 포맷을 웹뷰에 자산을 제공하는 용도에 맞춰 설계했습니다.
+
+- **인덱스를 앞에 둡니다.** 파일을 스캔하지 않고, 한 번의 조회로 경로에서 데이터 위치를 얻습니다.
+- **항목마다 HTTP 메타데이터를 저장합니다.** 호스트는 콘텐츠 타입과 헤더를 그대로 HTTP 응답으로 내보냅니다.
+- **바이트 배치를 고정합니다.** 오프셋과 길이로 필요한 범위만 읽으므로, 부분 요청(HTTP range)과 스트리밍에 유리합니다.
+- **가벼운 체크섬으로 검증합니다.** 헤더·인덱스·데이터를 각각 xxHash-32로 검증합니다.
+
+### LZ4를 선택한 이유
+
+자산 압축 해제는 웹뷰에 응답하는 경로에서 매번 일어나므로, 압축률보다 해제 속도가 중요합니다.
+
+- **압축 해제가 매우 빠릅니다.** 첫 화면을 그리는 순간을 지연시키지 않습니다.
+- **CPU를 적게 씁니다.** UI 스레드를 막지 않습니다.
+- **항목마다 독립적으로 압축합니다.** [LZ4 블록 포맷](https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md)으로 각 자산을 따로 해제하므로, 필요한 자산만 임의 접근으로 읽을 수 있습니다.
diff --git a/content/docs/guide/core-concepts/webview-bundle.mdx b/content/docs/guide/core-concepts/webview-bundle.mdx
new file mode 100644
index 0000000..59a18ed
--- /dev/null
+++ b/content/docs/guide/core-concepts/webview-bundle.mdx
@@ -0,0 +1,66 @@
+---
+title: Webview Bundle (.wvb)
+---
+
+A webview bundle packs many web resources into a single `.wvb` file. Packing a web app puts its assets — HTML, JS, CSS, images — into this one file. The native host intercepts the webview's requests and answers them by reading assets from the file. Each asset is stored with a checksum, so the host serves integrity-checked assets with no network.
+
+## Data structure
+
+
+
+A `.wvb` file has three sections: a header, an index, and data. Each section ends with an xxHash-32 checksum that verifies its integrity.
+
+### Header
+
+The header is the first 17 bytes of the file and holds the format metadata.
+
+- **Magic number (8 bytes)** — a fixed value that identifies the webview bundle format. It is `0xF09F8C90F09F8E81`, which is "🌐🎁" in UTF-8, written big-endian.
+- **Version (1 byte)** — the format version. The current value is v1 (`0x01`).
+- **Index size (4 bytes)** — the size of the index section in bytes, stored as a big-endian u32.
+- **Checksum (4 bytes)** — an xxHash-32 hash that verifies the preceding 13 bytes.
+
+### Index
+
+The index maps each request path one-to-one to the location of its data. The host looks a request path up in the index to find that asset's byte range, then reads the range to answer the request.
+
+
+
+- The index occupies the size declared in the header and is encoded big-endian.
+- It maps a path (key) to an entry (value), serialized in sorted key order so the output is deterministic.
+- Each entry holds:
+ - **Offset and length** — the byte range to read the file's data from the data section.
+ - **Content type and content length** — the asset's MIME type and its original size before compression.
+ - **HTTP headers** — headers to send when serving the asset over the protocol. (Optional)
+- The last 4 bytes are an xxHash-32 checksum that verifies the index.
+
+### Data
+
+The data section stores each entry's file bytes in order. The host reads a single asset by seeking to the offset and length it got from the index.
+
+
+
+For example, if the `/index.js` entry is recorded with offset `0` and length `768`, the host reads bytes `0` through `768` from the data section.
+
+- Each entry is compressed with the [LZ4 block format](https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md).
+- The last 4 bytes are an xxHash-32 checksum that verifies the data.
+
+## Design decisions
+
+### Why a new format instead of `.zip`
+
+`.zip` keeps its file listing (the central directory) at the end of the file, so finding an asset means reading the end first. It also carries none of the information — like content type or headers — needed to answer an HTTP request.
+
+The webview bundle format is built for one job: serving assets to a webview.
+
+- **The index comes first.** One lookup turns a path into a data location, with no scan of the file.
+- **Every entry stores HTTP metadata.** The host sends the content type and headers straight back as the HTTP response.
+- **The byte layout is fixed.** Reading only the needed range by offset and length makes HTTP range requests and streaming straightforward.
+- **A cheap checksum verifies it.** The header, index, and data are each verified with xxHash-32.
+
+### Why LZ4
+
+Decompression runs every time the host answers the webview, so decompression speed matters more than compression ratio.
+
+- **Decompression is very fast.** It does not delay first paint.
+- **It uses little CPU.** It does not block the UI thread.
+- **Each entry compresses independently.** The [LZ4 block format](https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md) decompresses each asset on its own, so the host reads only the assets it needs by random access.
diff --git a/content/docs/guide/frontend/bridges/checking-sources.ko.mdx b/content/docs/guide/frontend/bridges/checking-sources.ko.mdx
new file mode 100644
index 0000000..a99088c
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/checking-sources.ko.mdx
@@ -0,0 +1,37 @@
+---
+title: '소스 확인하기'
+description: 소스 브리지로 웹 앱에서 설치된 번들을 읽습니다. 현재 버전, 전체 버전, 빌트인과 리모트를 구분합니다.
+---
+
+`source` 브리지는 웹 앱에서 네이티브 번들 소스를 읽습니다. 활성 버전을 표시하거나, 설치된 목록을 나열하거나, 빌트인 번들과 리모트(원격) 번들을 구분하는 데 사용하세요.
+
+## 현재 버전 읽기
+
+`loadVersion`은 활성 버전과 그 유형을 반환하며, 번들이 로드되지 않았으면 `null`을 반환합니다.
+
+```ts title="src/version.ts"
+import { source } from '@wvb/bridge';
+
+const current = await source.loadVersion('app');
+// { type: 'builtin' | 'remote', version: '1.2.0' } | null
+```
+
+## 설치된 번들 나열하기
+
+`listBundles`는 호스트가 알고 있는 모든 번들을 반환합니다. 각 항목은 `type`과 매니페스트 항목을 담고 있으며, `current` 여부도 포함됩니다.
+
+```ts
+import { source } from '@wvb/bridge';
+
+const bundles = await source.listBundles();
+
+for (const { type, item } of bundles) {
+ console.log(item.name, item.version, type, item.current);
+}
+```
+
+## 활용 사례
+
+- 실행 중인 버전을 표시하는 "정보" 화면이나 진단 화면을 렌더링합니다.
+- 사용자가 빌트인 폴백을 사용 중인지, 다운로드한 리모트 번들을 사용 중인지 감지합니다.
+- `loadBuiltinMetadata` / `loadRemoteMetadata`를 읽어 무결성이나 마지막 수정 정보를 노출합니다.
diff --git a/content/docs/guide/frontend/bridges/checking-sources.mdx b/content/docs/guide/frontend/bridges/checking-sources.mdx
new file mode 100644
index 0000000..a2ce384
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/checking-sources.mdx
@@ -0,0 +1,37 @@
+---
+title: 'Checking the sources'
+description: Read installed bundles from the web app with the source bridge — current version, all versions, builtin vs remote.
+---
+
+The `source` bridge reads the native bundle source from your web app. Use it to show the active version, list what is installed, and tell builtin bundles apart from remote (over-the-air) ones.
+
+## Read the current version
+
+`loadVersion` returns the active version and its type, or `null` if the bundle is not loaded.
+
+```ts title="src/version.ts"
+import { source } from '@wvb/bridge';
+
+const current = await source.loadVersion('app');
+// { type: 'builtin' | 'remote', version: '1.2.0' } | null
+```
+
+## List installed bundles
+
+`listBundles` returns every bundle the host knows about. Each item carries its `type` and the manifest item, including whether it is `current`.
+
+```ts
+import { source } from '@wvb/bridge';
+
+const bundles = await source.listBundles();
+
+for (const { type, item } of bundles) {
+ console.log(item.name, item.version, type, item.current);
+}
+```
+
+## Use cases
+
+- Render an "about" or diagnostics screen with the running version.
+- Detect whether the user is on a builtin fallback or a downloaded remote bundle.
+- Read `loadBuiltinMetadata` / `loadRemoteMetadata` to surface integrity or last-modified details.
diff --git a/content/docs/guide/frontend/bridges/index.ko.mdx b/content/docs/guide/frontend/bridges/index.ko.mdx
new file mode 100644
index 0000000..149ceb1
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/index.ko.mdx
@@ -0,0 +1,22 @@
+---
+title: '개요'
+description: 자동 감지된 트랜스포트로 네이티브 호스트를 호출하는 웹 측 @wvb/bridge 패키지입니다.
+---
+
+`@wvb/bridge`은 웹 측 패키지입니다. 웹뷰 안의 JavaScript는 이 패키지를 임포트해서 번들 소스와 리모트, 업데이터를 관리하는 네이티브 호스트를 호출합니다. 플랫폼마다 트랜스포트를 자동으로 감지하므로, 동일한 웹 코드가 Electron, Tauri, Android, iOS에서 그대로 실행됩니다.
+
+```ts
+import { source, remote, updater } from '@wvb/bridge';
+```
+
+## 세 가지 표면
+
+- `source` — 네이티브 번들 소스를 확인하고 관리합니다: 번들 목록 조회, 버전 로드 및 전환, 파일 해석.
+- `remote` — 호스트를 통해 설정된 리모트와 통신합니다: 번들 목록 조회, 확인, 다운로드.
+- `updater` — 원격 업데이트 흐름을 구동합니다: 업데이트 확인, 다운로드, 설치.
+
+각 표면은 타입이 지정되어 있으며 저수준 `invoke()` 위에 구축됩니다. 전체 API는 [브릿지 레퍼런스](/docs/references/api/bridge)를 참고하세요.
+
+## 언제 사용하나요
+
+웹 코드가 어떤 번들이 제공되는지 알아야 하거나 바꿔야 할 때 `@wvb/bridge`을 사용하세요. 활성 소스를 확인하거나, 리모트에서 더 새로운 번들을 가져오거나, 앱 안에서 원격 업데이트를 실행하는 경우입니다.
diff --git a/content/docs/guide/frontend/bridges/index.mdx b/content/docs/guide/frontend/bridges/index.mdx
new file mode 100644
index 0000000..0380e90
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/index.mdx
@@ -0,0 +1,22 @@
+---
+title: 'Overview'
+description: The web-side @wvb/bridge package that calls the native host over an auto-detected transport.
+---
+
+`@wvb/bridge` is the web-side package. JavaScript inside the webview imports it to call the native host that manages your bundle source, remote, and updater. It auto-detects the transport per platform, so the same web code runs on Electron, Tauri, Android, and iOS.
+
+```ts
+import { source, remote, updater } from '@wvb/bridge';
+```
+
+## Three surfaces
+
+- `source` — inspect and manage the native bundle source: list bundles, load and switch versions, resolve files.
+- `remote` — talk to the configured remote through the host: list, inspect, and download bundles.
+- `updater` — drive the over-the-air update flow: check for an update, download it, install it.
+
+Each surface is typed and builds on the low-level `invoke()`. See the [bridge reference](/docs/references/api/bridge) for the full API.
+
+## When to reach for it
+
+Reach for `@wvb/bridge` when your web code needs to know or change which bundle is served — checking the active source, pulling a newer bundle from the remote, or running an OTA update from inside the app.
diff --git a/content/docs/guide/frontend/bridges/meta.json b/content/docs/guide/frontend/bridges/meta.json
new file mode 100644
index 0000000..03a1eb5
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Bridges",
+ "pages": ["index", "update-remote-bundles", "checking-sources", "testing"]
+}
diff --git a/content/docs/guide/frontend/bridges/meta.ko.json b/content/docs/guide/frontend/bridges/meta.ko.json
new file mode 100644
index 0000000..dedb0a4
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/meta.ko.json
@@ -0,0 +1 @@
+{ "title": "브릿지", "pages": ["index", "update-remote-bundles", "checking-sources", "testing"] }
diff --git a/content/docs/guide/frontend/bridges/testing.ko.mdx b/content/docs/guide/frontend/bridges/testing.ko.mdx
new file mode 100644
index 0000000..2dd6bcf
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/testing.ko.mdx
@@ -0,0 +1,65 @@
+---
+title: '테스트'
+description: mockBridge로 invoke 호출과 플랫폼을 목 처리하여, 네이티브 호스트 없이 브릿지를 호출하는 웹 코드를 테스트합니다.
+---
+
+네이티브 호스트 없이 브릿지를 호출하는 웹 코드를 테스트합니다. `@wvb/bridge/testing` 서브패스는 브릿지 호출을 목 처리하여, 실제 플랫폼 대신 픽스처를 기준으로 코드가 결과를 반환하도록 합니다.
+
+## 브릿지 목 만들기
+
+`mockBridge(options?)`은 핸들러를 등록하는 해제 가능한 브릿지를 생성합니다. 점으로 구분된 커맨드 키마다 `.mockInvoke(command, handler)`을 체이닝하세요. 핸들러는 메서드의 params를 위치 인자로 받고, 호출이 반환할 값(또는 그 값의 프로미스)을 돌려줍니다.
+
+```ts title="load-version.test.ts"
+import { expect, test } from 'vitest';
+import { source } from '@wvb/bridge';
+import { mockBridge } from '@wvb/bridge/testing';
+
+test('loads the current version', async () => {
+ using bridge = mockBridge({ platform: 'android' });
+ bridge.mockInvoke('source.loadVersion', bundleName => ({
+ type: 'remote',
+ version: '1.0.0',
+ }));
+
+ await expect(source.loadVersion('app')).resolves.toEqual({
+ type: 'remote',
+ version: '1.0.0',
+ });
+});
+```
+
+플랫폼을 함께 목 처리하려면 `{ platform }`을 전달하세요.
+
+```ts
+using bridge = mockBridge({ platform: 'ios' }).mockInvoke('source.loadVersion', () => ({
+ type: 'remote',
+ version: '1.0.0',
+}));
+```
+
+
+ `using`은 블록이 끝날 때 브릿지를 해제하며, 등록한 모든 목을 정리합니다. `using` 없이 사용한다면
+ 스텁이 다음 테스트에 영향가지 않도록 `bridge.clear()`을 직접 호출하세요.
+
+
+## 플랫폼 목 처리하기
+
+`mockPlatform(type)`은 `platform.type`(`platform.isIos`, `platform.isElectron` 등)으로 분기하는 코드를 위해 플랫폼 감지를 단독으로 재정의합니다. 스코프가 끝나면 이전 플랫폼을 복원하는 해제 가능한 핸들을 반환합니다. `type`은 `'electron'`, `'tauri'`, `'android'`, `'ios'`, `'deno'` 중 하나입니다.
+
+```ts
+import { platform } from '@wvb/bridge';
+import { mockPlatform } from '@wvb/bridge/testing';
+
+test('detects the iOS platform', () => {
+ using _platform = mockPlatform('ios');
+ expect(platform.isIos).toBe(true);
+});
+```
+
+## 단일 커맨드 스텁 처리하기
+
+`mockInvoke(command, handler)`은 브릿지 인스턴스 없이 커맨드 하나를 등록하며, `mockBridge`이 기반으로 삼는 기본 API입니다. 해제 가능한 핸들을 반환하며, 등록된 목을 모두 초기화하려면 `afterEach` 등에서 `clearInvokeMocks()`을 호출하세요.
+
+```ts
+using _mock = mockInvoke('source.loadVersion', () => ({ type: 'remote', version: '1.0.0' }));
+```
diff --git a/content/docs/guide/frontend/bridges/testing.mdx b/content/docs/guide/frontend/bridges/testing.mdx
new file mode 100644
index 0000000..42ea4fe
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/testing.mdx
@@ -0,0 +1,65 @@
+---
+title: Testing
+description: Test web code that calls the bridge without a native host — mock invoke calls and the platform with mockBridge.
+---
+
+Test web code that calls the bridge without a native host. The `@wvb/bridge/testing` subpath mocks bridge calls so your code resolves against fixtures instead of a real platform.
+
+## Mock a bridge
+
+`mockBridge(options?)` creates a disposable bridge you register handlers on. Chain `.mockInvoke(command, handler)` once per dotted command key; the handler receives the method's params positionally and returns the value the call resolves to (or a promise of it).
+
+```ts title="load-version.test.ts"
+import { expect, test } from 'vitest';
+import { source } from '@wvb/bridge';
+import { mockBridge } from '@wvb/bridge/testing';
+
+test('loads the current version', async () => {
+ using bridge = mockBridge({ platform: 'android' });
+ bridge.mockInvoke('source.loadVersion', bundleName => ({
+ type: 'remote',
+ version: '1.0.0',
+ }));
+
+ await expect(source.loadVersion('app')).resolves.toEqual({
+ type: 'remote',
+ version: '1.0.0',
+ });
+});
+```
+
+Pass `{ platform }` to mock the platform at the same time:
+
+```ts
+using bridge = mockBridge({ platform: 'ios' }).mockInvoke('source.loadVersion', () => ({
+ type: 'remote',
+ version: '1.0.0',
+}));
+```
+
+
+ `using` disposes the bridge at the end of the block, clearing every mock it registered. Without
+ it, call `bridge.clear()` yourself so the stubs do not leak into the next test.
+
+
+## Mock the platform
+
+`mockPlatform(type)` overrides platform detection on its own, for code that branches on `platform.type` (`platform.isIos`, `platform.isElectron`, …). It returns a disposable that restores the previous platform when the scope exits. `type` is one of `'electron'`, `'tauri'`, `'android'`, `'ios'`, or `'deno'`.
+
+```ts
+import { platform } from '@wvb/bridge';
+import { mockPlatform } from '@wvb/bridge/testing';
+
+test('detects the iOS platform', () => {
+ using _platform = mockPlatform('ios');
+ expect(platform.isIos).toBe(true);
+});
+```
+
+## Stub a single command
+
+`mockInvoke(command, handler)` registers one command without a bridge instance — the primitive `mockBridge` builds on. It returns a disposable handle; reset every ambient mock with `clearInvokeMocks()`, for example in `afterEach`.
+
+```ts
+using _mock = mockInvoke('source.loadVersion', () => ({ type: 'remote', version: '1.0.0' }));
+```
diff --git a/content/docs/guide/frontend/bridges/update-remote-bundles.ko.mdx b/content/docs/guide/frontend/bridges/update-remote-bundles.ko.mdx
new file mode 100644
index 0000000..7cf8861
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/update-remote-bundles.ko.mdx
@@ -0,0 +1,38 @@
+---
+title: '리모트 번들 업데이트'
+description: updater 브리지로 웹 앱에서 원격 업데이트를 진행합니다 — 확인, 다운로드, 설치, 리로드까지.
+---
+
+`updater` 브리지는 웹뷰 안의 웹 코드에서 원격 업데이트를 진행합니다. 새 번들이 있는지 확인하고, 다운로드하고, 설치한 뒤 리로드합니다.
+
+## 업데이트 흐름
+
+`getUpdate`은 새 버전이 있는지 알려줍니다. `download`은 새 버전을 준비하고(검증한 뒤 네이티브 소스 저장소에 기록), `install`은 활성화하며, 리로드하면 해당 번들이 제공됩니다.
+
+```ts title="src/update.ts"
+import { updater } from '@wvb/bridge';
+
+async function checkForUpdate(bundleName: string) {
+ const update = await updater.getUpdate(bundleName);
+ if (!update.isAvailable) {
+ return;
+ }
+
+ await updater.download(bundleName);
+ await updater.install(bundleName, update.version);
+
+ // 리로드하면 웹뷰가 새로 설치된 번들을 제공합니다.
+ window.location.reload();
+}
+```
+
+`getUpdate`은 `BundleUpdateInfo`로 resolve됩니다. `version`은 리모트가 제공하는 버전이고, `localVersion`은 설치되어 있는 버전입니다. 준비 전에 `isAvailable`을 확인하세요.
+
+## 먼저 updater를 구성하세요
+
+브리지는 이 호출들을 네이티브 호스트로 전달할 뿐입니다. updater와 그 리모트는 네이티브 쪽에 있습니다. 연동 방법은 플랫폼별 가이드를 참고하세요.
+
+
+ 호스트에 updater가 구성되어 있지 않으면 `updater` 호출은 `code`가
+ `BridgeErrorCode.UpdaterNotInitialized`인 `BridgeError`로 reject됩니다.
+
diff --git a/content/docs/guide/frontend/bridges/update-remote-bundles.mdx b/content/docs/guide/frontend/bridges/update-remote-bundles.mdx
new file mode 100644
index 0000000..319201a
--- /dev/null
+++ b/content/docs/guide/frontend/bridges/update-remote-bundles.mdx
@@ -0,0 +1,38 @@
+---
+title: 'Update remote bundles'
+description: Drive an over-the-air update from the web app with the updater bridge — check, download, install, and reload.
+---
+
+The `updater` bridge drives an over-the-air update from web code inside the webview: check for a newer bundle, download it, install it, then reload.
+
+## Update flow
+
+`getUpdate` reports whether a newer version is available. `download` stages it (verify and write to the native source store), `install` activates it, and a reload serves it.
+
+```ts title="src/update.ts"
+import { updater } from '@wvb/bridge';
+
+async function checkForUpdate(bundleName: string) {
+ const update = await updater.getUpdate(bundleName);
+ if (!update.isAvailable) {
+ return;
+ }
+
+ await updater.download(bundleName);
+ await updater.install(bundleName, update.version);
+
+ // Reload so the webview serves the newly installed bundle.
+ window.location.reload();
+}
+```
+
+`getUpdate` resolves to a `BundleUpdateInfo`: `version` is what the remote offers, `localVersion` is what is installed. Check `isAvailable` before staging.
+
+## Configure the updater first
+
+The bridge only forwards these calls to the native host — the updater and its remote live on the native side. See your platform's guide to wire them up.
+
+
+ If no updater is configured on the host, `updater` calls reject with a `BridgeError` whose `code`
+ is `BridgeErrorCode.UpdaterNotInitialized`.
+
diff --git a/content/docs/guide/frontend/deploy.ko.mdx b/content/docs/guide/frontend/deploy.ko.mdx
new file mode 100644
index 0000000..3eee359
--- /dev/null
+++ b/content/docs/guide/frontend/deploy.ko.mdx
@@ -0,0 +1,38 @@
+---
+title: 배포하기
+description: 이미 업로드된 버전을 현재 버전으로 승격하여 클라이언트가 원격으로 받아 가도록 합니다.
+---
+
+업로드는 버전을 리모트에 저장합니다. 배포는 그 버전을 **현재** 버전, 즉 클라이언트가 원격으로 다운로드하는 버전으로 만듭니다. 배포는 별도의 빠른 단계로, 다시 패킹하거나 다시 업로드하지 않습니다.
+
+[업로드](/docs/guide/frontend/upload) 뒤에 배포하거나, `wvb upload`에 `--deploy`를 전달해 둘을 한 번에 처리할 수 있습니다.
+
+## 버전 배포하기
+
+이미 업로드된 버전으로 리모트를 지정합니다.
+
+```sh
+wvb deploy app --version 1.2.0
+```
+
+`app`은 번들 이름입니다(설정이나 `package.json` 값으로 대체됩니다). 버전은 `--version`(`-V`) 플래그로 지정하며, 위치 인자로 버전을 넣는 방식은 없습니다.
+
+## 채널
+
+이름이 지정된 채널로 배포하면 릴리스 트랙(`stable`, `beta`, `internal`, …)을 따로 유지할 수 있습니다. 해당 채널의 클라이언트는 그 채널에 배포한 버전을 받습니다.
+
+```sh
+wvb deploy app --version 1.2.0 --channel beta
+```
+
+`--channel`을 생략하면 기본 채널로 배포됩니다.
+
+## 롤백
+
+전용 롤백 명령어는 없습니다. 롤백하려면 이전에 업로드해 둔 버전을 배포하세요. 그 버전이 다시 현재 버전이 됩니다.
+
+```sh
+wvb deploy app --version 1.1.0
+```
+
+리모트에 이미 올라와 있는 버전은 무엇이든 다시 배포할 수 있으므로, 문제없이 동작하는 버전들을 업로드된 상태로 유지하세요.
diff --git a/content/docs/guide/frontend/deploy.mdx b/content/docs/guide/frontend/deploy.mdx
new file mode 100644
index 0000000..177c053
--- /dev/null
+++ b/content/docs/guide/frontend/deploy.mdx
@@ -0,0 +1,38 @@
+---
+title: Deploy
+description: Promote an already-uploaded version to current so clients pick it up over the air.
+---
+
+Uploading stores a version on the remote. Deploying makes it the **current** version — the one clients download over the air. Deploy is a separate, fast step: no repack, no re-upload.
+
+Deploy after [uploading](/docs/guide/frontend/upload), or pass `--deploy` to `wvb upload` to do both at once.
+
+## Deploy a version
+
+Point the remote at a version that is already uploaded:
+
+```sh
+wvb deploy app --version 1.2.0
+```
+
+`app` is the bundle name (falls back to your config or `package.json`). The version is the `--version` (`-V`) flag — there is no positional version.
+
+## Channels
+
+Deploy to a named channel to keep separate release tracks (`stable`, `beta`, `internal`, …). Clients on that channel receive the version you deploy there.
+
+```sh
+wvb deploy app --version 1.2.0 --channel beta
+```
+
+Omit `--channel` to deploy to the default channel.
+
+## Rollback
+
+There is no dedicated rollback command. To roll back, deploy a previously uploaded version — it becomes current again:
+
+```sh
+wvb deploy app --version 1.1.0
+```
+
+Any version already on the remote can be re-deployed, so keep known-good versions uploaded.
diff --git a/content/docs/guide/frontend/development.ko.mdx b/content/docs/guide/frontend/development.ko.mdx
new file mode 100644
index 0000000..f9d979c
--- /dev/null
+++ b/content/docs/guide/frontend/development.ko.mdx
@@ -0,0 +1,68 @@
+---
+title: '개발'
+description: 프레임워크의 개발 서버를 실행하고 네이티브 로컬 프로토콜을 그 서버로 향하게 하여, 웹뷰가 핫 리로드되는 실시간 UI를 불러오도록 합니다.
+---
+
+개발 중에는 변경이 있을 때마다 `.wvb`을 패킹하고 싶지 않을 것입니다. 대신, 프레임워크의 개발 서버를 실행하고 네이티브 **로컬 프로토콜**을 그 서버로 향하게 하여, 웹뷰가 핫 리로드되는 실시간 UI를 불러오도록 합니다.
+
+## 개발 서버 실행하기
+
+평소처럼 프레임워크의 개발 서버를 시작하세요.
+
+```sh
+vite dev
+```
+
+출력되는 URL(예: `http://localhost:5173`)을 확인하세요. 네이티브 쪽에서 이 URL로 프록시합니다.
+
+## 로컬 프로토콜을 개발 서버로 향하게 하기
+
+네이티브 앱은 두 개의 스킴을 등록합니다.
+
+- 패킹된 `.wvb` 번들에서 파일을 제공하는 **번들 프로토콜**.
+- 요청을 개발 서버로 프록시하는 **로컬 프로토콜**.
+
+앱이 개발 환경에서 실행 중인지 프로덕션 환경에서 실행 중인지에 따라 웹뷰가 어느 쪽을 불러올지 선택합니다.
+
+- **개발** — 로컬 프로토콜 URL(예: `app-local://app.wvb`)을 불러옵니다. 요청이 개발 서버로 전달되므로 핫 리로드가 동작합니다.
+- **프로덕션** — 번들 프로토콜 URL(예: `app://app.wvb`)을 불러옵니다. 파일이 번들에서 곧바로 제공됩니다.
+
+정확한 스킴 이름, 호스트와 개발 서버 간의 매핑, 그리고 개발과 프로덕션을 전환하는 방식은 네이티브 쪽 설정입니다. 사용하는 플랫폼의 로컬 개발 페이지에서 설정하세요.
+
+
+
+
+
+
+
+
+
+## 패킹된 번들 미리보기
+
+배포 전에 프로덕션 빌드(개발 서버가 아니라 패킹된 `.wvb`)을 확인하려면, [`wvb serve`](/docs/references/cli/serve)로 HTTP를 통해 제공하세요.
+
+```sh
+wvb serve ./build/app.wvb
+```
+
+이 명령은 번들 하나를 풀어서 `http://localhost:4312`에 제공하며, 이는 런타임에 웹뷰가 번들을 불러오는 방식과 동일합니다.
diff --git a/content/docs/guide/frontend/development.mdx b/content/docs/guide/frontend/development.mdx
new file mode 100644
index 0000000..e07f34c
--- /dev/null
+++ b/content/docs/guide/frontend/development.mdx
@@ -0,0 +1,68 @@
+---
+title: 'Development'
+description: Run your framework's dev server and point the native Local Protocol at it so the webview loads your live, hot-reloading UI.
+---
+
+During development you don't want to pack a `.wvb` on every change. Instead, run your framework's dev server and point the native **Local Protocol** at it, so the webview loads your live, hot-reloading UI.
+
+## Run your dev server
+
+Start your framework's dev server as you normally would:
+
+```sh
+vite dev
+```
+
+Note the URL it prints (for example `http://localhost:5173`) — the native side proxies to it.
+
+## Point the Local Protocol at it
+
+The native app registers two schemes:
+
+- A **Bundle Protocol** that serves files from the packed `.wvb` bundle.
+- A **Local Protocol** that proxies requests to your dev server.
+
+You pick which one the webview loads based on whether the app is running in development or production:
+
+- **Development** — load the Local Protocol URL (e.g. `app-local://app.wvb`). Requests forward to your dev server, so hot reload works.
+- **Production** — load the Bundle Protocol URL (e.g. `app://app.wvb`). Files come straight from the bundle.
+
+The exact scheme names, the host-to-dev-server mapping, and the dev-vs-prod switch are native-side config. Set them up on your platform's Local development page:
+
+
+
+
+
+
+
+
+
+## Preview a packed bundle
+
+To check the production build — the packed `.wvb`, not the dev server — before shipping, serve it over HTTP with [`wvb serve`](/docs/references/cli/serve):
+
+```sh
+wvb serve ./build/app.wvb
+```
+
+This unpacks one bundle and serves it at `http://localhost:4312`, matching how the webview loads it at runtime.
diff --git a/content/docs/guide/frontend/packing.ko.mdx b/content/docs/guide/frontend/packing.ko.mdx
new file mode 100644
index 0000000..e1b8141
--- /dev/null
+++ b/content/docs/guide/frontend/packing.ko.mdx
@@ -0,0 +1,47 @@
+---
+title: '패킹'
+description: wvb pack으로 빌드된 웹 앱을 하나의 .wvb 번들로 패킹합니다.
+---
+
+패킹은 빌드된 웹 앱을 하나의 `.wvb` 파일로 만듭니다. 이 파일은 앱이 웹뷰에 배포하거나 원격 업데이트를 위해 업로드하는 단위입니다.
+
+## 번들 패킹하기
+
+빌드 산출물 디렉터리를 대상으로 `wvb pack`을 실행하세요. 소스 디렉터리 아래의 모든 파일을 읽어 하나의 압축된 `.wvb` 아카이브로 씁니다.
+
+```sh
+wvb pack # uses config defaults
+wvb pack ./dist # explicit source directory
+wvb pack ./dist --outfile ./build/app.wvb # explicit output path
+```
+
+소스 디렉터리의 기본값은 `pack.srcDir`(또는 `./dist`)이고, 출력 경로의 기본값은 `pack.outFile`입니다. `.wvb` 확장자는 생략하면 자동으로 붙습니다. 모든 플래그는 [`wvb pack`](/docs/references/cli/pack)을 참고하세요.
+
+## 이름과 버전
+
+번들 이름은 `package.json`의 `name` 필드에서 스코프 프리픽스를 제거한 값입니다. 이 값이 기본 출력 경로 `.wvb/.wvb`을 결정합니다.
+
+버전은 `package.json`의 `version` 필드에서 가져옵니다. 패킹은 이 값을 파일명에 넣지 않습니다. 버전은 번들을 업로드하거나 빌트인으로 설치할 때 새겨집니다.
+
+## 기본값 설정하기
+
+`wvb.config` 파일에 `pack` 기본값을 설정하면 인자 없이 명령을 실행할 수 있습니다.
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+
+export default defineConfig({
+ pack: {
+ srcDir: './dist',
+ outFile: '.wvb/app',
+ },
+});
+```
+
+`outFile`은 단일 출력 경로이며, 별도의 `outDir`은 없습니다. 명령줄에서 전달한 플래그는 항상 config 값을 덮어씁니다. 전체 `pack` 스키마는 [Configuration](/docs/references/configuration)을 참고하세요.
+
+## 빌트인으로 배포하기
+
+패킹된 `.wvb`은 네이티브 앱 안에 빌트인 번들로 배포할 수 있으며, 앱이 오프라인 상태이거나 새 번들을 원격으로 가져오기 전에 제공됩니다.
+
+`wvb builtin`은 번들을 출력 디렉터리(기본값 `.wvb/builtin/bundles`)로 모으며, `manifest.json`과 함께 `/_.wvb` 형태로 배치합니다.
diff --git a/content/docs/guide/frontend/packing.mdx b/content/docs/guide/frontend/packing.mdx
new file mode 100644
index 0000000..e172b01
--- /dev/null
+++ b/content/docs/guide/frontend/packing.mdx
@@ -0,0 +1,47 @@
+---
+title: 'Packing'
+description: Pack a built web app into a single .wvb bundle with wvb pack.
+---
+
+Packing turns a built web app into one `.wvb` file — the unit your app ships to its webview or uploads for over-the-air updates.
+
+## Pack a bundle
+
+Run `wvb pack` against your build output directory. It reads every file under the source directory and writes a single compressed `.wvb` archive.
+
+```sh
+wvb pack # uses config defaults
+wvb pack ./dist # explicit source directory
+wvb pack ./dist --outfile ./build/app.wvb # explicit output path
+```
+
+The source directory defaults to `pack.srcDir` (or `./dist`), and the output path defaults to `pack.outFile`. The `.wvb` extension is appended when you omit it. See [`wvb pack`](/docs/references/cli/pack) for every flag.
+
+## Name and version
+
+The bundle name comes from your `package.json` `name` field with any scope prefix stripped. It sets the default output path, `.wvb/.wvb`.
+
+The version comes from your `package.json` `version` field. Packing does not embed it in the filename — the version is stamped when you upload the bundle or install it as a builtin.
+
+## Configure defaults
+
+Set `pack` defaults in your `wvb.config` file so the command runs with no arguments.
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+
+export default defineConfig({
+ pack: {
+ srcDir: './dist',
+ outFile: '.wvb/app',
+ },
+});
+```
+
+`outFile` is a single output path — there is no separate `outDir`. A flag passed on the command line always overrides the config value. See [Configuration](/docs/references/configuration) for the full `pack` schema.
+
+## Ship as builtin
+
+A packed `.wvb` can ship inside your native app as a builtin bundle, served when the app is offline or before it fetches a newer bundle over the air.
+
+`wvb builtin` collects bundles into an output directory (default `.wvb/builtin/bundles`), laid out as `/_.wvb` alongside a `manifest.json`.
diff --git a/content/docs/guide/frontend/upload.ko.mdx b/content/docs/guide/frontend/upload.ko.mdx
new file mode 100644
index 0000000..b7e485d
--- /dev/null
+++ b/content/docs/guide/frontend/upload.ko.mdx
@@ -0,0 +1,38 @@
+---
+title: '리모트에 업로드'
+description: 패킹한 .wvb 버전을 리모트에 스테이징해 두었다가 나중에 배포합니다.
+---
+
+`wvb upload`은 패킹한 `.wvb`을 설정된 리모트로 전송합니다. 업로드는 버전을 *스테이징*할 뿐, 해당 버전을 현재 버전으로 만들지는 않습니다.
+
+## 버전 업로드
+
+```sh
+wvb upload app --version 1.2.0
+```
+
+기본적으로 `wvb upload`은 `pack.srcDir`을 패킹하고, 무결성 해시를 계산하고, (서명이 설정되어 있으면) 번들에 서명한 뒤, `remote.uploader`을 통해 업로드합니다. 성공하면 번들 엔드포인트를 출력합니다.
+
+이미 빌드해 둔 번들을 업로드하려면, 패킹을 건너뛰고 해당 파일을 지정하세요.
+
+```sh
+wvb upload app --version 1.2.0 --no-pack --file ./build/app.wvb
+```
+
+리모트에 이미 존재하는 버전을 덮어쓰려면 `--force`을 전달하세요.
+
+## 스테이징과 배포
+
+`--deploy`의 기본값은 `false`이므로, 업로드는 버전을 스테이징만 합니다. 이 버전을 활성화하기 전까지 클라이언트는 이전에 배포된 버전을 계속 받습니다.
+
+- **업로드**은 리모트에 버전을 저장합니다.
+- **배포**은 클라이언트가 다운로드하는 버전을 전환합니다.
+
+
+ `--channel`은 `--deploy`과 함께 사용할 때만 적용됩니다. 한 번에 업로드하고 활성화하려면
+ `--deploy`을 추가하세요(특정 채널을 대상으로 하려면 `--channel beta`도 함께 사용하세요).
+
+
+## 요구 사항
+
+업로드하려면 설정에 `remote.uploader`이 필요합니다. 선택 사항인 `--deploy` 단계에는 `remote.deployer`도 필요합니다. [리모트 설정](/docs/references/configuration/remote)을 참고하세요.
diff --git a/content/docs/guide/frontend/upload.mdx b/content/docs/guide/frontend/upload.mdx
new file mode 100644
index 0000000..25f2519
--- /dev/null
+++ b/content/docs/guide/frontend/upload.mdx
@@ -0,0 +1,38 @@
+---
+title: 'Upload to remote'
+description: Stage a packed .wvb version on the remote so you can deploy it later.
+---
+
+`wvb upload` sends a packed `.wvb` to your configured remote. Uploading _stages_ a version — it does not make it current.
+
+## Upload a version
+
+```sh
+wvb upload app --version 1.2.0
+```
+
+By default `wvb upload` packs `pack.srcDir`, computes an integrity hash, signs the bundle (when signing is configured), then uploads through `remote.uploader`. On success it prints the bundle endpoint.
+
+To upload a bundle you already built, skip packing and point at the file:
+
+```sh
+wvb upload app --version 1.2.0 --no-pack --file ./build/app.wvb
+```
+
+Pass `--force` to overwrite a version that already exists on the remote.
+
+## Stage vs deploy
+
+`--deploy` defaults to `false`, so an upload only stages the version. Clients keep receiving the previously deployed version until you activate this one.
+
+- **Upload** stores the version on the remote.
+- **Deploy** flips which version clients download.
+
+
+ `--channel` applies only with `--deploy`. To upload and activate in one step, add `--deploy` (and
+ `--channel beta` to target a channel).
+
+
+## Requirements
+
+Uploading requires `remote.uploader` in your config. The optional `--deploy` step also needs `remote.deployer`. See [Remote configuration](/docs/references/configuration/remote).
diff --git a/content/docs/guide/getting-started/example.ko.mdx b/content/docs/guide/getting-started/example.ko.mdx
new file mode 100644
index 0000000..45cd197
--- /dev/null
+++ b/content/docs/guide/getting-started/example.ko.mdx
@@ -0,0 +1,34 @@
+---
+title: 예제와 함께 시작하기
+---
+
+`create-wvb` 를 사용하여 선호하는 도구로 예제 프로젝트를 시작해 보세요.
+
+
+
+
+```sh
+npx create-wvb@latest
+```
+
+
+
+```sh
+pnpx create-wvb@latest
+```
+
+
+```sh
+yarn dlx create-wvb@latest
+```
+
+
+
+## 예제 목록
+
+사용가능한 예제 목록은 다음 테이블에서 확인해주세요.
+
+| 예제 | 설명 |
+| ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
+| [electron-builder-app](https://github.com/webview-bundle/webview-bundle/tree/main/examples/electron-builder-app) | electron-builder로 패키징하는 Electron 앱 |
+| [electron-forge-app](https://github.com/webview-bundle/webview-bundle/tree/main/examples/electron-forge-app) | Electron Forge로 패키징하는 Electron 앱 |
diff --git a/content/docs/guide/getting-started/example.mdx b/content/docs/guide/getting-started/example.mdx
new file mode 100644
index 0000000..cdfec1f
--- /dev/null
+++ b/content/docs/guide/getting-started/example.mdx
@@ -0,0 +1,34 @@
+---
+title: Start with an example
+---
+
+Use `create-wvb` to scaffold an example project with your preferred tool.
+
+
+
+
+```sh
+npx create-wvb@latest
+```
+
+
+
+```sh
+pnpx create-wvb@latest
+```
+
+
+```sh
+yarn dlx create-wvb@latest
+```
+
+
+
+## Example list
+
+Find the available examples in the table below.
+
+| Example | Description |
+| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
+| [electron-builder-app](https://github.com/webview-bundle/webview-bundle/tree/main/examples/electron-builder-app) | Electron app packaged with electron-builder |
+| [electron-forge-app](https://github.com/webview-bundle/webview-bundle/tree/main/examples/electron-forge-app) | Electron app packaged with Electron Forge |
diff --git a/content/docs/guide/getting-started/index.ko.mdx b/content/docs/guide/getting-started/index.ko.mdx
new file mode 100644
index 0000000..466a321
--- /dev/null
+++ b/content/docs/guide/getting-started/index.ko.mdx
@@ -0,0 +1,35 @@
+---
+title: 소개
+---
+
+---
+
+## 개요
+
+웹뷰 번들은 웹뷰 기반 프레임워크 및 플랫폼을 위한 "오프라인 우선(Offline First)" 방식의 웹 애플리케이션 배포 시스템입니다.
+
+웹 애플리케이션 코드를 네트워크를 통해 불러와 웹뷰에 렌더하는 대신, 웹뷰 번들 파일(`.wvb`)로 패킹하여 로컬에서 리소스를 불러와 웹뷰에 렌더합니다. 또한, 리모트를 구성하여 원격으로 웹뷰 번들을 다운받아 최신상태의 애플리케이션 코드를 네이티브 배포 없이 효과적으로 업데이트할 수 있습니다.
+
+웹뷰 번들은 웹뷰가 제공되는 [Electron](https://electronjs.org), [Tauri](https://tauri.app), Android, iOS, 그리고 [Deno Desktop](https://deno.com/blog/v2.9#deno-desktop)(실험적)을 지원하며, 웹뷰와 통신할 수 있는 [브릿지](/docs/guide/core-concepts/communicate-between-native-and-webview) 또한 제공됩니다.
+
+## 기능
+
+### 네이티브에 웹을 탑재
+
+웹뷰 번들을 이용하면 익숙한 프론트엔드 코드를 네이티브에 탑재하여 오프라인에서 동작하는 애플리케이션을 만들 수 있습니다. 이를 지원하기 위해 웹 애플리케이션을 웹뷰 번들(`.wvb`) 포맷으로 패킹하여, 네이티브 앱 번들에 포함시킵니다.
+
+네이티브에서는 웹뷰의 요청을 인터셉트하여 네트워크 요청을 웹뷰 번들 파일 내의 리소스를 읽어 응답합니다. 따라서, 네트워크 요청이 인터넷을 통할 필요없이 로컬데이터를 읽어 응답하므로 오프라인에서도 웹 애플리케이션이 동작할 수 있습니다.
+
+### 원격
+
+네이티브 앱 번들에 포함된 번들을 최신 상태로 업데이트하기 위해 웹뷰 번들 시스템은 원격 기술을 제공합니다. [리모트](/docs/guide/core-concepts/over-the-air) 제공자를 만들어 배포할 번들을 업로드할 수 있고, 프론트엔드 혹은 네이티브에서 원하는 타이밍에 신규 번들을 다운받아 최신 상태를 유지할 수 있습니다.
+
+웹뷰 번들을 원격으로 업데이트하는 과정은 네이티브 앱 배포 혹은 앱 재시작없이 수행되기에 UX를 해치지 않고 최신 상태를 유지할 수 있습니다. 예를들어, Electron은 업데이트 하려면 수십MB를 다운받고 앱을 재시작해야 업데이트가 반영되는데, 웹뷰 번들을 이용하면 이 과정을 생략할 수 있습니다.
+
+### 멀티 플랫폼 지원
+
+웹뷰 번들 코어는 Rust로 작성되었으며 여러 플랫폼에 호환가능하게 빌드됩니다.
+
+- Node.js : [napi.rs](https://napi.rs/)를 이용해 [네이티브 애드온](https://nodejs.org/api/n-api.html)을 만들어 사용합니다.
+- Android, iOS : [uniffi](https://github.com/mozilla/uniffi-rs)로 kotlin/swift 바인딩을 생성합니다.
+- Deno : [Deno FFI](https://docs.deno.com/runtime/fundamentals/ffi/) API를 이용해 동적 라이브러리를 링킹하여 사용합니다.
diff --git a/content/docs/guide/getting-started/index.mdx b/content/docs/guide/getting-started/index.mdx
new file mode 100644
index 0000000..1f45b7b
--- /dev/null
+++ b/content/docs/guide/getting-started/index.mdx
@@ -0,0 +1,35 @@
+---
+title: Introduction
+---
+
+---
+
+## Overview
+
+Webview Bundle is an offline-first distributing system for web applications on webview-based frameworks and platforms.
+
+Instead of loading your web app over the network and rendering it in the webview, you pack it into a webview bundle file (`.wvb`) and render it from local resources. You can also set up a remote, download bundles from it, and update your app's code over the air — with no native release.
+
+Webview Bundle supports the platforms that host a webview — [Electron](https://electronjs.org), [Tauri](https://tauri.app), Android, iOS, and [Deno Desktop](https://deno.com/blog/v2.9#deno-desktop) (experimental) — and provides a [bridge](/docs/guide/core-concepts/communicate-between-native-and-webview) for communicating with the webview.
+
+## Features
+
+### Web inside native
+
+With Webview Bundle, you embed familiar frontend code in a native app and ship an application that runs offline. You pack the web app into the webview bundle (`.wvb`) format and include it in the native app bundle.
+
+On the native side, the host intercepts the webview's requests and answers them by reading resources from the bundle file. Because it serves local data instead of going over the network, the web app works offline.
+
+### Over-the-air (OTA)
+
+Webview Bundle provides over-the-air (OTA) updates to keep the bundle inside the native app current. You create a [remote](/docs/guide/core-concepts/over-the-air) provider, upload the bundles you want to ship, and download new bundles from the frontend or the native side whenever you choose.
+
+An over-the-air update needs no native release and no app restart, so the app stays current without disrupting the user. Electron, for example, downloads tens of megabytes and restarts the app to apply an update; Webview Bundle skips that.
+
+### Multi-platform support
+
+The Webview Bundle core is written in Rust and builds for many platforms.
+
+- Node.js — a [native add-on](https://nodejs.org/api/n-api.html) built with [napi.rs](https://napi.rs/).
+- Android and iOS — Kotlin and Swift bindings generated with [UniFFI](https://github.com/mozilla/uniffi-rs).
+- Deno — a dynamic library linked through the [Deno FFI](https://docs.deno.com/runtime/fundamentals/ffi/) API.
diff --git a/content/docs/guide/getting-started/platform-and-framework-support.ko.mdx b/content/docs/guide/getting-started/platform-and-framework-support.ko.mdx
new file mode 100644
index 0000000..b5e71a9
--- /dev/null
+++ b/content/docs/guide/getting-started/platform-and-framework-support.ko.mdx
@@ -0,0 +1,18 @@
+---
+title: 플랫폼/프레임워크 지원
+description: Webview Bundle을 실행하는 플랫폼/프레임워크에 대해 정리합니다.
+---
+
+웹뷰 번들은 여러 네이티브 플랫폼/프레임워크를 지원합니다.
+
+## 지원 현황
+
+| 플랫폼 | 호환 버전 | 상태 |
+| ------------ | ----------------------------- | ------ |
+| Electron | Electron >= 15 | 안정적 |
+| Tauri | Tauri v2 / Windows >= 10 | 안정적 |
+| Android | Android minSdk >= 24 | 안정적 |
+| iOS | iOS 16 / macOS 12.0 / Swift 6 | 안정적 |
+| Deno Desktop | Deno >= 2.9.0 | 실험적 |
+
+더 많은 플랫폼/프레임워크 지원이 필요하다면 [깃헙 논의](https://github.com/webview-bundle/webview-bundle/discussions)에 요청사항을 적어주시면 감사하겠습니다.
diff --git a/content/docs/guide/getting-started/platform-and-framework-support.mdx b/content/docs/guide/getting-started/platform-and-framework-support.mdx
new file mode 100644
index 0000000..acdcfbf
--- /dev/null
+++ b/content/docs/guide/getting-started/platform-and-framework-support.mdx
@@ -0,0 +1,18 @@
+---
+title: Platform and framework support
+description: The platforms and frameworks that run Webview Bundle.
+---
+
+Webview Bundle supports several native platforms and frameworks.
+
+## Support status
+
+| Platform | Compatible versions | Status |
+| ------------ | ----------------------------- | ------------ |
+| Electron | Electron >= 15 | Stable |
+| Tauri | Tauri v2 / Windows >= 10 | Stable |
+| Android | Android minSdk >= 24 | Stable |
+| iOS | iOS 16 / macOS 12.0 / Swift 6 | Stable |
+| Deno Desktop | Deno >= 2.9.0 | Experimental |
+
+If you need support for more platforms or frameworks, please file a request in the [GitHub discussions](https://github.com/webview-bundle/webview-bundle/discussions).
diff --git a/content/docs/guide/getting-started/prerequisites.ko.mdx b/content/docs/guide/getting-started/prerequisites.ko.mdx
new file mode 100644
index 0000000..cd93292
--- /dev/null
+++ b/content/docs/guide/getting-started/prerequisites.ko.mdx
@@ -0,0 +1,38 @@
+---
+title: 필요 사항
+---
+
+프로젝트를 시작하려면 먼저 몇 가지 종속성을 설치해야 합니다.
+
+## Node.js
+
+웹뷰 번들 커맨드라인 사용을 위해 [Node.js](https://nodejs.org/ko)를 설치해주세요.
+
+- 최소 요구 버전 : 18
+- 권장 버전 : LTS
+
+## Android
+
+안드로이드 앱 개발을 위해서 Android SDK 설치가 필요합니다. (혹은 [Android Studio](https://developer.android.com/studio)를 설치)
+
+사용 도구의 최소 요구 버전은 다음과 같습니다.
+
+- JDK 17
+- Android 최소 SDK 버전 : 24
+
+## iOS
+
+iOS 앱 개발을 위해서 [Xcode](https://developer.apple.com/xcode/) 설치가 필요합니다.
+
+사용 도구의 최소 요구 버전은 다음과 같습니다.
+
+- Swift v5
+- iOS 16
+
+## Deno
+
+[Deno Desktop](/docs/guide/native/deno)을 사용한다면 Deno 설치가 필요합니다.
+
+---
+
+플랫폼 별로 필요한 종속성은 각 프레임워크의 가이드를 참고해주세요.
diff --git a/content/docs/guide/getting-started/prerequisites.mdx b/content/docs/guide/getting-started/prerequisites.mdx
new file mode 100644
index 0000000..dcf4948
--- /dev/null
+++ b/content/docs/guide/getting-started/prerequisites.mdx
@@ -0,0 +1,38 @@
+---
+title: Prerequisites
+---
+
+Install a few dependencies before you start a project.
+
+## Node.js
+
+Install [Node.js](https://nodejs.org/en) to use the Webview Bundle command line.
+
+- Minimum required version: 18
+- Recommended version: LTS
+
+## Android
+
+Android app development requires the Android SDK. (Or install [Android Studio](https://developer.android.com/studio).)
+
+The minimum required tool versions are:
+
+- JDK 17
+- Minimum Android SDK version: 24
+
+## iOS
+
+iOS app development requires [Xcode](https://developer.apple.com/xcode/).
+
+The minimum required tool versions are:
+
+- Swift v5
+- iOS 16
+
+## Deno
+
+Install Deno if you use [Deno Desktop](/docs/guide/native/deno).
+
+---
+
+For the dependencies each platform requires, see the guide for each framework.
diff --git a/content/docs/guide/index.ko.mdx b/content/docs/guide/index.ko.mdx
deleted file mode 100644
index 25b25a8..0000000
--- a/content/docs/guide/index.ko.mdx
+++ /dev/null
@@ -1,204 +0,0 @@
----
-title: 소개
-description: 웹뷰 기반 프레임워크와 플랫폼을 위한 오프라인 우선 웹 애플리케이션 배포 시스템입니다.
----
-
-Webview Bundle(`wvb`)은 빌드한 웹 자산(HTML/JS/CSS/미디어)을 압축되고 무결성이 검증된 단일
-아카이브(`.wvb`)로 묶습니다. 앱은 이 아카이브를 함께 배포하고, 커스텀 URL 스킴을 통해 웹뷰에
-제공합니다. 따라서 첫 화면은 네트워크를 기다리지 않습니다. 원격 서버를 설정하면 앱이 더 새로운
-번들을 무선(OTA, over-the-air)으로 다운로드하여, 네이티브 앱스토어 배포 없이 갱신된 코드를
-전달합니다. 하나의 `.wvb` 포맷이 공유 Rust 코어를 통해 모든 웹뷰 플랫폼에서 동작합니다.
-
-
-
-## 빠른 시작
-
-CLI를 설치하고, 빌드 산출물을 묶은 뒤, 로컬에서 미리 봅니다.
-
-
-
-
-```sh
-npm install --save-dev @wvb/cli
-```
-
-
-
-
-```sh
-pnpm add -D @wvb/cli
-```
-
-
-
-
-```sh
-yarn add -D @wvb/cli
-```
-
-
-
-
-```sh
-# ./dist를 .wvb/으로 묶습니다 (--outfile,-O로 변경).
-npx wvb pack
-
-# http://localhost:4312에서 로컬 미리 보기용으로 번들을 제공합니다.
-npx wvb serve
-```
-
-`wvb.config.ts`에서 `defineConfig`로 빌드를 설정하세요.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- pack: {
- // `.wvb`가 자동으로 붙습니다 -> .wvb/app.wvb
- outFile: '.wvb/app',
- },
- serve: {
- port: 4312,
- },
-});
-```
-
-모든 명령어와 필드는 [CLI 레퍼런스](/docs/guide/cli)와 [`wvb.config` 레퍼런스](/docs/config)를 참고하세요.
-
-## Webview Bundle을 쓰는 이유
-
-- **오프라인 우선.** 리소스가 로컬에 번들로 포함되므로 첫 화면은 네트워크를 기다리지 않습니다.
-- **무선 업데이트.** 새 번들 버전을 배포하면 수정 사항이나 기능을 전달합니다. 네이티브 배포가
- 필요 없습니다.
-- **무결성과 진위성.** 모든 번들은 내부 체크섬을 담고 있습니다. 다운로드는 SHA-2 무결성
- 해시(`sha256`, `sha384`, `sha512`)와 디지털 서명(ECDSA, Ed25519, RSA)으로 검증할 수 있습니다.
-- **하나의 포맷, 모든 플랫폼.** 동일한 `.wvb` 아카이브가 공유 Rust 코어를 통해 Electron, Tauri,
- Android, iOS, Deno Desktop에서 동작합니다.
-
-설정에서 원격의 무결성 및 서명 검증을 활성화하세요.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- remote: {
- integrity: { algorithm: 'sha256' },
- signature: {
- algorithm: 'ed25519',
- keyFormat: 'pkcs8',
- // privateKey: ...
- },
- },
-});
-```
-
-전체 스키마는 [원격, 무결성 및 서명 설정](/docs/config/remote)을 참고하세요.
-
-## 여기서 시작하세요
-
-
-
-
-
-
-
-
-
-
-
-## 기능
-
-
-
-
-
-
-
-
-
-## 패키지
-
-코어는 Rust 크레이트입니다. 각 플랫폼은 얇은 통합 패키지를 통해 코어를 사용합니다.
-
-| Package | Version | What it is |
-| ---------------------------- | ------- | ----------------------------------------------------------------------------------------- |
-| [`wvb`](https://docs.rs/wvb) | 0.2.0 | Rust 코어: 번들 포맷, 소스, 원격, 업데이터, 프로토콜, 무결성, 서명 |
-| `@wvb/cli` | 0.1.0 | 커맨드라인 도구(바이너리 `wvb`, `webview-bundle`): pack, serve, upload, deploy, 로컬 원격 |
-| `@wvb/config` | 0.1.0 | `wvb.config.ts`를 위한 `defineConfig` |
-| `@wvb/node` | 0.1.0 | Node.js용 코어 N-API 바인딩 |
-| `@wvb/bridge` | 0.1.0 | 웹 앱이 네이티브 호스트와 통신하기 위한 브리지 |
-| `@wvb/electron` | 0.1.0 | Electron 통합(프로토콜, IPC, 업데이터) |
-| `@wvb/electron-builder` | Stable | electron-builder 통합 |
-| `@wvb/electron-forge` | Stable | Electron Forge 플러그인 |
-| `wvb-tauri` | 0.1.0 | Tauri 통합, crates.io에 Rust 크레이트로 배포됨 |
-| `@wvb/remote-aws` | 0.1.0 | AWS용 원격 설정 및 프로바이더 |
-| `@wvb/remote-cloudflare` | 0.1.0 | Cloudflare용 원격 설정 및 프로바이더 |
-| `@wvb/remote-local` | 0.0.0 | 로컬 시뮬레이션을 위한 원격 설정 |
-
-CLI와 함께 플랫폼 통합을 설치하세요.
-
-```sh
-# Electron
-npm install @wvb/electron
-
-# Tauri (Rust 크레이트)
-cargo add wvb-tauri
-```
-
-
- Tauri 통합은 crates.io의 **`wvb-tauri` 크레이트**입니다. `@wvb/tauri` npm 패키지는 없습니다. 전체
- 상태는 [플랫폼 지원](/docs/guide/platform-support)을 참고하세요.
-
-
-## 다음 단계
-
-아카이브 안에 무엇이 담겨 배포되는지 이해하려면 [`.wvb` 번들 포맷](/docs/guide/bundle-format)을
-읽으세요. 전체 API는 [Rust 크레이트 문서](https://docs.rs/wvb)를 살펴보세요.
diff --git a/content/docs/guide/index.mdx b/content/docs/guide/index.mdx
deleted file mode 100644
index 765b30e..0000000
--- a/content/docs/guide/index.mdx
+++ /dev/null
@@ -1,205 +0,0 @@
----
-title: Introduction
-description: An offline-first web application deployment system for webview-based frameworks and platforms.
----
-
-Webview Bundle (`wvb`) packs your built web assets (HTML/JS/CSS/media) into a single compressed,
-integrity-checked archive (`.wvb`). Your app ships with the archive and serves it to its webview
-through a custom URL scheme — so first paint never waits on the network. Configure a remote and the
-app downloads newer bundles over the air (OTA = over-the-air), shipping updated code without a
-native app-store release. One `.wvb` format runs on every webview platform via a shared Rust core.
-
-
-
-## Quickstart
-
-Install the CLI, pack your build output, and preview it locally.
-
-
-
-
-```sh
-npm install --save-dev @wvb/cli
-```
-
-
-
-
-```sh
-pnpm add -D @wvb/cli
-```
-
-
-
-
-```sh
-yarn add -D @wvb/cli
-```
-
-
-
-
-```sh
-# Pack ./dist into .wvb/ (override with --outfile,-O)
-npx wvb pack
-
-# Serve the bundle for local preview on http://localhost:4312
-npx wvb serve
-```
-
-Configure the build in `wvb.config.ts` with `defineConfig`:
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- pack: {
- // `.wvb` is appended automatically -> .wvb/app.wvb
- outFile: '.wvb/app',
- },
- serve: {
- port: 4312,
- },
-});
-```
-
-See the [CLI reference](/docs/guide/cli) and the [`wvb.config` reference](/docs/config) for every command and field.
-
-## Why Webview Bundle?
-
-- **Offline-first.** Resources are bundled locally, so the first paint never waits on the network.
-- **Over-the-air updates.** Ship a fix or feature by deploying a new bundle version — no native
- release required.
-- **Integrity and authenticity.** Every bundle carries internal checksums, and downloads can be
- verified with SHA-2 integrity hashes (`sha256`, `sha384`, `sha512`) and digital signatures
- (ECDSA, Ed25519, RSA).
-- **One format, every platform.** The same `.wvb` archive runs on Electron, Tauri, Android, iOS,
- and Deno Desktop through a shared Rust core.
-
-Enable integrity and signature verification on the remote in config:
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- remote: {
- integrity: { algorithm: 'sha256' },
- signature: {
- algorithm: 'ed25519',
- keyFormat: 'pkcs8',
- // privateKey: ...
- },
- },
-});
-```
-
-See [Remote, integrity & signature config](/docs/config/remote) for the full schema.
-
-## Start here
-
-
-
-
-
-
-
-
-
-
-
-## Features
-
-
-
-
-
-
-
-
-
-## Packages
-
-The core is a Rust crate. Each platform consumes it through a thin integration package.
-
-| Package | Version | What it is |
-| ---------------------------- | ------- | ---------------------------------------------------------------------------------------------- |
-| [`wvb`](https://docs.rs/wvb) | 0.2.0 | Rust core: bundle format, source, remote, updater, protocol, integrity, signature |
-| `@wvb/cli` | 0.1.0 | Command-line tool (bins `wvb` and `webview-bundle`): pack, serve, upload, deploy, local remote |
-| `@wvb/config` | 0.1.0 | `defineConfig` for `wvb.config.ts` |
-| `@wvb/node` | 0.1.0 | N-API bindings to the core for Node.js |
-| `@wvb/bridge` | 0.1.0 | Bridge for the web app to talk to the native host |
-| `@wvb/electron` | 0.1.0 | Electron integration (protocols, IPC, updater) |
-| `@wvb/electron-builder` | Stable | electron-builder integration |
-| `@wvb/electron-forge` | Stable | Electron Forge plugin |
-| `wvb-tauri` | 0.1.0 | Tauri integration, published as a Rust crate on crates.io |
-| `@wvb/remote-aws` | 0.1.0 | Remote configuration and provider for AWS |
-| `@wvb/remote-cloudflare` | 0.1.0 | Remote configuration and provider for Cloudflare |
-| `@wvb/remote-local` | 0.0.0 | Remote configuration for local simulation |
-
-Install a platform integration alongside the CLI:
-
-```sh
-# Electron
-npm install @wvb/electron
-
-# Tauri (Rust crate)
-cargo add wvb-tauri
-```
-
-
- The Tauri integration is the **`wvb-tauri` crate** on crates.io — there is no `@wvb/tauri` npm
- package. See [Platform Support](/docs/guide/platform-support) for the full status.
-
-
-## Next steps
-
-Read [the `.wvb` bundle format](/docs/guide/bundle-format) to understand what ships inside an
-archive, or browse the [Rust crate docs](https://docs.rs/wvb) for the full API.
diff --git a/content/docs/guide/meta.json b/content/docs/guide/meta.json
index 913b498..8c15bc1 100644
--- a/content/docs/guide/meta.json
+++ b/content/docs/guide/meta.json
@@ -2,29 +2,34 @@
"root": true,
"title": "Guide",
"pages": [
- "---Introduction---",
- "index",
- "platform-support",
- "why-webview-bundle",
- "---Features---",
- "bundle-format",
- "bundle-sources",
- "protocol-handling",
- "remote-bundles",
- "platform-integration",
- "---Platforms---",
- "platforms/electron",
- "platforms/tauri",
- "platforms/android",
- "platforms/ios",
- "platforms/deno",
- "---CLI---",
- "cli",
- "commands",
- "cli-programmatic",
+ "---Getting Started---",
+ "getting-started/index",
+ "getting-started/prerequisites",
+ "getting-started/example",
+ "getting-started/platform-and-framework-support",
+ "---Core Concepts---",
+ "core-concepts/webview-bundle",
+ "core-concepts/glue-to-native",
+ "core-concepts/protocol-handling",
+ "core-concepts/bundle-source",
+ "core-concepts/over-the-air",
+ "core-concepts/communicate-between-native-and-webview",
+ "---Native---",
+ "native/electron",
+ "native/tauri",
+ "native/android",
+ "native/ios",
+ "native/deno",
+ "---Frontend---",
+ "frontend/development",
+ "frontend/packing",
+ "frontend/upload",
+ "frontend/deploy",
+ "frontend/bridges",
"---Remote---",
- "remote",
- "providers",
- "spec"
+ "remote/index",
+ "remote/using-cli",
+ "remote/providers",
+ "remote/http-spec"
]
}
diff --git a/content/docs/guide/meta.ko.json b/content/docs/guide/meta.ko.json
index c5a6c51..0035fc2 100644
--- a/content/docs/guide/meta.ko.json
+++ b/content/docs/guide/meta.ko.json
@@ -2,29 +2,34 @@
"root": true,
"title": "가이드",
"pages": [
- "---소개---",
- "index",
- "platform-support",
- "why-webview-bundle",
- "---기능---",
- "bundle-format",
- "bundle-sources",
- "protocol-handling",
- "remote-bundles",
- "platform-integration",
- "---플랫폼---",
- "platforms/electron",
- "platforms/tauri",
- "platforms/android",
- "platforms/ios",
- "platforms/deno",
- "---CLI---",
- "cli",
- "commands",
- "cli-programmatic",
+ "---시작하기---",
+ "getting-started/index",
+ "getting-started/prerequisites",
+ "getting-started/example",
+ "getting-started/platform-and-framework-support",
+ "---핵심 개념---",
+ "core-concepts/webview-bundle",
+ "core-concepts/glue-to-native",
+ "core-concepts/protocol-handling",
+ "core-concepts/bundle-source",
+ "core-concepts/over-the-air",
+ "core-concepts/communicate-between-native-and-webview",
+ "---네이티브---",
+ "native/electron",
+ "native/tauri",
+ "native/android",
+ "native/ios",
+ "native/deno",
+ "---프론트엔드---",
+ "frontend/development",
+ "frontend/packing",
+ "frontend/upload",
+ "frontend/deploy",
+ "frontend/bridges",
"---원격---",
- "remote",
- "providers",
- "spec"
+ "remote/index",
+ "remote/using-cli",
+ "remote/providers",
+ "remote/http-spec"
]
}
diff --git a/content/docs/guide/native/android/builtin.ko.mdx b/content/docs/guide/native/android/builtin.ko.mdx
new file mode 100644
index 0000000..7f9ca59
--- /dev/null
+++ b/content/docs/guide/native/android/builtin.ko.mdx
@@ -0,0 +1,51 @@
+---
+title: '내장 번들'
+description: wvb builtin으로 빌트인 Webview Bundle을 Android 앱 모듈에 설치하면, 소스가 오프라인에서 이를 제공합니다.
+---
+
+`wvb builtin`은 Android 앱이 함께 배포하는 번들을 모듈의 `assets/bundles/`에 설치합니다. [소스](/docs/guide/native/android)는 원격 업데이트가 이루어지기 전, 첫 실행 시 이곳에서 번들을 읽습니다.
+
+## 앱 모듈에 설치하기
+
+`--android` 프리셋으로 CLI를 실행하세요. `--android`만 쓰면 `com.android.application` 모듈을 자동으로 감지합니다. 모듈을 명시적으로 지정하려면 `--android=`을 전달하세요.
+
+```sh
+# 앱 모듈을 자동 감지하여 src/main/assets/bundles에 설치
+wvb builtin --android
+
+# 또는 특정 모듈을 지정
+wvb builtin --android=./app
+```
+
+번들은 config의 `builtin.target`에서 가져옵니다. 기본값은 원격 엔드포인트이며, 로컬 워크스페이스일 수도 있습니다. 원격 대상을 재정의하려면 `--endpoint`와 `--channel`을 사용하세요. 모든 플래그는 [`wvb builtin`](/docs/references/cli/builtin)을 참고하세요.
+
+프리셋은 `/src/main/assets/bundles/`에 파일을 씁니다. `manifest.json` 하나와, 번들마다 `/_.wvb` 파일 하나가 생성됩니다.
+
+```text
+app/src/main/assets/bundles/
+├── manifest.json
+└── app/
+ └── app_0.1.0.wvb
+```
+
+
+ `.wvb` 에셋이 APK에서 다시 압축되지 않도록, 모듈의 `build.gradle.kts`에 있는 `androidResources`
+ 블록에 `noCompress += "wvb"`을 추가하세요. 이 설정이 없으면 CLI가 경고합니다.
+
+
+## 소스가 번들을 로드하는 방식
+
+네이티브 소스는 에셋 스트림이 아니라 파일을 읽습니다. 그래서 라이브러리는 설치하거나 업데이트할 때마다 `assets/bundles/`를 앱의 `filesDir`로 복사합니다. 이 동작은 `SourceOptions.builtinAssetsDir`로 제어합니다.
+
+```kotlin
+import dev.wvb.SourceOptions
+
+WebViewBundleConfig(
+ protocols = listOf(WebViewBundleProtocol.bundle()),
+ source = SourceOptions(
+ builtinAssetsDir = "bundles", // APK의 assets/, null이면 추출 비활성화
+ ),
+)
+```
+
+`/wvb/builtin` 아래로 추출된 사본은 읽기 전용입니다. 원격 다운로드는 쓰기 가능한 `/wvb/remote`에 저장됩니다.
diff --git a/content/docs/guide/native/android/builtin.mdx b/content/docs/guide/native/android/builtin.mdx
new file mode 100644
index 0000000..9040380
--- /dev/null
+++ b/content/docs/guide/native/android/builtin.mdx
@@ -0,0 +1,52 @@
+---
+title: 'Builtin bundles'
+description: Install builtin Webview Bundles into an Android app module with wvb builtin so the Source serves them offline.
+---
+
+`wvb builtin` installs the bundles your Android app ships into the module's `assets/bundles/`, where the [Source](/docs/guide/native/android) reads them on first launch — before any over-the-air update.
+
+## Install into the app module
+
+Run the CLI with the `--android` preset. Bare `--android` auto-detects the `com.android.application` module; pass `--android=` to point at it explicitly.
+
+```sh
+# Auto-detect the app module and install into src/main/assets/bundles
+wvb builtin --android
+
+# Or target an explicit module
+wvb builtin --android=./app
+```
+
+The bundles come from `builtin.target` in your config — a remote endpoint by default, or local workspaces. Use `--endpoint` and `--channel` to override the remote target. See [`wvb builtin`](/docs/references/cli/builtin) for every flag.
+
+The preset writes into `/src/main/assets/bundles/`: a `manifest.json` plus one `/_.wvb` file per bundle.
+
+```text
+app/src/main/assets/bundles/
+├── manifest.json
+└── app/
+ └── app_0.1.0.wvb
+```
+
+
+ Keep `.wvb` assets uncompressed so the APK does not re-compress them: add `noCompress += "wvb"` to
+ the `androidResources` block in your module's `build.gradle.kts`. The CLI warns when it is
+ missing.
+
+
+## How the Source loads them
+
+The native Source reads files, not asset streams, so the library copies `assets/bundles/` into the app's `filesDir` on each install or update. This is controlled by `SourceOptions.builtinAssetsDir`:
+
+```kotlin
+import dev.wvb.SourceOptions
+
+WebViewBundleConfig(
+ protocols = listOf(WebViewBundleProtocol.bundle()),
+ source = SourceOptions(
+ builtinAssetsDir = "bundles", // APK assets/; null disables extraction
+ ),
+)
+```
+
+The extracted copy under `/wvb/builtin` is read-only; over-the-air downloads land in the writable `/wvb/remote`.
diff --git a/content/docs/guide/platforms/android.ko.mdx b/content/docs/guide/native/android/index.ko.mdx
similarity index 93%
rename from content/docs/guide/platforms/android.ko.mdx
rename to content/docs/guide/native/android/index.ko.mdx
index c65a787..e199143 100644
--- a/content/docs/guide/platforms/android.ko.mdx
+++ b/content/docs/guide/native/android/index.ko.mdx
@@ -1,5 +1,5 @@
---
-title: Android
+title: 설정
description: webview-bundle-android Kotlin 라이브러리로 Android WebView 안에서 Webview Bundle을 제공하고 업데이트합니다.
---
@@ -28,7 +28,8 @@ dependencies {
```
- 모든 플랫폼의 지원 현황은 [플랫폼 지원](/docs/guide/platform-support)을 참고하세요.
+ 모든 플랫폼의 지원 현황은 [플랫폼
+ 지원](/docs/guide/getting-started/platform-and-framework-support)을 참고하세요.
## 빠른 시작
@@ -110,7 +111,7 @@ wvb(context, config) // 짧은 별칭
커스텀 URL 스킴은 등록되지 않습니다. `install()`은 `WebViewClient`를 설정하며, 이 클라이언트의 `shouldInterceptRequest`가 일반적인 `http`/`https` 요청을 검사해 일치하는 요청을 번들 소스에서 제공합니다. 각 요청마다 핸들러는 호스트를 소문자로 바꾼 뒤 등록된 프로토콜을 순서대로 살펴봅니다. 호스트를 처음으로 받아들이는 매처가 선택됩니다. 번들 프로토콜은 첫 번째 호스트 레이블을 번들 이름으로 삼고, 요청 경로에 해당하는 파일을 반환합니다. 일치하는 것이 없으면 `null`을 반환하고 `WebView`는 네트워크에서 로드합니다.
-핸들러가 예외를 던지면 라이브러리는 `500 text/plain` 응답을 만들고 선택적 `onError` 콜백을 호출합니다. 요청 경로가 `.wvb` 안의 파일로 어떻게 매핑되는지는 [프로토콜 처리](/docs/guide/protocol-handling)를 참고하세요.
+핸들러가 예외를 던지면 라이브러리는 `500 text/plain` 응답을 만들고 선택적 `onError` 콜백을 호출합니다. 요청 경로가 `.wvb` 안의 파일로 어떻게 매핑되는지는 [프로토콜 처리](/docs/guide/core-concepts/protocol-handling)를 참고하세요.
`install()`은 메인 프레임에 네이티브 브리지를 `window.wvbAndroid`로 함께 연결합니다. install 옵션을 사용하면 추가 네이티브 핸들러를 등록하거나, 직접 만든 `WebViewClient`를 감싸거나, 브리지를 생략할 수 있습니다.
@@ -175,7 +176,7 @@ WebViewBundleConfig(
}
```
-매니페스트 포맷은 [번들 소스](/docs/guide/bundle-sources)를 참고하세요.
+매니페스트 포맷은 [번들 소스](/docs/guide/core-concepts/bundle-source)를 참고하세요.
## 프로토콜
@@ -314,24 +315,4 @@ await invoke('updaterDownload', { name: 'app' });
await invoke('updaterInstall', { name: 'app', version: '0.2.0' });
```
-플랫폼 전반의 원격 HTTP 규약, 무결성, 서명에 대해서는 [원격 번들](/docs/guide/remote-bundles)과 [원격 구축하기](/docs/guide/remote) 가이드를 참고하세요.
-
-## 다음 단계
-
-
-
-
-
-
+플랫폼 전반의 원격 HTTP 규약, 무결성, 서명에 대해서는 [원격 번들](/docs/guide/core-concepts/over-the-air)과 [원격 구축하기](/docs/guide/remote) 가이드를 참고하세요.
diff --git a/content/docs/guide/platforms/android.mdx b/content/docs/guide/native/android/index.mdx
similarity index 93%
rename from content/docs/guide/platforms/android.mdx
rename to content/docs/guide/native/android/index.mdx
index b977b65..a5dcaf1 100644
--- a/content/docs/guide/platforms/android.mdx
+++ b/content/docs/guide/native/android/index.mdx
@@ -1,5 +1,5 @@
---
-title: Android
+title: Setup
description: Serve and update Webview Bundles inside an Android WebView with the webview-bundle-android Kotlin library.
---
@@ -28,7 +28,8 @@ dependencies {
```
- See [Platform support](/docs/guide/platform-support) for the status of every platform.
+ See [Platform support](/docs/guide/getting-started/platform-and-framework-support) for the status
+ of every platform.
## Quick start
@@ -110,7 +111,7 @@ The manifest needs the internet permission. For local development against a clea
No custom URL scheme is registered. `install()` sets a `WebViewClient` whose `shouldInterceptRequest` inspects ordinary `http`/`https` requests and serves matching ones from the bundle source. For each request, the handler lowercases the host and walks your registered protocols in order; the first matcher to accept the host wins. The bundle protocol takes the bundle name from the first host label and returns the file at the request path. No match returns `null` and the `WebView` loads from the network.
-If a handler throws, the library synthesizes a `500 text/plain` response and calls the optional `onError` callback. See [Protocol handling](/docs/guide/protocol-handling) for how a request path maps to a file inside a `.wvb`.
+If a handler throws, the library synthesizes a `500 text/plain` response and calls the optional `onError` callback. See [Protocol handling](/docs/guide/core-concepts/protocol-handling) for how a request path maps to a file inside a `.wvb`.
`install()` also attaches the native bridge as `window.wvbAndroid` on the main frame. Use the install options to register extra native handlers, wrap your own `WebViewClient`, or skip the bridge:
@@ -175,7 +176,7 @@ Re-extraction happens on each APK install or update and is additive — removed
}
```
-See [Bundle sources](/docs/guide/bundle-sources) for the manifest format.
+See [Bundle sources](/docs/guide/core-concepts/bundle-source) for the manifest format.
## Protocols
@@ -314,24 +315,4 @@ await invoke('updaterDownload', { name: 'app' });
await invoke('updaterInstall', { name: 'app', version: '0.2.0' });
```
-For the remote HTTP contract, integrity, and signatures across platforms, see [Remote bundles](/docs/guide/remote-bundles) and the guide to [building a remote](/docs/guide/remote).
-
-## Next steps
-
-
-
-
-
-
+For the remote HTTP contract, integrity, and signatures across platforms, see [Remote bundles](/docs/guide/core-concepts/over-the-air) and the guide to [building a remote](/docs/guide/remote).
diff --git a/content/docs/guide/native/android/local-development.ko.mdx b/content/docs/guide/native/android/local-development.ko.mdx
new file mode 100644
index 0000000..7abb32b
--- /dev/null
+++ b/content/docs/guide/native/android/local-development.ko.mdx
@@ -0,0 +1,39 @@
+---
+title: '로컬 개발'
+description: Local 프로토콜로 번들 호스트를 개발 서버에 연결해, Android WebView가 실시간 개발 빌드를 로드하도록 합니다.
+---
+
+Local 프로토콜을 등록해 번들 호스트를 번들러의 개발 서버로 매핑하세요. 그러면 `WebView`는 핫 리로드를 그대로 유지한 채 실시간 개발 빌드를 로드하며, 매핑되지 않은 호스트는 여전히 배포된 번들로 폴백됩니다.
+
+## 번들 호스트를 개발 서버로 매핑하기
+
+`WebViewBundleProtocol.local(hosts)`은 전체 요청 호스트를 로컬 베이스 URL로 매핑합니다. 모든 호스트에 일치하는 `bundle()`보다 앞에 등록해, 폴백이 가장 마지막에 오도록 하세요.
+
+```kotlin title="MainActivity.kt"
+import dev.wvb.WebViewBundleConfig
+import dev.wvb.WebViewBundleProtocol
+
+WebViewBundleConfig(
+ protocols = listOf(
+ WebViewBundleProtocol.local(mapOf("app.wvb" to "http://10.0.2.2:3000")),
+ WebViewBundleProtocol.bundle(),
+ ),
+)
+```
+
+이제 `app.wvb`로 향하는 요청은 `http://10.0.2.2:3000`으로 프록시되고, 그 밖의 모든 호스트는 번들 소스에서 제공됩니다.
+
+## 에뮬레이터에서 호스트 머신에 연결하기
+
+Android 에뮬레이터에서 `10.0.2.2`는 호스트 머신의 루프백에 연결됩니다. 따라서 `localhost:3000`에서 실행 중인 개발 서버는 `http://10.0.2.2:3000`이 됩니다. 평문(cleartext) `http://` 서버를 사용하려면 매니페스트에서 `usesCleartextTraffic`을 활성화해야 합니다.
+
+```xml title="AndroidManifest.xml"
+
+
+
+```
+
+
+ `usesCleartextTraffic="true"`는 로컬 개발 전용입니다. 릴리스 빌드에서는 비활성화한 채로 두세요.
+
diff --git a/content/docs/guide/native/android/local-development.mdx b/content/docs/guide/native/android/local-development.mdx
new file mode 100644
index 0000000..915fade
--- /dev/null
+++ b/content/docs/guide/native/android/local-development.mdx
@@ -0,0 +1,39 @@
+---
+title: Local development
+description: Route a bundle host to your dev server with a Local protocol so the Android WebView loads your live dev build.
+---
+
+Register a Local protocol to map a bundle host to your bundler's dev server. The `WebView` then loads your live dev build with hot reload intact, while unmapped hosts still fall through to the shipped bundle.
+
+## Map the bundle host to your dev server
+
+`WebViewBundleProtocol.local(hosts)` maps a full request host to a local base URL. Register it before `bundle()`, which matches every host, so the fallback stays last.
+
+```kotlin title="MainActivity.kt"
+import dev.wvb.WebViewBundleConfig
+import dev.wvb.WebViewBundleProtocol
+
+WebViewBundleConfig(
+ protocols = listOf(
+ WebViewBundleProtocol.local(mapOf("app.wvb" to "http://10.0.2.2:3000")),
+ WebViewBundleProtocol.bundle(),
+ ),
+)
+```
+
+Requests to `app.wvb` now proxy to `http://10.0.2.2:3000`; every other host serves from the bundle source.
+
+## Reach the host machine from the emulator
+
+On the Android emulator, `10.0.2.2` reaches the host machine's loopback, so a dev server on `localhost:3000` is `http://10.0.2.2:3000`. A cleartext `http://` server needs `usesCleartextTraffic` enabled in the manifest.
+
+```xml title="AndroidManifest.xml"
+
+
+
+```
+
+
+ `usesCleartextTraffic="true"` is for local development only. Leave it disabled in release builds.
+
diff --git a/content/docs/guide/native/android/meta.json b/content/docs/guide/native/android/meta.json
new file mode 100644
index 0000000..737d79d
--- /dev/null
+++ b/content/docs/guide/native/android/meta.json
@@ -0,0 +1 @@
+{ "title": "Android", "pages": ["index", "local-development", "builtin"] }
diff --git a/content/docs/guide/native/android/meta.ko.json b/content/docs/guide/native/android/meta.ko.json
new file mode 100644
index 0000000..737d79d
--- /dev/null
+++ b/content/docs/guide/native/android/meta.ko.json
@@ -0,0 +1 @@
+{ "title": "Android", "pages": ["index", "local-development", "builtin"] }
diff --git a/content/docs/guide/native/deno/building.ko.mdx b/content/docs/guide/native/deno/building.ko.mdx
new file mode 100644
index 0000000..352c5ff
--- /dev/null
+++ b/content/docs/guide/native/deno/building.ko.mdx
@@ -0,0 +1,40 @@
+---
+title: '빌드'
+description: Deno 데스크톱 앱을 네이티브 cdylib와 빌트인 번들을 임베드한 자체 완결형 바이너리로 컴파일합니다.
+---
+
+`deno desktop`으로 Deno 데스크톱 앱을 배포 가능한 바이너리로 컴파일하세요. `@wvb/deno` 네이티브 라이브러리와 빌트인 번들을 임베드하므로 별도 설치 없이 오프라인으로 실행됩니다.
+
+
+ 컴파일된 바이너리 하나는 하나의 플랫폼만 대상으로 합니다. 배포하는 target triple마다 한 번씩
+ 벤더링하고 컴파일하세요.
+
+
+## 네이티브 라이브러리 벤더링
+
+`@wvb/deno`는 FFI를 통해 사전 빌드된 Rust `cdylib`를 로드합니다. 빌드하려는 대상에 맞는 라이브러리를 프로젝트로 다운로드하세요.
+
+```sh
+deno run -A jsr:@wvb/deno/install --out vendor/wvb --target aarch64-apple-darwin
+```
+
+라이브러리는 `libwvb_deno.dylib`, `libwvb_deno.so`, `wvb_deno.dll`처럼 단순한 플랫폼 이름으로 저장되며, 파일을 쓰기 전에 SHA-256 체크섬을 검증합니다. 지원하는 target triple은 [Deno Desktop 설정](/docs/guide/native/deno)을 참고하세요.
+
+## 배포 가능한 바이너리 컴파일
+
+`deno desktop --include`로 벤더링한 `cdylib`와 패킹된 `bundles` 디렉터리를 임베드한 다음, 런타임에 `import.meta.url`에서 라이브러리를 resolve하세요.
+
+```sh
+deno desktop --allow-ffi --include vendor/wvb/libwvb_deno.dylib --include bundles main.ts
+```
+
+```ts title="main.ts"
+import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
+
+const wvb = await webviewBundle({
+ lib: new URL('./vendor/wvb/libwvb_deno.dylib', import.meta.url),
+ protocols: [bundleProtocol('app')],
+});
+```
+
+앱은 진입 모듈 옆에 포함된 `bundles` 디렉터리에서 빌트인 번들을 읽으므로, 컴파일된 바이너리는 네트워크 접근 없이 이를 제공합니다.
diff --git a/content/docs/guide/native/deno/building.mdx b/content/docs/guide/native/deno/building.mdx
new file mode 100644
index 0000000..6886450
--- /dev/null
+++ b/content/docs/guide/native/deno/building.mdx
@@ -0,0 +1,39 @@
+---
+title: Building
+description: Compile a Deno Desktop app into a self-contained binary that embeds the native cdylib and your builtin bundles.
+---
+
+Compile a Deno Desktop app into a distributable binary with `deno desktop`, embedding the `@wvb/deno` native library and your builtin bundles so it runs offline with no separate install.
+
+
+ A single compiled binary targets one platform. Vendor and compile once per target triple you ship.
+
+
+## Vendor the native library
+
+`@wvb/deno` loads a prebuilt Rust `cdylib` over FFI. Download it into your project for the target you are building:
+
+```sh
+deno run -A jsr:@wvb/deno/install --out vendor/wvb --target aarch64-apple-darwin
+```
+
+The library is saved under the plain platform name — `libwvb_deno.dylib`, `libwvb_deno.so`, or `wvb_deno.dll` — and its SHA-256 checksum is verified before it is written. See the [Deno Desktop setup](/docs/guide/native/deno) for the supported target triples.
+
+## Compile a distributable binary
+
+Embed the vendored `cdylib` and your packed `bundles` directory with `deno desktop --include`, then resolve the library from `import.meta.url` at runtime:
+
+```sh
+deno desktop --allow-ffi --include vendor/wvb/libwvb_deno.dylib --include bundles main.ts
+```
+
+```ts title="main.ts"
+import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
+
+const wvb = await webviewBundle({
+ lib: new URL('./vendor/wvb/libwvb_deno.dylib', import.meta.url),
+ protocols: [bundleProtocol('app')],
+});
+```
+
+The app reads builtin bundles from the included `bundles` directory next to its entry module, so the compiled binary serves them without network access.
diff --git a/content/docs/guide/native/deno/builtin.ko.mdx b/content/docs/guide/native/deno/builtin.ko.mdx
new file mode 100644
index 0000000..3dbbe16
--- /dev/null
+++ b/content/docs/guide/native/deno/builtin.ko.mdx
@@ -0,0 +1,29 @@
+---
+title: '내장 번들'
+description: 번들을 BundleSource가 읽는 빌트인 디렉터리에 설치하여 Deno Desktop 앱 안에 함께 배포합니다.
+---
+
+빌트인 번들은 앱 안에 함께 배포되므로, 아직 원격으로 다운로드한 번들이 없는 첫 실행 시점에도 창이 오프라인으로 로드됩니다. `BundleSource`는 `builtinDir`에서 이 번들들을 읽고, `wvb builtin`은 설치되는 번들 세트를 그 디렉터리에 기록합니다.
+
+## 소스를 빌트인 디렉터리로 지정하기
+
+`webviewBundle`은 `source`로부터 `BundleSource`를 만듭니다. `builtinDir`에는 번들을 설치할 디렉터리를 지정하세요. 기본값은 엔트리 모듈 옆의 `bundles`입니다.
+
+```ts title="main.ts"
+import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
+
+const wvb = await webviewBundle({
+ protocols: [bundleProtocol('app')],
+ source: { builtinDir: './bundles' },
+});
+```
+
+## wvb builtin으로 설치하기
+
+`wvb builtin`을 실행하고 `--out`을 같은 디렉터리로 지정하세요. 이 명령은 `manifest.json` 하나와, 번들마다 `/_.wvb` 파일 하나씩을 기록합니다.
+
+```sh
+wvb builtin --out ./bundles
+```
+
+번들을 리모트 엔드포인트에서 가져올지 로컬 워크스페이스에서 가져올지는 CLI 플래그가 아니라 [설정](/docs/references/configuration)의 `builtin.target`으로 정합니다. 기본값은 리모트 타깃입니다. 모든 플래그와 두 가지 타깃 형태는 [`wvb builtin`](/docs/references/cli/builtin)을 참고하세요.
diff --git a/content/docs/guide/native/deno/builtin.mdx b/content/docs/guide/native/deno/builtin.mdx
new file mode 100644
index 0000000..e718693
--- /dev/null
+++ b/content/docs/guide/native/deno/builtin.mdx
@@ -0,0 +1,29 @@
+---
+title: 'Builtin bundles'
+description: Ship bundles inside a Deno Desktop app by installing them into the builtin directory the BundleSource reads.
+---
+
+Builtin bundles ship inside the app so the window loads offline on first launch, before any bundle has been downloaded over the air. The `BundleSource` reads them from its `builtinDir`; `wvb builtin` writes the installed set into that directory.
+
+## Point the source at a builtin directory
+
+`webviewBundle` builds its `BundleSource` from `source`. Set `builtinDir` to the directory you install into. It defaults to `bundles` next to your entry module.
+
+```ts title="main.ts"
+import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
+
+const wvb = await webviewBundle({
+ protocols: [bundleProtocol('app')],
+ source: { builtinDir: './bundles' },
+});
+```
+
+## Install with wvb builtin
+
+Run `wvb builtin` and point `--out` at that same directory. It writes a `manifest.json` plus one `/_.wvb` file per bundle.
+
+```sh
+wvb builtin --out ./bundles
+```
+
+Whether bundles come from a remote endpoint or from local workspaces is set by `builtin.target` in your [config](/docs/references/configuration), not by a CLI flag. It defaults to a remote target. See [`wvb builtin`](/docs/references/cli/builtin) for every flag and both target shapes.
diff --git a/content/docs/guide/platforms/deno.ko.mdx b/content/docs/guide/native/deno/index.ko.mdx
similarity index 82%
rename from content/docs/guide/platforms/deno.ko.mdx
rename to content/docs/guide/native/deno/index.ko.mdx
index de7ceb3..58d7f15 100644
--- a/content/docs/guide/platforms/deno.ko.mdx
+++ b/content/docs/guide/native/deno/index.ko.mdx
@@ -1,5 +1,5 @@
---
-title: Deno Desktop
+title: 설정
description: Deno.serve 요청 핸들러를 통해 Deno 데스크톱 웹뷰에서 Webview Bundle 아카이브를 제공하고 업데이트합니다.
---
@@ -11,7 +11,7 @@ Deno Desktop은 `Deno.BrowserWindow`로 네이티브 웹뷰를 렌더링하고 `
import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
});
const server = Deno.serve(wvb.fetch);
@@ -19,11 +19,11 @@ const win = new Deno.BrowserWindow({ url: `http://localhost:${server.addr.port}`
await win.closed;
```
-`@wvb/deno-desktop`은 통합 계층입니다. `@wvb/deno`는 [`@wvb/node`](/docs/references/node)의 FFI 짝으로, 공유 Rust 코어를 구동합니다.
+`@wvb/deno-desktop`은 통합 계층입니다. `@wvb/deno`는 [`@wvb/node`](/docs/references/api/node)의 FFI 짝으로, 공유 Rust 코어를 구동합니다.
## 구성 방식
-창은 `Deno.serve`가 제공하는 단일 로컬 origin을 가리킵니다. 따라서 이 통합은 **단일 origin**이며, 정확히 하나의 프로토콜만 허용합니다. 코어가 각 플랫폼에 도달하는 방식은 [플랫폼 통합](/docs/guide/platform-integration)을, 프로토콜 모델은 [프로토콜 처리](/docs/guide/protocol-handling)를 참고하세요.
+창은 `Deno.serve`가 제공하는 단일 로컬 origin을 가리킵니다. 따라서 이 통합은 **단일 origin**이며, 정확히 하나의 프로토콜만 허용합니다. 코어가 각 플랫폼에 도달하는 방식은 [플랫폼 통합](/docs/guide/core-concepts/glue-to-native)을, 프로토콜 모델은 [프로토콜 처리](/docs/guide/core-concepts/protocol-handling)를 참고하세요.
## @wvb/deno-desktop
@@ -33,7 +33,7 @@ await win.closed;
import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
updater: {
remote: { endpoint: 'https://bundles.example.com' },
},
@@ -52,7 +52,7 @@ await win.closed;
import { webviewBundle, bundleProtocol, registerBindings } from '@wvb/deno-desktop';
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
});
const server = Deno.serve(wvb.fetch);
@@ -85,7 +85,7 @@ if (update.isAvailable) {
## @wvb/deno
-`@wvb/deno`는 [`@wvb/node`](/docs/references/node)의 FFI 짝입니다. `Deno.dlopen`을 통해 사전 빌드된 Rust `cdylib`를 로드하고, 동일한 코어 클래스인 `BundleProtocol`, `LocalProtocol`, `Remote`, `BundleSource`, `Updater`를 노출합니다.
+`@wvb/deno`는 [`@wvb/node`](/docs/references/api/node)의 FFI 짝입니다. `Deno.dlopen`을 통해 사전 빌드된 Rust `cdylib`를 로드하고, 동일한 코어 클래스인 `BundleProtocol`, `LocalProtocol`, `Remote`, `BundleSource`, `Updater`를 노출합니다.
각 클래스는 `Disposable`입니다. `free()`로 명시적으로 해제하거나, `using` 선언으로 스코프를 벗어날 때 `[Symbol.dispose]`를 호출하게 하세요.
@@ -147,7 +147,7 @@ deno run -A jsr:@wvb/deno/install --out vendor/wvb
deno run -A jsr:@wvb/deno/install --out vendor/wvb --target x86_64-unknown-linux-gnu
```
-전체 클래스 및 메서드 레퍼런스는 [Deno API 레퍼런스](/docs/references/deno)를 참고하세요.
+전체 클래스 및 메서드 레퍼런스는 [Deno API 레퍼런스](/docs/references/api/deno)를 참고하세요.
## 제한 사항
@@ -157,7 +157,7 @@ deno run -A jsr:@wvb/deno/install --out vendor/wvb --target x86_64-unknown-linux
```ts title="updater.ts"
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
updater: {
remote: { endpoint: 'https://bundles.example.com' },
integrityPolicy: 'strict',
@@ -175,7 +175,7 @@ const wvb = await webviewBundle({
```ts title="http.ts"
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
updater: {
remote: {
endpoint: 'https://bundles.example.com',
@@ -185,24 +185,4 @@ const wvb = await webviewBundle({
});
```
-무결성과 서명이 플랫폼 전반에서 동작하는 방식은 [원격 번들](/docs/guide/remote-bundles)을 참고하세요.
-
-## 다음 단계
-
-
-
-
-
-
+무결성과 서명이 플랫폼 전반에서 동작하는 방식은 [원격 번들](/docs/guide/core-concepts/over-the-air)을 참고하세요.
diff --git a/content/docs/guide/platforms/deno.mdx b/content/docs/guide/native/deno/index.mdx
similarity index 81%
rename from content/docs/guide/platforms/deno.mdx
rename to content/docs/guide/native/deno/index.mdx
index 5bb4d53..1bea5eb 100644
--- a/content/docs/guide/platforms/deno.mdx
+++ b/content/docs/guide/native/deno/index.mdx
@@ -1,5 +1,5 @@
---
-title: Deno Desktop
+title: Setup
description: Serve and update Webview Bundle archives in a Deno desktop webview through a Deno.serve request handler.
---
@@ -11,7 +11,7 @@ Deno Desktop renders a native webview with `Deno.BrowserWindow` and serves it ov
import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
});
const server = Deno.serve(wvb.fetch);
@@ -19,11 +19,11 @@ const win = new Deno.BrowserWindow({ url: `http://localhost:${server.addr.port}`
await win.closed;
```
-`@wvb/deno-desktop` is the integration layer; `@wvb/deno` is the FFI peer of [`@wvb/node`](/docs/references/node) that drives the shared Rust core.
+`@wvb/deno-desktop` is the integration layer; `@wvb/deno` is the FFI peer of [`@wvb/node`](/docs/references/api/node) that drives the shared Rust core.
## How it fits together
-The window points at a single local origin served by `Deno.serve`, so the integration is **single-origin**: it allows exactly one protocol. See [Platform integration](/docs/guide/platform-integration) for how the core reaches each platform and [Protocol handling](/docs/guide/protocol-handling) for the protocol model.
+The window points at a single local origin served by `Deno.serve`, so the integration is **single-origin**: it allows exactly one protocol. See [Platform integration](/docs/guide/core-concepts/glue-to-native) for how the core reaches each platform and [Protocol handling](/docs/guide/core-concepts/protocol-handling) for the protocol model.
## @wvb/deno-desktop
@@ -33,7 +33,7 @@ Call `webviewBundle(config)` — aliased as `wvb`, backed by the `WebviewBundle`
import { webviewBundle, bundleProtocol } from '@wvb/deno-desktop';
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
updater: {
remote: { endpoint: 'https://bundles.example.com' },
},
@@ -52,7 +52,7 @@ Call `registerBindings(win, wvb)` to expose native commands to your web app. It
import { webviewBundle, bundleProtocol, registerBindings } from '@wvb/deno-desktop';
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
});
const server = Deno.serve(wvb.fetch);
@@ -85,7 +85,7 @@ if (update.isAvailable) {
## @wvb/deno
-`@wvb/deno` is the FFI peer of [`@wvb/node`](/docs/references/node). It loads a prebuilt Rust `cdylib` through `Deno.dlopen` and exposes the same core classes: `BundleProtocol`, `LocalProtocol`, `Remote`, `BundleSource`, and `Updater`.
+`@wvb/deno` is the FFI peer of [`@wvb/node`](/docs/references/api/node). It loads a prebuilt Rust `cdylib` through `Deno.dlopen` and exposes the same core classes: `BundleProtocol`, `LocalProtocol`, `Remote`, `BundleSource`, and `Updater`.
Each class is `Disposable` — free it explicitly with `free()`, or let a `using` declaration call `[Symbol.dispose]` at scope exit:
@@ -147,7 +147,7 @@ Vendor a specific target with `--target`:
deno run -A jsr:@wvb/deno/install --out vendor/wvb --target x86_64-unknown-linux-gnu
```
-For the full class and method reference, see the [Deno API reference](/docs/references/deno).
+For the full class and method reference, see the [Deno API reference](/docs/references/api/deno).
## Limitations
@@ -157,7 +157,7 @@ Two gaps apply to the Deno bindings today.
```ts title="updater.ts"
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
updater: {
remote: { endpoint: 'https://bundles.example.com' },
integrityPolicy: 'strict',
@@ -175,7 +175,7 @@ The custom `integrityChecker` / `signatureVerifier` function callbacks available
```ts title="http.ts"
const wvb = await webviewBundle({
- protocols: [bundleProtocol({ scheme: 'app' })],
+ protocols: [bundleProtocol('app')],
updater: {
remote: {
endpoint: 'https://bundles.example.com',
@@ -185,24 +185,4 @@ const wvb = await webviewBundle({
});
```
-For how integrity and signatures work across platforms, see [Remote bundles](/docs/guide/remote-bundles).
-
-## Next steps
-
-
-
-
-
-
+For how integrity and signatures work across platforms, see [Remote bundles](/docs/guide/core-concepts/over-the-air).
diff --git a/content/docs/guide/native/deno/local-development.ko.mdx b/content/docs/guide/native/deno/local-development.ko.mdx
new file mode 100644
index 0000000..3f6566a
--- /dev/null
+++ b/content/docs/guide/native/deno/local-development.ko.mdx
@@ -0,0 +1,32 @@
+---
+title: '로컬 개발'
+description: localProtocol로 Deno 데스크톱 웹뷰를 개발 서버에 프록시하여 라이브 리로드를 사용합니다.
+---
+
+실험적
+
+개발 중에는 `bundleProtocol`을 `localProtocol`로 바꾸세요. 웹뷰를 개발 서버에 프록시하므로, 패킹된 `.wvb` 자산을 로드하는 대신 수정 사항이 라이브로 리로드됩니다.
+
+## 개발 서버로 프록시하기
+
+`localProtocol(scheme, { hosts })`은 제공되는 host를 개발 서버의 base URL로 매핑합니다. 이를 Vite(또는 프런트엔드를 제공하는 무엇이든)로 향하게 하고, 프로덕션과 동일한 단일 origin `webviewBundle` 연결을 그대로 재사용하세요.
+
+```ts title="main.ts"
+import { webviewBundle, localProtocol } from '@wvb/deno-desktop';
+
+const wvb = await webviewBundle({
+ protocols: [
+ localProtocol('app', {
+ hosts: { app: 'http://localhost:5173' },
+ }),
+ ],
+});
+
+const server = Deno.serve(wvb.fetch);
+const win = new Deno.BrowserWindow({ url: `http://localhost:${server.addr.port}` });
+await win.closed;
+```
+
+## 프로덕션으로 되돌리기
+
+패킹된 `.wvb` 자산을 오프라인으로 제공하려면 `localProtocol`을 다시 `bundleProtocol`로 바꾸세요. 프로덕션 연결은 [설정](/docs/guide/native/deno)을 참고하세요.
diff --git a/content/docs/guide/native/deno/local-development.mdx b/content/docs/guide/native/deno/local-development.mdx
new file mode 100644
index 0000000..f038852
--- /dev/null
+++ b/content/docs/guide/native/deno/local-development.mdx
@@ -0,0 +1,32 @@
+---
+title: 'Local development'
+description: Proxy the Deno desktop webview to your dev server for live reload with localProtocol.
+---
+
+Experimental
+
+In development, swap `bundleProtocol` for `localProtocol`. It proxies the webview to your dev server, so edits reload live instead of loading packed `.wvb` assets.
+
+## Proxy to your dev server
+
+`localProtocol(scheme, { hosts })` maps the served host to your dev server's base URL. Point it at Vite (or whatever serves your frontend) and reuse the same single-origin `webviewBundle` wiring as production.
+
+```ts title="main.ts"
+import { webviewBundle, localProtocol } from '@wvb/deno-desktop';
+
+const wvb = await webviewBundle({
+ protocols: [
+ localProtocol('app', {
+ hosts: { app: 'http://localhost:5173' },
+ }),
+ ],
+});
+
+const server = Deno.serve(wvb.fetch);
+const win = new Deno.BrowserWindow({ url: `http://localhost:${server.addr.port}` });
+await win.closed;
+```
+
+## Switch back for production
+
+Swap `localProtocol` back for `bundleProtocol` to serve packed `.wvb` assets offline. See [Setup](/docs/guide/native/deno) for the production wiring.
diff --git a/content/docs/guide/native/deno/meta.json b/content/docs/guide/native/deno/meta.json
new file mode 100644
index 0000000..48ae628
--- /dev/null
+++ b/content/docs/guide/native/deno/meta.json
@@ -0,0 +1 @@
+{ "title": "Deno Desktop", "pages": ["index", "local-development", "building", "builtin"] }
diff --git a/content/docs/guide/native/deno/meta.ko.json b/content/docs/guide/native/deno/meta.ko.json
new file mode 100644
index 0000000..48ae628
--- /dev/null
+++ b/content/docs/guide/native/deno/meta.ko.json
@@ -0,0 +1 @@
+{ "title": "Deno Desktop", "pages": ["index", "local-development", "building", "builtin"] }
diff --git a/content/docs/guide/platforms/electron/builder.ko.mdx b/content/docs/guide/native/electron/builder.ko.mdx
similarity index 84%
rename from content/docs/guide/platforms/electron/builder.ko.mdx
rename to content/docs/guide/native/electron/builder.ko.mdx
index ba2d6ad..3e87be5 100644
--- a/content/docs/guide/platforms/electron/builder.ko.mdx
+++ b/content/docs/guide/native/electron/builder.ko.mdx
@@ -3,7 +3,7 @@ title: Electron Builder
description: 패키징 시점에 빌트인 .wvb 번들을 설치하고, electron-builder 앱에서 네이티브 @wvb/node 애드온을 유지합니다.
---
-`@wvb/electron-builder`는 Webview Bundle을 [electron-builder](https://www.electron.build/) 패키징과 통합합니다. electron-builder의 `afterPack` 단계에 훅을 걸어, 빌트인 `.wvb` 번들을 패키징된 앱의 리소스에 설치합니다. 그 결과 `@wvb/electron`이 런타임에 제공하는 번들이 앱과 함께 배포됩니다. 단, 네이티브 `@wvb/node` 애드온을 ASAR 아카이브에서 제외하도록 electron-builder를 직접 설정해야 합니다. 런타임 설정은 [Electron 가이드](/docs/guide/platforms/electron)를 먼저 읽으세요. Forge용 동일 기능은 [Electron Forge](/docs/guide/platforms/electron/forge)를 참고하세요.
+`@wvb/electron-builder`는 Webview Bundle을 [electron-builder](https://www.electron.build/) 패키징과 통합합니다. electron-builder의 `afterPack` 단계에 훅을 걸어, 빌트인 `.wvb` 번들을 패키징된 앱의 리소스에 설치합니다. 그 결과 `@wvb/electron`이 런타임에 제공하는 번들이 앱과 함께 배포됩니다. 단, 네이티브 `@wvb/node` 애드온을 ASAR 아카이브에서 제외하도록 electron-builder를 직접 설정해야 합니다. 런타임 설정은 [Electron 가이드](/docs/guide/native/electron)를 먼저 읽으세요. Forge용 동일 기능은 [Electron Forge](/docs/guide/native/electron/forge)를 참고하세요.
## 하는 일
@@ -57,7 +57,7 @@ export default withWebviewBundle(
| `channel` | `string` | — | 번들을 설치할 릴리스 채널(예: `"beta"`) |
| `throwWhenBuiltinIsEmpty` | `boolean` | `true` | 설치된 번들이 하나도 없으면 예외를 던짐 |
-`builtin.target`을 설정하지 않으면, 플러그인은 해석된 원격 엔드포인트를 사용하는 `remote` 타깃을 기본값으로 씁니다. `bundlesDir`은 `..` 세그먼트가 없는 상대 경로여야 합니다. 내부의 `builtin` 및 `remote` 설정은 [설정](/docs/config)을 참고하고, 설치 소스는 [원격 서버 구축하기](/docs/guide/remote)를 참고하세요.
+`builtin.target`을 설정하지 않으면, 플러그인은 해석된 원격 엔드포인트를 사용하는 `remote` 타깃을 기본값으로 씁니다. `bundlesDir`은 `..` 세그먼트가 없는 상대 경로여야 합니다. 내부의 `builtin` 및 `remote` 설정은 [설정](/docs/references/configuration)을 참고하고, 설치 소스는 [원격 서버 구축하기](/docs/guide/remote)를 참고하세요.
## 네이티브 애드온을 ASAR에서 제외하기
@@ -82,28 +82,3 @@ export default withWebviewBundle(
개발 환경에서는 앱이 동작하지만 패키징 후 `@wvb/node`에 대한 모듈 누락 오류가 발생한다면, 네이티브
애드온이 ASAR로 묶인 것입니다. 위의 `asarUnpack` 글로브를 추가하고 다시 패키징하세요.
-
-## 다음 단계
-
-
-
-
-
-
-
diff --git a/content/docs/guide/platforms/electron/builder.mdx b/content/docs/guide/native/electron/builder.mdx
similarity index 82%
rename from content/docs/guide/platforms/electron/builder.mdx
rename to content/docs/guide/native/electron/builder.mdx
index 12e9515..1996a32 100644
--- a/content/docs/guide/platforms/electron/builder.mdx
+++ b/content/docs/guide/native/electron/builder.mdx
@@ -3,7 +3,7 @@ title: Electron Builder
description: Install builtin .wvb bundles at package time and keep the native @wvb/node addon in your electron-builder app.
---
-`@wvb/electron-builder` integrates Webview Bundle with [electron-builder](https://www.electron.build/) packaging. It hooks electron-builder's `afterPack` step to install your builtin `.wvb` bundles into the packaged app's resources, so the bundles `@wvb/electron` serves at runtime ship with the app. You still configure electron-builder to keep the native `@wvb/node` addon out of the ASAR archive. Read the [Electron guide](/docs/guide/platforms/electron) first for the runtime setup, and see [Electron Forge](/docs/guide/platforms/electron/forge) for the Forge equivalent.
+`@wvb/electron-builder` integrates Webview Bundle with [electron-builder](https://www.electron.build/) packaging. It hooks electron-builder's `afterPack` step to install your builtin `.wvb` bundles into the packaged app's resources, so the bundles `@wvb/electron` serves at runtime ship with the app. You still configure electron-builder to keep the native `@wvb/node` addon out of the ASAR archive. Read the [Electron guide](/docs/guide/native/electron) first for the runtime setup, and see [Electron Forge](/docs/guide/native/electron/forge) for the Forge equivalent.
## What it does
@@ -57,7 +57,7 @@ Both `@wvb/electron-builder` and `@wvb/electron-forge` share these options:
| `channel` | `string` | — | release channel to install bundles from (e.g. `"beta"`) |
| `throwWhenBuiltinIsEmpty` | `boolean` | `true` | throw if zero bundles end up installed |
-When `builtin.target` is not set, the plugin defaults to a `remote` target using the resolved remote endpoint. `bundlesDir` must be a relative path with no `..` segments. See [configuration](/docs/config) for the underlying `builtin` and `remote` config, and [Building a remote](/docs/guide/remote) for the install source.
+When `builtin.target` is not set, the plugin defaults to a `remote` target using the resolved remote endpoint. `bundlesDir` must be a relative path with no `..` segments. See [configuration](/docs/references/configuration) for the underlying `builtin` and `remote` config, and [Building a remote](/docs/guide/remote) for the install source.
## Keep the native addon out of the ASAR
@@ -82,28 +82,3 @@ The plugin handles installing the builtin bundles, so you do not need `extraReso
If the app works in development but throws a missing-module error for `@wvb/node` once packaged,
the native addon was packed into the ASAR. Add the `asarUnpack` glob above and repackage.
-
-## Next steps
-
-
-
-
-
-
-
diff --git a/content/docs/guide/native/electron/builtin.ko.mdx b/content/docs/guide/native/electron/builtin.ko.mdx
new file mode 100644
index 0000000..10a054e
--- /dev/null
+++ b/content/docs/guide/native/electron/builtin.ko.mdx
@@ -0,0 +1,41 @@
+---
+title: '내장 번들'
+description: 오프라인 폴백으로 사용할 읽기 전용 .wvb 번들을 Electron 패키지 안에 포함해 배포하세요.
+---
+
+내장 번들은 Electron 패키지 안에 포함해 배포하는 `.wvb` 파일입니다. 읽기 전용이며 항상 존재하므로, 앱이 오프라인 상태로 시작하거나 원격으로 더 새로운 번들을 아직 가져오기 전에도 제공할 무언가가 있습니다. 런타임에서는 다운로드된 업데이트가 내장 번들보다 우선합니다. [번들 소스](/docs/guide/core-concepts/bundle-source)를 참고하세요.
+
+## 내장 번들이 위치하는 곳
+
+`@wvb/electron`은 `source.builtinDir`에서 내장 번들을 읽으며, 패키징된 앱에서는 기본값이 `process.resourcesPath/bundles`, 즉 `resources/bundles` 디렉터리입니다. 각 번들은 `manifest.json` 옆에 `/_.wvb` 형태로 배치됩니다.
+
+## `wvb builtin`으로 설치하기
+
+`wvb builtin`은 번들을 출력 디렉터리에 설치합니다. 소스는 [설정](/docs/references/configuration)의 `builtin.target`으로 지정합니다. `remote` 타깃은 업데이트 서버에서 번들을 다운로드하고, `local` 타깃은 로컬 워크스페이스에서 번들을 패킹합니다.
+
+```sh
+# 설정된 타깃에서 bundles 디렉터리로 설치합니다
+wvb builtin --out bundles
+
+# 또는 리모트 엔드포인트를 명시적으로 지정해 가져옵니다
+wvb builtin --endpoint https://updates.example.com --out bundles
+```
+
+`--out`은 앱과 함께 배포할 디렉터리를 가리키게 하세요. 모든 플래그는 [`wvb builtin`](/docs/references/cli/builtin)을 참고하세요.
+
+## 패키징 시점에 설치하기
+
+Forge 및 Builder 통합은 패키징 과정에서 내장 번들을 대신 설치하고 패키징된 앱의 `resources/bundles`로 복사해 주므로, `.wvb` 파일을 직접 준비할 필요가 없습니다.
+
+
+
+
+
diff --git a/content/docs/guide/native/electron/builtin.mdx b/content/docs/guide/native/electron/builtin.mdx
new file mode 100644
index 0000000..bdc35ee
--- /dev/null
+++ b/content/docs/guide/native/electron/builtin.mdx
@@ -0,0 +1,41 @@
+---
+title: 'Builtin bundles'
+description: Ship read-only .wvb bundles inside your Electron package as an offline fallback.
+---
+
+Builtin bundles are the `.wvb` files you ship inside your Electron package. They are read-only and always present, so your app has something to serve when it starts offline or before it has fetched a newer bundle over the air. Downloaded updates take priority over builtin bundles at runtime — see [Bundle sources](/docs/guide/core-concepts/bundle-source).
+
+## Where builtin bundles land
+
+`@wvb/electron` reads builtin bundles from `source.builtinDir`, which defaults to `process.resourcesPath/bundles` in a packaged app — the `resources/bundles` directory. Each bundle is laid out as `/_.wvb` next to a `manifest.json`.
+
+## Install with `wvb builtin`
+
+`wvb builtin` installs bundles into an output directory. The source is set by `builtin.target` in your [config](/docs/references/configuration): a `remote` target downloads them from your update server, a `local` target packs them from local workspaces.
+
+```sh
+# Install from the configured target into a bundles directory
+wvb builtin --out bundles
+
+# Or pull from a remote endpoint explicitly
+wvb builtin --endpoint https://updates.example.com --out bundles
+```
+
+Point `--out` at the directory you ship with your app. See [`wvb builtin`](/docs/references/cli/builtin) for every flag.
+
+## Install at package time
+
+The Forge and Builder integrations install builtin bundles for you during packaging and copy them into the packaged app's `resources/bundles`, so you never stage `.wvb` files by hand.
+
+
+
+
+
diff --git a/content/docs/guide/platforms/electron/forge.ko.mdx b/content/docs/guide/native/electron/forge.ko.mdx
similarity index 81%
rename from content/docs/guide/platforms/electron/forge.ko.mdx
rename to content/docs/guide/native/electron/forge.ko.mdx
index 6dc3f87..721d092 100644
--- a/content/docs/guide/platforms/electron/forge.ko.mdx
+++ b/content/docs/guide/native/electron/forge.ko.mdx
@@ -3,7 +3,7 @@ title: Electron Forge
description: 패킹된 .wvb 빌트인을 추가 리소스로 함께 배포하고, @wvb/electron-forge 플러그인으로 네이티브 @wvb/node 애드온을 ASAR에서 언팩합니다.
---
-`@wvb/electron-forge`는 Webview Bundle을 패키징 단계에 연결하는 Electron Forge 플러그인입니다. 패킹된 `.wvb` 빌트인을 패키징된 앱의 리소스에 설치하고, 네이티브 `@wvb/node` 애드온이 런타임에 항상 사용 가능하도록 도와줍니다. 덕분에 번들 파일을 직접 배치할 필요가 없습니다. 메인 [Electron 가이드](/docs/guide/platforms/electron)와 함께 사용하세요. 이 가이드는 커스텀 프로토콜로 번들을 제공하고 무선(OTA)으로 업데이트하는 방법을 다룹니다.
+`@wvb/electron-forge`는 Webview Bundle을 패키징 단계에 연결하는 Electron Forge 플러그인입니다. 패킹된 `.wvb` 빌트인을 패키징된 앱의 리소스에 설치하고, 네이티브 `@wvb/node` 애드온이 런타임에 항상 사용 가능하도록 도와줍니다. 덕분에 번들 파일을 직접 배치할 필요가 없습니다. 메인 [Electron 가이드](/docs/guide/native/electron)와 함께 사용하세요. 이 가이드는 커스텀 프로토콜로 번들을 제공하고 무선(OTA)으로 업데이트하는 방법을 다룹니다.
## 플러그인이 하는 일
@@ -35,7 +35,7 @@ export default config;
| `channel` | `string` | — | 번들을 설치할 릴리스 채널 |
| `throwWhenBuiltinIsEmpty` | `boolean` | `true` | 설치된 번들이 하나도 없으면 예외를 던짐 |
-또한 공유 [Webview Bundle 설정](/docs/config)에서 가져온 인라인 `root`와 `builtin` 설정을 받습니다. `builtin.target`을 지정하지 않으면, 플러그인은 확인된 원격 엔드포인트를 사용하는 `remote` 타깃을 기본값으로 씁니다.
+또한 공유 [Webview Bundle 설정](/docs/references/configuration)에서 가져온 인라인 `root`와 `builtin` 설정을 받습니다. `builtin.target`을 지정하지 않으면, 플러그인은 확인된 원격 엔드포인트를 사용하는 `remote` 타깃을 기본값으로 씁니다.
## 네이티브 바이너리와 추가 리소스
@@ -58,7 +58,7 @@ const config: ForgeConfig = {
};
```
-`extraResource: ['bundles']`는 빌트인 번들을 패키징된 앱의 리소스로 배포합니다. `AutoUnpackNativesPlugin`은 `@wvb/node` 네이티브 `.node` 바이너리를 ASAR에서 언팩하여 런타임에 로드할 수 있게 합니다. [`wvb pack`](/docs/guide/commands/pack)을 실행하면 그 `bundles` 디렉터리에 들어갈 `.wvb` 파일을 생성합니다.
+`extraResource: ['bundles']`는 빌트인 번들을 패키징된 앱의 리소스로 배포합니다. `AutoUnpackNativesPlugin`은 `@wvb/node` 네이티브 `.node` 바이너리를 ASAR에서 언팩하여 런타임에 로드할 수 있게 합니다. [`wvb pack`](/docs/references/cli/pack)을 실행하면 그 `bundles` 디렉터리에 들어갈 `.wvb` 파일을 생성합니다.
## node_modules를 유지해 네이티브 모듈을 번들에 포함하기
@@ -80,28 +80,3 @@ const config: ForgeConfig = {
`@wvb/node` 바이너리가 빠졌을 가능성이 큽니다. 위의 `extraResource`, `AutoUnpackNativesPlugin`,
`packagerConfig.ignore` 재정의를 확인하세요.
-
-## 다음 단계
-
-
-
-
-
-
-
diff --git a/content/docs/guide/platforms/electron/forge.mdx b/content/docs/guide/native/electron/forge.mdx
similarity index 82%
rename from content/docs/guide/platforms/electron/forge.mdx
rename to content/docs/guide/native/electron/forge.mdx
index 3cecc07..cec0848 100644
--- a/content/docs/guide/platforms/electron/forge.mdx
+++ b/content/docs/guide/native/electron/forge.mdx
@@ -3,7 +3,7 @@ title: Electron Forge
description: Ship packed .wvb builtins as an extra resource and unpack the native @wvb/node addon from the ASAR with the @wvb/electron-forge plugin.
---
-`@wvb/electron-forge` is an Electron Forge plugin that wires Webview Bundle into your packaging step. It installs your packed `.wvb` builtins into the packaged app's resources and helps keep the native `@wvb/node` addon available at runtime, so you do not stage bundle files by hand. Use it alongside the main [Electron guide](/docs/guide/platforms/electron), which covers serving bundles through a custom protocol and updating them over the air.
+`@wvb/electron-forge` is an Electron Forge plugin that wires Webview Bundle into your packaging step. It installs your packed `.wvb` builtins into the packaged app's resources and helps keep the native `@wvb/node` addon available at runtime, so you do not stage bundle files by hand. Use it alongside the main [Electron guide](/docs/guide/native/electron), which covers serving bundles through a custom protocol and updating them over the air.
## What the plugin does
@@ -35,7 +35,7 @@ The plugin reads its options from `WebviewBundlePluginConfig`:
| `channel` | `string` | — | release channel to install bundles from |
| `throwWhenBuiltinIsEmpty` | `boolean` | `true` | throw if zero bundles are installed |
-It also accepts inline `root` and `builtin` config picked from the shared [Webview Bundle config](/docs/config). When `builtin.target` is unset, the plugin defaults to a `remote` target using your resolved remote endpoint.
+It also accepts inline `root` and `builtin` config picked from the shared [Webview Bundle config](/docs/references/configuration). When `builtin.target` is unset, the plugin defaults to a `remote` target using your resolved remote endpoint.
## Native binary and extra resource
@@ -58,7 +58,7 @@ const config: ForgeConfig = {
};
```
-`extraResource: ['bundles']` ships the builtin bundles into the packaged app's resources. `AutoUnpackNativesPlugin` unpacks the `@wvb/node` native `.node` binary out of the ASAR so it can be loaded at runtime. Run [`wvb pack`](/docs/guide/commands/pack) to produce the `.wvb` files that land in that `bundles` directory.
+`extraResource: ['bundles']` ships the builtin bundles into the packaged app's resources. `AutoUnpackNativesPlugin` unpacks the `@wvb/node` native `.node` binary out of the ASAR so it can be loaded at runtime. Run [`wvb pack`](/docs/references/cli/pack) to produce the `.wvb` files that land in that `bundles` directory.
## Keep node_modules so the native module is bundled
@@ -80,28 +80,3 @@ const config: ForgeConfig = {
the native `@wvb/node` binary was most likely left out. Verify `extraResource`,
`AutoUnpackNativesPlugin`, and the `packagerConfig.ignore` override above.
-
-## Next steps
-
-
-
-
-
-
-
diff --git a/content/docs/guide/platforms/electron/index.ko.mdx b/content/docs/guide/native/electron/index.ko.mdx
similarity index 85%
rename from content/docs/guide/platforms/electron/index.ko.mdx
rename to content/docs/guide/native/electron/index.ko.mdx
index eeb8770..90269a6 100644
--- a/content/docs/guide/platforms/electron/index.ko.mdx
+++ b/content/docs/guide/native/electron/index.ko.mdx
@@ -1,11 +1,11 @@
---
-title: Electron
+title: 설정
description: 커스텀 프로토콜을 통해 Electron UI를 .wvb 번들에서 제공하고, 개발 서버 프록시와 무선(OTA) 업데이트를 활용하세요.
---
`@wvb/electron`은 세 단계로 Webview Bundle을 Electron에 연결합니다. 커스텀 프로토콜을 통해 UI를 `.wvb` 번들에서 제공하고, 개발 중에는 개발 서버로 프록시하며, 필요하면 번들을 무선(OTA)으로 업데이트합니다.
-## Install
+## 설치
`@wvb/electron`은 Electron 15 이상이 필요하며 `@wvb/node`(사전 빌드된 N-API 바이너리 — Rust 툴체인 불필요)를 함께 설치합니다. `@wvb/cli`는 빌드 시점에 번들을 패킹합니다.
@@ -36,7 +36,7 @@ yarn add -D @wvb/cli
-## Register the protocol in the main process
+## 메인 프로세스에서 프로토콜 등록하기
윈도우를 로드하기 전에 `wvb(...)`(`webviewBundle(...)`의 별칭)를 호출하세요. 스킴을 privileged로 등록하고, 소스를 빌드하며, 프로토콜 핸들러와 IPC를 연결합니다.
@@ -100,10 +100,11 @@ bundleProtocol('app', {
각 스킴은 다음 기본값으로 privileged로 등록됩니다. `standard`, `secure`, `bypassCSP`,
`allowServiceWorkers`, `supportFetchAPI`, `corsEnabled`, `codeCache`는 모두 `true`이고, `stream`은
- `false`입니다. 전체 모델은 [프로토콜 처리](/docs/guide/protocol-handling)를 참고하세요.
+ `false`입니다. 전체 모델은 [프로토콜 처리](/docs/guide/core-concepts/protocol-handling)를
+ 참고하세요.
-## Add the preload script
+## 프리로드 스크립트 추가하기
프리로드는 브리지가 소스, 원격, 업데이터 호출에 사용하는 안전한 전송 계층(`window.wvbElectron.invoke`)을 노출합니다.
@@ -115,7 +116,7 @@ preload();
`webPreferences.preload`가 컴파일된 프리로드를 가리키게 하고, `contextIsolation: true`와 `nodeIntegration: false`를 유지하세요.
-## Call the API from the renderer
+## 렌더러에서 API 호출하기
UI에서 업데이트를 제어하려면 렌더러 코드에서 브리지를 임포트하세요. 예를 들어 "업데이트 확인" 버튼을 만들 수 있습니다. 브리지는 IPC를 통해 메인 프로세스로 전달되므로 프리로드가 로드되었을 때만 동작합니다.
@@ -137,7 +138,7 @@ if (update) {
`source`, `remote`, `updater`는 `@wvb/bridge`에서 가져오며, `@wvb/bridge`는
`@wvb/electron/preload`가 설치한 Electron 전송 계층을 자동으로 감지합니다.
- `getUpdate`/`download`의 결과 형태는 [Node API 레퍼런스](/docs/references/node)에 있습니다.
+ `getUpdate`/`download`의 결과 형태는 [Node API 레퍼런스](/docs/references/api/node)에 있습니다.
`wvb(...)`가 반환하는 인스턴스에서 메인 프로세스의 동일한 인터페이스에 접근할 수 있습니다.
@@ -149,7 +150,7 @@ instance.updater; // Updater | null (null unless `updater` is configured)
await instance.whenProtocolRegistered();
```
-## Configure over-the-air updates
+## 원격 업데이트 구성하기
원격을 가리키는 `updater` 블록을 추가하세요. 이 블록이 있어야 `instance.remote`와 `instance.updater`가 활성화됩니다. 블록을 생략하면 렌더러의 `remote.*` / `updater.*` 호출이 "not initialized" 브리지 오류로 실패합니다.
@@ -178,9 +179,9 @@ updater: {
},
```
-`SignatureAlgorithm`은 `ecdsaSecp256R1`, `ecdsaSecp384R1`, `ed25519`, `rsaPkcs1V15`, `rsaPss` 중 하나입니다. 무결성과 서명에 대한 자세한 내용은 [원격 번들](/docs/guide/remote-bundles)을, 서버를 세우는 방법(테스트용 로컬 서버 포함)은 [원격 서버 구축](/docs/guide/remote)을 참고하세요.
+`SignatureAlgorithm`은 `ecdsaSecp256R1`, `ecdsaSecp384R1`, `ed25519`, `rsaPkcs1V15`, `rsaPss` 중 하나입니다. 무결성과 서명에 대한 자세한 내용은 [원격 번들](/docs/guide/core-concepts/over-the-air)을, 서버를 세우는 방법(테스트용 로컬 서버 포함)은 [원격 서버 구축](/docs/guide/remote)을 참고하세요.
-## Where bundles live
+## 번들이 저장되는 위치
`source`는 `builtinDir`(함께 배포되는 읽기 전용)와 `remoteDir`(다운로드된 업데이트)를 받으며, 둘 다 Electron에 맞는 기본값을 가집니다.
@@ -189,9 +190,9 @@ updater: {
| `builtinDir` | 패키징된 경우 `process.resourcesPath/bundles`, 그 외에는 `process.cwd()/bundles` |
| `remoteDir` | `app.getPath('userData')/bundles` |
-다운로드된 버전이 builtin 버전보다 우선하므로, `updater.download(...)`와 `updater.install(...)` 후에는 설치된 업데이트가 자동으로 제공됩니다. 해석 방식에 대한 자세한 내용은 [번들 소스](/docs/guide/bundle-sources)를 참고하세요.
+다운로드된 버전이 builtin 버전보다 우선하므로, `updater.download(...)`와 `updater.install(...)` 후에는 설치된 업데이트가 자동으로 제공됩니다. 해석 방식에 대한 자세한 내용은 [번들 소스](/docs/guide/core-concepts/bundle-source)를 참고하세요.
-## Pack and ship bundles
+## 번들을 패킹하고 배포하기
웹 앱을 빌드한 다음, 산출물을 `builtinDir`로 지정한 디렉터리에 패킹하세요.
@@ -200,53 +201,28 @@ updater: {
npx wvb pack ./dist --outfile bundles/app/app_1.0.0.wvb
```
-프로토콜이 런타임에 번들을 제공할 수 있도록 `bundles` 디렉터리를 앱과 함께 배포하세요. 이 파일들을 패키지에 스테이징하는 방법은 패키징 도구에 따라 다릅니다 — [패키징](#packaging)을 참고하세요.
+프로토콜이 런타임에 번들을 제공할 수 있도록 `bundles` 디렉터리를 앱과 함께 배포하세요. 이 파일들을 패키지에 스테이징하는 방법은 패키징 도구에 따라 다릅니다 — [패키징](#패키징)을 참고하세요.
-## Packaging
+## 패키징
`@wvb/electron`을 사용하는 Electron 앱을 패키징하려면 최종 빌드에 두 가지가 포함되어야 합니다. builtin `bundles` 디렉터리가 앱과 함께 배포되어야 하고, 네이티브 `@wvb/node` 바이너리가 런타임에 로드될 수 있도록 ASAR 아카이브 밖에 있어야 합니다. 전용 통합이 두 가지를 모두 처리하고 패키징 시점에 builtin 번들을 설치하므로, `.wvb` 파일을 직접 스테이징할 필요가 없습니다.
-## Troubleshooting
+## 문제 해결
- **빈 윈도우 또는 `ERR_FAILED`** — `wvb(...)`가 실행되고 `whenProtocolRegistered()`가 `loadURL` 전에 resolve되는지, 그리고 URL의 번들 이름이 패킹된 파일과 일치하는지 확인하세요(`app://app.wvb` → 번들 `app`).
- **렌더러가 브리지 API에 접근하지 못함** — 프리로드가 로드되지 않았습니다. `webPreferences.preload`와 프리로드가 `preload()`를 호출하는지 확인하세요.
-- **`remote_not_initialized` / `updater_not_initialized`** — `remote.*` / `updater.*` 호출이 있었지만 `wvb(...)`에 `updater` 블록을 전달하지 않았습니다. [무선(OTA) 업데이트](#configure-over-the-air-updates) 설정을 추가하세요.
-- **개발에서는 동작하지만 패키징하면 실패함** — `bundles` 리소스나 네이티브 `@wvb/node` 바이너리가 패키지에 포함되지 않았습니다. [패키징](#packaging)을 참고하세요.
-
-## Next steps
-
-
-
-
-
-
-
+- **`remote_not_initialized` / `updater_not_initialized`** — `remote.*` / `updater.*` 호출이 있었지만 `wvb(...)`에 `updater` 블록을 전달하지 않았습니다. [무선(OTA) 업데이트](#원격-업데이트-구성하기) 설정을 추가하세요.
+- **개발에서는 동작하지만 패키징하면 실패함** — `bundles` 리소스나 네이티브 `@wvb/node` 바이너리가 패키지에 포함되지 않았습니다. [패키징](#패키징)을 참고하세요.
diff --git a/content/docs/guide/platforms/electron/index.mdx b/content/docs/guide/native/electron/index.mdx
similarity index 88%
rename from content/docs/guide/platforms/electron/index.mdx
rename to content/docs/guide/native/electron/index.mdx
index 4c60aef..45be35c 100644
--- a/content/docs/guide/platforms/electron/index.mdx
+++ b/content/docs/guide/native/electron/index.mdx
@@ -1,5 +1,5 @@
---
-title: Electron
+title: Setup
description: Serve your Electron UI from a .wvb bundle through a custom protocol, with dev-server proxying and over-the-air updates.
---
@@ -100,7 +100,7 @@ bundleProtocol('app', {
Each scheme is registered as privileged with these defaults: `standard`, `secure`, `bypassCSP`,
`allowServiceWorkers`, `supportFetchAPI`, `corsEnabled`, `codeCache` all `true`; `stream` `false`.
- See [Protocol handling](/docs/guide/protocol-handling) for the full model.
+ See [Protocol handling](/docs/guide/core-concepts/protocol-handling) for the full model.
## Add the preload script
@@ -137,7 +137,7 @@ if (update) {
`source`, `remote`, and `updater` come from `@wvb/bridge`, which auto-detects the Electron
transport installed by `@wvb/electron/preload`. Result shapes for `getUpdate`/`download` are in
- the [Node API reference](/docs/references/node).
+ the [Node API reference](/docs/references/api/node).
The same surfaces are reachable in the main process on the instance returned by `wvb(...)`:
@@ -178,7 +178,7 @@ updater: {
},
```
-`SignatureAlgorithm` is one of `ecdsaSecp256R1`, `ecdsaSecp384R1`, `ed25519`, `rsaPkcs1V15`, `rsaPss`. See [Remote bundles](/docs/guide/remote-bundles) for integrity and signature details, and [Building a remote](/docs/guide/remote) for standing a server up — including a local one for testing.
+`SignatureAlgorithm` is one of `ecdsaSecp256R1`, `ecdsaSecp384R1`, `ed25519`, `rsaPkcs1V15`, `rsaPss`. See [Remote bundles](/docs/guide/core-concepts/over-the-air) for integrity and signature details, and [Building a remote](/docs/guide/remote) for standing a server up — including a local one for testing.
## Where bundles live
@@ -189,7 +189,7 @@ updater: {
| `builtinDir` | `process.resourcesPath/bundles` when packaged, else `process.cwd()/bundles` |
| `remoteDir` | `app.getPath('userData')/bundles` |
-Downloaded versions take priority over builtin ones, so an installed update is served automatically after `updater.download(...)` and `updater.install(...)`. See [Bundle sources](/docs/guide/bundle-sources) for resolution details.
+Downloaded versions take priority over builtin ones, so an installed update is served automatically after `updater.download(...)` and `updater.install(...)`. See [Bundle sources](/docs/guide/core-concepts/bundle-source) for resolution details.
## Pack and ship bundles
@@ -209,12 +209,12 @@ Packaging an Electron app that uses `@wvb/electron` needs two things to land in
@@ -225,28 +225,3 @@ Packaging an Electron app that uses `@wvb/electron` needs two things to land in
- **Renderer cannot reach the bridge API** — the preload is not loaded. Check `webPreferences.preload` and that it calls `preload()`.
- **`remote_not_initialized` / `updater_not_initialized`** — a `remote.*` / `updater.*` call was made but no `updater` block was passed to `wvb(...)`. Add the [over-the-air updates](#configure-over-the-air-updates) config.
- **Works in dev, fails when packaged** — the `bundles` resource or the native `@wvb/node` binary was not included in the package. See [Packaging](#packaging).
-
-## Next steps
-
-
-
-
-
-
-
diff --git a/content/docs/guide/native/electron/local-development.ko.mdx b/content/docs/guide/native/electron/local-development.ko.mdx
new file mode 100644
index 0000000..447377f
--- /dev/null
+++ b/content/docs/guide/native/electron/local-development.ko.mdx
@@ -0,0 +1,65 @@
+---
+title: '로컬 개발'
+description: 번들 호스트를 Vite 개발 서버로 프록시하여 핫 리로드를 지원하고, app.isPackaged에 따라 프로토콜을 전환하세요.
+---
+
+개발 중에는 패킹된 `.wvb`이 아니라 핫 리로드가 필요합니다. 번들 스킴에 `localProtocol`을 등록하면, 웹뷰는 동일한 URL을 로드하면서 요청은 프레임워크 개발 서버로 프록시됩니다.
+
+## 개발 서버로 프록시하기
+
+`localProtocol(scheme, { hosts })`은 매핑된 각 호스트를 실행 중인 개발 서버로 전달하므로, 프로덕션에서 사용하는 것과 동일한 `app://app.wvb` URL을 통해 수정 사항이 실시간으로 리로드됩니다. `hosts`은 `.wvb`을 개발 서버 오리진으로 매핑합니다.
+
+## `app.isPackaged`로 전환하기
+
+개발에서는 `localProtocol`을, 프로덕션에서는 `bundleProtocol`을 같은 스킴에 등록하세요. 렌더러 URL은 절대 바뀌지 않으며, 그 뒤의 핸들러만 바뀝니다.
+
+```ts title="src/main.ts"
+import path from 'node:path';
+import { app, BrowserWindow } from 'electron';
+import { bundleProtocol, localProtocol, wvb } from '@wvb/electron';
+
+const DEV_SERVER_URL = 'http://localhost:5173';
+
+const instance = wvb({
+ source: {
+ builtinDir: path.join(process.resourcesPath, 'bundles'),
+ },
+ protocols: app.isPackaged
+ ? // Prod: serve `app://app.wvb/...` from the packed bundle.
+ [bundleProtocol('app')]
+ : // Dev: proxy `app://app.wvb/...` to the Vite dev server for hot reload.
+ [localProtocol('app', { hosts: { 'app.wvb': DEV_SERVER_URL } })],
+});
+
+async function createWindow() {
+ await instance.whenProtocolRegistered();
+ const win = new BrowserWindow({ width: 800, height: 600 });
+ await win.loadURL('app://app.wvb');
+}
+
+app.whenReady().then(createWindow);
+```
+
+
+ 하나의 스킴은 한 번에 하나의 핸들러만 소유할 수 있으므로, 둘 다 등록하지 말고 `protocols` 배열을
+ 조건에 따라 구성하세요. `loadURL` 전에는 항상 `await instance.whenProtocolRegistered()`를
+ 호출하세요.
+
+
+## 여러 호스트
+
+`hosts`은 항목 수에 제한이 없습니다 — 각 윈도우의 번들 호스트를 저마다의 개발 서버로 매핑한 다음, 호스트별로 로드하세요.
+
+```ts
+localProtocol('app', {
+ hosts: {
+ 'app.wvb': 'http://localhost:5173',
+ 'admin.wvb': 'http://localhost:5174',
+ },
+});
+
+// win.loadURL('app://app.wvb');
+// adminWin.loadURL('app://admin.wvb');
+```
+
+프로덕션에서는 `bundleProtocol('app')`이 소스에 있는 `app`과 `admin` 번들에서 둘 다 제공합니다.
diff --git a/content/docs/guide/native/electron/local-development.mdx b/content/docs/guide/native/electron/local-development.mdx
new file mode 100644
index 0000000..36c4fff
--- /dev/null
+++ b/content/docs/guide/native/electron/local-development.mdx
@@ -0,0 +1,64 @@
+---
+title: Local development
+description: Proxy your bundle host to the Vite dev server for hot reload, and switch protocols on app.isPackaged.
+---
+
+In development you want hot reload, not a packed `.wvb`. Register a `localProtocol` on your bundle scheme so the webview loads the same URL while requests proxy to your framework dev server.
+
+## Proxy to the dev server
+
+`localProtocol(scheme, { hosts })` forwards each mapped host to a running dev server, so edits reload live over the same `app://app.wvb` URL you use in production. `hosts` maps `.wvb` to the dev server origin.
+
+## Switch on `app.isPackaged`
+
+Register `localProtocol` in development and `bundleProtocol` in production, both on the same scheme. The renderer URL never changes — only the handler behind it does.
+
+```ts title="src/main.ts"
+import path from 'node:path';
+import { app, BrowserWindow } from 'electron';
+import { bundleProtocol, localProtocol, wvb } from '@wvb/electron';
+
+const DEV_SERVER_URL = 'http://localhost:5173';
+
+const instance = wvb({
+ source: {
+ builtinDir: path.join(process.resourcesPath, 'bundles'),
+ },
+ protocols: app.isPackaged
+ ? // Prod: serve `app://app.wvb/...` from the packed bundle.
+ [bundleProtocol('app')]
+ : // Dev: proxy `app://app.wvb/...` to the Vite dev server for hot reload.
+ [localProtocol('app', { hosts: { 'app.wvb': DEV_SERVER_URL } })],
+});
+
+async function createWindow() {
+ await instance.whenProtocolRegistered();
+ const win = new BrowserWindow({ width: 800, height: 600 });
+ await win.loadURL('app://app.wvb');
+}
+
+app.whenReady().then(createWindow);
+```
+
+
+ Only one handler can own a scheme at a time, so build the `protocols` array conditionally rather
+ than registering both. Always `await instance.whenProtocolRegistered()` before `loadURL`.
+
+
+## Multiple hosts
+
+`hosts` takes any number of entries — map each window's bundle host to its own dev server, then load each by host.
+
+```ts
+localProtocol('app', {
+ hosts: {
+ 'app.wvb': 'http://localhost:5173',
+ 'admin.wvb': 'http://localhost:5174',
+ },
+});
+
+// win.loadURL('app://app.wvb');
+// adminWin.loadURL('app://admin.wvb');
+```
+
+In production, `bundleProtocol('app')` serves both from the `app` and `admin` bundles in your source.
diff --git a/content/docs/guide/native/electron/meta.json b/content/docs/guide/native/electron/meta.json
new file mode 100644
index 0000000..7051cfc
--- /dev/null
+++ b/content/docs/guide/native/electron/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Electron",
+ "pages": ["index", "local-development", "builtin", "---Integration---", "forge", "builder"]
+}
diff --git a/content/docs/guide/native/electron/meta.ko.json b/content/docs/guide/native/electron/meta.ko.json
new file mode 100644
index 0000000..859f3d5
--- /dev/null
+++ b/content/docs/guide/native/electron/meta.ko.json
@@ -0,0 +1,4 @@
+{
+ "title": "Electron",
+ "pages": ["index", "local-development", "builtin", "---통합---", "forge", "builder"]
+}
diff --git a/content/docs/guide/native/ios/builtin.ko.mdx b/content/docs/guide/native/ios/builtin.ko.mdx
new file mode 100644
index 0000000..2d69cff
--- /dev/null
+++ b/content/docs/guide/native/ios/builtin.ko.mdx
@@ -0,0 +1,30 @@
+---
+title: '내장 번들'
+description: wvb builtin으로 내장 .wvb 번들을 iOS 앱 리소스에 설치하면 Source가 오프라인으로 이를 제공합니다.
+---
+
+iOS Source는 내장 번들을 `/bundles`에서 읽습니다. 앱 번들의 읽기 전용 `bundles` 폴더입니다. `wvb builtin`으로 그 위치에 설치한 뒤, 폴더를 폴더 레퍼런스로 배포하세요.
+
+## 설치
+
+`wvb builtin`은 설정에 지정된 타깃(`builtin.target`, 기본값은 리모트)에서 번들을 받아와 `manifest.json`과 함께 번들마다 `/_.wvb` 파일을 하나씩 씁니다.
+
+```sh
+# Tuist 프로젝트: 프로젝트를 자동 감지하고 폴더 레퍼런스를 연결
+wvb builtin --ios
+
+# 그 외: 앱 타깃에 추가할 폴더에 설치
+wvb builtin --out path/to/App/bundles
+```
+
+폴더 레퍼런스는 앱 리소스의 `bundles/`에 놓여야 하므로, 산출물 폴더 이름을 `bundles`로 지정하세요.
+
+## 폴더 레퍼런스로 배포하기
+
+Source는 디렉터리를 있는 그대로 로드하므로, 앱 타깃에 **폴더 레퍼런스**(Xcode의 파란색 폴더)로 추가하세요. 그룹으로 추가하면 매니페스트가 가리키는 `/_.wvb` 구조가 평탄화됩니다. `--ios` 프리셋은 `Project.swift`에 `folderReference`를 추가해 이를 대신 처리합니다(Tuist 프로젝트).
+
+다른 위치에서 읽으려면 `WebViewBundleConfig.source`에 `builtinDir`을 설정하세요. iOS 설정 가이드의 [Sources 섹션](/docs/guide/native/ios#sources)을 참고하세요.
+
+## 리모트 또는 로컬
+
+설치 소스는 플래그가 아니라 설정으로 정해집니다. `builtin.target`은 `remote`(기본값)와 `local`의 구별 합집합입니다. `--endpoint`, `--channel`, `--concurrency`, `--progress`는 리모트 타깃에만 적용됩니다. 모든 옵션은 [wvb builtin](/docs/references/cli/builtin)을 참고하세요.
diff --git a/content/docs/guide/native/ios/builtin.mdx b/content/docs/guide/native/ios/builtin.mdx
new file mode 100644
index 0000000..fac2e2f
--- /dev/null
+++ b/content/docs/guide/native/ios/builtin.mdx
@@ -0,0 +1,30 @@
+---
+title: Builtin bundles
+description: Install builtin .wvb bundles into an iOS app's resources with wvb builtin so the Source serves them offline.
+---
+
+The iOS Source reads builtin bundles from `/bundles` — the app bundle's read-only `bundles` folder. Install them there with `wvb builtin`, then ship the folder as a folder reference.
+
+## Install
+
+`wvb builtin` pulls bundles from the target set in your config (`builtin.target`, remote by default) and writes a `manifest.json` plus one `/_.wvb` file per bundle.
+
+```sh
+# Tuist project: auto-detect the project and wire the folder reference
+wvb builtin --ios
+
+# Otherwise: install into a folder you add to the app target
+wvb builtin --out path/to/App/bundles
+```
+
+The folder reference must land at `bundles/` in the app's resources, so name the output folder `bundles`.
+
+## Ship it as a folder reference
+
+The Source loads the directory as-is, so add it to the app target as a **folder reference** (blue folder in Xcode) — a group would flatten the `/_.wvb` layout the manifest points at. The `--ios` preset does this for you by adding a `folderReference` to `Project.swift` (Tuist projects).
+
+To read from a different location, set `builtinDir` on `WebViewBundleConfig.source`. See the [Sources section](/docs/guide/native/ios#sources) of the iOS setup guide.
+
+## Remote or local
+
+The install source is config-driven, not a flag: `builtin.target` is a discriminated union of `remote` (the default) and `local`. `--endpoint`, `--channel`, `--concurrency`, and `--progress` apply to the remote target only. See [wvb builtin](/docs/references/cli/builtin) for every option.
diff --git a/content/docs/guide/platforms/ios.ko.mdx b/content/docs/guide/native/ios/index.ko.mdx
similarity index 80%
rename from content/docs/guide/platforms/ios.ko.mdx
rename to content/docs/guide/native/ios/index.ko.mdx
index 24c63f1..d7e8fff 100644
--- a/content/docs/guide/platforms/ios.ko.mdx
+++ b/content/docs/guide/native/ios/index.ko.mdx
@@ -1,5 +1,5 @@
---
-title: iOS
+title: 설정
description: webview-bundle-ios Swift 패키지로 WKWebView에서 Webview Bundle 아카이브를 제공하고 업데이트합니다.
---
@@ -8,7 +8,7 @@ description: webview-bundle-ios Swift 패키지로 WKWebView에서 Webview Bundl
가리키면, 이 패키지가 디스크에 있는 번들에서 모든 요청에 응답합니다. 오프라인 우선으로 동작하며,
업데이터가 백그라운드에서 더 새로운 번들을 받아오므로 앱스토어 출시가 필요 없습니다.
-## Requirements
+## 요구 사항
| Requirement | Value |
| ---------------- | ---------------------------------------------------------- |
@@ -18,13 +18,13 @@ description: webview-bundle-ios Swift 패키지로 WKWebView에서 Webview Bundl
이 패키지는 `WebViewBundleLibrary`라는 UniFFI 생성 모듈을 통해 Rust 코어에 바인딩되며, 둘 다
`WebViewBundle` 모듈로 노출됩니다. 공유 코어가 각 플랫폼에 어떻게 도달하는지는
-[플랫폼 통합](/docs/guide/platform-integration)을 참고하세요.
+[플랫폼 통합](/docs/guide/core-concepts/glue-to-native)을 참고하세요.
-패키지를 의존성으로 추가하세요(네이티브 FFI는 아래 설치 스크립트가 연결합니다).
+패키지를 의존성으로 추가하세요.
```swift title="Package.swift"
dependencies: [
- .package(path: "../webview-bundle-ios"),
+ .package(url: "https://github.com/webview-bundle/webview-bundle-ios", from: "0.1.0"),
],
targets: [
.target(name: "App", dependencies: [
@@ -33,30 +33,9 @@ targets: [
]
```
-## Install the native FFI
+Xcode 앱 프로젝트라면 **File → Add Package Dependencies…** 메뉴에서 같은 URL을 추가하세요.
-빌드하기 전에 릴리스에서 네이티브 바이너리를 해석하세요.
-
-```sh
-node scripts/install.mjs latest # 가장 높은 ffi/* 릴리스
-node scripts/install.mjs 0.1.0 # -> ffi/0.1.0 태그
-node scripts/install.mjs ffi/0.1.0 # 명시적 릴리스 태그
-```
-
-이 스크립트는 해석된 태그의 GitHub 릴리스를 읽어 다음을 수행합니다.
-
-- `apple.zip` 자산에서 생성된 Swift 바인딩을 추출해 `Sources/WebViewBundle/`에 넣습니다.
-- `WebViewBundleFFI.xcframework.zip` 자산의 SHA-256 체크섬을 해석하고(GitHub가 보고하는 다이제스트를
- 우선 사용하며, 없으면 직접 다운로드해 해시함), 그 체크섬과 태그를 `Package.swift`에 기록합니다.
-
-`PATH`에 `unzip`이 필요합니다. 소스 저장소를 재정의하고, 비공개 저장소는 인증하세요.
-
-```sh
-export GITHUB_TOKEN=ghp_xxx # 비공개 소스 저장소의 경우, 또는 GH_TOKEN
-node scripts/install.mjs latest --repo webview-bundle/webview-bundle
-```
-
-## Quick start
+## 빠른 시작
한 번 설정하고, `WKWebViewConfiguration`에 스킴을 등록한 뒤, 엔트리 URL을 로드하세요. **커스텀 스킴**을
사용하세요. `http`와 `https`는 예약되어 있어 초기화 시 거부됩니다.
@@ -104,7 +83,7 @@ let wvb = WebViewBundle.shared // 아직 설정되지 않았으면 pre
let maybe = WebViewBundle.safeShared // 아직 설정되지 않았으면 nil
```
-## How serving works
+## 제공이 동작하는 방식
각 스킴에는 `setURLSchemeHandler(_:forURLScheme:)`으로 등록된 `WKURLSchemeHandler`가 부여됩니다. 이
핸들러는 각 요청을 Rust 코어에 매핑하고, Rust 코어는 디스크에 있는 번들에서 응답합니다.
@@ -127,7 +106,7 @@ do {
프로토콜 종류는 두 가지입니다. `.bundle`은 번들 엔트리를 제공하고, `.local`은 로컬 HTTP 서버로
프록시합니다(전체 요청 호스트를 `hosts` 맵과 대조). 모델은
-[프로토콜 처리](/docs/guide/protocol-handling)를 참고하세요.
+[프로토콜 처리](/docs/guide/core-concepts/protocol-handling)를 참고하세요.
```swift
WebViewBundleConfig(protocols: [
@@ -136,7 +115,7 @@ WebViewBundleConfig(protocols: [
])
```
-## The bridge
+## 브리지
`install(on:)`은 JavaScript-네이티브 브리지도 함께 연결합니다. 웹 앱은
`window.webkit.messageHandlers.wvbIos`로 메시지를 보내며, 브리지는 **메인 프레임 메시지만** 받습니다
@@ -149,9 +128,9 @@ window.webkit.messageHandlers.wvbIos.postMessage({
});
```
-## Sources
+## 소스
-각 앱은 두 개의 소스에서 읽습니다. 전체 모델은 [번들 소스](/docs/guide/bundle-sources)를 참고하세요.
+각 앱은 두 개의 소스에서 읽습니다. 전체 모델은 [번들 소스](/docs/guide/core-concepts/bundle-source)를 참고하세요.
- **Builtin** — 앱 안에 함께 배포되는 읽기 전용 번들입니다. 기본 디렉터리는 앱 번들의 `/bundles`
폴더입니다. 파일이 그 위치에 놓이도록 폴더 레퍼런스로 배포하세요.
@@ -177,7 +156,7 @@ WebViewBundleConfig(
클래스로 해석됩니다. 앱 번들을 의미할 때는 `Foundation.Bundle`을 명시적으로 쓰세요.
-## OTA updates
+## 원격 업데이트
`WebViewBundleConfig.updater`를 설정하면 패키지가 `Remote`와 `Updater`를 만들어 주며, 각각
`instance.remote`와 `instance.updater`로 접근할 수 있습니다.
@@ -227,21 +206,5 @@ await webView.evaluateJavaScript("location.reload()")
서명 실패는 FFI 오류 `Error.Signature(message:)`로 나타납니다.
-원격 HTTP 규약, 무결성, 서명 세부 사항은 [원격 번들](/docs/guide/remote-bundles)과
+원격 HTTP 규약, 무결성, 서명 세부 사항은 [원격 번들](/docs/guide/core-concepts/over-the-air)과
[원격 구축하기](/docs/guide/remote)를 참고하세요.
-
-## Next steps
-
-
-
-
-
-
diff --git a/content/docs/guide/platforms/ios.mdx b/content/docs/guide/native/ios/index.mdx
similarity index 81%
rename from content/docs/guide/platforms/ios.mdx
rename to content/docs/guide/native/ios/index.mdx
index d376dcc..5157b3a 100644
--- a/content/docs/guide/platforms/ios.mdx
+++ b/content/docs/guide/native/ios/index.mdx
@@ -1,5 +1,5 @@
---
-title: iOS
+title: Setup
description: Serve and update Webview Bundle archives in a WKWebView using the webview-bundle-ios Swift package.
---
@@ -17,14 +17,14 @@ with the updater pulling newer bundles in the background without an App Store re
| SwiftPM product | `WebViewBundle` |
The package binds the Rust core through a UniFFI-generated module named `WebViewBundleLibrary`, both
-exposed under the `WebViewBundle` module. See [Platform integration](/docs/guide/platform-integration)
+exposed under the `WebViewBundle` module. See [Platform integration](/docs/guide/core-concepts/glue-to-native)
for how the shared core reaches each platform.
-Add the package as a dependency (the install script below wires in the native FFI):
+Add the package as a dependency:
```swift title="Package.swift"
dependencies: [
- .package(path: "../webview-bundle-ios"),
+ .package(url: "https://github.com/webview-bundle/webview-bundle-ios", from: "0.1.0"),
],
targets: [
.target(name: "App", dependencies: [
@@ -33,29 +33,7 @@ targets: [
]
```
-## Install the native FFI
-
-Resolve the native binary from a release before you build:
-
-```sh
-node scripts/install.mjs latest # highest ffi/* release
-node scripts/install.mjs 0.1.0 # -> tag ffi/0.1.0
-node scripts/install.mjs ffi/0.1.0 # explicit release tag
-```
-
-The script reads the GitHub release for the resolved tag and:
-
-- Extracts the generated Swift bindings from the `apple.zip` asset into `Sources/WebViewBundle/`.
-- Resolves the SHA-256 checksum of the `WebViewBundleFFI.xcframework.zip` asset (preferring the digest
- GitHub reports, falling back to downloading and hashing) and writes the checksum and tag into
- `Package.swift`.
-
-It needs `unzip` on `PATH`. Override the source repo and authenticate for private repos:
-
-```sh
-export GITHUB_TOKEN=ghp_xxx # or GH_TOKEN, for a private source repo
-node scripts/install.mjs latest --repo webview-bundle/webview-bundle
-```
+In an Xcode app project, add the same URL through **File → Add Package Dependencies…** instead.
## Quick start
@@ -129,7 +107,7 @@ do {
Two protocol kinds exist — `.bundle` serves bundle entries, `.local` proxies to a local HTTP server
(matching the full request host against the `hosts` map). See
-[Protocol handling](/docs/guide/protocol-handling) for the model.
+[Protocol handling](/docs/guide/core-concepts/protocol-handling) for the model.
```swift
WebViewBundleConfig(protocols: [
@@ -153,7 +131,7 @@ window.webkit.messageHandlers.wvbIos.postMessage({
## Sources
-Each app reads from two sources. See [Bundle sources](/docs/guide/bundle-sources) for the full model.
+Each app reads from two sources. See [Bundle sources](/docs/guide/core-concepts/bundle-source) for the full model.
- **Builtin** — read-only bundles shipped inside the app. Default dir is the app bundle's `/bundles`
folder; ship it as a folder reference so files land there.
@@ -229,24 +207,4 @@ await webView.evaluateJavaScript("location.reload()")
A failed signature surfaces as the FFI error `Error.Signature(message:)`.
For the remote HTTP contract, integrity, and signing details, see
-[Remote bundles](/docs/guide/remote-bundles) and [Building a remote](/docs/guide/remote).
-
-## Next steps
-
-
-
-
-
-
+[Remote bundles](/docs/guide/core-concepts/over-the-air) and [Building a remote](/docs/guide/remote).
diff --git a/content/docs/guide/native/ios/local-development.ko.mdx b/content/docs/guide/native/ios/local-development.ko.mdx
new file mode 100644
index 0000000..0e054ca
--- /dev/null
+++ b/content/docs/guide/native/ios/local-development.ko.mdx
@@ -0,0 +1,45 @@
+---
+title: '로컬 개발'
+description: 로컬 프로토콜을 등록해 WKWebView가 번들 스킴으로 개발 서버를 로드하게 하고, #if DEBUG로 전환합니다.
+---
+
+개발 중에는 `WKWebView`가 패킹된 `.wvb`이 아니라 실행 중인 개발 서버를 로드하기를 원할 것입니다. 번들 스킴에 `.local` 프로토콜을 등록하면, 동일한 `app://app.wvb` URL이 개발 서버로 프록시되고 수정 사항이 실시간으로 다시 로드됩니다.
+
+## 개발 서버 프록시하기
+
+`.local(scheme:hosts:)`은 매핑된 각 호스트를 실행 중인 개발 서버로 전달합니다. `hosts` 키는 요청 호스트 **전체**와 대조되므로, 로드하는 전체 호스트인 `app.wvb`을 개발 서버 오리진에 매핑하세요.
+
+`protocols` 배열은 동일한 스킴에 대해 조건부로 구성하세요. `#if DEBUG`에서는 `.local`을, 릴리스에서는 `.bundle`을 사용합니다. 로드되는 URL은 절대 바뀌지 않고, 그 뒤의 핸들러만 바뀝니다.
+
+```swift title="ContentView.swift"
+import WebKit
+import WebViewBundle
+
+#if DEBUG
+let protocols: [WebViewBundleProtocol] = [
+ // Dev: proxy `app://app.wvb/...` to the Vite dev server for hot reload.
+ .local(scheme: "app", hosts: ["app.wvb": "http://localhost:5173"]),
+]
+#else
+let protocols: [WebViewBundleProtocol] = [
+ // Prod: serve `app://app.wvb/...` from the packed bundle.
+ .bundle(scheme: "app"),
+]
+#endif
+
+let instance = try WebViewBundle.configure(WebViewBundleConfig(protocols: protocols))
+
+let config = WKWebViewConfiguration()
+instance.install(on: config)
+
+let webView = WKWebView(frame: .zero, configuration: config)
+webView.load(URLRequest(url: URL(string: "app://app.wvb")!))
+```
+
+## App Transport Security
+
+
+ 개발 서버가 평문 `http://localhost`라면, iOS App Transport Security가 평문 요청을 차단할 수
+ 있습니다. 개발 빌드용으로 앱의 `Info.plist`에서 `NSAppTransportSecurity` 아래에
+ `NSAllowsLocalNetworking` 예외를 추가하세요.
+
diff --git a/content/docs/guide/native/ios/local-development.mdx b/content/docs/guide/native/ios/local-development.mdx
new file mode 100644
index 0000000..5489d05
--- /dev/null
+++ b/content/docs/guide/native/ios/local-development.mdx
@@ -0,0 +1,45 @@
+---
+title: Local development
+description: Register a local protocol so WKWebView loads your dev server over the bundle scheme, and switch on #if DEBUG.
+---
+
+In development you want `WKWebView` to load your running dev server, not a packed `.wvb`. Register a `.local` protocol on your bundle scheme so the same `app://app.wvb` URL proxies to the dev server and edits reload live.
+
+## Proxy the dev server
+
+`.local(scheme:hosts:)` forwards each mapped host to a running dev server. The `hosts` key is matched against the **entire** request host, so map `app.wvb` — the full host you load — to the dev server origin.
+
+Build the `protocols` array conditionally on the same scheme: `.local` in `#if DEBUG`, `.bundle` in release. The loaded URL never changes — only the handler behind it does.
+
+```swift title="ContentView.swift"
+import WebKit
+import WebViewBundle
+
+#if DEBUG
+let protocols: [WebViewBundleProtocol] = [
+ // Dev: proxy `app://app.wvb/...` to the Vite dev server for hot reload.
+ .local(scheme: "app", hosts: ["app.wvb": "http://localhost:5173"]),
+]
+#else
+let protocols: [WebViewBundleProtocol] = [
+ // Prod: serve `app://app.wvb/...` from the packed bundle.
+ .bundle(scheme: "app"),
+]
+#endif
+
+let instance = try WebViewBundle.configure(WebViewBundleConfig(protocols: protocols))
+
+let config = WKWebViewConfiguration()
+instance.install(on: config)
+
+let webView = WKWebView(frame: .zero, configuration: config)
+webView.load(URLRequest(url: URL(string: "app://app.wvb")!))
+```
+
+## App Transport Security
+
+
+ If your dev server is plain `http://localhost`, iOS App Transport Security may block the cleartext
+ request. Add an `NSAllowsLocalNetworking` exception under `NSAppTransportSecurity` in your app's
+ `Info.plist` for development builds.
+
diff --git a/content/docs/guide/native/ios/meta.json b/content/docs/guide/native/ios/meta.json
new file mode 100644
index 0000000..620022e
--- /dev/null
+++ b/content/docs/guide/native/ios/meta.json
@@ -0,0 +1 @@
+{ "title": "iOS", "pages": ["index", "local-development", "builtin"] }
diff --git a/content/docs/guide/native/ios/meta.ko.json b/content/docs/guide/native/ios/meta.ko.json
new file mode 100644
index 0000000..620022e
--- /dev/null
+++ b/content/docs/guide/native/ios/meta.ko.json
@@ -0,0 +1 @@
+{ "title": "iOS", "pages": ["index", "local-development", "builtin"] }
diff --git a/content/docs/guide/native/tauri/builtin.ko.mdx b/content/docs/guide/native/tauri/builtin.ko.mdx
new file mode 100644
index 0000000..a39e39d
--- /dev/null
+++ b/content/docs/guide/native/tauri/builtin.ko.mdx
@@ -0,0 +1,48 @@
+---
+title: '내장 번들'
+description: wvb builtin으로 빌드 시점에 빌트인 .wvb 번들을 Tauri 앱에 설치하고 Tauri 리소스로 함께 배포하세요.
+---
+
+하나 이상의 `.wvb` 번들을 Tauri 앱 안에 포함하여 배포하면, 원격 업데이트가 이루어지기 전에도 웹뷰가 오프라인으로 로드됩니다. `wvb builtin`으로 빌드 시점에 번들을 설치한 뒤, 해당 디렉터리를 Tauri 리소스로 배포하세요.
+
+## 빌드 시점에 번들 설치하기
+
+`wvb builtin`은 `--out`에 `manifest.json`과 함께 번들마다 하나씩 `/_.wvb` 파일을 기록합니다. 리소스로 배포할 디렉터리를 가리키도록 설정하세요.
+
+```sh
+wvb builtin --out src-tauri/bundles
+```
+
+소스는 [설정](/docs/references/configuration)의 `builtin.target`으로 선택됩니다. `remote` 타겟은 엔드포인트에서 다운로드하고, `local` 타겟은 워크스페이스의 번들을 패킹합니다. 모든 플래그는 [`wvb builtin` 레퍼런스](/docs/references/cli/builtin)를 참고하세요.
+
+## 디렉터리를 리소스로 배포하기
+
+Tauri가 번들 디렉터리를 앱에 패킹하도록 `bundle.resources` 아래에 해당 디렉터리를 나열하세요.
+
+```json title="src-tauri/tauri.conf.json"
+{
+ "bundle": {
+ "resources": ["bundles/**/*"]
+ }
+}
+```
+
+## 런타임에 번들이 해석되는 위치
+
+기본적으로 플러그인은 앱의 리소스 디렉터리 내 `bundles` 디렉터리에서 빌트인 번들을 제공합니다. 위치는 `Source`에서 재정의하세요.
+
+```rust title="src-tauri/src/lib.rs"
+use wvb_tauri::Source;
+
+// 정적 경로. Tauri의 path API를 통해 해석됩니다.
+Source::new().builtin_dir("$RESOURCE/bundles");
+
+// 또는 AppHandle에서 계산할 수도 있습니다.
+Source::new().builtin_dir_fn(|app| Ok(app.path().resource_dir()?.join("bundles")));
+```
+
+
+ Android에서 빌트인 번들은 APK 안에 파일시스템이 직접 읽을 수 없는 `asset://` 리소스로 포함되어
+ 배포됩니다. 플러그인은 첫 요청 시 APK에서 번들을 추출하므로, 앱에서 `tauri_plugin_fs::init()`도
+ 함께 등록해야 합니다. [Tauri 모바일](/docs/guide/native/tauri/mobile)을 참고하세요.
+
diff --git a/content/docs/guide/native/tauri/builtin.mdx b/content/docs/guide/native/tauri/builtin.mdx
new file mode 100644
index 0000000..3628693
--- /dev/null
+++ b/content/docs/guide/native/tauri/builtin.mdx
@@ -0,0 +1,48 @@
+---
+title: Builtin bundles
+description: Install builtin .wvb bundles into a Tauri app at build time with wvb builtin and ship them as Tauri resources.
+---
+
+Ship one or more `.wvb` bundles inside your Tauri app so the webview loads offline, before any over-the-air update. Install them at build time with `wvb builtin`, then ship the directory as a Tauri resource.
+
+## Install bundles at build time
+
+`wvb builtin` writes a `manifest.json` plus one `/_.wvb` file per bundle into `--out`. Point it at the directory you ship as a resource:
+
+```sh
+wvb builtin --out src-tauri/bundles
+```
+
+The source is chosen by `builtin.target` in your [config](/docs/references/configuration) — a `remote` target downloads from an endpoint, a `local` target packs bundles from your workspaces. See the [`wvb builtin` reference](/docs/references/cli/builtin) for every flag.
+
+## Ship the directory as a resource
+
+List the bundles directory under `bundle.resources` so Tauri packs it into the app:
+
+```json title="src-tauri/tauri.conf.json"
+{
+ "bundle": {
+ "resources": ["bundles/**/*"]
+ }
+}
+```
+
+## Where bundles resolve at runtime
+
+By default the plugin serves builtin bundles from a `bundles` directory in the app's resource directory. Override the location on the `Source`:
+
+```rust title="src-tauri/src/lib.rs"
+use wvb_tauri::Source;
+
+// Static path, resolved through Tauri's path API.
+Source::new().builtin_dir("$RESOURCE/bundles");
+
+// Or compute it from the AppHandle.
+Source::new().builtin_dir_fn(|app| Ok(app.path().resource_dir()?.join("bundles")));
+```
+
+
+ On Android, builtin bundles ship inside the APK as `asset://` resources that the filesystem cannot
+ read directly. The plugin extracts them from the APK on first request, so the app must also
+ register `tauri_plugin_fs::init()`. See [Tauri mobile](/docs/guide/native/tauri/mobile).
+
diff --git a/content/docs/guide/platforms/tauri.ko.mdx b/content/docs/guide/native/tauri/index.ko.mdx
similarity index 92%
rename from content/docs/guide/platforms/tauri.ko.mdx
rename to content/docs/guide/native/tauri/index.ko.mdx
index 81b4b58..3cbbe12 100644
--- a/content/docs/guide/platforms/tauri.ko.mdx
+++ b/content/docs/guide/native/tauri/index.ko.mdx
@@ -1,5 +1,5 @@
---
-title: Tauri
+title: 설정
description: Tauri v2 앱에 wvb-tauri 플러그인을 추가하면 웹뷰를 .wvb 번들에서 제공하며, 개발 서버 프록시와 무선(OTA) 업데이트를 사용할 수 있습니다.
---
@@ -96,7 +96,7 @@ Source::new().builtin_dir("$RESOURCE/bundles").remote_dir("$APPLOCALDATA/bundles
Source::new().builtin_dir_fn(|app| Ok(app.path().resource_dir()?.join("bundles")));
```
-값을 설정하지 않으면 builtin 디렉터리는 앱 리소스 디렉터리의 `bundles`로, remote 디렉터리는 앱 로컬 데이터의 `bundles`로 기본 설정됩니다. 각 `.protocol(...)` 호출은 자체 비동기 URI 스킴을 등록합니다. 스킴이 요청을 해석하는 방식은 [프로토콜 처리](/docs/guide/protocol-handling)를 참고하세요.
+값을 설정하지 않으면 builtin 디렉터리는 앱 리소스 디렉터리의 `bundles`로, remote 디렉터리는 앱 로컬 데이터의 `bundles`로 기본 설정됩니다. 각 `.protocol(...)` 호출은 자체 비동기 URI 스킴을 등록합니다. 스킴이 요청을 해석하는 방식은 [프로토콜 처리](/docs/guide/core-concepts/protocol-handling)를 참고하세요.
## 창을 커스텀 스킴으로 지정
@@ -158,7 +158,7 @@ resources에 디렉터리를 나열하여 빌드와 함께 배포하세요.
}
```
-전체 패킹 워크플로와 플래그는 [CLI 레퍼런스](/docs/guide/cli)와 [번들 소스](/docs/guide/bundle-sources)를 참고하세요.
+전체 패킹 워크플로와 플래그는 [CLI 레퍼런스](/docs/references/cli)와 [번들 소스](/docs/guide/core-concepts/bundle-source)를 참고하세요.
## 프런트엔드에서 업데이트 제어
@@ -254,7 +254,7 @@ await invoke('plugin:wvb-tauri|source_update_version', {
});
```
-전체 커맨드 집합(`source_*` 12개, `remote_*` 4개, `updater_*` 4개)은 [`packages/tauri/src/commands.rs`](https://github.com/webview-bundle/webview-bundle/blob/main/packages/tauri/src/commands.rs)에 정의되어 있습니다. 무결성 및 서명 검증이 다운로드 흐름에 어떻게 들어맞는지는 [원격 번들](/docs/guide/remote-bundles)을, 서버 쪽은 [원격 설정](/docs/config/remote)을 참고하세요.
+전체 커맨드 집합(`source_*` 12개, `remote_*` 4개, `updater_*` 4개)은 [`packages/tauri/src/commands.rs`](https://github.com/webview-bundle/webview-bundle/blob/main/packages/tauri/src/commands.rs)에 정의되어 있습니다. 무결성 및 서명 검증이 다운로드 흐름에 어떻게 들어맞는지는 [원격 번들](/docs/guide/core-concepts/over-the-air)을, 서버 쪽은 [원격 설정](/docs/references/configuration/remote)을 참고하세요.
`remote_*` 커맨드는 설정에 `Remote`가 필요하고, `updater_*` 커맨드는 추가로 `Updater`가
@@ -327,7 +327,7 @@ tauri::Builder::default()
.plugin(wvb_tauri::init(/* ... */));
```
-그러면 플러그인이 첫 요청 시 APK에서 각 번들을 추출하여 앱 로컬 데이터에 캐시합니다. 원격 전용 앱(builtin 번들 없음)은 추가 Android 설정이 필요하지 않습니다. 플랫폼별 세부 사항은 [Android](/docs/guide/platforms/android) 및 [iOS](/docs/guide/platforms/ios) 가이드를, 현재 상태는 [플랫폼 지원](/docs/guide/platform-support)을 참고하세요.
+그러면 플러그인이 첫 요청 시 APK에서 각 번들을 추출하여 앱 로컬 데이터에 캐시합니다. 원격 전용 앱(builtin 번들 없음)은 추가 Android 설정이 필요하지 않습니다. 플랫폼별 세부 사항은 [Android](/docs/guide/native/android) 및 [iOS](/docs/guide/native/ios) 가이드를, 현재 상태는 [플랫폼 지원](/docs/guide/getting-started/platform-and-framework-support)을 참고하세요.
## 문제 해결
@@ -335,29 +335,3 @@ tauri::Builder::default()
- **`remote_not_initialized` / `updater_not_initialized`** — `Config`에 `.remote(...)`(및 `.updater(...)`) 없이 `remote_*` 또는 `updater_*` 커맨드를 호출했습니다. `error.code`로 분기하세요.
- **ACL이 커맨드를 거부함** — 호출하는 창을 대상으로 하는 capability에 `wvb-tauri:default`(또는 특정 `wvb-tauri:allow-*` 권한)를 추가하세요.
- **런타임에 번들을 찾을 수 없음** — `bundles` 디렉터리가 `tauri.conf.json`의 resources에 있는지, 그리고 `Source`가 이를 올바르게 해석하는지 확인하세요. Android에서는 builtin 번들을 배포할 때 `tauri_plugin_fs::init()`이 등록되어 있는지 확인하세요.
-
-## 더 알아보기
-
-
-
-
-
-
-
-```
diff --git a/content/docs/guide/platforms/tauri.mdx b/content/docs/guide/native/tauri/index.mdx
similarity index 91%
rename from content/docs/guide/platforms/tauri.mdx
rename to content/docs/guide/native/tauri/index.mdx
index 3a7d0d1..b27f5b3 100644
--- a/content/docs/guide/platforms/tauri.mdx
+++ b/content/docs/guide/native/tauri/index.mdx
@@ -1,5 +1,5 @@
---
-title: Tauri
+title: Setup
description: Add the wvb-tauri plugin to a Tauri v2 app so the webview is served from a .wvb bundle, with dev proxying and over-the-air updates.
---
@@ -96,7 +96,7 @@ Source::new().builtin_dir("$RESOURCE/bundles").remote_dir("$APPLOCALDATA/bundles
Source::new().builtin_dir_fn(|app| Ok(app.path().resource_dir()?.join("bundles")));
```
-When unset, the builtin dir defaults to `bundles` in the app's resource directory and the remote dir to `bundles` in app local data. Each `.protocol(...)` call registers its own asynchronous URI scheme. See [Protocol handling](/docs/guide/protocol-handling) for how the schemes resolve requests.
+When unset, the builtin dir defaults to `bundles` in the app's resource directory and the remote dir to `bundles` in app local data. Each `.protocol(...)` call registers its own asynchronous URI scheme. See [Protocol handling](/docs/guide/core-concepts/protocol-handling) for how the schemes resolve requests.
## Point a window at the custom scheme
@@ -158,7 +158,7 @@ Ship the directory with the build by listing it in resources:
}
```
-For the full packing workflow and flags, see the [CLI reference](/docs/guide/cli) and [Bundle sources](/docs/guide/bundle-sources).
+For the full packing workflow and flags, see the [CLI reference](/docs/references/cli) and [Bundle sources](/docs/guide/core-concepts/bundle-source).
## Drive updates from the frontend
@@ -254,7 +254,7 @@ await invoke('plugin:wvb-tauri|source_update_version', {
});
```
-The full command set (twelve `source_*`, four `remote_*`, and four `updater_*` commands) is defined in [`packages/tauri/src/commands.rs`](https://github.com/webview-bundle/webview-bundle/blob/main/packages/tauri/src/commands.rs). See [Remote bundles](/docs/guide/remote-bundles) for how integrity and signature verification fit into the download flow, and [Remote config](/docs/config/remote) for the server side.
+The full command set (twelve `source_*`, four `remote_*`, and four `updater_*` commands) is defined in [`packages/tauri/src/commands.rs`](https://github.com/webview-bundle/webview-bundle/blob/main/packages/tauri/src/commands.rs). See [Remote bundles](/docs/guide/core-concepts/over-the-air) for how integrity and signature verification fit into the download flow, and [Remote config](/docs/references/configuration/remote) for the server side.
The `remote_*` commands require a `Remote` on the config; the `updater_*` commands additionally
@@ -327,7 +327,7 @@ tauri::Builder::default()
.plugin(wvb_tauri::init(/* ... */));
```
-The plugin then extracts each bundle from the APK on first request and caches it in app local data. Remote-only apps (no builtin bundles) need no extra Android setup. See the [Android](/docs/guide/platforms/android) and [iOS](/docs/guide/platforms/ios) guides for platform specifics, and [Platform support](/docs/guide/platform-support) for current status.
+The plugin then extracts each bundle from the APK on first request and caches it in app local data. Remote-only apps (no builtin bundles) need no extra Android setup. See the [Android](/docs/guide/native/android) and [iOS](/docs/guide/native/ios) guides for platform specifics, and [Platform support](/docs/guide/getting-started/platform-and-framework-support) for current status.
## Troubleshooting
@@ -335,28 +335,3 @@ The plugin then extracts each bundle from the APK on first request and caches it
- **`remote_not_initialized` / `updater_not_initialized`** — you called a `remote_*` or `updater_*` command without `.remote(...)` (and `.updater(...)`) on the `Config`. Branch on `error.code`.
- **Command rejected by the ACL** — add `wvb-tauri:default` (or the specific `wvb-tauri:allow-*` permission) to a capability targeting the calling window.
- **Bundle not found at runtime** — confirm the `bundles` directory is in `tauri.conf.json` resources and `Source` resolves to it. On Android, confirm `tauri_plugin_fs::init()` is registered when shipping builtin bundles.
-
-## Learn more
-
-
-
-
-
-
-
diff --git a/content/docs/guide/native/tauri/local-development.ko.mdx b/content/docs/guide/native/tauri/local-development.ko.mdx
new file mode 100644
index 0000000..17e734f
--- /dev/null
+++ b/content/docs/guide/native/tauri/local-development.ko.mdx
@@ -0,0 +1,41 @@
+---
+title: '로컬 개발'
+description: wvb 커스텀 스킴을 Tauri/Vite 개발 서버로 프록시하여 웹뷰를 실시간으로 로드하고, 개발 번들과 패킹된 번들을 전환합니다.
+---
+
+개발 중에는 웹뷰가 패킹된 `.wvb`가 아니라 실행 중인 개발 서버에서 로드되어야 합니다. `local` 프로토콜을 등록하면 커스텀 스킴이 Tauri의 Vite 개발 서버로 프록시되어 핫 리로드가 유지됩니다.
+
+## 개발 서버 프록시
+
+`Protocol::bundle` 옆에 `Protocol::local`을 등록하여 스킴 호스트를 Tauri의 Vite 개발 서버(기본값 `http://localhost:1420`)에 매핑하세요.
+
+```rust title="src-tauri/src/lib.rs"
+use wvb_tauri::{Config, Protocol, Source};
+
+Config::new()
+ .source(Source::new().builtin_dir_fn(|app| Ok(app.path().resource_dir()?.join("bundles"))))
+ // 프로덕션: 패킹된 번들에서 파일을 제공합니다.
+ .protocol(Protocol::bundle("bundle"))
+ // 개발: 동일한 호스트를 실행 중인 개발 서버로 프록시합니다.
+ .protocol(Protocol::local("local").host("example.com", "http://localhost:1420"));
+```
+
+`Protocol::local(scheme)`는 `local://example.com/...`을 개발 서버로 전달하므로 편집 내용이 실시간으로 다시 로드됩니다. `.host(...)` 호출을 여러 번 사용하거나 하나의 `.hosts(...)`로 여러 호스트를 매핑하세요.
+
+## 개발과 프로덕션 전환
+
+개발 중에는 창을 `local` 스킴으로, 배포할 때는 `bundle` 스킴으로 지정하세요.
+
+```json title="src-tauri/tauri.conf.json"
+{
+ "app": {
+ "windows": [
+ {
+ "url": "local://example.com"
+ }
+ ]
+ }
+}
+```
+
+프로덕션 빌드에서는 창의 `url`을 `bundle://app.wvb`로 바꾸거나, `cfg!(debug_assertions)`로 런타임에 선택하여 전환이 자동으로 이뤄지도록 하세요.
diff --git a/content/docs/guide/native/tauri/local-development.mdx b/content/docs/guide/native/tauri/local-development.mdx
new file mode 100644
index 0000000..986b6de
--- /dev/null
+++ b/content/docs/guide/native/tauri/local-development.mdx
@@ -0,0 +1,41 @@
+---
+title: Local development
+description: Proxy the wvb custom scheme to your Tauri/Vite dev server so the webview loads live, and switch between dev and packed bundles.
+---
+
+In development you want the webview to load from your running dev server, not from a packed `.wvb`. Register a `local` protocol so the custom scheme proxies to Tauri's Vite dev server and keeps hot reload.
+
+## Proxy the dev server
+
+Register `Protocol::local` next to `Protocol::bundle`, mapping a scheme host to Tauri's Vite dev server (default `http://localhost:1420`):
+
+```rust title="src-tauri/src/lib.rs"
+use wvb_tauri::{Config, Protocol, Source};
+
+Config::new()
+ .source(Source::new().builtin_dir_fn(|app| Ok(app.path().resource_dir()?.join("bundles"))))
+ // Production: serve files from the packed bundle.
+ .protocol(Protocol::bundle("bundle"))
+ // Development: proxy the same host to the running dev server.
+ .protocol(Protocol::local("local").host("example.com", "http://localhost:1420"));
+```
+
+`Protocol::local(scheme)` forwards `local://example.com/...` to the dev server, so edits reload live. Map several hosts with more `.host(...)` calls or a single `.hosts(...)`.
+
+## Switch dev and prod
+
+Point the window at the `local` scheme while developing and at the `bundle` scheme when you ship:
+
+```json title="src-tauri/tauri.conf.json"
+{
+ "app": {
+ "windows": [
+ {
+ "url": "local://example.com"
+ }
+ ]
+ }
+}
+```
+
+Swap the window `url` to `bundle://app.wvb` for production builds, or pick it at runtime with `cfg!(debug_assertions)` so the switch is automatic.
diff --git a/content/docs/guide/native/tauri/meta.json b/content/docs/guide/native/tauri/meta.json
new file mode 100644
index 0000000..98d3a12
--- /dev/null
+++ b/content/docs/guide/native/tauri/meta.json
@@ -0,0 +1 @@
+{ "title": "Tauri", "pages": ["index", "local-development", "builtin", "mobile"] }
diff --git a/content/docs/guide/native/tauri/meta.ko.json b/content/docs/guide/native/tauri/meta.ko.json
new file mode 100644
index 0000000..98d3a12
--- /dev/null
+++ b/content/docs/guide/native/tauri/meta.ko.json
@@ -0,0 +1 @@
+{ "title": "Tauri", "pages": ["index", "local-development", "builtin", "mobile"] }
diff --git a/content/docs/guide/native/tauri/mobile.ko.mdx b/content/docs/guide/native/tauri/mobile.ko.mdx
new file mode 100644
index 0000000..02fcbd7
--- /dev/null
+++ b/content/docs/guide/native/tauri/mobile.ko.mdx
@@ -0,0 +1,22 @@
+---
+title: '모바일'
+description: wvb-tauri 플러그인을 Tauri 모바일(iOS 및 Android)에서 실행할 때 무엇이 달라지는지, 그리고 builtin 번들이 어디에 위치하는지 설명합니다.
+---
+
+동일한 `wvb-tauri` 플러그인이 데스크톱과 Tauri 모바일에서 모두 동작합니다. [설정](/docs/guide/native/tauri)에서와 똑같이 등록하세요 — 아래 차이점은 각 플랫폼에서 builtin 번들이 위치하는 곳에 관한 것뿐입니다.
+
+## iOS
+
+builtin 번들은 실제 파일 시스템 리소스 디렉터리에 위치합니다. 데스크톱 설정 외에 추가 설정은 필요하지 않습니다.
+
+## Android
+
+builtin 번들은 APK 안에 `asset://` 리소스로 배포되는데, 파일 시스템이 이를 직접 읽을 수 없습니다. builtin 번들을 배포하는 앱은 Tauri 파일 시스템 플러그인도 등록해야 합니다.
+
+```rust title="src-tauri/src/lib.rs"
+tauri::Builder::default()
+ .plugin(tauri_plugin_fs::init())
+ .plugin(wvb_tauri::init(/* ... */));
+```
+
+플러그인은 첫 요청 시 APK에서 각 번들을 추출하여 앱 로컬 데이터에 캐시합니다. 원격 전용 앱(builtin 번들 없음)은 추가 Android 설정이 필요하지 않습니다.
diff --git a/content/docs/guide/native/tauri/mobile.mdx b/content/docs/guide/native/tauri/mobile.mdx
new file mode 100644
index 0000000..f797da6
--- /dev/null
+++ b/content/docs/guide/native/tauri/mobile.mdx
@@ -0,0 +1,22 @@
+---
+title: 'For mobile'
+description: What changes when you run the wvb-tauri plugin on Tauri mobile (iOS and Android), and where builtin bundles live.
+---
+
+The same `wvb-tauri` plugin runs on desktop and on Tauri mobile. Register it exactly as in [Setup](/docs/guide/native/tauri) — the differences below are only about where builtin bundles live on each platform.
+
+## iOS
+
+Builtin bundles live in a real filesystem resource directory. No extra setup beyond the desktop configuration.
+
+## Android
+
+Builtin bundles ship inside the APK as `asset://` resources, which the filesystem cannot read directly. An app that ships builtin bundles must also register the Tauri filesystem plugin:
+
+```rust title="src-tauri/src/lib.rs"
+tauri::Builder::default()
+ .plugin(tauri_plugin_fs::init())
+ .plugin(wvb_tauri::init(/* ... */));
+```
+
+The plugin extracts each bundle from the APK on first request and caches it in app local data. Remote-only apps (no builtin bundles) need no extra Android setup.
diff --git a/content/docs/guide/platform-integration.ko.mdx b/content/docs/guide/platform-integration.ko.mdx
deleted file mode 100644
index 498352b..0000000
--- a/content/docs/guide/platform-integration.ko.mdx
+++ /dev/null
@@ -1,296 +0,0 @@
----
-title: 플랫폼 통합
-description: 하나의 Rust 코어가 얇은 바인딩과 @wvb/bridge 웹 레이어를 통해 Electron, Tauri, Android, iOS, Deno에 도달하는 방법.
----
-
-Webview Bundle은 다섯 개가 아니라 하나의 시스템입니다. 포맷, 번들 소스, 프로토콜 핸들러, 원격 클라이언트, 업데이터, 무결성 및 서명 검사가 모두 하나의 Rust 크레이트 안에 있습니다. 모든 플랫폼은 얇은 바인딩을 통해 바로 그 크레이트를 사용합니다. 그래서 Electron용으로 빌드한 `.wvb`는 Tauri, Android, iOS, Deno에서도 똑같이 동작합니다. 개별 플랫폼 가이드를 읽기 전에 이 문서를 먼저 읽으세요. 각 가이드는 어떤 패키지가 어디에 속하는지 이미 안다고 가정합니다.
-
-## 하나의 코어, 여러 호스트
-
-Rust 크레이트 `wvb`([crates.io](https://crates.io/crates/wvb), 버전 0.2.0)가 단일 진실 공급원입니다. 이 크레이트는 다음을 구현합니다.
-
-- `.wvb` 번들 포맷(헤더, 인덱스, LZ4로 압축된 데이터),
-- 번들 소스(builtin 및 remote 디렉터리),
-- 번들 프로토콜 핸들러와 로컬 프로토콜 핸들러,
-- 원격 HTTP 클라이언트,
-- 업데이터(확인, 다운로드, 검증, 설치),
-- 무결성(SHA-2) 및 서명 검증.
-
-각 바인딩은 의도적으로 얇습니다. 코어를 호스트 언어에 노출하고, 해당 호스트에 URL 스킴을 등록하며, 요청을 코어로 전달합니다. 코어를 수정하거나 기능을 추가하면 다음 바인딩 릴리스에서 모든 플랫폼에 반영됩니다. 전체 API는 [docs.rs/wvb](https://docs.rs/wvb)에서 확인하세요.
-
-
- Rust 코어는 URL 스킴을 등록하거나 검증하지 않습니다. `app://`이나 `bundle://` 같은 스킴을 선택하고
- OS에 등록하는 일은 바인딩의 몫입니다. 요청이 파일로 매핑되는 방식은 [프로토콜
- 처리](/docs/guide/protocol-handling)를 참고하세요.
-
-
-## 전달 방식
-
-각 플랫폼은 서로 다른 배포 채널을 통해 코어에 도달합니다.
-
-| 플랫폼 | 바인딩 | 배포 방식 |
-| ------------------------ | ------------------------- | --------------------------------------------------------------------- |
-| Electron / Node.js | `@wvb/node` | N-API 네이티브 애드온(NAPI-RS), npm에 플랫폼별 프리빌드 바이너리 제공 |
-| Tauri(데스크톱 + 모바일) | `wvb-tauri` Rust 크레이트 | crates.io의 Tauri v2 플러그인, 버전 0.1.0 |
-| Android | `packages/ffi`(UniFFI) | Kotlin 바인딩, `webview-bundle-android` 저장소에서 사용 |
-| iOS | `packages/ffi`(UniFFI) | Swift 바인딩, `webview-bundle-ios` 저장소에서 사용 |
-| Deno Desktop | `@wvb/deno` | 프리빌드 dylib 위의 Deno FFI(실험적) |
-
-### Electron과 Node.js
-
-`@wvb/node`(0.1.0)는 코어를 프리빌드 바이너리가 포함된 N-API 네이티브 애드온으로 감쌉니다. 여느 npm 의존성처럼 설치하세요.
-
-```sh
-npm install @wvb/node
-```
-
-```ts title="main.ts"
-import { readBundle, BundleSource } from '@wvb/node';
-
-const source = new BundleSource({
- builtinDir: './bundles',
- remoteDir: './remote',
-});
-const bundle = await readBundle('./app.wvb');
-```
-
-Electron에서는 `@wvb/electron`(0.1.0)을 사용하세요. 이 패키지는 `@wvb/node` 위에 만들어졌으며, 스킴 권한, 프로토콜 등록, IPC 채널을 대신 연결해 줍니다.
-
-```sh
-npm install @wvb/electron
-```
-
-```js title="main.cjs"
-const { app, BrowserWindow } = require('electron');
-const { bundleProtocol, wvb } = require('@wvb/electron');
-
-const instance = wvb({
- source: { builtinDir: path.join(__dirname, 'bundles') },
- protocols: [bundleProtocol('app', { onError: e => console.error('[wvb]', e) })],
-});
-
-app.whenReady().then(async () => {
- await instance.whenProtocolRegistered();
- const window = new BrowserWindow({
- webPreferences: { contextIsolation: true, nodeIntegration: false },
- });
- await window.loadURL('app://hacker-news.wvb');
-});
-```
-
-electron-builder와 Electron Forge용 패키징 헬퍼인 `@wvb/electron-builder`와 `@wvb/electron-forge`는 번들 패킹을 빌드에 연결합니다. [Electron 가이드](/docs/guide/platforms/electron)와 [Node API 레퍼런스](/docs/references/node)를 참고하세요.
-
-### Tauri
-
-Tauri 통합은 Rust 크레이트 `wvb-tauri`(crates.io의 0.1.0)입니다. `@wvb/tauri` npm 패키지는 없습니다. 크레이트를 추가하고 Tauri v2 플러그인으로 등록하세요.
-
-```toml title="src-tauri/Cargo.toml"
-[dependencies]
-wvb-tauri = "0.1.0"
-```
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol, Source};
-
-tauri::Builder::default()
- .plugin(wvb_tauri::init(
- Config::new()
- .source(Source::new().builtin_dir("bundles"))
- .protocol(Protocol::bundle("bundle")),
- ))
- .run(tauri::generate_context!())
- .unwrap();
-```
-
-플러그인 명령어를 호출하는 프런트엔드는 capability 파일에 설정된 ACL 권한을 부여해야 합니다.
-
-```json title="src-tauri/capabilities/default.json"
-{
- "permissions": ["core:default", "wvb-tauri:default"]
-}
-```
-
-같은 크레이트가 데스크톱과 모바일을 모두 처리합니다. Android에서는 builtin 번들이 APK 안에 `asset://` 리소스로 포함됩니다. 그래서 builtin 번들을 함께 배포하는 앱은 `tauri_plugin_fs`도 등록해야 합니다.
-
-```rust
-tauri::Builder::default()
- .plugin(tauri_plugin_fs::init()) // Android에서 builtin 번들에 필요함
- .plugin(wvb_tauri::init(config))
-```
-
-[Tauri 가이드](/docs/guide/platforms/tauri)를 참고하세요.
-
-### Android와 iOS
-
-Android와 iOS는 하나의 바인딩 생성기 `packages/ffi`를 공유합니다. 이는 [UniFFI](https://mozilla.github.io/uniffi-rs/)를 사용해 코어로부터 Kotlin과 Swift 바인딩을 생성하는 Rust 크레이트입니다. 빌드하면 세 개의 GitHub 릴리스 자산이 만들어집니다.
-
-- `android.zip` — Android용 Kotlin 바인딩과 `jniLibs`,
-- `apple.zip` — Apple 플랫폼용 Swift 바인딩과 정적 라이브러리,
-- `WebViewBundleFFI.xcframework.zip` — iOS용 xcframework.
-
-두 개의 전용 저장소가 이 자산들을 사용하며, 릴리스 태그 `ffi/`으로 해석합니다.
-
-```kotlin title="Android — dev.wvb 바인딩"
-import dev.wvb.BundleSource
-import dev.wvb.BundleSourceConfig
-
-val source = BundleSource(
- BundleSourceConfig(
- builtinDir = "$filesDir/bundles",
- remoteDir = "$cacheDir/remote",
- builtinManifestFilepath = null,
- remoteManifestFilepath = null,
- ),
-)
-val bundle = source.fetchBundle("app")
-```
-
-```swift title="iOS — WebViewBundleLibrary 바인딩"
-import WebViewBundleLibrary
-
-let source = BundleSource(
- config: BundleSourceConfig(
- builtinDir: "\(docs)/bundles",
- remoteDir: "\(caches)/remote",
- builtinManifestFilepath: nil,
- remoteManifestFilepath: nil
- )
-)
-let bundle = try await source.fetchBundle(bundleName: "app")
-```
-
-```swift title="Package.swift"
-.binaryTarget(
- name: "WebViewBundleFFI",
- url: "https://github.com/webview-bundle/webview-bundle/releases/download/ffi/0.1.0/WebViewBundleFFI.xcframework.zip",
- checksum: ""
-)
-```
-
-### Deno
-
-`@wvb/deno`는 프리빌드 동적 라이브러리 위의 Deno FFI를 통해 코어에 도달합니다. GitHub Releases에서 dylib를 설치한 다음 로드하세요.
-
-```sh
-deno run -A jsr:@wvb/deno/install --out vendor/wvb
-```
-
-이 통합은 실험적입니다. [Deno 가이드](/docs/guide/platforms/deno)와 [Deno API 레퍼런스](/docs/references/deno)를 참고하세요.
-
-## 브리지
-
-위의 바인딩들은 네이티브 쪽에서 실행됩니다. 브리지 `@wvb/bridge`(0.1.0)는 웹 쪽입니다. 웹뷰 안의 JavaScript가 호스트와 상관없이 하나의 통일된 API로 네이티브 소스, 원격, 업데이터 작업을 호출하게 해 줍니다.
-
-```sh
-npm install @wvb/bridge
-```
-
-단일 진입점은 `invoke()`입니다.
-
-```ts
-import { invoke } from '@wvb/bridge';
-
-const update = await invoke('updaterGetUpdate', { bundleName: 'app' });
-```
-
-`invoke()`를 이름으로 직접 호출하는 경우는 드뭅니다. 브리지는 네이티브 명령어를 세 개의 타입이 지정된 객체 `source.*`, `remote.*`, `updater.*`로 묶으며, 각 메서드는 타입이 지정된 `invoke()` 호출입니다.
-
-```ts
-import { source, remote, updater } from '@wvb/bridge';
-
-const bundles = await source.listBundles();
-const info = await updater.getUpdate('app');
-if (info.isAvailable) {
- await updater.download('app');
- await updater.install('app', info.version);
-}
-```
-
-- `source.*` — 번들 목록 조회, 파일 경로 해석, 메타데이터 읽기, 다운로드한 버전 관리.
-- `remote.*` — 원격 서버에서 번들 목록 조회, 검사, 다운로드.
-- `updater.*` — 업데이트 확인, 다운로드, 설치.
-
-브리지는 런타임에 호스트를 감지하고 각 호출을 알맞은 전송 방식으로 라우팅합니다.
-
-| 플랫폼 | 전송 방식 |
-| -------- | ------------------------------------------------------- |
-| Electron | `window.wvbElectron.invoke(name, params)` |
-| Tauri | Tauri `invoke('plugin:wvb-tauri\|', params)` |
-| Android | `window.wvbAndroid.postMessage(...)` |
-| iOS | `window.webkit.messageHandlers.wvbIos.postMessage(...)` |
-
-어디서나 동일한 `source`, `remote`, `updater` 메서드를 사용합니다. `@wvb/bridge` 클라이언트는 electron, tauri, android, ios를 지원합니다. Deno Desktop은 `@wvb/deno-desktop`을 통해 자체 바인딩을 제공합니다.
-
-`@wvb/bridge/testing`을 임포트하면 네이티브 호스트 없이 웹뷰 코드를 테스트할 수 있습니다.
-
-```ts title="app.test.ts"
-import { mockBridge } from '@wvb/bridge/testing';
-import { updater } from '@wvb/bridge';
-
-using bridge = mockBridge({ platform: 'electron' });
-bridge.mockInvoke('updater.getUpdate', () => ({
- name: 'app',
- version: '1.2.0',
- isAvailable: true,
-}));
-
-const info = await updater.getUpdate('app');
-```
-
-
- `@wvb/bridge/testing`은 `mockInvoke`, `mockPlatform`, `mockBridge`를 제공하므로, 단위 테스트에서
- 명령어 응답을 스텁으로 대체할 수 있습니다.
-
-
-## 아키텍처 한눈에 보기
-
-웹 앱에서 온 요청은 브리지를 지나 네이티브 호스트로 내려가고, 코어로 들어간 다음, 번들 소스나 원격 서버로 나갑니다.
-
-
-
-웹뷰도 같은 코어를 통해 자산을 로드합니다. 번들 프로토콜이 소스에서 바로 파일을 제공하므로, 페이지 바이트와 `invoke()` 명령어가 하나의 번들 소스를 공유합니다.
-
-## 다음으로 볼 곳
-
-
-
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/platform-integration.mdx b/content/docs/guide/platform-integration.mdx
deleted file mode 100644
index 9d69162..0000000
--- a/content/docs/guide/platform-integration.mdx
+++ /dev/null
@@ -1,296 +0,0 @@
----
-title: Platform integration
-description: How one Rust core reaches Electron, Tauri, Android, iOS, and Deno through thin bindings and the @wvb/bridge web layer.
----
-
-Webview Bundle is one system, not five. The format, bundle source, protocol handlers, remote client, updater, and integrity and signature checks all live in a single Rust crate. Every platform consumes that same crate through a thin binding, so a `.wvb` built for Electron behaves identically on Tauri, Android, iOS, and Deno. Read this before the individual platform guides — they assume you know which package belongs where.
-
-## One core, many hosts
-
-The Rust crate `wvb` ([crates.io](https://crates.io/crates/wvb), version 0.2.0) is the single source of truth. It implements:
-
-- the `.wvb` bundle format (header, index, LZ4-compressed data),
-- the bundle source (builtin and remote directories),
-- the bundle and local protocol handlers,
-- the remote HTTP client,
-- the updater (check, download, verify, install),
-- integrity (SHA-2) and signature verification.
-
-Each binding is intentionally thin: it exposes the core to the host language, registers a URL scheme with that host, and forwards requests into the core. A core fix or feature reaches every platform on the next binding release. Browse the full API on [docs.rs/wvb](https://docs.rs/wvb).
-
-
- The Rust core never registers or validates a URL scheme. Picking a scheme such as `app://` or
- `bundle://` and registering it with the OS is the binding's job. See [Protocol
- handling](/docs/guide/protocol-handling) for how a request maps to a file.
-
-
-## Delivery mechanisms
-
-Each platform reaches the core through a different distribution channel.
-
-| Platform | Binding | How it is shipped |
-| ------------------------ | ----------------------- | ------------------------------------------------------------------- |
-| Electron / Node.js | `@wvb/node` | N-API native addon (NAPI-RS), prebuilt per-platform binaries on npm |
-| Tauri (desktop + mobile) | `wvb-tauri` Rust crate | Tauri v2 plugin on crates.io, version 0.1.0 |
-| Android | `packages/ffi` (UniFFI) | Kotlin bindings, consumed by the `webview-bundle-android` repo |
-| iOS | `packages/ffi` (UniFFI) | Swift bindings, consumed by the `webview-bundle-ios` repo |
-| Deno Desktop | `@wvb/deno` | Deno FFI over a prebuilt dylib (experimental) |
-
-### Electron and Node.js
-
-`@wvb/node` (0.1.0) wraps the core as an N-API native addon with prebuilt binaries — install it like any npm dependency.
-
-```sh
-npm install @wvb/node
-```
-
-```ts title="main.ts"
-import { readBundle, BundleSource } from '@wvb/node';
-
-const source = new BundleSource({
- builtinDir: './bundles',
- remoteDir: './remote',
-});
-const bundle = await readBundle('./app.wvb');
-```
-
-For Electron, use `@wvb/electron` (0.1.0) — it builds on `@wvb/node` and wires scheme privileges, protocol registration, and the IPC channel for you.
-
-```sh
-npm install @wvb/electron
-```
-
-```js title="main.cjs"
-const { app, BrowserWindow } = require('electron');
-const { bundleProtocol, wvb } = require('@wvb/electron');
-
-const instance = wvb({
- source: { builtinDir: path.join(__dirname, 'bundles') },
- protocols: [bundleProtocol('app', { onError: e => console.error('[wvb]', e) })],
-});
-
-app.whenReady().then(async () => {
- await instance.whenProtocolRegistered();
- const window = new BrowserWindow({
- webPreferences: { contextIsolation: true, nodeIntegration: false },
- });
- await window.loadURL('app://hacker-news.wvb');
-});
-```
-
-The electron-builder and Electron Forge packaging helpers, `@wvb/electron-builder` and `@wvb/electron-forge`, wire bundle packaging into your build. See the [Electron guide](/docs/guide/platforms/electron) and the [Node API reference](/docs/references/node).
-
-### Tauri
-
-The Tauri integration is the Rust crate `wvb-tauri` (0.1.0 on crates.io) — there is no `@wvb/tauri` npm package. Add the crate and register it as a Tauri v2 plugin.
-
-```toml title="src-tauri/Cargo.toml"
-[dependencies]
-wvb-tauri = "0.1.0"
-```
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol, Source};
-
-tauri::Builder::default()
- .plugin(wvb_tauri::init(
- Config::new()
- .source(Source::new().builtin_dir("bundles"))
- .protocol(Protocol::bundle("bundle")),
- ))
- .run(tauri::generate_context!())
- .unwrap();
-```
-
-A frontend that calls plugin commands must grant the ACL permission set in a capability file.
-
-```json title="src-tauri/capabilities/default.json"
-{
- "permissions": ["core:default", "wvb-tauri:default"]
-}
-```
-
-The same crate handles desktop and mobile. On Android, builtin bundles ship inside the APK as `asset://` resources, so an app that ships builtin bundles must also register `tauri_plugin_fs`.
-
-```rust
-tauri::Builder::default()
- .plugin(tauri_plugin_fs::init()) // required on Android for builtin bundles
- .plugin(wvb_tauri::init(config))
-```
-
-See the [Tauri guide](/docs/guide/platforms/tauri).
-
-### Android and iOS
-
-Android and iOS share one binding generator: `packages/ffi`, a Rust crate that uses [UniFFI](https://mozilla.github.io/uniffi-rs/) to generate Kotlin and Swift bindings from the core. The build produces three GitHub release assets:
-
-- `android.zip` — Kotlin bindings and `jniLibs` for Android,
-- `apple.zip` — Swift bindings and static libraries for Apple platforms,
-- `WebViewBundleFFI.xcframework.zip` — the xcframework for iOS.
-
-Two dedicated repositories consume these assets and resolve them by release tag `ffi/`.
-
-```kotlin title="Android — dev.wvb bindings"
-import dev.wvb.BundleSource
-import dev.wvb.BundleSourceConfig
-
-val source = BundleSource(
- BundleSourceConfig(
- builtinDir = "$filesDir/bundles",
- remoteDir = "$cacheDir/remote",
- builtinManifestFilepath = null,
- remoteManifestFilepath = null,
- ),
-)
-val bundle = source.fetchBundle("app")
-```
-
-```swift title="iOS — WebViewBundleLibrary bindings"
-import WebViewBundleLibrary
-
-let source = BundleSource(
- config: BundleSourceConfig(
- builtinDir: "\(docs)/bundles",
- remoteDir: "\(caches)/remote",
- builtinManifestFilepath: nil,
- remoteManifestFilepath: nil
- )
-)
-let bundle = try await source.fetchBundle(bundleName: "app")
-```
-
-```swift title="Package.swift"
-.binaryTarget(
- name: "WebViewBundleFFI",
- url: "https://github.com/webview-bundle/webview-bundle/releases/download/ffi/0.1.0/WebViewBundleFFI.xcframework.zip",
- checksum: ""
-)
-```
-
-### Deno
-
-`@wvb/deno` reaches the core through Deno FFI over a prebuilt dynamic library. Install the dylib from GitHub Releases, then load it.
-
-```sh
-deno run -A jsr:@wvb/deno/install --out vendor/wvb
-```
-
-It is experimental. See the [Deno guide](/docs/guide/platforms/deno) and the [Deno API reference](/docs/references/deno).
-
-## The bridge
-
-The bindings above run on the native side. The bridge, `@wvb/bridge` (0.1.0), is the web side. It lets JavaScript inside the webview call native source, remote, and updater operations through one uniform API, regardless of host.
-
-```sh
-npm install @wvb/bridge
-```
-
-The single entry point is `invoke()`:
-
-```ts
-import { invoke } from '@wvb/bridge';
-
-const update = await invoke('updaterGetUpdate', { bundleName: 'app' });
-```
-
-You rarely call `invoke()` by name. The bridge groups native commands into three typed objects — `source.*`, `remote.*`, and `updater.*` — each method a typed `invoke()` call:
-
-```ts
-import { source, remote, updater } from '@wvb/bridge';
-
-const bundles = await source.listBundles();
-const info = await updater.getUpdate('app');
-if (info.isAvailable) {
- await updater.download('app');
- await updater.install('app', info.version);
-}
-```
-
-- `source.*` — list bundles, resolve filepaths, read metadata, manage downloaded versions.
-- `remote.*` — list, inspect, and download bundles from a remote server.
-- `updater.*` — check for an update, download it, and install it.
-
-The bridge detects the host at runtime and routes each call over the right transport:
-
-| Platform | Transport |
-| -------- | ------------------------------------------------------- |
-| Electron | `window.wvbElectron.invoke(name, params)` |
-| Tauri | Tauri `invoke('plugin:wvb-tauri\|', params)` |
-| Android | `window.wvbAndroid.postMessage(...)` |
-| iOS | `window.webkit.messageHandlers.wvbIos.postMessage(...)` |
-
-Your code uses the same `source`, `remote`, and `updater` methods everywhere. The `@wvb/bridge` client supports electron, tauri, android, and ios; Deno Desktop provides its own bindings through `@wvb/deno-desktop`.
-
-Test webview code without a native host by importing `@wvb/bridge/testing`:
-
-```ts title="app.test.ts"
-import { mockBridge } from '@wvb/bridge/testing';
-import { updater } from '@wvb/bridge';
-
-using bridge = mockBridge({ platform: 'electron' });
-bridge.mockInvoke('updater.getUpdate', () => ({
- name: 'app',
- version: '1.2.0',
- isAvailable: true,
-}));
-
-const info = await updater.getUpdate('app');
-```
-
-
- `@wvb/bridge/testing` provides `mockInvoke`, `mockPlatform`, and `mockBridge` so you can stub
- command responses in unit tests.
-
-
-## Architecture at a glance
-
-A request from your web app flows down through the bridge to the native host, into the core, and out to a bundle source or a remote server.
-
-
-
-The webview also loads its assets through the same core: the bundle protocol serves files straight out of the source, so the page bytes and the `invoke()` commands share one bundle source.
-
-## Where to go next
-
-
-
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/platform-support.ko.mdx b/content/docs/guide/platform-support.ko.mdx
deleted file mode 100644
index 35f0c67..0000000
--- a/content/docs/guide/platform-support.ko.mdx
+++ /dev/null
@@ -1,155 +0,0 @@
----
-title: 플랫폼 지원
-description: Webview Bundle을 실행하는 플랫폼과 각 플랫폼에서 설치할 패키지를 정리합니다.
----
-
-하나의 공유 Rust 코어가 모든 통합을 구동합니다. 따라서 한 번 패킹한 `.wvb`는 Electron, Tauri, Android, iOS, Deno Desktop에서 동일하게 로드됩니다. 코어가 각 호스트에 도달하는 방식과 웹뷰가 브리지를 통해 응답하는 방식은 [플랫폼 통합](/docs/guide/platform-integration)을 참고하세요.
-
-이 페이지는 지원 매트릭스입니다. 플랫폼별 패키지 또는 크레이트, 최소 호스트 버전, 상태를 정리합니다.
-
-## 지원 매트릭스
-
-| 플랫폼 | 웹뷰 호스트 | 패키지 / 크레이트 | 최소 버전 | 상태 |
-| -------------------------------------------- | -------------- | --------------------------------- | -------------------------------------------------------------------------------- | ------ |
-| [Electron](/docs/guide/platforms/electron) | Chromium | `@wvb/electron` 0.1.0 (npm) | `electron >= 15` | 안정 |
-| [Tauri desktop](/docs/guide/platforms/tauri) | System WebView | `wvb-tauri` 0.1.0 (crate) | Tauri v2 | 안정 |
-| Tauri mobile | System WebView | `wvb-tauri` 0.1.0 (crate) | [Android](/docs/guide/platforms/android) / [iOS](/docs/guide/platforms/ios) 참고 | 안정 |
-| [Android](/docs/guide/platforms/android) | System WebView | `webview-bundle-android` (Kotlin) | minSdk 24 / Android 7.0 | 안정 |
-| [iOS](/docs/guide/platforms/ios) | WKWebView | `webview-bundle-ios` (Swift) | iOS 16 / macOS 12 | 안정 |
-| [Deno Desktop](/docs/guide/platforms/deno) | Deno webview | `@wvb/deno-desktop` (JSR) | — | 실험적 |
-
-## 데스크톱
-
-Electron과 Tauri 데스크톱은 가장 성숙한 통합입니다. 둘 다 배포되어 있으며, Rust 코어를 기반으로 호출자가 제공한 URL 스킴을 통해 번들을 제공합니다.
-
-### Electron
-
-npm 패키지를 설치하세요.
-
-```sh
-npm install @wvb/electron
-```
-
-프로토콜을 등록하고 이름으로 번들을 로드하세요(`://.wvb`).
-
-```ts title="main.ts"
-import { app, BrowserWindow } from 'electron';
-import { bundleProtocol, wvb } from '@wvb/electron';
-
-const instance = wvb({
- protocols: [bundleProtocol('app', { onError: e => console.error('[wvb]', e) })],
-});
-
-app.whenReady().then(async () => {
- await instance.whenProtocolRegistered();
- const window = new BrowserWindow({
- webPreferences: { contextIsolation: true, nodeIntegration: false },
- });
- await window.loadURL('app://hacker-news.wvb');
-});
-```
-
-[Electron 가이드](/docs/guide/platforms/electron)에서 시작하세요.
-
-### Tauri
-
-Tauri 통합은 **Tauri v2**를 대상으로 하는 crates.io의 `wvb-tauri` Rust 크레이트입니다. `@wvb/tauri` npm 패키지는 없습니다. 동일한 크레이트가 Tauri의 모바일 타깃도 구동합니다.
-
-```toml title="src-tauri/Cargo.toml"
-[dependencies]
-wvb-tauri = "0.1.0"
-```
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol, Source};
-
-tauri::Builder::default()
- .plugin(wvb_tauri::init(
- Config::new()
- .source(Source::new())
- .protocol(Protocol::bundle("bundle")),
- ))
- .run(tauri::generate_context!())
- .expect("error while running tauri application");
-```
-
-그러면 윈도우가 `bundle://hacker-news.wvb`를 로드합니다. [Tauri 가이드](/docs/guide/platforms/tauri)에서 시작하세요.
-
-## 모바일
-
-Android와 iOS는 실제로 동작하는 통합입니다.
-
-### Android
-
-`webview-bundle-android` Kotlin 라이브러리(네임스페이스 `dev.wvb`)는 **minSdk 24(Android 7.0)**를 요구하며, `WebViewClient`를 통해 번들을 제공합니다. 네이티브 바인딩을 받은 다음 `WebView`에 설치하세요.
-
-```sh
-node scripts/install.mjs
-```
-
-```kotlin title="MainActivity.kt"
-val wvb = WebViewBundle.getInstance(
- this,
- WebViewBundleConfig(protocols = listOf(WebViewBundleProtocol.bundle())),
-)
-val webView = WebView(this)
-webView.settings.javaScriptEnabled = true
-wvb.install(webView)
-webView.loadUrl("https://app.wvb/")
-```
-
-번들은 일반적인 `https://.wvb/...` URL로 제공됩니다(번들 이름이 첫 번째 호스트 레이블입니다). [Android 가이드](/docs/guide/platforms/android)를 참고하세요.
-
-### iOS
-
-`webview-bundle-ios` Swift 패키지는 **iOS 16**과 **macOS 12**를 대상으로 하며, `WKURLSchemeHandler`를 통해 번들을 제공합니다. 네이티브 xcframework 릴리스를 설치한 다음 `WKWebViewConfiguration`에 커스텀 스킴을 등록하세요.
-
-```sh
-node scripts/install.mjs ffi/0.1.0
-```
-
-```swift title="ContentView.swift"
-let instance = try WebViewBundle.configure(
- WebViewBundleConfig(protocols: [.bundle(scheme: "app")])
-)
-let config = WKWebViewConfiguration()
-instance.install(on: config)
-let webView = WKWebView(frame: .zero, configuration: config)
-webView.load(URLRequest(url: URL(string: "app://app.wvb")!))
-```
-
-[iOS 가이드](/docs/guide/platforms/ios)를 참고하세요.
-
-## Deno Desktop
-
-Deno Desktop은 가장 최신 통합이며, JSR에 `@wvb/deno-desktop`으로 올라와 있습니다. 번들 소스를 빌드하고 `Deno.serve` 호환 핸들러를 노출하며, 윈도우마다 하나의 프로토콜을 사용합니다.
-
-
- Deno Desktop은 실험적입니다. [Deno Desktop 가이드](/docs/guide/platforms/deno)와 [Deno API
- 레퍼런스](/docs/references/deno)를 참고하세요.
-
-
-## 다음 단계
-
-
-
-
-
-
-
diff --git a/content/docs/guide/platform-support.mdx b/content/docs/guide/platform-support.mdx
deleted file mode 100644
index cfbccef..0000000
--- a/content/docs/guide/platform-support.mdx
+++ /dev/null
@@ -1,155 +0,0 @@
----
-title: Platform Support
-description: Which platforms run Webview Bundle and the package to install for each.
----
-
-One shared Rust core powers every integration, so a `.wvb` you pack once loads identically on Electron, Tauri, Android, iOS, and Deno Desktop. For how the core reaches each host and how the webview talks back through the bridge, see [Platform integration](/docs/guide/platform-integration).
-
-This page is the support matrix: the package or crate per platform, its minimum host version, and its status.
-
-## Support matrix
-
-| Platform | Webview host | Package / crate | Min version | Status |
-| -------------------------------------------- | -------------- | --------------------------------- | ------------------------------------------------------------------------------- | ------------ |
-| [Electron](/docs/guide/platforms/electron) | Chromium | `@wvb/electron` 0.1.0 (npm) | `electron >= 15` | Stable |
-| [Tauri desktop](/docs/guide/platforms/tauri) | System WebView | `wvb-tauri` 0.1.0 (crate) | Tauri v2 | Stable |
-| Tauri mobile | System WebView | `wvb-tauri` 0.1.0 (crate) | See [Android](/docs/guide/platforms/android) / [iOS](/docs/guide/platforms/ios) | Stable |
-| [Android](/docs/guide/platforms/android) | System WebView | `webview-bundle-android` (Kotlin) | minSdk 24 / Android 7.0 | Stable |
-| [iOS](/docs/guide/platforms/ios) | WKWebView | `webview-bundle-ios` (Swift) | iOS 16 / macOS 12 | Stable |
-| [Deno Desktop](/docs/guide/platforms/deno) | Deno webview | `@wvb/deno-desktop` (JSR) | — | Experimental |
-
-## Desktop
-
-Electron and Tauri desktop are the most mature integrations. Both are published and both serve bundles through a caller-supplied URL scheme backed by the Rust core.
-
-### Electron
-
-Install the npm package:
-
-```sh
-npm install @wvb/electron
-```
-
-Register a protocol and load a bundle by name (`://.wvb`):
-
-```ts title="main.ts"
-import { app, BrowserWindow } from 'electron';
-import { bundleProtocol, wvb } from '@wvb/electron';
-
-const instance = wvb({
- protocols: [bundleProtocol('app', { onError: e => console.error('[wvb]', e) })],
-});
-
-app.whenReady().then(async () => {
- await instance.whenProtocolRegistered();
- const window = new BrowserWindow({
- webPreferences: { contextIsolation: true, nodeIntegration: false },
- });
- await window.loadURL('app://hacker-news.wvb');
-});
-```
-
-Start with the [Electron guide](/docs/guide/platforms/electron).
-
-### Tauri
-
-The Tauri integration is the `wvb-tauri` Rust crate on crates.io targeting **Tauri v2** — there is no `@wvb/tauri` npm package. The same crate also drives Tauri's mobile targets.
-
-```toml title="src-tauri/Cargo.toml"
-[dependencies]
-wvb-tauri = "0.1.0"
-```
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol, Source};
-
-tauri::Builder::default()
- .plugin(wvb_tauri::init(
- Config::new()
- .source(Source::new())
- .protocol(Protocol::bundle("bundle")),
- ))
- .run(tauri::generate_context!())
- .expect("error while running tauri application");
-```
-
-The window then loads `bundle://hacker-news.wvb`. Start with the [Tauri guide](/docs/guide/platforms/tauri).
-
-## Mobile
-
-Android and iOS are real, functional integrations.
-
-### Android
-
-The `webview-bundle-android` Kotlin library (namespace `dev.wvb`) requires **minSdk 24 (Android 7.0)** and serves bundles through a `WebViewClient`. Pull the native bindings, then install on a `WebView`:
-
-```sh
-node scripts/install.mjs
-```
-
-```kotlin title="MainActivity.kt"
-val wvb = WebViewBundle.getInstance(
- this,
- WebViewBundleConfig(protocols = listOf(WebViewBundleProtocol.bundle())),
-)
-val webView = WebView(this)
-webView.settings.javaScriptEnabled = true
-wvb.install(webView)
-webView.loadUrl("https://app.wvb/")
-```
-
-Bundles are served over ordinary `https://.wvb/...` URLs (the bundle name is the first host label). See the [Android guide](/docs/guide/platforms/android).
-
-### iOS
-
-The `webview-bundle-ios` Swift package targets **iOS 16** and **macOS 12** and serves bundles through a `WKURLSchemeHandler`. Install a release of the native xcframework, then register a custom scheme on `WKWebViewConfiguration`:
-
-```sh
-node scripts/install.mjs ffi/0.1.0
-```
-
-```swift title="ContentView.swift"
-let instance = try WebViewBundle.configure(
- WebViewBundleConfig(protocols: [.bundle(scheme: "app")])
-)
-let config = WKWebViewConfiguration()
-instance.install(on: config)
-let webView = WKWebView(frame: .zero, configuration: config)
-webView.load(URLRequest(url: URL(string: "app://app.wvb")!))
-```
-
-See the [iOS guide](/docs/guide/platforms/ios).
-
-## Deno Desktop
-
-Deno Desktop is the newest integration, on JSR as `@wvb/deno-desktop`. It builds a bundle source and exposes a `Deno.serve`-compatible handler, with one protocol per window.
-
-
- Deno Desktop is experimental. See the [Deno Desktop guide](/docs/guide/platforms/deno) and the
- [Deno API reference](/docs/references/deno).
-
-
-## Next steps
-
-
-
-
-
-
-
diff --git a/content/docs/guide/platforms/electron/meta.json b/content/docs/guide/platforms/electron/meta.json
deleted file mode 100644
index 5c496a3..0000000
--- a/content/docs/guide/platforms/electron/meta.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "Electron",
- "pages": ["index", "forge", "builder"]
-}
diff --git a/content/docs/guide/platforms/electron/meta.ko.json b/content/docs/guide/platforms/electron/meta.ko.json
deleted file mode 100644
index 5c496a3..0000000
--- a/content/docs/guide/platforms/electron/meta.ko.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "Electron",
- "pages": ["index", "forge", "builder"]
-}
diff --git a/content/docs/guide/protocol-handling.ko.mdx b/content/docs/guide/protocol-handling.ko.mdx
deleted file mode 100644
index f2c8409..0000000
--- a/content/docs/guide/protocol-handling.ko.mdx
+++ /dev/null
@@ -1,243 +0,0 @@
----
-title: 프로토콜 처리
-description: 웹뷰 요청이 bundle 프로토콜과 local 프로토콜을 거쳐 .wvb 번들 안의 파일로 매핑되는 방식.
----
-
-프로토콜 핸들러는 웹뷰 요청을 네트워크의 바이트가 아니라 번들의 바이트로 바꿉니다. Webview Bundle은 두 가지 프로토콜을 제공합니다.
-
-- **bundle 프로토콜** — `.wvb` 소스에서 파일을 그대로 제공합니다.
-- **local 프로토콜** — 번들러의 개발 서버로 요청을 전달하는 개발용 프록시로, 핫 리로드를 유지합니다.
-
-Rust 코어는 스킴을 등록하거나 검증하지 않습니다. 코어는 URI의 **호스트**와 **경로**만 읽으며, 스킴 자체는 읽지 않습니다. 스킴을 정하고 OS에 등록하는 일은 플랫폼 통합의 몫입니다.
-
-## bundle 프로토콜
-
-요청은 URI의 두 부분을 기준으로 번들 소스에서 해석됩니다. 하나는 **번들 이름**(첫 `.`까지의 호스트)이고, 다른 하나는 **경로**(퍼센트 디코딩된 값)입니다.
-
-```text
-app://app.wvb/index.html -> bundle "app", file "/index.html"
-app://myapp.wvb/assets/logo.png -> bundle "myapp", file "/assets/logo.png"
-```
-
-경로 해석은 정적 파일 서버처럼 `index.html`을 채워 넣습니다.
-
-```text
-/ -> /index.html # 끝에 슬래시가 있는 경우
-/about -> /about/index.html # 마지막 세그먼트에 "."이 없는 경우
-/a.js -> /a.js # 마지막 세그먼트에 "."이 있으면 그대로 제공
-```
-
-라벨이 없는 호스트는 bundle-not-found 오류로 실패합니다. 번들 인덱스에 없는 경로는 **404 Not Found**를 반환합니다.
-
-### 메서드, 헤더, 응답
-
-`GET`과 `HEAD`만 제공하며, 그 외의 메서드는 **405 Method Not Allowed**를 반환합니다. `HEAD`는 본문이 비어 있는 헤더를 반환합니다.
-
-파일을 제공할 때 핸들러는 인덱스에 저장된 해당 파일의 헤더를 재생한 뒤, `Content-Type`(인덱스 엔트리에서 가져옴)과 `Content-Length`(압축 해제된 크기)를 덮어씁니다.
-
-```text
-GET app://app.wvb/assets/app.js
--> 200 OK
- cache-control: public, max-age=31536000 # 인덱스에서 재생됨
- content-type: application/javascript # 인덱스 엔트리에서 가져옴
- content-length: 84213 # 압축 해제된 크기
-```
-
-
- 헤더는 번들을 빌드할 때 파일별로 저장됩니다. 따라서 빌드 시점에 설정한 캐싱 지시자와 그 밖의 응답
- 헤더가 제공되는 응답에까지 그대로 유지됩니다.
-
-
-### 범위 요청
-
-웹뷰는 미디어 탐색과 대용량 다운로드 재개를 위해 범위 요청에 의존합니다. `Range` 헤더가 있으면 `Accept-Ranges: bytes`와 `Content-Range`를 담아 **206 Partial Content**를 반환합니다.
-
-```text
-GET app://app.wvb/media/clip.mp4
-Range: bytes=0-1023
--> 206 Partial Content
- accept-ranges: bytes
- content-range: bytes 0-1023/5242880
- content-length: 1024
-```
-
-- 반환되는 각 범위는 `1000 * 1024` 바이트(약 1 MB)로 제한되며, 이보다 큰 요청 범위는 잘립니다.
-- 여러 범위는 `multipart/byteranges` 응답으로 반환됩니다.
-- 충족할 수 없는 범위는 `Content-Range: bytes */`와 함께 **416 Range Not Satisfiable**를 반환합니다.
-
-```text
-Range: bytes=99999999-
--> 416 Range Not Satisfiable
- content-range: bytes */5242880
-```
-
-범위는 압축 해제된 콘텐츠를 기준으로 계산됩니다. 따라서 오프셋은 빌드가 생성한 파일과 일치합니다.
-
-## local 프로토콜
-
-개발용 프록시입니다. `.wvb` 파일을 읽는 대신, 호스트를 localhost 기준 URL로 매핑하고 요청을 전달합니다. 번들러가 핫 리로드되는 자산을 실시간으로 제공하는 동안, 커스텀 스킴은 프로덕션과 동일하게 유지됩니다.
-
-```text
-app://myapp/api/data?foo=bar -> http://localhost:3000/api/data?foo=bar
-```
-
-호스트-URL 매핑을 제공하세요. Electron에서는 `localProtocol(scheme, { hosts })`를 전달합니다.
-
-```ts title="main.ts"
-import { localProtocol, wvb } from '@wvb/electron';
-
-const instance = wvb({
- protocols: [
- localProtocol('app', {
- hosts: { 'myapp.wvb': 'http://localhost:3000' },
- }),
- ],
-});
-```
-
-Tauri에서는 `Protocol::local`에 호스트 맵을 설정합니다.
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol};
-
-Config::new().protocol(
- Protocol::local("app").host("myapp.wvb", "http://localhost:3000"),
-);
-```
-
-매핑이 없는 호스트로 들어온 요청은 resolve 오류로 실패합니다. 프록시는 응답을 캐시하며, 캐시된 응답을 재생하는 방식으로 업스트림의 `304 Not Modified`를 따릅니다.
-
-
- 개발 중에는 local 프로토콜을 사용하고, 출시 빌드에는 bundle 프로토콜을 사용하세요. 두 프로토콜은
- 같은 스킴을 공유하므로, 웹 앱의 URL은 둘 사이에서 바뀌지 않습니다.
-
-
-## 플랫폼별 스킴
-
-Electron, Tauri, iOS는 실제 커스텀 스킴을 웹뷰에 등록합니다. 반면 Android는 `WebViewClient.shouldInterceptRequest`를 통해 일반 `https://.wvb` 요청을 가로채므로, 등록할 커스텀 스킴이 없습니다. 어느 경우든 번들 이름은 호스트 라벨에서, 경로는 URI 경로에서 가져옵니다. 예제는 `app`(Electron, iOS)과 `bundle`(Tauri)을 등록하지만, 이름은 원하는 대로 정할 수 있습니다.
-
-
-
-
-`@wvb/electron`은 스킴을 privileged로 등록하고 핸들러를 연결합니다. `bundleProtocol(scheme)`을 `webviewBundle`에 전달하고, `whenProtocolRegistered()`를 await한 뒤 `://.wvb`를 로드하세요.
-
-```js title="main.cjs"
-const path = require('path');
-const { app, BrowserWindow } = require('electron');
-const { bundleProtocol, wvb } = require('@wvb/electron');
-
-const instance = wvb({
- source: { builtinDir: path.join(__dirname, 'bundles') },
- protocols: [bundleProtocol('app', { onError: e => console.error('[wvb]', e) })],
-});
-
-app.whenReady().then(async () => {
- await instance.whenProtocolRegistered();
- const window = new BrowserWindow();
- await window.loadURL('app://app.wvb/index.html');
-});
-```
-
-스킴은 다음의 기본 권한으로 등록됩니다(`bundleProtocol('app', { privileges })`로 프로토콜별로 재정의 가능).
-
-```ts
-{
- standard: true, secure: true, bypassCSP: true,
- allowServiceWorkers: true, supportFetchAPI: true,
- corsEnabled: true, stream: false, codeCache: true,
-}
-```
-
-권한과 렌더러 브리지는 [Electron 가이드](/docs/guide/platforms/electron)를 참고하세요.
-
-
-
-
-`wvb-tauri` 크레이트는 설정된 `Protocol`마다 비동기 URI 스킴 프로토콜을 등록합니다. `Protocol::bundle(scheme)`을 플러그인 설정에 전달한 뒤, 윈도우가 `://.wvb`를 가리키게 하세요.
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol, Source};
-
-tauri::Builder::default()
- .plugin(wvb_tauri::init(
- Config::new()
- .source(Source::new())
- .protocol(Protocol::bundle("bundle")),
- ))
- .run(tauri::generate_context!())
- .unwrap();
-```
-
-`tauri.conf.json`에서 윈도우가 스킴을 가리키게 하세요.
-
-```json title="src-tauri/tauri.conf.json"
-{
- "app": {
- "windows": [{ "url": "bundle://app.wvb" }]
- }
-}
-```
-
-전체 플러그인 설정은 [Tauri 가이드](/docs/guide/platforms/tauri)를 참고하세요.
-
-
-
-
-Android는 커스텀 스킴을 등록하지 않습니다. 통합은 `WebViewClient`에서 일반 `https://.wvb` 내비게이션을 가로챕니다. 동일한 호스트-경로 매핑이 적용되므로, `https://app.wvb/index.html`은 번들 `app`의 파일 `/index.html`로 해석됩니다.
-
-```kotlin
-class BundleWebViewClient : WebViewClient() {
- override fun shouldInterceptRequest(
- view: WebView,
- request: WebResourceRequest,
- ): WebResourceResponse? {
- // 요청을 Webview Bundle 핸들러에 넘깁니다.
- return webviewBundle.handle(request)
- }
-}
-```
-
-동작하는 설정은 [Android 가이드](/docs/guide/platforms/android)를 참고하세요.
-
-
-
-
-iOS는 `WKURLSchemeHandler`를 통해 `WKWebView` 설정에 실제 커스텀 스킴을 등록한 뒤, `://.wvb`를 로드합니다. 최소 배포 대상은 iOS 16입니다.
-
-```swift
-let configuration = WKWebViewConfiguration()
-configuration.setURLSchemeHandler(bundleSchemeHandler, forURLScheme: "app")
-
-let webView = WKWebView(frame: .zero, configuration: configuration)
-webView.load(URLRequest(url: URL(string: "app://app.wvb/index.html")!))
-```
-
-동작하는 설정은 [iOS 가이드](/docs/guide/platforms/ios)를 참고하세요.
-
-
-
-
-## 관련 문서
-
-
-
-
-
-
-
diff --git a/content/docs/guide/protocol-handling.mdx b/content/docs/guide/protocol-handling.mdx
deleted file mode 100644
index a0d92ca..0000000
--- a/content/docs/guide/protocol-handling.mdx
+++ /dev/null
@@ -1,243 +0,0 @@
----
-title: Protocol handling
-description: How a webview request maps to a file inside a .wvb bundle through the bundle and local protocols.
----
-
-A protocol handler turns a webview request into bytes from a bundle instead of bytes from the network. Webview Bundle ships two:
-
-- **bundle protocol** — serves files straight out of a `.wvb` source.
-- **local protocol** — a development proxy that forwards to your bundler's dev server, keeping hot reload.
-
-The Rust core does not register or validate the scheme. It reads only the URI **host** and **path** — never the scheme itself. Picking a scheme and registering it with the OS is the platform integration's job.
-
-## The bundle protocol
-
-A request resolves against a bundle source by two parts of the URI: the **bundle name** (host up to the first `.`) and the **path** (percent-decoded).
-
-```text
-app://app.wvb/index.html -> bundle "app", file "/index.html"
-app://myapp.wvb/assets/logo.png -> bundle "myapp", file "/assets/logo.png"
-```
-
-Path resolution fills in `index.html` like a static file server:
-
-```text
-/ -> /index.html # trailing slash
-/about -> /about/index.html # last segment has no "."
-/a.js -> /a.js # last segment has a "." -> served as-is
-```
-
-A host with no label fails with a bundle-not-found error. A path missing from the bundle index returns **404 Not Found**.
-
-### Methods, headers, and responses
-
-Only `GET` and `HEAD` are served; anything else returns **405 Method Not Allowed**. `HEAD` returns headers with an empty body.
-
-For a served file, the handler replays the headers stored for that file in the index, then overrides `Content-Type` (from the index entry) and `Content-Length` (the uncompressed size):
-
-```text
-GET app://app.wvb/assets/app.js
--> 200 OK
- cache-control: public, max-age=31536000 # replayed from the index
- content-type: application/javascript # from the index entry
- content-length: 84213 # uncompressed size
-```
-
-
- Headers are stored per file when the bundle is built, so caching directives and other response
- headers you set at build time survive into the served response.
-
-
-### Range requests
-
-Webviews rely on range requests for seeking media and resuming large downloads. A `Range` header gets **206 Partial Content** with `Accept-Ranges: bytes` and a `Content-Range`:
-
-```text
-GET app://app.wvb/media/clip.mp4
-Range: bytes=0-1023
--> 206 Partial Content
- accept-ranges: bytes
- content-range: bytes 0-1023/5242880
- content-length: 1024
-```
-
-- Each returned range is capped at `1000 * 1024` bytes (~1 MB); larger requested ranges are truncated.
-- Multiple ranges come back as a `multipart/byteranges` response.
-- An unsatisfiable range returns **416 Range Not Satisfiable** with `Content-Range: bytes */`.
-
-```text
-Range: bytes=99999999-
--> 416 Range Not Satisfiable
- content-range: bytes */5242880
-```
-
-Ranges are computed over the uncompressed content, so offsets match the file your build produced.
-
-## The local protocol
-
-A development proxy: instead of reading a `.wvb` file, it maps a host to a localhost base URL and forwards the request. The custom scheme stays the same as production while your bundler serves live, hot-reloading assets.
-
-```text
-app://myapp/api/data?foo=bar -> http://localhost:3000/api/data?foo=bar
-```
-
-Provide a host-to-URL map. In Electron, pass `localProtocol(scheme, { hosts })`:
-
-```ts title="main.ts"
-import { localProtocol, wvb } from '@wvb/electron';
-
-const instance = wvb({
- protocols: [
- localProtocol('app', {
- hosts: { 'myapp.wvb': 'http://localhost:3000' },
- }),
- ],
-});
-```
-
-In Tauri, configure the host map on `Protocol::local`:
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol};
-
-Config::new().protocol(
- Protocol::local("app").host("myapp.wvb", "http://localhost:3000"),
-);
-```
-
-A request whose host has no mapping fails with a resolve error. The proxy caches responses and honors upstream `304 Not Modified` by replaying the cached response.
-
-
- Use the local protocol during development and the bundle protocol for shipped builds. Because both
- share the same scheme, your web app's URLs do not change between the two.
-
-
-## Per-platform schemes
-
-Electron, Tauri, and iOS register a real custom scheme with the webview. Android instead intercepts ordinary `https://.wvb` requests through `WebViewClient.shouldInterceptRequest`, so there is no custom scheme to register. In every case the bundle name comes from the host label and the path comes from the URI path. The examples register `app` (Electron, iOS), `bundle` (Tauri); the name is yours to choose.
-
-
-
-
-`@wvb/electron` registers the scheme as privileged and wires the handler. Pass `bundleProtocol(scheme)` to `webviewBundle`, await `whenProtocolRegistered()`, then load `://.wvb`.
-
-```js title="main.cjs"
-const path = require('path');
-const { app, BrowserWindow } = require('electron');
-const { bundleProtocol, wvb } = require('@wvb/electron');
-
-const instance = wvb({
- source: { builtinDir: path.join(__dirname, 'bundles') },
- protocols: [bundleProtocol('app', { onError: e => console.error('[wvb]', e) })],
-});
-
-app.whenReady().then(async () => {
- await instance.whenProtocolRegistered();
- const window = new BrowserWindow();
- await window.loadURL('app://app.wvb/index.html');
-});
-```
-
-The scheme is registered with these default privileges (override per protocol via `bundleProtocol('app', { privileges })`):
-
-```ts
-{
- standard: true, secure: true, bypassCSP: true,
- allowServiceWorkers: true, supportFetchAPI: true,
- corsEnabled: true, stream: false, codeCache: true,
-}
-```
-
-See the [Electron guide](/docs/guide/platforms/electron) for privileges and the renderer bridge.
-
-
-
-
-The `wvb-tauri` crate registers an async URI-scheme protocol per configured `Protocol`. Pass `Protocol::bundle(scheme)` to the plugin config, then point the window at `://.wvb`.
-
-```rust title="src-tauri/src/lib.rs"
-use wvb_tauri::{Config, Protocol, Source};
-
-tauri::Builder::default()
- .plugin(wvb_tauri::init(
- Config::new()
- .source(Source::new())
- .protocol(Protocol::bundle("bundle")),
- ))
- .run(tauri::generate_context!())
- .unwrap();
-```
-
-Point the window at the scheme in `tauri.conf.json`:
-
-```json title="src-tauri/tauri.conf.json"
-{
- "app": {
- "windows": [{ "url": "bundle://app.wvb" }]
- }
-}
-```
-
-See the [Tauri guide](/docs/guide/platforms/tauri) for the full plugin setup.
-
-
-
-
-Android does not register a custom scheme. The integration intercepts plain `https://.wvb` navigations from a `WebViewClient`. The same host-and-path mapping applies, so `https://app.wvb/index.html` resolves to bundle `app`, file `/index.html`.
-
-```kotlin
-class BundleWebViewClient : WebViewClient() {
- override fun shouldInterceptRequest(
- view: WebView,
- request: WebResourceRequest,
- ): WebResourceResponse? {
- // Hand the request to the Webview Bundle handler.
- return webviewBundle.handle(request)
- }
-}
-```
-
-See the [Android guide](/docs/guide/platforms/android) for the working setup.
-
-
-
-
-iOS registers a real custom scheme on the `WKWebView` configuration through a `WKURLSchemeHandler`, then loads `://.wvb`. The minimum deployment target is iOS 16.
-
-```swift
-let configuration = WKWebViewConfiguration()
-configuration.setURLSchemeHandler(bundleSchemeHandler, forURLScheme: "app")
-
-let webView = WKWebView(frame: .zero, configuration: configuration)
-webView.load(URLRequest(url: URL(string: "app://app.wvb/index.html")!))
-```
-
-See the [iOS guide](/docs/guide/platforms/ios) for the working setup.
-
-
-
-
-## Related
-
-
-
-
-
-
-
diff --git a/content/docs/guide/providers/aws.ko.mdx b/content/docs/guide/providers/aws.ko.mdx
deleted file mode 100644
index 00dd16b..0000000
--- a/content/docs/guide/providers/aws.ko.mdx
+++ /dev/null
@@ -1,210 +0,0 @@
----
-title: AWS 프로바이더
-description: S3, CloudFront, Lambda@Edge와 선택적 KMS 서명을 사용해 AWS에서 원격 Webview Bundle을 호스팅하고, 제공하고, 프로비저닝합니다.
----
-
-직접 관리하는 AWS 계정에 번들을 게시하고 글로벌 CDN으로 제공합니다. 번들은 **S3**에 저장되고, **CloudFront**가 이를 캐싱하며, 두 개의 **Lambda@Edge** 함수가 원격 HTTP 계약을 S3 읽기로 변환합니다. 그리고 **KMS**로 각 번들에 선택적으로 서명할 수 있습니다.
-이 구성 요소들이 구현하는 계약과 프로바이더를 선택하는 방법은 [원격 구축하기](/docs/guide/remote)를 참고하세요.
-
-## 지원 서비스
-
-| Service | Role |
-| ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
-| S3 | 각 번들 객체와 현재 버전을 기록하는 `deployment.json` 포인터를 저장합니다. |
-| CloudFront | S3 앞단의 CDN입니다. 배포기는 배포 후 캐시 무효화를 요청할 수 있습니다. |
-| Lambda@Edge | 두 개의 함수입니다. origin-request는 URL을 번들 키로 재작성하고, origin-response는 S3 객체 메타데이터를 계약 응답 헤더로 변환합니다. |
-| KMS | 선택 사항입니다. 각 번들의 무결성 문자열에 서명하여 클라이언트가 게시자를 검증할 수 있게 합니다. |
-
-## 패키지
-
-| Package | Version | Role |
-| --------------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
-| `@wvb/remote-aws` | `0.1.0` | 게시 측입니다. `wvb.config.ts`에 사용할 `uploader`, `deployer`와 선택적 `signature`를 생성합니다. |
-| `@wvb/remote-aws-provider` | `0.1.0` | Lambda@Edge 서버(Hono 앱)입니다. `./origin-request`와 `./origin-response` 하위 경로를 노출합니다. |
-| `@wvb/remote-aws-provider-pulumi` | `0.1.0` | S3, CloudFront, 두 개의 Lambda@Edge 함수, IAM을 프로비저닝하는 Pulumi 컴포넌트입니다. |
-
-
-
-
-```sh
-npm install @wvb/remote-aws @wvb/remote-aws-provider @wvb/remote-aws-provider-pulumi
-```
-
-
-
-
-```sh
-pnpm add @wvb/remote-aws @wvb/remote-aws-provider @wvb/remote-aws-provider-pulumi
-```
-
-
-
-
-```sh
-yarn add @wvb/remote-aws @wvb/remote-aws-provider @wvb/remote-aws-provider-pulumi
-```
-
-
-
-
-## 게시 측 설정
-
-`awsRemote()`는 `{ uploader, deployer, signature? }`를 반환합니다. 이를 설정의 `remote` 블록에 펼쳐 넣으세요.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { awsRemote } from '@wvb/remote-aws';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://bundles.example.com',
- ...awsRemote({
- bucket: 'my-app-bundles',
- aws: {
- region: 'us-east-1',
- profile: 'my-app-deploy',
- },
- }),
- },
-});
-```
-
-`remote` 스키마 전체는 [원격, 무결성, 서명 설정](/docs/config/remote)을 참고하세요.
-
-### uploader와 deployer 커스터마이징
-
-`uploader`와 `deployer`는 각자의 옵션을 받습니다. uploader는 멀티파트 업로드로 S3에 스트리밍하고, 계약 값을 S3 객체 메타데이터(`webview-bundle-name`, `webview-bundle-version`, 그리고 존재할 경우 `webview-bundle-integrity`와 `webview-bundle-signature`)로 기록합니다. deployer는 `deployment.json`을 GET한 뒤 PUT하며, 배포 후 CloudFront를 무효화할 수 있습니다.
-
-```ts title="wvb.config.ts"
-...awsRemote({
- bucket: 'my-app-bundles',
- aws: { region: 'us-east-1' },
- uploader: {
- // 기본값: bundles/${name}/${name}_${version}.wvb
- key: (name, version) => `bundles/${name}/${version}/${name}_${version}.wvb`,
- cacheControl: 'public, max-age=31536000, immutable',
- contentType: 'application/webview-bundle',
- },
- deployer: {
- invalidation: {
- distributionId: 'E1ABCDEF2GHIJK',
- },
- },
-}),
-```
-
-번들이 이미 존재하고 `force`를 전달하지 않으면, uploader는 `HeadObject` 사전 확인을 실행하고 `BundleAlreadyUploadedError`를 던집니다.
-
-```ts
-import { isBundleAlreadyUploadedError } from '@wvb/remote-aws';
-
-try {
- await uploader.upload({ bundle, bundleName, version });
-} catch (err) {
- if (isBundleAlreadyUploadedError(err)) {
- // 덮어쓰려면 --force로 다시 실행하세요.
- }
- throw err;
-}
-```
-
-### KMS로 서명하기
-
-각 번들의 무결성 문자열에 서명하려면 `signature`에 KMS 서명자 설정을 지정하세요. 서명자는 `MessageType: 'DIGEST'`로 KMS를 호출하고 base64 서명을 반환합니다.
-
-```ts title="wvb.config.ts"
-import { awsRemote } from '@wvb/remote-aws';
-
-...awsRemote({
- bucket: 'my-app-bundles',
- aws: { region: 'us-east-1' },
- signature: {
- keyId: 'arn:aws:kms:us-east-1:111122223333:key/abcd-1234',
- algorithm: 'ECDSA_SHA_256',
- },
-}),
-```
-
-서명자를 단독으로 만들려면 `awsKmsSignatureSigner({ keyId, algorithm })`를 사용하세요. 서명 함수를 반환합니다. 서명을 비활성화하려면 `signature: false`를 전달하세요.
-
-
- 기본 uploader 키와 프로바이더가 기대하는 키는 일치하지 않습니다. uploader는 `bundles/{name}/{name}
- _{version}.wvb`에 기록하지만, Lambda@Edge 프로바이더는 요청을 `bundles/
- {name}/{version}/{name}_{version}.wvb`로 재작성합니다. 즉 `{version}` 디렉터리가 하나 더 있습니다.
- 엔드투엔드 AWS 구성에서는 위 예시처럼 프로바이더의 레이아웃을 만드는 커스텀 uploader `key`를
- 설정하거나, 일치하는 S3 레이아웃을 마련하세요. 기본값에 의존하기 전에 패키지 소스와 대조해
- 확인하세요.
-
-
-## Lambda@Edge에서 제공하기
-
-`@wvb/remote-aws-provider`는 계약을 두 개의 CloudFront 이벤트 핸들러로 분리합니다. 각각을 별도의 Lambda에 연결하세요.
-
-```ts title="origin-request.ts"
-import { originRequest } from '@wvb/remote-aws-provider/origin-request';
-
-export const handler = originRequest({
- bucketName: 'my-app-bundles',
- region: 'us-east-1',
- // allowOtherVersions: true, // GET /bundles/{name}/{version} 허용, 그렇지 않으면 403
-});
-```
-
-```ts title="origin-response.ts"
-import { originResponse } from '@wvb/remote-aws-provider/origin-response';
-
-export const handler = originResponse();
-```
-
-origin-request 핸들러는 `deployment.json`을 읽고, 버전을 해석하며(채널에서 기본 버전으로 폴백), origin URI를 번들 키로 재작성하고, 요청에 태그를 붙입니다. origin-response 핸들러는 S3의 `x-amz-meta-webview-bundle-*` 메타데이터를 `webview-bundle-*` 응답 헤더로 변환합니다. `allowOtherVersions: true`가 아니면 `GET /bundles/{name}/{version}`은 `403`을 반환합니다.
-
-## Pulumi로 프로비저닝하기
-
-`WebviewBundleRemoteProvider`(토큰 `webview-bundle:aws:RemoteProvider`)는 S3 버킷과 정책, Lambda@Edge용 IAM 역할, `us-east-1`에 두 개의 Lambda@Edge 함수(Lambda@Edge에 필수), 그리고 두 함수가 모두 연결된 CloudFront 배포를 생성합니다.
-
-```ts title="index.ts"
-import { WebviewBundleRemoteProvider } from '@wvb/remote-aws-provider-pulumi';
-
-const provider = new WebviewBundleRemoteProvider('webview-bundle', {});
-
-export const bucketName = provider.bucketName;
-export const distributionDomainName = provider.cloudfrontDistributionDomainName;
-export const distributionId = provider.cloudfrontDistributionId;
-export const originRequestArn = provider.lambdaOriginRequestArn;
-export const originResponseArn = provider.lambdaOriginResponseArn;
-```
-
-이 컴포넌트는 배포 시점에 Lambda 코드를 번들링하고 각 함수에 `bucketName`, `region`, `allowOtherVersions`를 주입합니다. 함수는 기본적으로 `nodejs22.x` 런타임을 사용합니다. 함수별 설정은 `lambdaOriginRequest` / `lambdaOriginResponse`로 재정의하세요.
-
-```ts title="index.ts"
-new WebviewBundleRemoteProvider('webview-bundle', {
- lambdaOriginRequest: {
- runtime: 'nodejs22.x',
- architecture: 'arm64',
- memorySize: 256,
- },
-});
-```
-
-Lambda@Edge는 `us-east-1`을 요구합니다. Pulumi AWS 프로바이더를 해당 리전으로 설정하세요.
-
-```ts title="index.ts"
-import * as aws from '@pulumi/aws';
-
-const usEast1 = new aws.Provider('us-east-1', { region: 'us-east-1' });
-```
-
-## 관련 문서
-
-
-
-
-
diff --git a/content/docs/guide/providers/aws.mdx b/content/docs/guide/providers/aws.mdx
deleted file mode 100644
index dcf3bc3..0000000
--- a/content/docs/guide/providers/aws.mdx
+++ /dev/null
@@ -1,209 +0,0 @@
----
-title: AWS provider
-description: Host, serve, and provision remote Webview Bundles on AWS using S3, CloudFront, Lambda@Edge, and optional KMS signing.
----
-
-Publish bundles to your own AWS account and serve them over a global CDN. Bundles live in **S3**, **CloudFront** caches them, two **Lambda@Edge** functions translate the remote HTTP contract into S3 reads, and **KMS** can optionally sign each bundle.
-For the contract these pieces implement and how to choose a provider, see [Building a remote](/docs/guide/remote).
-
-## Backing services
-
-| Service | Role |
-| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
-| S3 | Stores each bundle object and the `deployment.json` pointer that records the current version. |
-| CloudFront | CDN in front of S3; the deployer can issue cache invalidations after a deploy. |
-| Lambda@Edge | Two functions — origin-request rewrites the URL to the bundle key, origin-response turns S3 object metadata into the contract response headers. |
-| KMS | Optional. Signs the integrity string of each bundle so clients can verify who published it. |
-
-## Packages
-
-| Package | Version | Role |
-| --------------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
-| `@wvb/remote-aws` | `0.1.0` | Publish side. Produces `uploader`, `deployer`, and optional `signature` for `wvb.config.ts`. |
-| `@wvb/remote-aws-provider` | `0.1.0` | The Lambda@Edge server (a Hono app). Exposes `./origin-request` and `./origin-response` subpaths. |
-| `@wvb/remote-aws-provider-pulumi` | `0.1.0` | Pulumi component that provisions S3, CloudFront, the two Lambda@Edge functions, and IAM. |
-
-
-
-
-```sh
-npm install @wvb/remote-aws @wvb/remote-aws-provider @wvb/remote-aws-provider-pulumi
-```
-
-
-
-
-```sh
-pnpm add @wvb/remote-aws @wvb/remote-aws-provider @wvb/remote-aws-provider-pulumi
-```
-
-
-
-
-```sh
-yarn add @wvb/remote-aws @wvb/remote-aws-provider @wvb/remote-aws-provider-pulumi
-```
-
-
-
-
-## Configure the publish side
-
-`awsRemote()` returns `{ uploader, deployer, signature? }`. Spread it into the `remote` block of your config:
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { awsRemote } from '@wvb/remote-aws';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://bundles.example.com',
- ...awsRemote({
- bucket: 'my-app-bundles',
- aws: {
- region: 'us-east-1',
- profile: 'my-app-deploy',
- },
- }),
- },
-});
-```
-
-See [Remote, integrity and signature config](/docs/config/remote) for the full `remote` schema.
-
-### Customize the uploader and deployer
-
-`uploader` and `deployer` accept their own options. The uploader streams to S3 with a multipart upload and writes the contract values as S3 object metadata (`webview-bundle-name`, `webview-bundle-version`, and, when present, `webview-bundle-integrity` and `webview-bundle-signature`). The deployer GET-then-PUTs `deployment.json` and can invalidate CloudFront after a deploy:
-
-```ts title="wvb.config.ts"
-...awsRemote({
- bucket: 'my-app-bundles',
- aws: { region: 'us-east-1' },
- uploader: {
- // Default: bundles/${name}/${name}_${version}.wvb
- key: (name, version) => `bundles/${name}/${version}/${name}_${version}.wvb`,
- cacheControl: 'public, max-age=31536000, immutable',
- contentType: 'application/webview-bundle',
- },
- deployer: {
- invalidation: {
- distributionId: 'E1ABCDEF2GHIJK',
- },
- },
-}),
-```
-
-If the bundle already exists and you do not pass `force`, the uploader runs a `HeadObject` pre-check and throws `BundleAlreadyUploadedError`:
-
-```ts
-import { isBundleAlreadyUploadedError } from '@wvb/remote-aws';
-
-try {
- await uploader.upload({ bundle, bundleName, version });
-} catch (err) {
- if (isBundleAlreadyUploadedError(err)) {
- // Re-run with --force to overwrite.
- }
- throw err;
-}
-```
-
-### Sign with KMS
-
-Set `signature` to a KMS signer config to sign each bundle's integrity string. The signer calls KMS with `MessageType: 'DIGEST'` and returns a base64 signature:
-
-```ts title="wvb.config.ts"
-import { awsRemote } from '@wvb/remote-aws';
-
-...awsRemote({
- bucket: 'my-app-bundles',
- aws: { region: 'us-east-1' },
- signature: {
- keyId: 'arn:aws:kms:us-east-1:111122223333:key/abcd-1234',
- algorithm: 'ECDSA_SHA_256',
- },
-}),
-```
-
-Build the signer standalone with `awsKmsSignatureSigner({ keyId, algorithm })`, which returns a signing function. Pass `signature: false` to disable signing.
-
-
- The default uploader key and the provider's expected key do not match. The uploader writes to
- `bundles/{name}/{name}_{version}.wvb`, but the Lambda@Edge provider rewrites requests to `bundles/
- {name}/{version}/{name}_{version}.wvb` — an extra `{version}` directory. For an end-to-end AWS
- setup, set a custom uploader `key` that produces the provider's layout (as shown above), or
- arrange a matching S3 layout. Verify against the package source before relying on the defaults.
-
-
-## Serve from Lambda@Edge
-
-`@wvb/remote-aws-provider` splits the contract across two CloudFront event handlers. Wire each to its own Lambda:
-
-```ts title="origin-request.ts"
-import { originRequest } from '@wvb/remote-aws-provider/origin-request';
-
-export const handler = originRequest({
- bucketName: 'my-app-bundles',
- region: 'us-east-1',
- // allowOtherVersions: true, // allow GET /bundles/{name}/{version}; otherwise 403
-});
-```
-
-```ts title="origin-response.ts"
-import { originResponse } from '@wvb/remote-aws-provider/origin-response';
-
-export const handler = originResponse();
-```
-
-The origin-request handler reads `deployment.json`, resolves the version (falling back from a channel to the default version), rewrites the origin URI to the bundle key, and tags the request. The origin-response handler converts S3 `x-amz-meta-webview-bundle-*` metadata into the `webview-bundle-*` response headers. `GET /bundles/{name}/{version}` returns `403` unless `allowOtherVersions: true`.
-
-## Provision with Pulumi
-
-`WebviewBundleRemoteProvider` (token `webview-bundle:aws:RemoteProvider`) creates the S3 bucket and policy, an IAM role for Lambda@Edge, the two Lambda@Edge functions in `us-east-1` (required for Lambda@Edge), and a CloudFront distribution with both functions attached:
-
-```ts title="index.ts"
-import { WebviewBundleRemoteProvider } from '@wvb/remote-aws-provider-pulumi';
-
-const provider = new WebviewBundleRemoteProvider('webview-bundle', {});
-
-export const bucketName = provider.bucketName;
-export const distributionDomainName = provider.cloudfrontDistributionDomainName;
-export const distributionId = provider.cloudfrontDistributionId;
-export const originRequestArn = provider.lambdaOriginRequestArn;
-export const originResponseArn = provider.lambdaOriginResponseArn;
-```
-
-The component bundles the Lambda code at deploy time and injects `bucketName`, `region`, and `allowOtherVersions` into each function. Functions default to the `nodejs22.x` runtime. Override per-function settings with `lambdaOriginRequest` / `lambdaOriginResponse`:
-
-```ts title="index.ts"
-new WebviewBundleRemoteProvider('webview-bundle', {
- lambdaOriginRequest: {
- runtime: 'nodejs22.x',
- architecture: 'arm64',
- memorySize: 256,
- },
-});
-```
-
-Lambda@Edge requires `us-east-1`. Configure the Pulumi AWS provider for that region:
-
-```ts title="index.ts"
-import * as aws from '@pulumi/aws';
-
-const usEast1 = new aws.Provider('us-east-1', { region: 'us-east-1' });
-```
-
-## Related
-
-
-
-
-
diff --git a/content/docs/guide/providers/cloudflare.ko.mdx b/content/docs/guide/providers/cloudflare.ko.mdx
deleted file mode 100644
index 10e6be7..0000000
--- a/content/docs/guide/providers/cloudflare.ko.mdx
+++ /dev/null
@@ -1,231 +0,0 @@
----
-title: Cloudflare 프로바이더
-description: R2, Workers KV, Cloudflare Workers를 사용해 Cloudflare에서 번들을 저장하고 배포하고 제공합니다.
----
-
-Webview Bundle 원격을 Cloudflare 엣지에서 완전히 실행할 수 있습니다. 번들은 **R2**에, 버전 포인터는 **Workers KV**에 저장하고, **Cloudflare Worker**가 업데이터와 통신하는 HTTP 계약을 제공합니다. 관리할 서버 없이 전 세계에 낮은 지연으로 무선(OTA) 전달을 제공합니다.
-
-세 개의 패키지가 이 워크플로를 담당합니다.
-
-| Package | 역할 | 실행 위치 |
-| ---------------------------------------- | --------------------------------------- | ------------------ |
-| `@wvb/remote-cloudflare` | `wvb.config`용 업로더 + 디플로이어 | 로컬 머신 / CI |
-| `@wvb/remote-cloudflare-provider` | 번들을 제공하는 Worker | Cloudflare Workers |
-| `@wvb/remote-cloudflare-provider-pulumi` | 인프라를 프로비저닝하는 Pulumi 컴포넌트 | `pulumi up` |
-
-세 패키지 모두 `0.1.0`입니다. `wvb.config`에서 버전을 고정하세요.
-
-```sh
-npm i -D @wvb/remote-cloudflare
-```
-
-
- 원격이 처음이신가요? [원격 만들기](/docs/guide/remote)에서 계약과 로컬 테스트 루프를 먼저
- 살펴보세요. 전체 `wvb.config` 레퍼런스는 [원격, 무결성 및 서명 설정](/docs/config/remote)을
- 참고하세요.
-
-
-## 각 구성 요소가 맞물리는 방식
-
-각 역할은 하나의 Cloudflare 서비스에 매핑됩니다.
-
-- **R2**는 R2의 S3 호환 API를 통해 각 번들을 객체로 저장합니다. `@wvb/remote-cloudflare` 업로더는 `@wvb/remote-aws`의 S3 업로더를 R2로 향하게 하여 재사용합니다.
-- **Workers KV**는 배포 포인터를 보관합니다. 번들마다(또는 번들과 채널마다) 키 하나를 두고, 그 값은 배포된 버전입니다.
-- `@wvb/remote-cloudflare-provider`를 실행하는 **Cloudflare Worker**는 KV를 읽어 버전을 확인한 뒤, R2에서 일치하는 객체를 스트리밍합니다.
-
-
- Cloudflare에는 기본 서명 기능이 없습니다. KMS에 해당하는 것이 없고, `@wvb/remote-cloudflare`도
- 서명자를 제공하지 않습니다. [서명](/docs/config/remote)이 필요하면 다른 곳에서 생성한 뒤, 결과로
- 나온 `signature` 문자열을 `wvb.config`를 통해 전달하세요.
-
-
-## 업로더와 디플로이어 설정
-
-`cloudflareRemote()`를 호출하고 그 결과를 `remote` 블록에 펼쳐 넣으세요. 이 함수는 `{ uploader, deployer }`를 반환합니다. 업로더는 번들을 R2에 쓰고, 디플로이어는 버전을 KV에 씁니다.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { cloudflareRemote } from '@wvb/remote-cloudflare';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://bundles.example.com',
- bundleName: 'my-app',
- ...cloudflareRemote({
- bucket: 'webview-bundle',
- accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
- kvNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID!,
- }),
- },
-});
-```
-
-| Option | Type | 설명 |
-| --------------- | -------- | ------------------------------------------------------------------------------------- |
-| `bucket` | `string` | 번들 객체를 저장하는 R2 버킷. |
-| `accountId` | `string` | Cloudflare 계정 ID. R2 엔드포인트와 KV 대상을 설정합니다. |
-| `kvNamespaceId` | `string` | 배포 포인터를 보관하는 Workers KV 네임스페이스. |
-| `uploader` | object | 내부 S3 업로더에 대한 선택적 재정의(`key`, `contentType`, `metadata` 등). |
-| `deployer` | object | KV 디플로이어에 대한 선택적 재정의(`key`, `expiration`, `expirationTtl`, `metadata`). |
-
-기본값: 업로더는 `region: 'auto'`와 엔드포인트 `https://.r2.cloudflarestorage.com`을 설정한 다음, `bundles//_.wvb` 키에 객체를 저장하고 `webview-bundle-*` 커스텀 메타데이터를 붙입니다. 디플로이어는 버전을 KV의 `` 키(채널 배포일 때는 `/`)에 씁니다.
-
-레이아웃이 다르면 둘 중 어느 쪽이든 재정의하세요.
-
-```ts title="wvb.config.ts"
-...cloudflareRemote({
- bucket: 'webview-bundle',
- accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
- kvNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID!,
- uploader: {
- key: (name, version) => `releases/${name}/${version}.wvb`,
- cacheControl: 'public, max-age=31536000, immutable',
- },
- deployer: {
- key: (name, _version, channel) => (channel ? `${name}/${channel}` : name),
- expirationTtl: 60 * 60 * 24,
- },
-}),
-```
-
-R2에는 S3 자격 증명이 필요합니다. AWS와 동일한 방식으로, S3 클라이언트가 읽는 환경변수를 통해 제공하세요.
-
-```sh
-export AWS_ACCESS_KEY_ID=
-export AWS_SECRET_ACCESS_KEY=
-```
-
-설정을 마쳤으면 번들을 게시하세요.
-
-```sh
-wvb pack ./dist
-wvb upload --version 1.2.0
-wvb deploy --version 1.2.0
-```
-
-`wvb upload`는 기본적으로 배포하지 않습니다. `wvb deploy`를 실행하거나 `upload`에 `--deploy`를 전달해 해당 버전을 라이브 상태로 만드세요.
-
-```sh
-wvb upload --version 1.2.0 --deploy
-```
-
-기본 포인터 대신 채널로 배포하려면 다음과 같이 합니다.
-
-```sh
-wvb deploy --version 1.2.0 --channel beta
-```
-
-모든 플래그는 [CLI 레퍼런스](/docs/guide/cli)를 참고하세요.
-
-## Worker로 번들 제공하기
-
-제공 측은 `@wvb/remote-cloudflare-provider`의 Hono 앱입니다. `wvbRemote()`로 생성한 뒤 Worker의 바인딩을 그 `Context`로 전달하세요. `KV` 바인딩은 `kv`가 되고, `BUCKET`(R2) 바인딩은 `r2`가 됩니다.
-
-```ts title="src/worker.ts"
-import { wvbRemote } from '@wvb/remote-cloudflare-provider';
-
-const remote = wvbRemote();
-
-export interface Env {
- KV: KVNamespace;
- BUCKET: R2Bucket;
-}
-
-export default {
- fetch(request: Request, env: Env): Response | Promise {
- return remote.fetch(request, { kv: env.KV, r2: env.BUCKET });
- },
-};
-```
-
-`wvbRemote()`는 옵션 하나 `allowOtherVersions`(기본값 `false`)를 받습니다. `false`일 때 `GET /bundles/:name/:version`은 `403`을 반환합니다. 배포된 버전 외의 버전도 클라이언트가 다운로드하게 하려면 `true`로 설정하세요.
-
-```ts title="src/worker.ts"
-const remote = wvbRemote({ allowOtherVersions: true });
-```
-
-Worker는 KV에서 버전을 확인하고, R2에서 `bundles//_.wvb`를 읽어, 필수 헤더인 `Webview-Bundle-Name`과 `Webview-Bundle-Version`(객체 메타데이터에 있으면 `Webview-Bundle-Integrity`와 `Webview-Bundle-Signature`도 함께)을 붙여 반환합니다.
-
-이름이 `Context`와 맞도록 `wrangler.jsonc`에서 `KV`와 `BUCKET`을 바인딩하세요.
-
-```json title="wrangler.jsonc"
-{
- "name": "webview-bundle",
- "main": "src/worker.ts",
- "compatibility_date": "2025-01-01",
- "kv_namespaces": [{ "binding": "KV", "id": "" }],
- "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "webview-bundle" }]
-}
-```
-
-Worker를 배포하세요.
-
-```sh
-npx wrangler deploy
-```
-
-라이브 엔드포인트에서 계약을 검증하세요.
-
-```sh
-curl https://bundles.example.com/bundles
-# [{ "name": "my-app", "version": "1.2.0" }]
-
-curl -I https://bundles.example.com/bundles/my-app
-# Webview-Bundle-Name: my-app
-# Webview-Bundle-Version: 1.2.0
-# Content-Type: application/webview-bundle
-```
-
-## Pulumi로 프로비저닝하기
-
-`@wvb/remote-cloudflare-provider-pulumi`는 전체 스택을 하나의 컴포넌트 리소스로 프로비저닝합니다. R2 버킷, Workers KV 네임스페이스, Worker(버전과 배포 포함)를 만들고, `Context`가 기대하는 바인딩 `BUCKET`(R2)과 `KV`로 서로 연결합니다.
-
-```ts title="index.ts"
-import { WebviewBundleRemoteProvider } from '@wvb/remote-cloudflare-provider-pulumi';
-
-const provider = new WebviewBundleRemoteProvider('webview-bundle', {
- accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
- // Worker를 점진적으로 롤아웃합니다. 기본값은 100입니다.
- workerDeploymentPercentage: 25,
-});
-
-export const bucketName = provider.bucketName;
-export const kvNamespaceId = provider.kvNamespaceId;
-export const workerId = provider.workerId;
-export const workerVersionId = provider.workerVersionId;
-export const workerDeploymentId = provider.workerDeploymentId;
-```
-
-이 컴포넌트는 Pulumi 리소스 토큰 `webview-bundle:cloudflare:RemoteProvider`를 등록합니다. `workerDeploymentPercentage`는 새 Worker 버전이 받는 트래픽 비율입니다(기본값 `100`).
-
-```sh
-pulumi up
-pulumi stack output kvNamespaceId
-```
-
-내보낸 `bucketName`과 `kvNamespaceId`를 위의 `cloudflareRemote()`에 넣은 다음, CLI로 게시하세요.
-
-## 다음 단계
-
-
-
-
-
-
-
diff --git a/content/docs/guide/providers/cloudflare.mdx b/content/docs/guide/providers/cloudflare.mdx
deleted file mode 100644
index fc53b3b..0000000
--- a/content/docs/guide/providers/cloudflare.mdx
+++ /dev/null
@@ -1,231 +0,0 @@
----
-title: Cloudflare provider
-description: Store, deploy, and serve bundles on Cloudflare using R2, Workers KV, and Cloudflare Workers.
----
-
-Run a Webview Bundle remote entirely on Cloudflare's edge: bundles live in **R2**, version pointers live in **Workers KV**, and a **Cloudflare Worker** serves the HTTP contract the updater talks to. You get global, low-latency over-the-air (OTA) delivery with no servers to manage.
-
-Three packages cover the workflow:
-
-| Package | Role | Runs |
-| ---------------------------------------- | --------------------------------------------------- | ------------------ |
-| `@wvb/remote-cloudflare` | Uploader + deployer for `wvb.config` | Your machine / CI |
-| `@wvb/remote-cloudflare-provider` | The Worker that serves bundles | Cloudflare Workers |
-| `@wvb/remote-cloudflare-provider-pulumi` | Pulumi component that provisions the infrastructure | `pulumi up` |
-
-All three are `0.1.0`. Pin the version in your `wvb.config`.
-
-```sh
-npm i -D @wvb/remote-cloudflare
-```
-
-
- New to remotes? Start with [Building a remote](/docs/guide/remote) for the contract and the local
- testing loop, and see [Remote, integrity & signature config](/docs/config/remote) for the full
- `wvb.config` reference.
-
-
-## How the pieces fit
-
-Each role maps onto a Cloudflare service:
-
-- **R2** stores each bundle as an object, via R2's S3-compatible API. The `@wvb/remote-cloudflare` uploader reuses the S3 uploader from `@wvb/remote-aws`, pointed at R2.
-- **Workers KV** holds the deployment pointer — one key per bundle (or per bundle and channel) whose value is the deployed version.
-- A **Cloudflare Worker** running `@wvb/remote-cloudflare-provider` reads KV to resolve the version, then streams the matching object from R2.
-
-
- Cloudflare has no built-in signing — no KMS equivalent, and `@wvb/remote-cloudflare` exposes no
- signer. If you need [signatures](/docs/config/remote), produce them elsewhere and pass the
- resulting `signature` string through your `wvb.config`.
-
-
-## Configure the uploader and deployer
-
-Call `cloudflareRemote()` and spread the result into the `remote` block. It returns `{ uploader, deployer }`: the uploader writes the bundle to R2, the deployer writes the version to KV.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { cloudflareRemote } from '@wvb/remote-cloudflare';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://bundles.example.com',
- bundleName: 'my-app',
- ...cloudflareRemote({
- bucket: 'webview-bundle',
- accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
- kvNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID!,
- }),
- },
-});
-```
-
-| Option | Type | Notes |
-| --------------- | -------- | ----------------------------------------------------------------------------------------------- |
-| `bucket` | `string` | R2 bucket that stores bundle objects. |
-| `accountId` | `string` | Cloudflare account ID; sets the R2 endpoint and the KV target. |
-| `kvNamespaceId` | `string` | Workers KV namespace that holds deployment pointers. |
-| `uploader` | object | Optional overrides for the underlying S3 uploader (`key`, `contentType`, `metadata`, and more). |
-| `deployer` | object | Optional overrides for the KV deployer (`key`, `expiration`, `expirationTtl`, `metadata`). |
-
-Defaults: the uploader sets `region: 'auto'` and endpoint `https://.r2.cloudflarestorage.com`, then stores the object at key `bundles//_.wvb` with `webview-bundle-*` custom metadata. The deployer writes the version into KV under key `` (or `/` for a channel deploy).
-
-Override either when your layout differs:
-
-```ts title="wvb.config.ts"
-...cloudflareRemote({
- bucket: 'webview-bundle',
- accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
- kvNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID!,
- uploader: {
- key: (name, version) => `releases/${name}/${version}.wvb`,
- cacheControl: 'public, max-age=31536000, immutable',
- },
- deployer: {
- key: (name, _version, channel) => (channel ? `${name}/${channel}` : name),
- expirationTtl: 60 * 60 * 24,
- },
-}),
-```
-
-R2 needs S3 credentials. Provide them the same way as AWS — through environment variables the S3 client reads:
-
-```sh
-export AWS_ACCESS_KEY_ID=
-export AWS_SECRET_ACCESS_KEY=
-```
-
-With the config in place, publish a bundle:
-
-```sh
-wvb pack ./dist
-wvb upload --version 1.2.0
-wvb deploy --version 1.2.0
-```
-
-`wvb upload` does not deploy by default. Run `wvb deploy` (or pass `--deploy` to `upload`) to make the version live:
-
-```sh
-wvb upload --version 1.2.0 --deploy
-```
-
-Deploy to a channel instead of the default pointer:
-
-```sh
-wvb deploy --version 1.2.0 --channel beta
-```
-
-See the [CLI reference](/docs/guide/cli) for every flag.
-
-## Serve bundles with a Worker
-
-The serving side is a Hono app from `@wvb/remote-cloudflare-provider`. Create it with `wvbRemote()` and forward the Worker's bindings into its `Context` — the `KV` binding becomes `kv`, the `BUCKET` (R2) binding becomes `r2`.
-
-```ts title="src/worker.ts"
-import { wvbRemote } from '@wvb/remote-cloudflare-provider';
-
-const remote = wvbRemote();
-
-export interface Env {
- KV: KVNamespace;
- BUCKET: R2Bucket;
-}
-
-export default {
- fetch(request: Request, env: Env): Response | Promise {
- return remote.fetch(request, { kv: env.KV, r2: env.BUCKET });
- },
-};
-```
-
-`wvbRemote()` takes one option, `allowOtherVersions` (default `false`). When `false`, `GET /bundles/:name/:version` returns `403`; set it to `true` to let clients download versions other than the deployed one:
-
-```ts title="src/worker.ts"
-const remote = wvbRemote({ allowOtherVersions: true });
-```
-
-The Worker resolves the version from KV, reads `bundles//_.wvb` from R2, and returns it with the required `Webview-Bundle-Name` and `Webview-Bundle-Version` headers (plus `Webview-Bundle-Integrity` and `Webview-Bundle-Signature` when present in the object's metadata).
-
-Bind `KV` and `BUCKET` in `wrangler.jsonc` so the names match the `Context`:
-
-```json title="wrangler.jsonc"
-{
- "name": "webview-bundle",
- "main": "src/worker.ts",
- "compatibility_date": "2025-01-01",
- "kv_namespaces": [{ "binding": "KV", "id": "" }],
- "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "webview-bundle" }]
-}
-```
-
-Deploy the Worker:
-
-```sh
-npx wrangler deploy
-```
-
-Verify the contract against the live endpoint:
-
-```sh
-curl https://bundles.example.com/bundles
-# [{ "name": "my-app", "version": "1.2.0" }]
-
-curl -I https://bundles.example.com/bundles/my-app
-# Webview-Bundle-Name: my-app
-# Webview-Bundle-Version: 1.2.0
-# Content-Type: application/webview-bundle
-```
-
-## Provision with Pulumi
-
-`@wvb/remote-cloudflare-provider-pulumi` provisions the whole stack as one component resource: an R2 bucket, a Workers KV namespace, and a Worker (with a version and a deployment), wired together with the bindings `BUCKET` (R2) and `KV` that the `Context` expects.
-
-```ts title="index.ts"
-import { WebviewBundleRemoteProvider } from '@wvb/remote-cloudflare-provider-pulumi';
-
-const provider = new WebviewBundleRemoteProvider('webview-bundle', {
- accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
- // Roll the Worker out gradually; defaults to 100.
- workerDeploymentPercentage: 25,
-});
-
-export const bucketName = provider.bucketName;
-export const kvNamespaceId = provider.kvNamespaceId;
-export const workerId = provider.workerId;
-export const workerVersionId = provider.workerVersionId;
-export const workerDeploymentId = provider.workerDeploymentId;
-```
-
-The component registers the Pulumi resource token `webview-bundle:cloudflare:RemoteProvider`. `workerDeploymentPercentage` is the share of traffic the new Worker version receives (default `100`).
-
-```sh
-pulumi up
-pulumi stack output kvNamespaceId
-```
-
-Feed the exported `bucketName` and `kvNamespaceId` into `cloudflareRemote()` above, then publish with the CLI.
-
-## Next steps
-
-
-
-
-
-
-
diff --git a/content/docs/guide/providers/local.ko.mdx b/content/docs/guide/providers/local.ko.mdx
deleted file mode 100644
index 65e307c..0000000
--- a/content/docs/guide/providers/local.ko.mdx
+++ /dev/null
@@ -1,242 +0,0 @@
----
-title: 로컬 프로바이더
-description: 클라우드 프로바이더를 도입하기 전에, 내 컴퓨터에서 파일시스템 기반 원격 서버를 실행해 업로드, 배포, 무선(OTA) 업데이트로 이어지는 전체 흐름을 테스트하세요.
----
-
-내 컴퓨터에서 실행되는 파일시스템 기반 원격 서버입니다. 클라우드 프로바이더와 동일한 HTTP 규약을 구현하므로, 인프라 없이도 전체 수명 주기(패킹, 업로드, 배포, 무선(OTA) 업데이트)를 실행할 수 있습니다. 이 서버로 개발한 다음, 프로덕션에서는 [클라우드 프로바이더](/docs/guide/remote)로 전환하세요.
-
-
- 개발과 테스트 전용입니다. 번들을 한 컴퓨터에 일반 파일로 저장하며, `wvb remote local`은
- `localhost`에 바인딩됩니다. 프로덕션 트래픽에는 [AWS](/docs/guide/providers/aws) 또는
- [Cloudflare](/docs/guide/providers/cloudflare) 프로바이더를 사용하세요.
-
-
-## 두 개의 패키지
-
-둘 다 `0.0.0` 버전입니다.
-
-| Package | Role |
-| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ |
-| `@wvb/remote-local` | 게시(publish) 측 클라이언트입니다. `localRemote()`는 `wvb.config`에 연결하는 `uploader`와 `deployer`를 생성합니다. |
-| `@wvb/remote-local-provider` | 서버입니다. 저장된 번들을 HTTP로 제공하는 [Hono](https://hono.dev) 앱입니다. CLI가 대신 실행해 줍니다. |
-
-이 분리 구조는 모든 백엔드에서 동일합니다. 일반 패키지는 번들을 푸시하고, `-provider` 패키지는 번들을 제공합니다. 공유 규약은 [원격 서버 만들기](/docs/guide/remote)를 참고하세요.
-
-## 게시 측 설정하기
-
-`{ uploader, deployer }`를 반환하는 `localRemote()`를 `remote` 블록에 펼쳐 넣으세요.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { localRemote } from '@wvb/remote-local';
-
-export default defineConfig({
- remote: {
- endpoint: 'http://localhost:4313',
- bundleName: 'app',
- ...localRemote(),
- },
-});
-```
-
-`localRemote()`는 선택적 필드 하나를 받습니다.
-
-| Option | Type | Default | Description |
-| --------- | -------- | -------------- | ---------------------------------------------------- |
-| `baseDir` | `string` | `~/.wvb/local` | 번들과 배포 상태를 디스크에 저장하는 디렉터리입니다. |
-
-`baseDir`을 설정하면 프로젝트의 번들을 기본 저장소와 분리할 수 있습니다.
-
-```ts title="wvb.config.ts"
-import os from 'node:os';
-import path from 'node:path';
-import { defineConfig } from '@wvb/config';
-import { localRemote } from '@wvb/remote-local';
-
-export default defineConfig({
- remote: {
- endpoint: 'http://localhost:4313',
- bundleName: 'app',
- ...localRemote({
- baseDir: path.join(os.homedir(), '.wvb', 'my-app'),
- }),
- },
-});
-```
-
-`remote` 블록 전체(`endpoint`, `bundleName`, `packBeforeUpload`, 무결성, 서명)는 [원격 설정 레퍼런스](/docs/config/remote)를 참고하세요.
-
-## 디스크 레이아웃
-
-모든 것은 `{baseDir}/bundles` 아래에 기록되며, 번들 이름마다 디렉터리 하나가 만들어집니다.
-
-```text
-{baseDir}/
-└── bundles/
- └── {name}/
- ├── {name}_{version}.wvb # 번들 바이너리 (업로드된 버전마다 하나)
- ├── {name}_{version}.json # 버전별 메타데이터: { integrity?, signature? }
- └── deployment.json # 채널별로 어떤 버전이 현재 버전인지 기록
-```
-
-번들 `app`의 `1.1.0`을 기본 저장소에 업로드하고 배포한 뒤의 모습입니다.
-
-```text
-~/.wvb/local/
-└── bundles/
- └── app/
- ├── app_1.1.0.wvb
- ├── app_1.1.0.json
- └── deployment.json
-```
-
-`deployment.json`은 현재 버전을 기록합니다. 채널 없이 배포하면 기본 `version`을 설정하고, 채널과 함께 배포하면 `channels[channel]`을 설정합니다. 따라서 채널마다 서로 다른 버전을 가리킬 수 있습니다.
-
-```json title="deployment.json"
-{
- "name": "app",
- "version": "1.1.0",
- "channels": {
- "beta": "1.2.0"
- }
-}
-```
-
-## 서버 실행하기
-
-`@wvb/remote-local-provider`는 Hono 앱을 빌드하기만 하고 포트에 바인딩하지는 않습니다. `wvb remote local`이 그 앱을 Node HTTP 서버로 제공합니다.
-
-```sh
-wvb remote local
-```
-
-기본적으로 `~/.wvb/local`을 `http://localhost:4313`에서 제공합니다. 플래그는 다음과 같습니다.
-
-| Flag | Default | Description |
-| ------------------------ | -------------- | ------------------------------------------------------------------------- |
-| `--base-dir` | `~/.wvb/local` | 제공할 디렉터리입니다. `localRemote()`에 설정한 `baseDir`과 일치시키세요. |
-| `--port`, `-P` | `4313` | 수신 대기할 포트입니다(`PORT`도 읽습니다). |
-| `--hostname`, `-H` | `localhost` | 바인딩할 호스트입니다(`HOSTNAME`도 읽습니다). |
-| `--allow-other-versions` | `false` | 배포된 버전 외의 버전 다운로드를 허용합니다. |
-| `--silent` | `false` | 서버 로깅을 억제합니다. |
-
-설정에 지정한 것과 같은 경로를 전달하세요.
-
-```sh
-wvb remote local --base-dir ~/.wvb/my-app --port 4313
-```
-
-`remote` 명령어 그룹 전체(`remote list`, 별칭 `remote ls`)는 [CLI 레퍼런스](/docs/guide/cli)를 참고하세요.
-
-### allowOtherVersions
-
-기본적으로 서버는 배포된 버전만 제공합니다. `GET /bundles/{name}/{version}`은 `403 Forbidden`을 반환하는데, 이는 클라우드 프로바이더와 동일하며 클라이언트를 특정 버전에 고정해 둡니다.
-
-```sh
-curl -i http://localhost:4313/bundles/app/1.0.0
-# HTTP/1.1 403 Forbidden
-```
-
-이 옵션을 켜면 버전별 경로가 열립니다. 롤백을 테스트하거나 이전 번들을 살펴볼 때 유용합니다.
-
-```sh
-wvb remote local --allow-other-versions
-```
-
-```sh
-curl -i http://localhost:4313/bundles/app/1.0.0
-# HTTP/1.1 200 OK
-# Webview-Bundle-Name: app
-# Webview-Bundle-Version: 1.0.0
-```
-
-프로바이더 앱을 직접 빌드할 때는 `allowOtherVersions: true`를 전달하세요.
-
-```ts title="server.ts"
-import { wvbRemote } from '@wvb/remote-local-provider';
-
-const app = wvbRemote({
- baseDir: '~/.wvb/local',
- allowOtherVersions: true,
-});
-```
-
-
- `allowOtherVersions`를 비활성화하면 배포된 버전만 `GET /bundles/{name}`으로 접근할 수 있습니다.
- 버전별 경로는 이 옵션을 켜기 전까지 `403` 뒤에 숨겨진 상태로 유지됩니다.
-
-
-## 전체 흐름 따라가기
-
-로컬 서버를 상대로 OTA 업데이트를 처음부터 끝까지 테스트해 봅니다.
-
-
- `wvb upload`는 기본적으로 배포하지 않습니다. `--deploy`를 전달하지 않으면 `false`입니다. `wvb
- upload --deploy`로 업로드와 현재 버전 지정을 한 번에 하거나, `wvb deploy`를 따로 실행하세요.
-
-
-위에서 보여준 대로 `localRemote()`를 연결한 다음, 패킹하고 업로드하고 배포하세요. `packBeforeUpload`가 기본값이면 `wvb upload`가 대신 패킹하므로, 명시적인 `wvb pack`은 선택 사항입니다.
-
-```sh
-wvb pack ./dist
-wvb upload --version 1.1.0 --deploy
-```
-
-같은 저장소를 상대로 서버를 시작하고, 번들이 목록에 나오는지 확인하세요.
-
-```sh
-wvb remote local
-```
-
-```sh
-curl http://localhost:4313/bundles
-# [{"name":"app","version":"1.1.0"}]
-```
-
-```sh
-curl -I http://localhost:4313/bundles/app
-# Webview-Bundle-Name: app
-# Webview-Bundle-Version: 1.1.0
-# Content-Type: application/webview-bundle
-```
-
-앱의 업데이터가 `http://localhost:4313`을 가리키게 하세요. 업데이터는 `GET /bundles/{name}`을 호출하고, 번들을 검증한 뒤, `remote` 소스에 설치합니다. 원격 대상은 플랫폼 통합에서 설정하세요. [Electron](/docs/guide/platforms/electron), [Tauri](/docs/guide/platforms/tauri), [Android](/docs/guide/platforms/android), [iOS](/docs/guide/platforms/ios) 가이드를 참고하세요.
-
-버전을 올리고 다시 배포하면 업데이트가 게시됩니다. 다음 업데이터 확인 시점에 이를 가져갑니다.
-
-```sh
-wvb upload --version 1.2.0 --deploy
-```
-
-`--channel`을 전달하면 기본값 대신 채널로 배포합니다.
-
-```sh
-wvb deploy --version 1.2.0 --channel beta
-curl 'http://localhost:4313/bundles?channel=beta'
-# [{"name":"app","version":"1.2.0"}]
-```
-
-## 다음으로 볼 내용
-
-
-
-
-
-
-
diff --git a/content/docs/guide/providers/local.mdx b/content/docs/guide/providers/local.mdx
deleted file mode 100644
index 855f22c..0000000
--- a/content/docs/guide/providers/local.mdx
+++ /dev/null
@@ -1,242 +0,0 @@
----
-title: Local provider
-description: Run a filesystem-backed remote on your own machine to test the full upload, deploy, and over-the-air update loop before reaching for a cloud provider.
----
-
-A filesystem-backed remote that runs on your own machine. It implements the same HTTP contract as the cloud providers, so you can exercise the full lifecycle — pack, upload, deploy, and over-the-air (OTA) update — with no infrastructure. Develop against it, then switch to a [cloud provider](/docs/guide/remote) for production.
-
-
- Development and testing only. It stores bundles as plain files on one machine and `wvb remote
- local` binds to `localhost`. For production traffic, use the [AWS](/docs/guide/providers/aws) or
- [Cloudflare](/docs/guide/providers/cloudflare) providers.
-
-
-## Two packages
-
-Both at `0.0.0`:
-
-| Package | Role |
-| ---------------------------- | --------------------------------------------------------------------------------------------------------- |
-| `@wvb/remote-local` | Publish-side client. `localRemote()` produces the `uploader` and `deployer` you wire into `wvb.config`. |
-| `@wvb/remote-local-provider` | The server. A [Hono](https://hono.dev) app that serves stored bundles over HTTP. The CLI runs it for you. |
-
-This split mirrors every backend: the plain package pushes bundles, the `-provider` package serves them. See [Building a remote](/docs/guide/remote) for the shared contract.
-
-## Configure the publish side
-
-Spread `localRemote()` — which returns `{ uploader, deployer }` — into the `remote` block:
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { localRemote } from '@wvb/remote-local';
-
-export default defineConfig({
- remote: {
- endpoint: 'http://localhost:4313',
- bundleName: 'app',
- ...localRemote(),
- },
-});
-```
-
-`localRemote()` accepts one optional field:
-
-| Option | Type | Default | Description |
-| --------- | -------- | -------------- | ---------------------------------------------------------------- |
-| `baseDir` | `string` | `~/.wvb/local` | Directory where bundles and deployment state are stored on disk. |
-
-Set `baseDir` to isolate a project's bundles from the default store:
-
-```ts title="wvb.config.ts"
-import os from 'node:os';
-import path from 'node:path';
-import { defineConfig } from '@wvb/config';
-import { localRemote } from '@wvb/remote-local';
-
-export default defineConfig({
- remote: {
- endpoint: 'http://localhost:4313',
- bundleName: 'app',
- ...localRemote({
- baseDir: path.join(os.homedir(), '.wvb', 'my-app'),
- }),
- },
-});
-```
-
-For the full `remote` block — `endpoint`, `bundleName`, `packBeforeUpload`, integrity, and signature — see the [remote config reference](/docs/config/remote).
-
-## On-disk layout
-
-Everything is written under `{baseDir}/bundles`, one directory per bundle name:
-
-```text
-{baseDir}/
-└── bundles/
- └── {name}/
- ├── {name}_{version}.wvb # bundle binary (one per uploaded version)
- ├── {name}_{version}.json # per-version metadata: { integrity?, signature? }
- └── deployment.json # which version is current, per channel
-```
-
-After uploading and deploying `1.1.0` of bundle `app` to the default store:
-
-```text
-~/.wvb/local/
-└── bundles/
- └── app/
- ├── app_1.1.0.wvb
- ├── app_1.1.0.json
- └── deployment.json
-```
-
-`deployment.json` records the current version. Deploying without a channel sets the default `version`; with a channel it sets `channels[channel]`, so channels can point at different versions:
-
-```json title="deployment.json"
-{
- "name": "app",
- "version": "1.1.0",
- "channels": {
- "beta": "1.2.0"
- }
-}
-```
-
-## Run the server
-
-`@wvb/remote-local-provider` only builds the Hono app — it does not bind a port. `wvb remote local` serves that app over a Node HTTP server:
-
-```sh
-wvb remote local
-```
-
-Defaults to serving `~/.wvb/local` on `http://localhost:4313`. Flags:
-
-| Flag | Default | Description |
-| ------------------------ | -------------- | --------------------------------------------------------------- |
-| `--base-dir` | `~/.wvb/local` | Directory to serve. Match the `baseDir` set in `localRemote()`. |
-| `--port`, `-P` | `4313` | Port to listen on (also reads `PORT`). |
-| `--hostname`, `-H` | `localhost` | Host to bind (also reads `HOSTNAME`). |
-| `--allow-other-versions` | `false` | Allow downloading versions other than the deployed one. |
-| `--silent` | `false` | Suppress server logging. |
-
-Pass the same path you set in your config:
-
-```sh
-wvb remote local --base-dir ~/.wvb/my-app --port 4313
-```
-
-See the [CLI reference](/docs/guide/cli) for the full `remote` command group (`remote list`, alias `remote ls`).
-
-### allowOtherVersions
-
-By default the server serves only the deployed version. `GET /bundles/{name}/{version}` returns `403 Forbidden`, matching the cloud providers and keeping clients pinned:
-
-```sh
-curl -i http://localhost:4313/bundles/app/1.0.0
-# HTTP/1.1 403 Forbidden
-```
-
-Opt in to unlock the version-specific route — useful for testing rollbacks or inspecting an older bundle:
-
-```sh
-wvb remote local --allow-other-versions
-```
-
-```sh
-curl -i http://localhost:4313/bundles/app/1.0.0
-# HTTP/1.1 200 OK
-# Webview-Bundle-Name: app
-# Webview-Bundle-Version: 1.0.0
-```
-
-When building the provider app directly, pass `allowOtherVersions: true`:
-
-```ts title="server.ts"
-import { wvbRemote } from '@wvb/remote-local-provider';
-
-const app = wvbRemote({
- baseDir: '~/.wvb/local',
- allowOtherVersions: true,
-});
-```
-
-
- With `allowOtherVersions` disabled, only the deployed version is reachable via `GET /bundles/
- {name}`. The version-specific route stays behind `403` until you opt in.
-
-
-## Walk the full loop
-
-Test an OTA update against a local server end to end.
-
-
- `wvb upload` does not deploy by default — `--deploy` is `false` unless passed. Use `wvb upload
- --deploy` to upload and mark current in one step, or run `wvb deploy` separately.
-
-
-Wire up `localRemote()` as shown above, then pack, upload, and deploy. With `packBeforeUpload` at its default, `wvb upload` packs for you, so the explicit `wvb pack` is optional:
-
-```sh
-wvb pack ./dist
-wvb upload --version 1.1.0 --deploy
-```
-
-Start the server against the same store and confirm the bundle is listed:
-
-```sh
-wvb remote local
-```
-
-```sh
-curl http://localhost:4313/bundles
-# [{"name":"app","version":"1.1.0"}]
-```
-
-```sh
-curl -I http://localhost:4313/bundles/app
-# Webview-Bundle-Name: app
-# Webview-Bundle-Version: 1.1.0
-# Content-Type: application/webview-bundle
-```
-
-Point your app's updater at `http://localhost:4313`. It calls `GET /bundles/{name}`, verifies the bundle, and installs it into the `remote` source. Configure the remote target in your platform integration — see the [Electron](/docs/guide/platforms/electron), [Tauri](/docs/guide/platforms/tauri), [Android](/docs/guide/platforms/android), and [iOS](/docs/guide/platforms/ios) guides.
-
-Publish an update by bumping the version and deploying again; the next updater check picks it up:
-
-```sh
-wvb upload --version 1.2.0 --deploy
-```
-
-Deploy to a channel instead of the default by passing `--channel`:
-
-```sh
-wvb deploy --version 1.2.0 --channel beta
-curl 'http://localhost:4313/bundles?channel=beta'
-# [{"name":"app","version":"1.2.0"}]
-```
-
-## Where to go next
-
-
-
-
-
-
-
diff --git a/content/docs/guide/providers/meta.json b/content/docs/guide/providers/meta.json
deleted file mode 100644
index f9ea627..0000000
--- a/content/docs/guide/providers/meta.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "Providers",
- "pages": ["local", "aws", "cloudflare"]
-}
diff --git a/content/docs/guide/providers/meta.ko.json b/content/docs/guide/providers/meta.ko.json
deleted file mode 100644
index 087d8ac..0000000
--- a/content/docs/guide/providers/meta.ko.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "프로바이더",
- "pages": ["local", "aws", "cloudflare"]
-}
diff --git a/content/docs/guide/remote-bundles.ko.mdx b/content/docs/guide/remote-bundles.ko.mdx
deleted file mode 100644
index b7a4381..0000000
--- a/content/docs/guide/remote-bundles.ko.mdx
+++ /dev/null
@@ -1,259 +0,0 @@
----
-title: 원격 번들과 업데이트
-description: 무선(OTA) 업데이트가 동작하는 방식 — 업데이트 라이프사이클, 업데이터 모델, HTTP 규약, 그리고 무결성과 서명이 각 번들을 보호하는 원리.
----
-
-Webview Bundle은 새 번들을 무선(OTA)으로 다운로드합니다. 따라서 네이티브 앱스토어 배포 없이도 업데이트된 앱 코드를 배포할 수 있습니다. 이 페이지에서는 업데이트 라이프사이클, 언어에 종속되지 않는 업데이터 모델, 모든 원격 서버가 구현하는 HTTP 규약, 그리고 무결성과 서명이 각 번들의 신뢰성을 지키는 방법을 다룹니다. 서버를 구축하려면 [원격 서버 구축하기](/docs/guide/remote)를 참고하세요. 언어별 업데이터 API는 [Node](/docs/references/node)와 [Deno](/docs/references/deno) 레퍼런스를 참고하세요.
-
-## 업데이트 라이프사이클
-
-번들은 빌드 산출물에서 기기까지 다섯 단계를 거칩니다. 앞의 세 단계는 사용자의 머신이나 CI에서 실행되고, 마지막 두 단계는 업데이터가 앱 내부에서 구동합니다.
-
-
-
-1. 빌드 산출물을 `.wvb` 아카이브로 **패킹**합니다.
-2. 해당 아카이브를 **업로드**합니다. 무결성 해시와 서명을 함께 첨부할 수도 있습니다.
-3. 업로드한 버전을 **배포**하면 해당 채널의 클라이언트가 이를 보기 시작합니다.
-4. 기기에서 업데이터가 새 버전을 **확인**한 뒤 **다운로드**합니다.
-5. 업데이터가 다운로드를 **검증**하고 이를 `remote` 소스에 **설치**합니다.
-
-1~3단계는 CLI 명령어입니다.
-
-```sh
-wvb pack # 빌드 산출물 -> .wvb/
-wvb upload --version 1.2.0 # 서버로 푸시 (배포하지 않음)
-wvb deploy --version 1.2.0 # 1.2.0을 현재 버전으로 지정
-```
-
-`wvb upload --deploy`(기본값 `false`)는 업로드와 배포를 한 번에 처리합니다. 모든 플래그는 [CLI 레퍼런스](/docs/guide/cli)를 참고하세요. 4~5단계는 업데이터이며, 다음에서 다룹니다.
-
-## 업데이터 모델
-
-업데이터는 [번들 소스](/docs/guide/bundle-sources)를 원격 엔드포인트에 연결하고 세 가지 작업을 제공합니다. 이 작업들은 분리되어 있어, 기기가 언제 바이트를 가져올지와 언제 버전을 전환할지를 정확히 제어할 수 있습니다.
-
-- **`getUpdate`** — 확인만 합니다. 서버에 현재 배포된 버전을 요청하고 이를 로컬 버전과 비교합니다. 로컬 버전이 없거나 로컬 버전이 배포된 버전과 다르면 `isAvailable`을 `true`로 보고합니다. 아무것도 다운로드하지 않습니다.
-- **`download`** — 번들을 가져와 무결성 및 서명 정책에 따라 검증하고 `remote` 소스에 스테이징합니다. 새 버전을 활성화하지는 **않습니다**.
-- **`install`** — 스테이징된 번들을 디스크에서 다시 검증하고, 현재 버전으로 활성화하고, 이전 디스크립터를 언로드하고, 더 이상 유지하지 않는 버전을 정리합니다.
-
-이렇게 분리하면 백그라운드에서 다운로드하고 다음 앱 실행 시 설치할 수 있습니다.
-
-
- `builtin` 소스 — 앱 내부에 포함되어 배포되는 번들 — 는 항상 폴백으로 남아 있습니다. 버전이
- 설치되면 `remote` 소스가 우선하므로, 다운로드된 업데이트가 builtin 번들보다 우선합니다. 원격
- 버전이 없으면 앱은 builtin으로 폴백합니다. [번들 소스](/docs/guide/bundle-sources)를 참고하세요.
-
-
-Rust 코어 흐름입니다. 플랫폼 래퍼는 이를 일대일로 그대로 따릅니다.
-
-```rust
-use std::sync::Arc;
-use wvb::updater::Updater;
-
-let updater = Updater::new(source, remote, None); // None -> UpdaterConfig::default()
-
-let update = updater.get_update("app").await?;
-if update.is_available {
- // download는 가져오고, 검증하고, 스테이징하지만 활성화하지는 않음
- let info = updater.download("app", None).await?;
- // install은 다시 검증하고, 활성화하고, 이전 버전을 정리함
- updater.install("app", info.version).await?;
- // 새 버전을 제공하도록 웹뷰를 리로드
-}
-```
-
-`get_update`는 `BundleUpdateInfo`를 반환합니다.
-
-```rust
-pub struct BundleUpdateInfo {
- pub name: String,
- pub version: String,
- pub local_version: Option,
- pub is_available: bool,
- pub etag: Option,
- pub integrity: Option,
- pub signature: Option,
- pub last_modified: Option,
-}
-```
-
-`download`에 `Some(version)`을 전달하면 현재 버전 대신 특정 버전을 가져옵니다.
-
-```rust
-// 현재 배포된 버전을 가져옴
-let info = updater.download("app", None).await?;
-// 정확한 버전을 가져옴 (GET /bundles/{name}/{version})
-let info = updater.download("app", Some("1.1.0".into())).await?;
-```
-
-플랫폼 가이드에는 각 대상에 맞는 관용적인 호출 방법이 나와 있습니다.
-[Electron](/docs/guide/platforms/electron), [Tauri](/docs/guide/platforms/tauri),
-[Android](/docs/guide/platforms/android), [iOS](/docs/guide/platforms/ios),
-[Deno Desktop](/docs/guide/platforms/deno)을 참고하세요.
-
-## 원격 HTTP 규약
-
-Webview Bundle 서버는 네 개의 엔드포인트를 구현합니다. 프로바이더 패키지(local, AWS, Cloudflare)가 이 명세를 구현하며, 이에 맞춰 직접 만들 수도 있습니다.
-
-| 메서드와 경로 | 목적 |
-| ------------------------------- | ------------------------------------------- |
-| `GET /bundles` | 배포된 번들 목록: `[{ "name", "version" }]` |
-| `HEAD /bundles/{name}` | 현재 버전의 메타데이터 (헤더만, 본문 없음) |
-| `GET /bundles/{name}` | 현재 버전 다운로드 |
-| `GET /bundles/{name}/{version}` | 특정 버전 다운로드 |
-
-`GET /bundles`, `HEAD /bundles/{name}`, `GET /bundles/{name}`은 선택적인 `?channel=` 쿼리를 받습니다. `GET /bundles/{name}/{version}`은 받지 않습니다. 메타데이터는 응답 헤더로 전달됩니다.
-
-| 헤더 | 필수 여부 | 의미 |
-| -------------------------- | --------- | ------------- |
-| `Webview-Bundle-Name` | 예 | 번들 이름 |
-| `Webview-Bundle-Version` | 예 | 버전 |
-| `Webview-Bundle-Integrity` | 아니오 | 무결성 문자열 |
-| `Webview-Bundle-Signature` | 아니오 | 서명 |
-| `ETag`, `Last-Modified` | 아니오 | 표준 검증자 |
-
-다운로드 응답은 다음과 같습니다.
-
-```sh
-$ curl -sI http://localhost:4313/bundles/app
-HTTP/1.1 200 OK
-Content-Type: application/webview-bundle
-Webview-Bundle-Name: app
-Webview-Bundle-Version: 1.2.0
-Webview-Bundle-Integrity: sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-ETag: "abc123"
-```
-
-목록 엔드포인트는 `application/json`을 반환합니다.
-
-```json
-[
- { "name": "app", "version": "1.2.0" },
- { "name": "admin", "version": "0.4.1" }
-]
-```
-
-번들이나 버전이 없으면 `404`를 반환합니다. 서버가 허용하지 않는 경우, 배포되지 않은 특정 버전을 요청하면 `403`을 반환합니다. 필수 헤더가 없거나 2xx가 아닌 응답이 오면 클라이언트는 다운로드를 거부합니다. 기본 요청 타임아웃은 120초입니다. 실제 서버 예제와 로컬 테스트는 [원격 서버 구축하기](/docs/guide/remote)를 참고하세요.
-
-## 무결성
-
-무결성 해시를 사용하면 기기가 다운로드한 바이트가 게시한 바이트와 정확히 일치하는지 확인할 수 있습니다. Webview Bundle은 **SHA-2**를 사용합니다. `sha256`(기본값), `sha384`, `sha512`를 지원하며, `":"` 형식으로 직렬화됩니다.
-
-```text
-sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-```
-
-게시자는 업로드 시점에 이 문자열을 첨부하고([원격 설정](/docs/config/remote)), 서버는 이를 `Webview-Bundle-Integrity`로 반환하며, 업데이터는 다운로드 시 **무결성 정책**에 따라 이를 적용합니다.
-
-| 정책 | 동작 |
-| ---------- | ------------------------------------------------------------- |
-| `Strict` | 무결성 값이 반드시 존재하고 일치해야 하며, 그렇지 않으면 거부 |
-| `Optional` | **기본값** — 존재하면 검증하고, 첨부되지 않았으면 허용 |
-| `None` | 무결성 검사를 완전히 건너뜀 |
-
-```rust
-use wvb::integrity::IntegrityPolicy;
-use wvb::updater::UpdaterConfig;
-
-let config = UpdaterConfig::new()
- .integrity_policy(IntegrityPolicy::Strict);
-```
-
-## 서명
-
-무결성이 바이트가 손상되지 않았음을 증명한다면, 서명은 _누가_ 게시했는지를 증명합니다. 서명은 원본 번들이 아니라 **무결성 문자열 바이트**(`":"`)를 대상으로 합니다. 따라서 서명 검증에는 무결성 값이 반드시 존재해야 합니다. 공개 키를 제공하면, 업데이터는 서명이 검증되지 않는 모든 다운로드를 거부합니다.
-
-검증 알고리즘은 다음과 같습니다.
-
-- **ECDSA** — 곡선 P-256(secp256r1) 또는 P-384(secp384r1)
-- **Ed25519**
-- **RSA** — PKCS#1 v1.5 또는 PSS 패딩, SHA-256으로 해싱
-
-공개 키 형식은 다음과 같습니다.
-
-| 형식 | 로더 | 적용 대상 |
-| ---------------- | ------------------------- | ------------ |
-| SPKI PEM | `from_public_key_pem` | 전체 |
-| SPKI DER | `from_public_key_der` | 전체 |
-| PKCS#1 PEM/DER | `from_pkcs1_pem` / `_der` | RSA 전용 |
-| SEC1 point bytes | `from_sec1_bytes` | ECDSA 전용 |
-| Raw 32-byte key | `from_public_key_bytes` | Ed25519 전용 |
-
-일치하는 무결성 해시와 유효한 Ed25519 서명을 모두 요구하는 업데이터입니다.
-
-```rust
-use wvb::integrity::IntegrityPolicy;
-use wvb::signature::{Ed25519Verifier, SignatureVerifier};
-use wvb::updater::{Updater, UpdaterConfig};
-use std::sync::Arc;
-
-let verifier = SignatureVerifier::Ed25519(Arc::new(
- Ed25519Verifier::from_public_key_pem(PUBLIC_KEY_PEM)?,
-));
-let config = UpdaterConfig::new()
- .integrity_policy(IntegrityPolicy::Strict)
- .signature_verifier(verifier);
-let updater = Updater::new(source, remote, Some(config));
-```
-
-SPKI PEM에서 로드한 ECDSA P-256입니다.
-
-```rust
-use wvb::signature::{EcdsaSecp256r1Verifier, SignatureVerifier};
-use std::sync::Arc;
-
-let verifier = SignatureVerifier::EcdsaSecp256r1(Arc::new(
- EcdsaSecp256r1Verifier::from_public_key_pem(PUBLIC_KEY_PEM)?,
-));
-```
-
-게시 측 대응 항목 — 어떤 알고리즘으로 서명할지와 개인 키 형식 — 은 [원격 설정](/docs/config/remote)에서 지정합니다.
-
-## 채널
-
-채널은 단계적 롤아웃을 위해 서로 다른 버전을 서로 다른 대상에게 라우팅합니다. 채널에 배포하면 해당 채널을 요청하는 클라이언트만 이를 수신합니다. 채널이 없는 클라이언트는 기본 배포를 받습니다.
-
-```sh
-wvb upload --version 1.2.0 --deploy --channel beta # beta 클라이언트만 1.2.0을 봄
-wvb deploy --version 1.2.0 # 나중에 기본 채널로 승격
-```
-
-기기에서 업데이터는 설정에 채널을 담아 `?channel=`로 전달합니다.
-
-```rust
-let config = UpdaterConfig::new().channel("beta");
-let updater = Updater::new(source, remote, Some(config));
-// 이제 get_update / download는 ?channel=beta를 요청함
-```
-
-채널은 자유 형식 문자열입니다. 하드코딩된 기본 이름은 없으며, 채널이 설정되지 않으면 쿼리 파라미터가 생략됩니다.
-
-## 다음으로 볼 내용
-
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/remote-bundles.mdx b/content/docs/guide/remote-bundles.mdx
deleted file mode 100644
index be7b4bb..0000000
--- a/content/docs/guide/remote-bundles.mdx
+++ /dev/null
@@ -1,260 +0,0 @@
----
-title: Remote bundles & updates
-description: How over-the-air updating works — the lifecycle, the updater model, the HTTP contract, and how integrity and signatures protect each bundle.
----
-
-Webview Bundle downloads newer bundles over the air (OTA), so you ship updated app code without a native app-store release. This page covers the update lifecycle, the language-neutral updater model, the HTTP contract every remote server implements, and how integrity and signatures keep each bundle trustworthy. To stand up a server, see [Building a remote](/docs/guide/remote); for per-language updater APIs, see the [Node](/docs/references/node) and [Deno](/docs/references/deno) references.
-
-## The update lifecycle
-
-A bundle travels from build output to device in five stages. The first three run on your machine or CI; the last two run inside the app, driven by the updater.
-
-
-
-1. **Pack** your build into a `.wvb` archive.
-2. **Upload** that archive, optionally attaching an integrity hash and a signature.
-3. **Deploy** the uploaded version so clients on a channel start seeing it.
-4. On the device, the updater **checks** for a newer version, then **downloads** it.
-5. The updater **verifies** the download and **installs** it into the `remote` source.
-
-Stages 1–3 are CLI commands:
-
-```sh
-wvb pack # build output -> .wvb/
-wvb upload --version 1.2.0 # push to the server (does not deploy)
-wvb deploy --version 1.2.0 # make 1.2.0 the current version
-```
-
-`wvb upload --deploy` (default `false`) uploads and deploys in one step. See the [CLI reference](/docs/guide/cli) for every flag. Stages 4–5 are the updater, covered next.
-
-## The updater model
-
-The updater ties a [bundle source](/docs/guide/bundle-sources) to a remote endpoint and exposes three operations, kept separate so you control exactly when a device fetches bytes and when it switches versions.
-
-- **`getUpdate`** — a check only. Asks the server for the current deployed version and compares it to the local version. Reports `isAvailable` as `true` when there is no local version, or when the local version differs from the deployed version. Downloads nothing.
-- **`download`** — fetches the bundle, verifies it against your integrity and signature policy, and stages it into the `remote` source. Does **not** activate the new version.
-- **`install`** — re-verifies the staged bundle from disk, activates it as the current version, unloads the old descriptor, and prunes versions no longer retained.
-
-This split lets you download in the background and install on the next app launch.
-
-
- The `builtin` source — the bundle shipped inside the app — always remains as a fallback. The
- `remote` source takes priority once a version is installed, so a downloaded update wins over the
- builtin bundle; if no remote version exists, the app falls back to builtin. See [Bundle
- sources](/docs/guide/bundle-sources).
-
-
-The Rust core flow; platform wrappers mirror it one-to-one:
-
-```rust
-use std::sync::Arc;
-use wvb::updater::Updater;
-
-let updater = Updater::new(source, remote, None); // None -> UpdaterConfig::default()
-
-let update = updater.get_update("app").await?;
-if update.is_available {
- // download fetches, verifies, and stages — but does not activate
- let info = updater.download("app", None).await?;
- // install re-verifies, activates, and prunes old versions
- updater.install("app", info.version).await?;
- // reload the webview to serve the new version
-}
-```
-
-`get_update` returns a `BundleUpdateInfo`:
-
-```rust
-pub struct BundleUpdateInfo {
- pub name: String,
- pub version: String,
- pub local_version: Option,
- pub is_available: bool,
- pub etag: Option,
- pub integrity: Option,
- pub signature: Option,
- pub last_modified: Option,
-}
-```
-
-Pass `Some(version)` to `download` to fetch a specific version instead of the current one:
-
-```rust
-// fetch current deployed version
-let info = updater.download("app", None).await?;
-// fetch an exact version (GET /bundles/{name}/{version})
-let info = updater.download("app", Some("1.1.0".into())).await?;
-```
-
-The platform guides show the idiomatic call for each target:
-[Electron](/docs/guide/platforms/electron), [Tauri](/docs/guide/platforms/tauri),
-[Android](/docs/guide/platforms/android), [iOS](/docs/guide/platforms/ios), and
-[Deno Desktop](/docs/guide/platforms/deno).
-
-## The remote HTTP contract
-
-A Webview Bundle server implements four endpoints. The provider packages (local, AWS, Cloudflare) implement this spec, and you can build your own to match.
-
-| Method and path | Purpose |
-| ------------------------------- | -------------------------------------------------- |
-| `GET /bundles` | List deployed bundles: `[{ "name", "version" }]` |
-| `HEAD /bundles/{name}` | Current version's metadata (headers only, no body) |
-| `GET /bundles/{name}` | Download the current version |
-| `GET /bundles/{name}/{version}` | Download a specific version |
-
-`GET /bundles`, `HEAD /bundles/{name}`, and `GET /bundles/{name}` accept an optional `?channel=` query. `GET /bundles/{name}/{version}` does not. Metadata travels in response headers:
-
-| Header | Required | Meaning |
-| -------------------------- | -------- | ------------------- |
-| `Webview-Bundle-Name` | yes | bundle name |
-| `Webview-Bundle-Version` | yes | version |
-| `Webview-Bundle-Integrity` | no | integrity string |
-| `Webview-Bundle-Signature` | no | signature |
-| `ETag`, `Last-Modified` | no | standard validators |
-
-A download response looks like:
-
-```sh
-$ curl -sI http://localhost:4313/bundles/app
-HTTP/1.1 200 OK
-Content-Type: application/webview-bundle
-Webview-Bundle-Name: app
-Webview-Bundle-Version: 1.2.0
-Webview-Bundle-Integrity: sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-ETag: "abc123"
-```
-
-The list endpoint returns `application/json`:
-
-```json
-[
- { "name": "app", "version": "1.2.0" },
- { "name": "admin", "version": "0.4.1" }
-]
-```
-
-A missing bundle or version returns `404`; requesting a specific non-deployed version returns `403` when the server disallows it. Missing required headers, or any non-2xx response, cause the client to reject the download. The default request timeout is 120s. For a worked server and local testing, see [Building a remote](/docs/guide/remote).
-
-## Integrity
-
-An integrity hash lets a device confirm the downloaded bytes are exactly the bytes you published. Webview Bundle uses **SHA-2**: `sha256` (the default), `sha384`, or `sha512`, serialized as `":"`:
-
-```text
-sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-```
-
-The publisher attaches this string at upload time ([Remote config](/docs/config/remote)), the server returns it in `Webview-Bundle-Integrity`, and the updater enforces it on download per an **integrity policy**:
-
-| Policy | Behavior |
-| ---------- | ------------------------------------------------------------ |
-| `Strict` | integrity value must be present and must match, or reject |
-| `Optional` | **default** — verify if present; allow when none is attached |
-| `None` | skip integrity checking entirely |
-
-```rust
-use wvb::integrity::IntegrityPolicy;
-use wvb::updater::UpdaterConfig;
-
-let config = UpdaterConfig::new()
- .integrity_policy(IntegrityPolicy::Strict);
-```
-
-## Signatures
-
-Where integrity proves the bytes are intact, a signature proves _who_ published them. The signature covers the **integrity string bytes** (`":"`), not the raw bundle — so signature verification requires an integrity value to be present. Supply a public key, and the updater rejects any download whose signature does not verify.
-
-Verify algorithms:
-
-- **ECDSA** with curve P-256 (secp256r1) or P-384 (secp384r1)
-- **Ed25519**
-- **RSA** with PKCS#1 v1.5 or PSS padding, hashing with SHA-256
-
-Public-key formats:
-
-| Format | Loader | Applies to |
-| ---------------- | ------------------------- | ------------ |
-| SPKI PEM | `from_public_key_pem` | all |
-| SPKI DER | `from_public_key_der` | all |
-| PKCS#1 PEM/DER | `from_pkcs1_pem` / `_der` | RSA only |
-| SEC1 point bytes | `from_sec1_bytes` | ECDSA only |
-| Raw 32-byte key | `from_public_key_bytes` | Ed25519 only |
-
-An updater requiring both a matching integrity hash and a valid Ed25519 signature:
-
-```rust
-use wvb::integrity::IntegrityPolicy;
-use wvb::signature::{Ed25519Verifier, SignatureVerifier};
-use wvb::updater::{Updater, UpdaterConfig};
-use std::sync::Arc;
-
-let verifier = SignatureVerifier::Ed25519(Arc::new(
- Ed25519Verifier::from_public_key_pem(PUBLIC_KEY_PEM)?,
-));
-let config = UpdaterConfig::new()
- .integrity_policy(IntegrityPolicy::Strict)
- .signature_verifier(verifier);
-let updater = Updater::new(source, remote, Some(config));
-```
-
-ECDSA P-256 from an SPKI PEM:
-
-```rust
-use wvb::signature::{EcdsaSecp256r1Verifier, SignatureVerifier};
-use std::sync::Arc;
-
-let verifier = SignatureVerifier::EcdsaSecp256r1(Arc::new(
- EcdsaSecp256r1Verifier::from_public_key_pem(PUBLIC_KEY_PEM)?,
-));
-```
-
-The publish-side counterpart — which algorithm signs, and the private-key format — is set in [Remote config](/docs/config/remote).
-
-## Channels
-
-A channel routes different versions to different audiences for staged rollouts. Deploy to a channel, and only clients requesting that channel receive it; clients with no channel get the default deployment.
-
-```sh
-wvb upload --version 1.2.0 --deploy --channel beta # only beta clients see 1.2.0
-wvb deploy --version 1.2.0 # later, promote to the default channel
-```
-
-On the device, the updater carries the channel in its config and forwards it as `?channel=`:
-
-```rust
-let config = UpdaterConfig::new().channel("beta");
-let updater = Updater::new(source, remote, Some(config));
-// get_update / download now request ?channel=beta
-```
-
-A channel is a free-form string; there is no hardcoded default name — when no channel is set, the query parameter is omitted.
-
-## Where to go next
-
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/remote.ko.mdx b/content/docs/guide/remote.ko.mdx
deleted file mode 100644
index 3e356d3..0000000
--- a/content/docs/guide/remote.ko.mdx
+++ /dev/null
@@ -1,235 +0,0 @@
----
-title: 원격 서버 구축하기
-description: 무선(OTA) 번들 전송을 위한 업데이트 서버를 세우고, 전체 흐름을 로컬에서 실행한 뒤 프로바이더를 선택하세요.
----
-
-원격 서버는 작고 고정된 규약에 따라 앱의 업데이터에 번들을 제공하는 HTTP 서버입니다. [원격 번들](/docs/guide/remote-bundles)의 서버 측 짝에 해당합니다. 원격 서버를 만들고, 클라우드 계정 없이 로컬에서 배포와 업데이트 전체 흐름을 실행한 다음, 프로덕션용 프로바이더를 고르세요.
-
-Webview Bundle은 `local`, `aws`, `cloudflare` 세 가지 백엔드용 원격 서버를 제공합니다. 각 백엔드는 역할에 따라 세 개의 패키지로 나뉩니다. 작업에 필요한 것만 사용하세요.
-
-## 패키지 역할
-
-모든 백엔드는 최대 세 개의 패키지를 가집니다.
-
-| 역할 | 패키지 | 하는 일 |
-| --------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
-| 설정 클라이언트 | `@wvb/remote-` | `{ uploader, deployer }`를 만드는 `wvb.config` 플러그인입니다. 퍼블리셔의 머신이나 CI에서 실행되어 번들을 푸시하고 배포합니다. |
-| HTTP 서버 | `@wvb/remote--provider` | HTTP 규약을 구현하고 클라이언트에 번들을 제공하는 서버입니다. |
-| Pulumi IaC | `@wvb/remote--provider-pulumi` | 클라우드 리소스를 프로비저닝하고 프로바이더를 배포하는 코드형 인프라입니다. |
-
-구체적으로, 백엔드별로 정리하면 다음과 같습니다.
-
-| 백엔드 | 설정 클라이언트 | HTTP 서버 | Pulumi IaC |
-| ------------ | ------------------------ | --------------------------------- | ---------------------------------------- |
-| `local` | `@wvb/remote-local` | `@wvb/remote-local-provider` | — |
-| `aws` | `@wvb/remote-aws` | `@wvb/remote-aws-provider` | `@wvb/remote-aws-provider-pulumi` |
-| `cloudflare` | `@wvb/remote-cloudflare` | `@wvb/remote-cloudflare-provider` | `@wvb/remote-cloudflare-provider-pulumi` |
-
-설정 클라이언트는 `wvb.config.ts`의 `remote` 블록에 연결하는 대상입니다. `uploader`는 패킹된 `.wvb`를 스토리지에 쓰고, `deployer`는 클라이언트가 받아야 할 버전을 지정합니다. 그 결과를 `remote`에 펼쳐 넣으세요.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { awsRemote } from '@wvb/remote-aws';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://cdn.example.com',
- bundleName: 'app',
- ...awsRemote({ bucket: 'my-bundles', aws: { region: 'us-east-1' } }),
- },
-});
-```
-
-퍼블리시 측 무결성과 서명 옵션은 `uploader`/`deployer`와 함께 위치합니다. [원격, 무결성 및 서명 설정](/docs/config/remote)을 참고하세요.
-
-## HTTP 규약
-
-모든 프로바이더는 동일한 네 개의 엔드포인트를 구현합니다. 따라서 클라이언트는 뒤에 어떤 백엔드가 있는지 신경 쓰지 않습니다.
-
-| 메서드와 경로 | 목적 | 특이 사항 |
-| ------------------------------- | ------------------------------ | -------------------------------------------------------- |
-| `GET /bundles` | 배포된 번들 목록 조회 | `200` JSON `[{ "name", "version" }]`; 선택적 `?channel=` |
-| `HEAD /bundles/{name}` | 현재 버전의 메타데이터(헤더만) | 배포되지 않았으면 `404` |
-| `GET /bundles/{name}` | 현재 버전 다운로드 | 배포되지 않았으면 `404` |
-| `GET /bundles/{name}/{version}` | 특정 버전 다운로드 | 다른 버전 허용이 아니면 `403` |
-
-필수 응답 헤더는 `Webview-Bundle-Name`과 `Webview-Bundle-Version`입니다. 선택 헤더는 `Webview-Bundle-Integrity`, `Webview-Bundle-Signature`, `ETag`, `Last-Modified`입니다. 다운로드는 `Content-Type: application/webview-bundle`을 사용합니다. 상태 코드와 채널 쿼리 전체 명세는 [원격 번들](/docs/guide/remote-bundles)에 있습니다.
-
-이 네 개의 라우트에 응답하는 최소한의 핸들러도 유효한 원격 서버입니다. 프로바이더 패키지는 필요하지 않습니다.
-
-```ts title="minimal-remote.ts"
-import { Hono } from 'hono';
-
-const app = new Hono();
-
-app.get('/bundles', c => c.json([{ name: 'app', version: '1.1.0' }]));
-
-app.get('/bundles/:name', c => {
- const body = readCurrentBundle(c.req.param('name')); // 여러분의 스토리지
- return new Response(body, {
- headers: {
- 'Content-Type': 'application/webview-bundle',
- 'Webview-Bundle-Name': 'app',
- 'Webview-Bundle-Version': '1.1.0',
- },
- });
-});
-
-app.get(
- '/bundles/:name/:version',
- c => c.body(null, 403) // 기본적으로 특정 버전은 금지
-);
-
-export default app;
-```
-
-## 로컬에서 흐름 테스트하기
-
-업데이트 전체 흐름을 여러분의 머신에서 실행하세요. `@wvb/remote-local`은 업로드를 로컬 디렉터리(기본값 `~/.wvb/local`)에 쓰고, `wvb remote local`은 프로덕션 서버가 구현하는 것과 동일한 규약으로 그 디렉터리를 제공합니다.
-
-### 설정을 로컬 프로바이더로 지정하기
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { localRemote } from '@wvb/remote-local';
-
-export default defineConfig({
- pack: {
- outFile: 'app', // → app.wvb
- },
- remote: {
- endpoint: 'http://localhost:4313',
- bundleName: 'app',
- ...localRemote(), // ~/.wvb/local에 쓰는 { uploader, deployer }
- },
-});
-```
-
-홈 디렉터리 대신 프로젝트 안에 스토어를 두려면 다음과 같이 하세요.
-
-```ts
-...localRemote({ baseDir: './.wvb/local' }),
-```
-
-로컬 스토어는 번들과 버전별로 파일을 배치합니다.
-
-```text
-~/.wvb/local/bundles/app/app_1.1.0.wvb # 번들 바이너리
-~/.wvb/local/bundles/app/app_1.1.0.json # { integrity?, signature? }
-~/.wvb/local/bundles/app/deployment.json # 배포 포인터
-```
-
-### 버전 퍼블리시하기
-
-```sh
-wvb pack # 설정으로부터 .wvb 빌드
-wvb upload app --version 1.1.0 --deploy # app_1.1.0.wvb를 ~/.wvb/local에 쓰고 배포
-```
-
-`wvb upload`는 기본적으로 업로드 전에 패킹하므로, 명시적인 `wvb pack`은 선택 사항입니다. `--deploy`의 기본값은 `false`입니다. 이 플래그를 전달하거나, 배포를 따로 실행하세요.
-
-```sh
-wvb deploy app --version 1.1.0 # 1.1.0을 클라이언트가 받는 버전으로 지정
-```
-
-버전은 위치 인자가 아니라 `--version,-V` 플래그입니다. 모든 플래그는 [CLI 레퍼런스](/docs/guide/cli)를 참고하세요.
-
-### 제공하기
-
-```sh
-wvb remote local # ~/.wvb/local을 http://localhost:4313에서 제공
-wvb remote local --base-dir ./.wvb/local --port 4313 --allow-other-versions
-```
-
-`wvb remote local`은 기본적으로 포트 `4313`(베이스 디렉터리 `~/.wvb/local`)에서 대기합니다. `--allow-other-versions`를 지정하면 클라이언트가 배포되지 않은 버전을 `403` 대신 `GET /bundles/{name}/{version}`로 가져올 수 있습니다.
-
-### 앱을 원격 서버로 지정하기
-
-앱의 업데이터 엔드포인트를 `http://localhost:4313`으로 설정하세요. 이제 `getUpdate`, `download`, `install`이 로컬 서버에 요청하며, 프로덕션과 똑같이 번들을 다운로드하고 검증하고 활성화합니다.
-
-```rust
-let remote = Remote::builder()
- .endpoint("http://localhost:4313")
- .build()?;
-let updater = Updater::new(source, Arc::new(remote), None);
-
-let info = updater.get_update("app").await?; // HEAD /bundles/app
-if info.is_available {
- updater.download("app", None).await?; // GET /bundles/app
- updater.install("app", &info.version).await?; // 다운로드한 버전 활성화
-}
-```
-
-
- Android 에뮬레이터에서는 호스트 머신이 `localhost`가 아닙니다. 업데이터를
- `http://10.0.2.2:4313`으로 지정하세요.
-
-
-### 클라이언트 명령어로 확인하기
-
-CLI는 어떤 원격 서버에 대해서도 클라이언트로 동작할 수 있습니다. 앱을 지정하기 전에 서버가 제대로 동작하는지 확인할 때 유용합니다.
-
-```sh
-wvb remote list --endpoint http://localhost:4313 # GET /bundles (별칭: remote ls)
-wvb remote current app --endpoint http://localhost:4313 # HEAD /bundles/app
-wvb remote download app --endpoint http://localhost:4313 --outfile ./app.wvb # GET /bundles/app
-```
-
-### 단일 번들 미리 보기
-
-서버나 배포 없이 `.wvb`를 살펴보려면, 그 파일들을 직접 제공하세요.
-
-```sh
-wvb serve ./app.wvb # 번들의 파일들을 http://localhost:4312에서 제공
-wvb serve ./app.wvb --port 8080
-```
-
-`wvb serve`는 기본적으로 포트 `4312`에서 대기합니다. 업로드하기 전에 패킹이 기대한 파일과 헤더를 만들었는지 확인할 때 사용하세요.
-
-## 프로바이더 선택하기
-
-로컬 테스트를 넘어서면 `localRemote()`를 클라우드 백엔드로 바꾸세요. 각 프로바이더는 `wvb.config.ts`용으로 호환되는 `uploader`와 `deployer`, 위 규약을 구현하는 서버, 그리고 이를 프로비저닝하는 Pulumi 패키지를 제공합니다.
-
-```ts title="wvb.config.ts — Cloudflare"
-import { cloudflareRemote } from '@wvb/remote-cloudflare';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://bundles.example.com',
- bundleName: 'app',
- ...cloudflareRemote({
- accountId: process.env.CF_ACCOUNT_ID!,
- bucket: 'webview-bundle',
- kvNamespaceId: process.env.CF_KV_NAMESPACE_ID!,
- }),
- },
-});
-```
-
-Pulumi로 그에 맞는 인프라를 프로비저닝하세요.
-
-```ts title="index.ts — pulumi up"
-import { WebviewBundleRemoteProvider } from '@wvb/remote-cloudflare-provider-pulumi';
-
-export const remote = new WebviewBundleRemoteProvider('app', {
- accountId: process.env.CF_ACCOUNT_ID!,
-});
-```
-
-
-
-
-
-
diff --git a/content/docs/guide/remote.mdx b/content/docs/guide/remote.mdx
deleted file mode 100644
index 64cbcc5..0000000
--- a/content/docs/guide/remote.mdx
+++ /dev/null
@@ -1,235 +0,0 @@
----
-title: Building a remote
-description: Stand up an update server for over-the-air bundle delivery, run the whole loop locally, and choose a provider.
----
-
-A remote is any HTTP server that serves bundles to your app's updater over a small, fixed contract. This is the server-side companion to [Remote bundles](/docs/guide/remote-bundles): build a remote, run the full publish-and-update loop locally with no cloud account, then pick a provider for production.
-
-Webview Bundle ships remotes for three backends — `local`, `aws`, and `cloudflare` — each split into three packages by role. Use only what the job needs.
-
-## Package roles
-
-Every backend has up to three packages:
-
-| Role | Package | What it does |
-| ------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
-| Config client | `@wvb/remote-` | A `wvb.config` plug-in that produces `{ uploader, deployer }`. Runs on the publisher's machine or CI to push and deploy bundles. |
-| HTTP server | `@wvb/remote--provider` | The server that implements the HTTP contract and serves bundles to clients. |
-| Pulumi IaC | `@wvb/remote--provider-pulumi` | Infrastructure-as-code that provisions the cloud resources and deploys the provider. |
-
-Concretely, per backend:
-
-| Backend | Config client | HTTP server | Pulumi IaC |
-| ------------ | ------------------------ | --------------------------------- | ---------------------------------------- |
-| `local` | `@wvb/remote-local` | `@wvb/remote-local-provider` | — |
-| `aws` | `@wvb/remote-aws` | `@wvb/remote-aws-provider` | `@wvb/remote-aws-provider-pulumi` |
-| `cloudflare` | `@wvb/remote-cloudflare` | `@wvb/remote-cloudflare-provider` | `@wvb/remote-cloudflare-provider-pulumi` |
-
-The config client is what you wire into the `remote` block of `wvb.config.ts`. The `uploader` writes a packed `.wvb` to storage; the `deployer` marks a version as the one clients should receive. Spread its result into `remote`:
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { awsRemote } from '@wvb/remote-aws';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://cdn.example.com',
- bundleName: 'app',
- ...awsRemote({ bucket: 'my-bundles', aws: { region: 'us-east-1' } }),
- },
-});
-```
-
-Publish-side integrity and signature options live alongside `uploader`/`deployer` — see [Remote, integrity & signature config](/docs/config/remote).
-
-## The HTTP contract
-
-Every provider implements the same four endpoints, so the client never cares which backend is behind it.
-
-| Method and path | Purpose | Notable |
-| ------------------------------- | ----------------------------------------- | ---------------------------------------------------------- |
-| `GET /bundles` | List deployed bundles | `200` JSON `[{ "name", "version" }]`; optional `?channel=` |
-| `HEAD /bundles/{name}` | Current version's metadata (headers only) | `404` if undeployed |
-| `GET /bundles/{name}` | Download the current version | `404` if undeployed |
-| `GET /bundles/{name}/{version}` | Download a specific version | `403` unless other-versions allowed |
-
-Required response headers are `Webview-Bundle-Name` and `Webview-Bundle-Version`. Optional: `Webview-Bundle-Integrity`, `Webview-Bundle-Signature`, `ETag`, `Last-Modified`. Downloads use `Content-Type: application/webview-bundle`. The full status-code and channel-query spec is in [Remote bundles](/docs/guide/remote-bundles).
-
-A minimal handler that answers these four routes is a valid remote — no provider package required:
-
-```ts title="minimal-remote.ts"
-import { Hono } from 'hono';
-
-const app = new Hono();
-
-app.get('/bundles', c => c.json([{ name: 'app', version: '1.1.0' }]));
-
-app.get('/bundles/:name', c => {
- const body = readCurrentBundle(c.req.param('name')); // your storage
- return new Response(body, {
- headers: {
- 'Content-Type': 'application/webview-bundle',
- 'Webview-Bundle-Name': 'app',
- 'Webview-Bundle-Version': '1.1.0',
- },
- });
-});
-
-app.get(
- '/bundles/:name/:version',
- c => c.body(null, 403) // forbid specific versions by default
-);
-
-export default app;
-```
-
-## Test the loop locally
-
-Run the entire update loop on your machine. `@wvb/remote-local` writes uploads to a local directory (default `~/.wvb/local`), and `wvb remote local` serves that directory over the same contract a production server implements.
-
-### Point your config at the local provider
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-import { localRemote } from '@wvb/remote-local';
-
-export default defineConfig({
- pack: {
- outFile: 'app', // → app.wvb
- },
- remote: {
- endpoint: 'http://localhost:4313',
- bundleName: 'app',
- ...localRemote(), // { uploader, deployer } writing to ~/.wvb/local
- },
-});
-```
-
-Keep the store inside your project instead of the home directory:
-
-```ts
-...localRemote({ baseDir: './.wvb/local' }),
-```
-
-The local store lays out files per bundle and version:
-
-```text
-~/.wvb/local/bundles/app/app_1.1.0.wvb # bundle binary
-~/.wvb/local/bundles/app/app_1.1.0.json # { integrity?, signature? }
-~/.wvb/local/bundles/app/deployment.json # deployment pointer
-```
-
-### Publish a version
-
-```sh
-wvb pack # build the .wvb from your config
-wvb upload app --version 1.1.0 --deploy # write app_1.1.0.wvb into ~/.wvb/local and deploy it
-```
-
-`wvb upload` packs before uploading by default, so the explicit `wvb pack` is optional. `--deploy` defaults to `false`; pass it, or deploy separately:
-
-```sh
-wvb deploy app --version 1.1.0 # make 1.1.0 the version clients receive
-```
-
-The version is the `--version,-V` flag, not a positional. See the [CLI reference](/docs/guide/cli) for every flag.
-
-### Serve it
-
-```sh
-wvb remote local # serves ~/.wvb/local on http://localhost:4313
-wvb remote local --base-dir ./.wvb/local --port 4313 --allow-other-versions
-```
-
-`wvb remote local` listens on port `4313` by default (base dir `~/.wvb/local`). `--allow-other-versions` lets clients fetch a non-deployed version through `GET /bundles/{name}/{version}` instead of getting a `403`.
-
-### Point your app at it
-
-Set the app's updater endpoint to `http://localhost:4313`. Now `getUpdate`, `download`, and `install` hit your local server and download, verify, and activate the bundle exactly as production would:
-
-```rust
-let remote = Remote::builder()
- .endpoint("http://localhost:4313")
- .build()?;
-let updater = Updater::new(source, Arc::new(remote), None);
-
-let info = updater.get_update("app").await?; // HEAD /bundles/app
-if info.is_available {
- updater.download("app", None).await?; // GET /bundles/app
- updater.install("app", &info.version).await?; // activate the downloaded version
-}
-```
-
-
- On the Android emulator, the host machine is not `localhost`. Point the updater at
- `http://10.0.2.2:4313` instead.
-
-
-### Inspect with the client commands
-
-The CLI can act as a client against any remote — handy for confirming a server behaves before pointing an app at it.
-
-```sh
-wvb remote list --endpoint http://localhost:4313 # GET /bundles (alias: remote ls)
-wvb remote current app --endpoint http://localhost:4313 # HEAD /bundles/app
-wvb remote download app --endpoint http://localhost:4313 --outfile ./app.wvb # GET /bundles/app
-```
-
-### Preview a single bundle
-
-To inspect a `.wvb` without a server or deployment, serve its files directly:
-
-```sh
-wvb serve ./app.wvb # serves the bundle's files on http://localhost:4312
-wvb serve ./app.wvb --port 8080
-```
-
-`wvb serve` listens on port `4312` by default. Use it to confirm a pack produced the files and headers you expect before you upload.
-
-## Choose a provider
-
-When you move past local testing, swap `localRemote()` for a cloud backend. Each provider exposes a compatible `uploader` and `deployer` for `wvb.config.ts`, a server that implements the contract above, and a Pulumi package to provision it.
-
-```ts title="wvb.config.ts — Cloudflare"
-import { cloudflareRemote } from '@wvb/remote-cloudflare';
-
-export default defineConfig({
- remote: {
- endpoint: 'https://bundles.example.com',
- bundleName: 'app',
- ...cloudflareRemote({
- accountId: process.env.CF_ACCOUNT_ID!,
- bucket: 'webview-bundle',
- kvNamespaceId: process.env.CF_KV_NAMESPACE_ID!,
- }),
- },
-});
-```
-
-Provision the matching infrastructure with Pulumi:
-
-```ts title="index.ts — pulumi up"
-import { WebviewBundleRemoteProvider } from '@wvb/remote-cloudflare-provider-pulumi';
-
-export const remote = new WebviewBundleRemoteProvider('app', {
- accountId: process.env.CF_ACCOUNT_ID!,
-});
-```
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/get-current.ko.mdx b/content/docs/guide/remote/http-spec/endpoints/get-current.ko.mdx
similarity index 73%
rename from content/docs/guide/spec/get-current.ko.mdx
rename to content/docs/guide/remote/http-spec/endpoints/get-current.ko.mdx
index 1280941..3bc33c2 100644
--- a/content/docs/guide/spec/get-current.ko.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/get-current.ko.mdx
@@ -3,7 +3,7 @@ title: 현재 번들 다운로드
description: 배포된 번들의 현재 버전을 원본 .wvb 바이트로 다운로드합니다. 채널을 선택할 수도 있습니다.
---
-배포된 번들의 현재 버전을 다운로드합니다. 서버는 `.wvb` 아카이브를 응답 본문으로 스트리밍하고, 번들의 메타데이터를 `Webview-Bundle-*` 헤더에 담아 전달합니다. [업데이터](/docs/references/node/updater)는 검사 결과 새 버전이 있다고 확인되면 이 호출로 새 버전을 가져옵니다.
+배포된 번들의 현재 버전을 다운로드합니다. 서버는 `.wvb` 아카이브를 응답 본문으로 스트리밍하고, 번들의 메타데이터를 `Webview-Bundle-*` 헤더에 담아 전달합니다. [업데이터](/docs/references/api/node/updater)는 검사 결과 새 버전이 있다고 확인되면 이 호출로 새 버전을 가져옵니다.
```http
GET /bundles/{name}
@@ -25,11 +25,11 @@ GET /bundles/{name}
| `200` | 번들을 찾았습니다. 본문은 `.wvb` 아카이브입니다. |
| `404` | 해당 이름의 번들이 배포되지 않았습니다. |
-`404` 형태와 클라이언트가 그 밖의 non-2xx 응답을 매핑하는 방식은 [오류](/docs/guide/spec/errors)를 참고하세요.
+`404` 형태와 클라이언트가 그 밖의 non-2xx 응답을 매핑하는 방식은 [오류](/docs/guide/remote/http-spec/errors)를 참고하세요.
### 응답 헤더
-`200` 응답은 [HEAD /bundles/{name}](/docs/guide/spec/head-current)과 동일한 번들 메타데이터 헤더를 담습니다. 클라이언트는 헤더를 대소문자 구분 없이 읽습니다. 여기서는 표준 표기를 보여줍니다.
+`200` 응답은 [HEAD /bundles/{name}](/docs/guide/remote/http-spec/endpoints/head-current)과 동일한 번들 메타데이터 헤더를 담습니다. 클라이언트는 헤더를 대소문자 구분 없이 읽습니다. 여기서는 표준 표기를 보여줍니다.
| Header | Required | Description |
| -------------------------- | -------- | --------------------------------- |
@@ -77,23 +77,3 @@ curl -sI http://localhost:4313/bundles/unknown
```http
HTTP/1.1 404 Not Found
```
-
-## 관련 항목
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/get-current.mdx b/content/docs/guide/remote/http-spec/endpoints/get-current.mdx
similarity index 78%
rename from content/docs/guide/spec/get-current.mdx
rename to content/docs/guide/remote/http-spec/endpoints/get-current.mdx
index e182f6f..50739e4 100644
--- a/content/docs/guide/spec/get-current.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/get-current.mdx
@@ -3,7 +3,7 @@ title: Download current bundle
description: Download the current deployed version of a bundle as raw .wvb bytes, optionally selecting a channel.
---
-Download the current deployed version of a bundle. The server streams the `.wvb` archive as the response body and carries the bundle's metadata in `Webview-Bundle-*` headers. This is the call the [updater](/docs/references/node/updater) makes to fetch a new version after a check reports one is available.
+Download the current deployed version of a bundle. The server streams the `.wvb` archive as the response body and carries the bundle's metadata in `Webview-Bundle-*` headers. This is the call the [updater](/docs/references/api/node/updater) makes to fetch a new version after a check reports one is available.
```http
GET /bundles/{name}
@@ -25,11 +25,11 @@ Pass `?channel=` to download the current version for a specific channel instead
| `200` | The bundle was found; the body is the `.wvb` archive. |
| `404` | The named bundle is not deployed. |
-For the `404` shape and how the client maps other non-2xx responses, see [Errors](/docs/guide/spec/errors).
+For the `404` shape and how the client maps other non-2xx responses, see [Errors](/docs/guide/remote/http-spec/errors).
### Response headers
-A `200` response carries the same bundle metadata headers as [HEAD /bundles/{name}](/docs/guide/spec/head-current). The client reads them case-insensitively; the canonical casing is shown here.
+A `200` response carries the same bundle metadata headers as [HEAD /bundles/{name}](/docs/guide/remote/http-spec/endpoints/head-current). The client reads them case-insensitively; the canonical casing is shown here.
| Header | Required | Description |
| -------------------------- | -------- | ------------------------------------ |
@@ -77,23 +77,3 @@ curl -sI http://localhost:4313/bundles/unknown
```http
HTTP/1.1 404 Not Found
```
-
-## Related
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/get-version.ko.mdx b/content/docs/guide/remote/http-spec/endpoints/get-version.ko.mdx
similarity index 80%
rename from content/docs/guide/spec/get-version.ko.mdx
rename to content/docs/guide/remote/http-spec/endpoints/get-version.ko.mdx
index 6a8e904..5558e6f 100644
--- a/content/docs/guide/spec/get-version.ko.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/get-version.ko.mdx
@@ -28,7 +28,7 @@ GET /bundles/{name}/{version}
| `403` | 서버가 다른 버전 다운로드를 비활성화한 경우. |
| `404` | 이름이 지정된 번들 또는 버전이 배포되지 않은 경우. |
-클라이언트는 `403`을 "forbidden" 오류로, `404`를 "bundle not found" 오류로 매핑합니다. 전체 매핑과 JSON 오류 본문 형태는 [오류](/docs/guide/spec/errors)를 참고하세요.
+클라이언트는 `403`을 "forbidden" 오류로, `404`를 "bundle not found" 오류로 매핑합니다. 전체 매핑과 JSON 오류 본문 형태는 [오류](/docs/guide/remote/http-spec/errors)를 참고하세요.
### 응답 헤더
@@ -47,7 +47,7 @@ GET /bundles/{name}/{version}
이 경로는 채널을 선택하지 않습니다. 채널 지정 여부와 관계없이 현재 버전을 다운로드하려면 [GET
- /bundles/{name}](/docs/guide/spec/get-current)을 사용하세요.
+ /bundles/{name}](/docs/guide/remote/http-spec/endpoints/get-current)을 사용하세요.
## 예제
@@ -79,28 +79,3 @@ HTTP/1.1 403 Forbidden
$ curl -sI http://localhost:4313/bundles/app/9.9.9
HTTP/1.1 404 Not Found
```
-
-## 관련 문서
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/get-version.mdx b/content/docs/guide/remote/http-spec/endpoints/get-version.mdx
similarity index 79%
rename from content/docs/guide/spec/get-version.mdx
rename to content/docs/guide/remote/http-spec/endpoints/get-version.mdx
index 86e0fb3..3f14f36 100644
--- a/content/docs/guide/spec/get-version.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/get-version.mdx
@@ -28,7 +28,7 @@ This route does not accept a `channel` query parameter.
| `403` | The server disables other-version downloads. |
| `404` | The named bundle or version is not deployed. |
-The client maps `403` to a "forbidden" error and `404` to a "bundle not found" error. See [Errors](/docs/guide/spec/errors) for the full mapping and the JSON error body shape.
+The client maps `403` to a "forbidden" error and `404` to a "bundle not found" error. See [Errors](/docs/guide/remote/http-spec/errors) for the full mapping and the JSON error body shape.
### Response headers
@@ -47,7 +47,7 @@ The `200` body is the raw `.wvb` bytes, served with `Content-Type: application/w
This route never selects a channel. To download the current version, with or without a channel,
- use [GET /bundles/{name}](/docs/guide/spec/get-current).
+ use [GET /bundles/{name}](/docs/guide/remote/http-spec/endpoints/get-current).
## Example
@@ -79,28 +79,3 @@ A version that is not deployed returns `404`:
$ curl -sI http://localhost:4313/bundles/app/9.9.9
HTTP/1.1 404 Not Found
```
-
-## Related
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/head-current.ko.mdx b/content/docs/guide/remote/http-spec/endpoints/head-current.ko.mdx
similarity index 80%
rename from content/docs/guide/spec/head-current.ko.mdx
rename to content/docs/guide/remote/http-spec/endpoints/head-current.ko.mdx
index 5133a60..0e937c8 100644
--- a/content/docs/guide/spec/head-current.ko.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/head-current.ko.mdx
@@ -23,7 +23,7 @@ HEAD /bundles/{name}
| `204` | 번들이 배포되어 있습니다. 메타데이터가 헤더로 반환됩니다. |
| `404` | 지정한 번들이 배포되어 있지 않습니다. |
-성공은 `204 No Content`입니다. 응답은 메타데이터 헤더만 담으며 본문은 절대 포함하지 않습니다. `404`는 지정한 번들에 현재 배포된 버전이 없다는 뜻입니다. 오류 본문 형태와 다른 상태 코드는 [오류](/docs/guide/spec/errors)를 참고하세요.
+성공은 `204 No Content`입니다. 응답은 메타데이터 헤더만 담으며 본문은 절대 포함하지 않습니다. `404`는 지정한 번들에 현재 배포된 버전이 없다는 뜻입니다. 오류 본문 형태와 다른 상태 코드는 [오류](/docs/guide/remote/http-spec/errors)를 참고하세요.
### 응답 헤더
@@ -40,7 +40,7 @@ HEAD /bundles/{name}
이 경로는 메타데이터만 반환합니다. 번들 바이트를 가져오려면 [GET /bundles/{name}
- ](/docs/guide/spec/get-current)을 사용하세요.
+ ](/docs/guide/remote/http-spec/endpoints/get-current)을 사용하세요.
## 예시
@@ -63,28 +63,3 @@ Last-Modified: Tue, 01 Jul 2026 00:00:00 GMT
```sh
$ curl -sI 'http://localhost:4313/bundles/app?channel=beta'
```
-
-## 관련 문서
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/head-current.mdx b/content/docs/guide/remote/http-spec/endpoints/head-current.mdx
similarity index 79%
rename from content/docs/guide/spec/head-current.mdx
rename to content/docs/guide/remote/http-spec/endpoints/head-current.mdx
index 9a2b63d..2ed04fb 100644
--- a/content/docs/guide/spec/head-current.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/head-current.mdx
@@ -23,7 +23,7 @@ HEAD /bundles/{name}
| `204` | The bundle is deployed. Metadata is returned in the headers. |
| `404` | The named bundle is not deployed. |
-Success is `204 No Content`: the response carries metadata headers only and never a body. A `404` means the named bundle has no current deployment. See [Errors](/docs/guide/spec/errors) for the error body shape and other status codes.
+Success is `204 No Content`: the response carries metadata headers only and never a body. A `404` means the named bundle has no current deployment. See [Errors](/docs/guide/remote/http-spec/errors) for the error body shape and other status codes.
### Response headers
@@ -40,7 +40,7 @@ The client reads these headers case-insensitively and rejects a response that om
This route returns metadata only. To fetch the bundle bytes, use [GET /bundles/{name}
- ](/docs/guide/spec/get-current).
+ ](/docs/guide/remote/http-spec/endpoints/get-current).
## Example
@@ -63,28 +63,3 @@ Read a specific channel's current version with `?channel=`:
```sh
$ curl -sI 'http://localhost:4313/bundles/app?channel=beta'
```
-
-## Related
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/list-bundles.ko.mdx b/content/docs/guide/remote/http-spec/endpoints/list-bundles.ko.mdx
similarity index 73%
rename from content/docs/guide/spec/list-bundles.ko.mdx
rename to content/docs/guide/remote/http-spec/endpoints/list-bundles.ko.mdx
index 153a9e6..c73f7f0 100644
--- a/content/docs/guide/spec/list-bundles.ko.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/list-bundles.ko.mdx
@@ -21,7 +21,7 @@ GET /bundles
| ------ | ----------------------------------- |
| `200` | 현재 배포된 번들의 JSON 배열입니다. |
-4xx 응답은 [오류](/docs/guide/spec/errors)를 참고하세요.
+4xx 응답은 [오류](/docs/guide/remote/http-spec/errors)를 참고하세요.
### 응답 헤더
@@ -68,30 +68,5 @@ curl -s 'http://localhost:4313/bundles?channel=beta'
이 작업은 원격 HTTP 계약의 진입점입니다. 번들의 현재 메타데이터를 읽거나 바이트를 다운로드하려면
- [원격 HTTP 명세](/docs/guide/spec) 개요를 참고하세요.
+ [원격 HTTP 명세](/docs/guide/remote/http-spec) 개요를 참고하세요.
-
-## 다음으로 볼 내용
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/list-bundles.mdx b/content/docs/guide/remote/http-spec/endpoints/list-bundles.mdx
similarity index 75%
rename from content/docs/guide/spec/list-bundles.mdx
rename to content/docs/guide/remote/http-spec/endpoints/list-bundles.mdx
index c3fc6fc..f5b9a3e 100644
--- a/content/docs/guide/spec/list-bundles.mdx
+++ b/content/docs/guide/remote/http-spec/endpoints/list-bundles.mdx
@@ -21,7 +21,7 @@ GET /bundles
| ------ | ----------------------------------------------- |
| `200` | A JSON array of the currently deployed bundles. |
-For 4xx responses, see [Errors](/docs/guide/spec/errors).
+For 4xx responses, see [Errors](/docs/guide/remote/http-spec/errors).
### Response headers
@@ -68,30 +68,5 @@ curl -s 'http://localhost:4313/bundles?channel=beta'
This operation is the entry point of the remote HTTP contract. To read a bundle's current metadata
- or download its bytes, see the [Remote HTTP Spec](/docs/guide/spec) overview.
+ or download its bytes, see the [Remote HTTP Spec](/docs/guide/remote/http-spec) overview.
-
-## Where to go next
-
-
-
-
-
-
-
diff --git a/content/docs/guide/remote/http-spec/endpoints/meta.json b/content/docs/guide/remote/http-spec/endpoints/meta.json
new file mode 100644
index 0000000..52349f4
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/endpoints/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Endpoints",
+ "pages": ["list-bundles", "head-current", "get-current", "get-version"]
+}
diff --git a/content/docs/guide/remote/http-spec/endpoints/meta.ko.json b/content/docs/guide/remote/http-spec/endpoints/meta.ko.json
new file mode 100644
index 0000000..d5f17d6
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/endpoints/meta.ko.json
@@ -0,0 +1,4 @@
+{
+ "title": "엔드포인트",
+ "pages": ["list-bundles", "head-current", "get-current", "get-version"]
+}
diff --git a/content/docs/guide/remote/http-spec/errors.ko.mdx b/content/docs/guide/remote/http-spec/errors.ko.mdx
new file mode 100644
index 0000000..f15cc78
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/errors.ko.mdx
@@ -0,0 +1,31 @@
+---
+title: 오류
+description: 리모트가 non-2xx 응답으로 반환하는 상태 코드와, 클라이언트가 이를 매핑하는 방식입니다.
+---
+
+리모트는 실패를 HTTP 상태 코드로 알립니다. 클라이언트는 그중 두 가지를 특별히 처리하고, 나머지는 일반 HTTP 오류로 취급합니다.
+
+| Status | Meaning | Client error message |
+| --------------- | ----------------------------------------------------------- | -------------------------------------- |
+| `403` | 다른 버전 다운로드가 비활성화됨(`allowOtherVersions` 꺼짐). | `remote forbidden` |
+| `404` | 요청한 번들 또는 버전이 배포되지 않음. | `remote bundle not found` |
+| 그 밖의 non-2xx | 그 외의 실패. | `remote http error with status ` |
+
+`403`과 `404`에서는 클라이언트가 응답 본문을 읽지 않습니다. 그 밖의 non-2xx 상태에서는 아래 형태의 JSON 본문을 읽어 `message`을 오류에 노출합니다.
+
+```json
+{ "message": "요청이 실패한 이유" }
+```
+
+
+ 이 오류들은 일반 JavaScript `Error`로 전달됩니다. 메시지는 위 텍스트와 같으며, 별도의 오류
+ 클래스나 상태 필드는 없습니다. `403`과 `404`은 메시지 문자열로 구분하세요.
+
+
+## 잘못된 응답
+
+필수 메타데이터가 빠지면 `2xx` 응답이라도 거부됩니다. 번들 다운로드에서 `Webview-Bundle-Name` 또는 `Webview-Bundle-Version`이 없으면, 클라이언트는 설치하지 않고 `invalid remote bundle`로 실패합니다.
+
+## 업로드 충돌
+
+게시는 위의 다운로드 규약과 별개입니다. 이미 존재하는 버전을 업로드하면 `BundleAlreadyUploadedError`을 던집니다. 덮어쓰려면 `--force`(또는 `force: true`)을 전달하세요. [리모트에 업로드](/docs/guide/frontend/upload)를 참고하세요.
diff --git a/content/docs/guide/remote/http-spec/errors.mdx b/content/docs/guide/remote/http-spec/errors.mdx
new file mode 100644
index 0000000..3948661
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/errors.mdx
@@ -0,0 +1,31 @@
+---
+title: Errors
+description: The status codes a remote returns for non-2xx responses and how the client maps them.
+---
+
+The remote signals failures with HTTP status codes. The client handles two of them specially and treats the rest as generic HTTP errors.
+
+| Status | Meaning | Client error message |
+| ------------- | ------------------------------------------------------------------- | -------------------------------------- |
+| `403` | Other-version downloads are disabled (`allowOtherVersions` is off). | `remote forbidden` |
+| `404` | The requested bundle or version is not deployed. | `remote bundle not found` |
+| other non-2xx | Any other failure. | `remote http error with status ` |
+
+For `403` and `404` the client does not read the response body. For any other non-2xx status it reads a JSON body of the shape below and surfaces `message` on the error.
+
+```json
+{ "message": "why the request failed" }
+```
+
+
+ These surface as ordinary JavaScript `Error`s — the message equals the text above, with no typed
+ error class or status field. Tell `403` from `404` by the message string.
+
+
+## Invalid responses
+
+A `2xx` response is still rejected when required metadata is missing. If `Webview-Bundle-Name` or `Webview-Bundle-Version` is absent from a bundle download, the client fails with `invalid remote bundle` rather than installing it.
+
+## Upload conflicts
+
+Publishing is separate from the download contract above. Uploading a version that already exists throws `BundleAlreadyUploadedError`; pass `--force` (or `force: true`) to overwrite it. See [Upload to remote](/docs/guide/frontend/upload).
diff --git a/content/docs/guide/remote/http-spec/index.ko.mdx b/content/docs/guide/remote/http-spec/index.ko.mdx
new file mode 100644
index 0000000..b3a3abc
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/index.ko.mdx
@@ -0,0 +1,46 @@
+---
+title: 요약
+---
+
+이 HTTP 규약을 구현한 서버라면 무엇이든 리모트로 동작할 수 있습니다. [프로바이더](/docs/guide/remote/providers/local)는 이 규약을 대신 구현하므로, 직접 서버를 구축할 때만 읽으면 됩니다.
+
+클라이언트는 세 단계로 번들을 읽습니다.
+
+1. [`GET /bundles`](/docs/guide/remote/http-spec/endpoints/list-bundles) — 배포된 번들을 나열합니다.
+2. [`HEAD /bundles/{name}`](/docs/guide/remote/http-spec/endpoints/head-current) — 현재 버전의 메타데이터를 읽어 업데이트를 확인합니다.
+3. [`GET /bundles/{name}`](/docs/guide/remote/http-spec/endpoints/get-current) — 현재 버전의 바이트를 다운로드합니다. [`GET /bundles/{name}/{version}`](/docs/guide/remote/http-spec/endpoints/get-version)은 특정 버전을 다운로드합니다.
+
+## 메타데이터 헤더
+
+번들 응답은 메타데이터를 본문이 아니라 `Webview-Bundle-*` 헤더에 담습니다. `Webview-Bundle-Name`과 `Webview-Bundle-Version`은 필수이며, 둘 중 하나라도 빠지면 클라이언트가 응답을 거부합니다. `Webview-Bundle-Integrity`과 `Webview-Bundle-Signature`은 선택입니다. 번들 바이트는 `application/webview-bundle`로 제공됩니다.
+
+## 채널
+
+채널은 별도의 배포 트랙을 선택합니다. 목록, 메타데이터, 현재 버전 다운로드 경로는 선택적인 `?channel=` 쿼리를 받으며, 생략하면 기본 배포를 대상으로 합니다. 특정 버전 경로는 채널을 받지 않습니다.
+
+## 엔드포인트
+
+
+
+
+
+
+
+
+non-2xx 응답은 [오류](/docs/guide/remote/http-spec/errors)를 참고하세요.
diff --git a/content/docs/guide/remote/http-spec/index.mdx b/content/docs/guide/remote/http-spec/index.mdx
new file mode 100644
index 0000000..aa0d769
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/index.mdx
@@ -0,0 +1,46 @@
+---
+title: Overview
+---
+
+Any server that implements this HTTP contract can act as a remote. The [providers](/docs/guide/remote/providers/local) implement it for you; read this only when you build your own.
+
+A client reads bundles in three steps:
+
+1. [`GET /bundles`](/docs/guide/remote/http-spec/endpoints/list-bundles) — list the deployed bundles.
+2. [`HEAD /bundles/{name}`](/docs/guide/remote/http-spec/endpoints/head-current) — read the current version's metadata to check for an update.
+3. [`GET /bundles/{name}`](/docs/guide/remote/http-spec/endpoints/get-current) — download the current version's bytes. [`GET /bundles/{name}/{version}`](/docs/guide/remote/http-spec/endpoints/get-version) downloads an exact version.
+
+## Metadata headers
+
+Bundle responses carry metadata in `Webview-Bundle-*` headers, not the body. `Webview-Bundle-Name` and `Webview-Bundle-Version` are required — the client rejects a response missing either. `Webview-Bundle-Integrity` and `Webview-Bundle-Signature` are optional. Bundle bytes are served as `application/webview-bundle`.
+
+## Channels
+
+A channel selects a separate deployment track. The list, metadata, and current-download routes accept an optional `?channel=` query; omit it to target the default deployment. The exact-version route takes no channel.
+
+## Endpoints
+
+
+
+
+
+
+
+
+For non-2xx responses, see [Errors](/docs/guide/remote/http-spec/errors).
diff --git a/content/docs/guide/remote/http-spec/meta.json b/content/docs/guide/remote/http-spec/meta.json
new file mode 100644
index 0000000..eee67ba
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "HTTP Spec",
+ "pages": ["index", "endpoints", "errors"]
+}
diff --git a/content/docs/guide/remote/http-spec/meta.ko.json b/content/docs/guide/remote/http-spec/meta.ko.json
new file mode 100644
index 0000000..39e6b55
--- /dev/null
+++ b/content/docs/guide/remote/http-spec/meta.ko.json
@@ -0,0 +1,4 @@
+{
+ "title": "HTTP 스펙",
+ "pages": ["index", "endpoints", "errors"]
+}
diff --git a/content/docs/guide/remote/index.ko.mdx b/content/docs/guide/remote/index.ko.mdx
new file mode 100644
index 0000000..7617591
--- /dev/null
+++ b/content/docs/guide/remote/index.ko.mdx
@@ -0,0 +1,40 @@
+---
+title: 개요
+---
+
+리모트는 앱이 새 번들을 원격(Over-The-Air)으로 다운로드하는 HTTP 서버입니다. 패킹한 `.wvb`을 리모트에 업로드하고 현재 버전을 지정하면, 클라이언트는 네이티브 앱을 새로 배포하지 않고도 해당 버전을 받아옵니다.
+
+## 게시와 다운로드
+
+게시는 두 단계로 이뤄지며, 모두 [CLI](/docs/guide/remote/using-cli)에서 실행합니다.
+
+1. **업로드**은 패킹한 버전을 리모트에 저장합니다.
+2. **배포**은 클라이언트가 받을 현재 버전을 지정합니다.
+
+이후 클라이언트는 업데이터를 통해 배포된 버전을 확인하고 다운로드합니다. 클라이언트 쪽 흐름은 [Over-The-Air](/docs/guide/core-concepts/over-the-air) 문서를, 이를 연결하는 `remote` 블록은 [리모트 설정](/docs/references/configuration/remote)을 참고하세요.
+
+## 리모트 실행
+
+리모트를 실행하는 방법은 두 가지입니다.
+
+**프로바이더 사용하기.** 프로바이더는 배포할 서버와, 설정에 넣을 `uploader`/`deployer` 쌍을 제공합니다. HTTP 규약을 직접 구현할 필요가 없습니다.
+
+
+
+
+
+
+
+**직접 구축하기.** 리모트 HTTP 규약을 구현한 서버라면 무엇이든 동작합니다. [HTTP 규약](/docs/guide/remote/http-spec)을 참고하세요.
diff --git a/content/docs/guide/remote/index.mdx b/content/docs/guide/remote/index.mdx
new file mode 100644
index 0000000..c85c76a
--- /dev/null
+++ b/content/docs/guide/remote/index.mdx
@@ -0,0 +1,40 @@
+---
+title: Overview
+---
+
+A remote is the HTTP server your app downloads new bundles from over the air. You publish a packed `.wvb` to it, mark a version as current, and clients fetch that version without a native app release.
+
+## Publish and download
+
+Publishing is a two-step model, both run from the [CLI](/docs/guide/remote/using-cli):
+
+1. **Upload** stores a packed version on the remote.
+2. **Deploy** marks which version is current — the one clients receive.
+
+Clients then check for and download the deployed version through the updater. See [Over-the-air](/docs/guide/core-concepts/over-the-air) for the client-side flow, and [Remote configuration](/docs/references/configuration/remote) for the `remote` block that wires it up.
+
+## Run a remote
+
+There are two ways to run a remote.
+
+**Use a provider.** A provider gives you a server to deploy and an `uploader`/`deployer` pair to drop into your config — no HTTP contract to implement.
+
+
+
+
+
+
+
+**Build your own.** Any server that implements the remote HTTP contract works. See the [HTTP spec](/docs/guide/remote/http-spec).
diff --git a/content/docs/guide/remote/providers/aws.ko.mdx b/content/docs/guide/remote/providers/aws.ko.mdx
new file mode 100644
index 0000000..8fea998
--- /dev/null
+++ b/content/docs/guide/remote/providers/aws.ko.mdx
@@ -0,0 +1,59 @@
+---
+title: AWS
+description: S3와 CloudFront에서 번들을 제공하며, KMS 서명을 선택적으로 사용합니다.
+---
+
+AWS 프로바이더는 CloudFront 배포 뒤의 S3 버킷에서 번들을 제공합니다. 두 개의 Lambda@Edge 함수가 엣지에서 리모트 HTTP 규약을 구현합니다.
+
+## 서버 프로비저닝
+
+`@wvb/remote-aws-provider-pulumi`은 버킷, 배포, 엣지 함수까지 전체 스택을 [Pulumi](https://www.pulumi.com) 컴포넌트로 프로비저닝합니다.
+
+```sh
+npm install @wvb/remote-aws-provider-pulumi
+```
+
+```ts title="index.ts"
+import { WebviewBundleRemoteProvider } from '@wvb/remote-aws-provider-pulumi';
+
+const remote = new WebviewBundleRemoteProvider('wvb', {
+ bucketName: 'my-bundles',
+ // allowOtherVersions: true, // GET /bundles/{name}/{version}도 제공
+});
+
+export const endpoint = remote.cloudfrontDistributionDomainName;
+```
+
+요청 핸들러 자체는 `@wvb/remote-aws-provider`(`webviewBundleRemote`)이며, 컴포넌트가 대신 배포합니다. 엣지 함수를 직접 연결하려면 [패키지 레퍼런스](/docs/references/api/remotes/remote-aws-provider)를 참고하세요.
+
+## 게시하기
+
+`@wvb/remote-aws`은 `uploader`과 `deployer`, 그리고 선택적인 AWS KMS `signature` 서명기를 설정용으로 제공합니다.
+
+```sh
+npm install -D @wvb/remote-aws
+```
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { awsRemote } from '@wvb/remote-aws';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'https://d111111abcdef8.cloudfront.net',
+ integrity: { algorithm: 'sha256' },
+ ...awsRemote({
+ bucket: 'my-bundles',
+ // deployer: { invalidation: { distributionId: 'E1234567890ABC' } },
+ // signature: { keyId: 'arn:aws:kms:…', algorithm: 'ECDSA_SHA_256' },
+ }),
+ },
+});
+```
+
+`awsRemote()`은 `{ uploader, deployer, signature? }`을 반환합니다. 자격 증명과 리전은 표준 AWS SDK 체인을 따르며, `aws` 옵션으로 재정의할 수 있습니다. 모든 옵션은 [AWS 클라이언트 레퍼런스](/docs/references/api/remotes/remote-aws)를 참고하세요.
+
+
+ 서버 옵션은 `bucketName`, 클라이언트 옵션은 `bucket`입니다. 둘 다 같은 버킷을 가리켜야 합니다.
+ Lambda@Edge는 버킷 리전과 무관하게 항상 `us-east-1`에서 실행됩니다.
+
diff --git a/content/docs/guide/remote/providers/aws.mdx b/content/docs/guide/remote/providers/aws.mdx
new file mode 100644
index 0000000..7a0f5b2
--- /dev/null
+++ b/content/docs/guide/remote/providers/aws.mdx
@@ -0,0 +1,59 @@
+---
+title: AWS
+description: Serve bundles from S3 behind CloudFront, with optional KMS signing.
+---
+
+The AWS provider serves bundles from an S3 bucket behind a CloudFront distribution. Two Lambda@Edge functions implement the remote HTTP contract at the edge.
+
+## Provision the server
+
+`@wvb/remote-aws-provider-pulumi` provisions the whole stack — bucket, distribution, and edge functions — as a [Pulumi](https://www.pulumi.com) component:
+
+```sh
+npm install @wvb/remote-aws-provider-pulumi
+```
+
+```ts title="index.ts"
+import { WebviewBundleRemoteProvider } from '@wvb/remote-aws-provider-pulumi';
+
+const remote = new WebviewBundleRemoteProvider('wvb', {
+ bucketName: 'my-bundles',
+ // allowOtherVersions: true, // also serve GET /bundles/{name}/{version}
+});
+
+export const endpoint = remote.cloudfrontDistributionDomainName;
+```
+
+The request handler itself is `@wvb/remote-aws-provider` (`webviewBundleRemote`), which the component deploys for you. To wire the edge functions manually, see the [package reference](/docs/references/api/remotes/remote-aws-provider).
+
+## Publish to it
+
+`@wvb/remote-aws` provides the `uploader` and `deployer` — and an optional AWS KMS `signature` signer — for your config:
+
+```sh
+npm install -D @wvb/remote-aws
+```
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { awsRemote } from '@wvb/remote-aws';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'https://d111111abcdef8.cloudfront.net',
+ integrity: { algorithm: 'sha256' },
+ ...awsRemote({
+ bucket: 'my-bundles',
+ // deployer: { invalidation: { distributionId: 'E1234567890ABC' } },
+ // signature: { keyId: 'arn:aws:kms:…', algorithm: 'ECDSA_SHA_256' },
+ }),
+ },
+});
+```
+
+`awsRemote()` returns `{ uploader, deployer, signature? }`. Credentials and region come from the standard AWS SDK chain; override them with the `aws` option. See the [AWS client reference](/docs/references/api/remotes/remote-aws) for every option.
+
+
+ The server option is `bucketName`; the client option is `bucket`. Both must point at the same
+ bucket. Lambda@Edge always runs in `us-east-1`, independent of the bucket's region.
+
diff --git a/content/docs/guide/remote/providers/cloudflare.ko.mdx b/content/docs/guide/remote/providers/cloudflare.ko.mdx
new file mode 100644
index 0000000..5d72330
--- /dev/null
+++ b/content/docs/guide/remote/providers/cloudflare.ko.mdx
@@ -0,0 +1,70 @@
+---
+title: Cloudflare
+description: Cloudflare Worker 뒤의 R2에서 번들을 제공합니다.
+---
+
+Cloudflare 프로바이더는 Worker를 통해 R2 버킷에서 번들을 제공하며, KV 네임스페이스로 어떤 버전이 배포되었는지 기록합니다.
+
+## 서버 프로비저닝
+
+`@wvb/remote-cloudflare-provider-pulumi`은 Worker, R2 버킷, KV 네임스페이스를 [Pulumi](https://www.pulumi.com) 컴포넌트로 프로비저닝합니다.
+
+```sh
+npm install @wvb/remote-cloudflare-provider-pulumi
+```
+
+```ts title="index.ts"
+import { WebviewBundleRemoteProvider } from '@wvb/remote-cloudflare-provider-pulumi';
+
+const remote = new WebviewBundleRemoteProvider('wvb', {
+ accountId: '',
+});
+
+export const bucketName = remote.bucketName;
+export const kvNamespaceId = remote.kvNamespaceId;
+```
+
+Worker를 직접 작성하려면 `@wvb/remote-cloudflare-provider`이 [Hono](https://hono.dev) 앱을 내보냅니다. R2 버킷을 `BUCKET`으로, KV 네임스페이스를 `KV`로 바인딩한 뒤 핸들러에 매핑하세요.
+
+```ts title="src/worker.ts"
+import { wvbRemote } from '@wvb/remote-cloudflare-provider';
+
+const app = wvbRemote();
+
+export default {
+ fetch(req: Request, env: { KV: KVNamespace; BUCKET: R2Bucket }) {
+ return app.fetch(req, { kv: env.KV, r2: env.BUCKET });
+ },
+};
+```
+
+## 게시하기
+
+`@wvb/remote-cloudflare`은 `uploader`과 `deployer`을 설정용으로 제공합니다.
+
+```sh
+npm install -D @wvb/remote-cloudflare
+```
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { cloudflareRemote } from '@wvb/remote-cloudflare';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'https://updates.example.com',
+ ...cloudflareRemote({
+ accountId: '',
+ bucket: 'webview-bundle',
+ kvNamespaceId: '',
+ }),
+ },
+});
+```
+
+업로더는 S3 호환 API로 R2에 기록하므로 R2 액세스 키를 `s3ClientConfig.credentials`로 전달하고, 디플로이어는 Cloudflare API 토큰으로 KV를 갱신합니다. 전체 설정은 [Cloudflare 클라이언트 레퍼런스](/docs/references/api/remotes/remote-cloudflare)를 참고하세요.
+
+
+ Worker는 `BUCKET`이라는 R2 바인딩과 `KV`라는 KV 바인딩을 읽습니다. `wrangler.jsonc`에서 이 이름을
+ 맞춰주세요.
+
diff --git a/content/docs/guide/remote/providers/cloudflare.mdx b/content/docs/guide/remote/providers/cloudflare.mdx
new file mode 100644
index 0000000..4f878ac
--- /dev/null
+++ b/content/docs/guide/remote/providers/cloudflare.mdx
@@ -0,0 +1,70 @@
+---
+title: Cloudflare
+description: Serve bundles from R2 behind a Cloudflare Worker.
+---
+
+The Cloudflare provider serves bundles from an R2 bucket through a Worker, using a KV namespace to record which version is deployed.
+
+## Provision the server
+
+`@wvb/remote-cloudflare-provider-pulumi` provisions the Worker, R2 bucket, and KV namespace as a [Pulumi](https://www.pulumi.com) component:
+
+```sh
+npm install @wvb/remote-cloudflare-provider-pulumi
+```
+
+```ts title="index.ts"
+import { WebviewBundleRemoteProvider } from '@wvb/remote-cloudflare-provider-pulumi';
+
+const remote = new WebviewBundleRemoteProvider('wvb', {
+ accountId: '',
+});
+
+export const bucketName = remote.bucketName;
+export const kvNamespaceId = remote.kvNamespaceId;
+```
+
+To write the Worker yourself, `@wvb/remote-cloudflare-provider` exports a [Hono](https://hono.dev) app. Bind an R2 bucket as `BUCKET` and a KV namespace as `KV`, then map them into the handler:
+
+```ts title="src/worker.ts"
+import { wvbRemote } from '@wvb/remote-cloudflare-provider';
+
+const app = wvbRemote();
+
+export default {
+ fetch(req: Request, env: { KV: KVNamespace; BUCKET: R2Bucket }) {
+ return app.fetch(req, { kv: env.KV, r2: env.BUCKET });
+ },
+};
+```
+
+## Publish to it
+
+`@wvb/remote-cloudflare` provides the `uploader` and `deployer` for your config:
+
+```sh
+npm install -D @wvb/remote-cloudflare
+```
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { cloudflareRemote } from '@wvb/remote-cloudflare';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'https://updates.example.com',
+ ...cloudflareRemote({
+ accountId: '',
+ bucket: 'webview-bundle',
+ kvNamespaceId: '',
+ }),
+ },
+});
+```
+
+The uploader writes to R2 over the S3-compatible API, so pass R2 access keys through its `s3ClientConfig.credentials`; the deployer updates KV with a Cloudflare API token. See the [Cloudflare client reference](/docs/references/api/remotes/remote-cloudflare) for the full config.
+
+
+ The Worker reads an R2 binding named `BUCKET` and a KV binding named `KV`. Match those names in
+ your `wrangler.jsonc`.
+
diff --git a/content/docs/guide/remote/providers/local.ko.mdx b/content/docs/guide/remote/providers/local.ko.mdx
new file mode 100644
index 0000000..858d8a6
--- /dev/null
+++ b/content/docs/guide/remote/providers/local.ko.mdx
@@ -0,0 +1,62 @@
+---
+title: Local
+description: 개발과 테스트를 위해 로컬 디렉터리에서 번들을 제공합니다.
+---
+
+로컬 프로바이더는 디렉터리를 기반으로 내 컴퓨터에서 리모트를 실행합니다. 호스팅형 프로바이더를 연동하기 전에 업로드 → 배포 → 다운로드 흐름 전체를 오프라인에서 실행해 볼 때 사용합니다.
+
+## 서버 실행
+
+가장 빠른 방법은 코드가 필요 없는 CLI입니다. 서버가 함께 제공됩니다.
+
+```sh
+wvb remote local
+```
+
+`~/.wvb/local`을 `http://localhost:4313`에서 제공합니다. 플래그(`--base-dir`, `--port`, `--allow-other-versions`)는 [`wvb remote local`](/docs/references/cli/remote)을 참고하세요.
+
+서버를 직접 임베드하려면 `@wvb/remote-local-provider`이 [Hono](https://hono.dev) 앱을 내보냅니다. 아무 Hono 어댑터로 실행하세요.
+
+```sh
+npm install @wvb/remote-local-provider @hono/node-server
+```
+
+```ts title="server.ts"
+import { serve } from '@hono/node-server';
+import { wvbRemote } from '@wvb/remote-local-provider';
+
+const app = wvbRemote({ baseDir: '~/.wvb/local' });
+serve({ fetch: app.fetch, port: 4313 });
+```
+
+| Option | Type | Default | Description |
+| -------------------- | --------- | -------------- | ------------------------------------------------- |
+| `baseDir` | `string` | `~/.wvb/local` | 번들과 배포 정보를 저장하는 디렉터리. |
+| `allowOtherVersions` | `boolean` | `false` | `GET /bundles/{name}/{version}`로 특정 버전 제공. |
+
+## 게시하기
+
+`@wvb/remote-local`은 서버가 읽는 디렉터리에 그대로 기록하는 `uploader`과 `deployer`을 설정용으로 제공합니다.
+
+```sh
+npm install -D @wvb/remote-local
+```
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { localRemote } from '@wvb/remote-local';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'http://localhost:4313',
+ ...localRemote({ baseDir: '~/.wvb/local' }),
+ },
+});
+```
+
+`localRemote()`은 `{ uploader, deployer }`을 반환합니다. 이를 `remote`에 펼쳐 넣으면 `wvb upload`과 `wvb deploy`이 로컬 서버가 제공할 수 있는 번들을 기록합니다.
+
+
+ 서버와 클라이언트가 같은 `baseDir`을 가리키게 하세요. [CLI
+ 가이드](/docs/guide/remote/using-cli)에서 이 프로바이더를 처음부터 끝까지 사용합니다.
+
diff --git a/content/docs/guide/remote/providers/local.mdx b/content/docs/guide/remote/providers/local.mdx
new file mode 100644
index 0000000..244d39a
--- /dev/null
+++ b/content/docs/guide/remote/providers/local.mdx
@@ -0,0 +1,62 @@
+---
+title: Local
+description: Serve bundles from a local directory for development and testing.
+---
+
+The local provider runs a remote on your machine, backed by a directory. Use it to exercise the full upload → deploy → download loop offline before wiring in a hosted provider.
+
+## Run the server
+
+The quickest way needs no code — the CLI ships the server:
+
+```sh
+wvb remote local
+```
+
+This serves `~/.wvb/local` on `http://localhost:4313`. See [`wvb remote local`](/docs/references/cli/remote) for its flags (`--base-dir`, `--port`, `--allow-other-versions`).
+
+To embed the server yourself, `@wvb/remote-local-provider` exports a [Hono](https://hono.dev) app. Serve it with any Hono adapter:
+
+```sh
+npm install @wvb/remote-local-provider @hono/node-server
+```
+
+```ts title="server.ts"
+import { serve } from '@hono/node-server';
+import { wvbRemote } from '@wvb/remote-local-provider';
+
+const app = wvbRemote({ baseDir: '~/.wvb/local' });
+serve({ fetch: app.fetch, port: 4313 });
+```
+
+| Option | Type | Default | Description |
+| -------------------- | --------- | -------------- | --------------------------------------------------------- |
+| `baseDir` | `string` | `~/.wvb/local` | Directory that stores bundles and deployments. |
+| `allowOtherVersions` | `boolean` | `false` | Serve exact versions via `GET /bundles/{name}/{version}`. |
+
+## Publish to it
+
+`@wvb/remote-local` provides the `uploader` and `deployer` for your config, writing to the same directory the server reads.
+
+```sh
+npm install -D @wvb/remote-local
+```
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { localRemote } from '@wvb/remote-local';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'http://localhost:4313',
+ ...localRemote({ baseDir: '~/.wvb/local' }),
+ },
+});
+```
+
+`localRemote()` returns `{ uploader, deployer }`. Spread it into `remote` so `wvb upload` and `wvb deploy` write bundles the local server can serve.
+
+
+ Point the server and the client at the same `baseDir`. The [CLI
+ walkthrough](/docs/guide/remote/using-cli) uses this provider end to end.
+
diff --git a/content/docs/guide/remote/providers/meta.json b/content/docs/guide/remote/providers/meta.json
new file mode 100644
index 0000000..3545a1e
--- /dev/null
+++ b/content/docs/guide/remote/providers/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Providers",
+ "pages": ["aws", "cloudflare", "local"]
+}
diff --git a/content/docs/guide/remote/providers/meta.ko.json b/content/docs/guide/remote/providers/meta.ko.json
new file mode 100644
index 0000000..152646c
--- /dev/null
+++ b/content/docs/guide/remote/providers/meta.ko.json
@@ -0,0 +1,4 @@
+{
+ "title": "프로바이더",
+ "pages": ["aws", "cloudflare", "local"]
+}
diff --git a/content/docs/guide/remote/using-cli.ko.mdx b/content/docs/guide/remote/using-cli.ko.mdx
new file mode 100644
index 0000000..1d586e5
--- /dev/null
+++ b/content/docs/guide/remote/using-cli.ko.mdx
@@ -0,0 +1,59 @@
+---
+title: CLI 사용하기
+description: 로컬 리모트를 실행하고, 버전을 게시하고, 다시 다운로드하는 전체 업데이트 흐름을 터미널에서 실행합니다.
+---
+
+`wvb` CLI는 리모트 작업 전체를 처리합니다. 로컬 서버를 실행하고, 번들을 게시하고, 다시 다운로드할 수 있습니다. 이 가이드는 호스팅형 [프로바이더](/docs/guide/remote/providers/local)를 배포하기 전에 전체 흐름을 로컬에서 실행해 봅니다.
+
+## 리모트 설정
+
+설정에서 리모트를 지정하고 로컬 `uploader`/`deployer`을 넣습니다.
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { localRemote } from '@wvb/remote-local';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'http://localhost:4313',
+ integrity: { algorithm: 'sha256' },
+ ...localRemote({ baseDir: '.wvb/local' }),
+ },
+});
+```
+
+`endpoint`은 다운로드를 읽어오는 위치이며, `uploader`과 `deployer`은 `wvb upload`·`wvb deploy` 명령이 게시할 때 사용합니다. 모든 필드는 [리모트 설정](/docs/references/configuration/remote)을 참고하세요.
+
+## 로컬 리모트 실행
+
+같은 디렉터리를 사용하는 서버를 실행하고, 별도 터미널에서 켜 둡니다.
+
+```sh
+wvb remote local --base-dir .wvb/local
+```
+
+`http://localhost:4313`에서 제공합니다. 플래그는 [`wvb remote local`](/docs/references/cli/remote)을 참고하세요.
+
+## 버전 게시
+
+패킹, 업로드, 배포를 한 명령으로 실행합니다.
+
+```sh
+wvb upload app --version 1.0.0 --deploy
+```
+
+`--deploy` 없이 실행하면 버전은 스테이징만 됩니다. 현재 버전으로 만들려면 [`wvb deploy app --version 1.0.0`](/docs/references/cli/remote-deploy)을 실행하세요. 채널에 게시하려면 `--channel beta`도 함께 사용합니다.
+
+## 조회와 다운로드
+
+리모트가 제공하는 내용을 확인합니다.
+
+```sh
+wvb remote current app # 현재 버전과 메타데이터
+wvb remote list # 리모트의 모든 번들
+wvb download app # 현재 .wvb을 디스크에 다운로드
+```
+
+## 앱에 설치
+
+CLI 다운로드는 리모트가 제공하는 내용을 확인하는 용도이며, 실제 앱은 CLI가 아니라 업데이터로 업데이트를 설치합니다. 앱의 리모트를 같은 엔드포인트(`http://localhost:4313`)로 지정한 뒤 확인 → 다운로드 → 설치 흐름을 실행하세요. 클라이언트 API는 [Over-The-Air](/docs/guide/core-concepts/over-the-air) 문서를, 연동 방법은 **네이티브** 섹션의 플랫폼별 가이드를 참고하세요.
diff --git a/content/docs/guide/remote/using-cli.mdx b/content/docs/guide/remote/using-cli.mdx
new file mode 100644
index 0000000..b20374e
--- /dev/null
+++ b/content/docs/guide/remote/using-cli.mdx
@@ -0,0 +1,59 @@
+---
+title: Using the CLI
+description: Run a local remote, publish a version, and download it — the whole update loop from the terminal.
+---
+
+The `wvb` CLI drives the entire remote workflow: run a local server, publish bundles, and download them back. This walkthrough exercises the full loop on your machine before you deploy a hosted [provider](/docs/guide/remote/providers/local).
+
+## Configure the remote
+
+Point your config at a remote and give it the local `uploader`/`deployer`:
+
+```ts title="wvb.config.ts"
+import { defineConfig } from '@wvb/config';
+import { localRemote } from '@wvb/remote-local';
+
+export default defineConfig({
+ remote: {
+ endpoint: 'http://localhost:4313',
+ integrity: { algorithm: 'sha256' },
+ ...localRemote({ baseDir: '.wvb/local' }),
+ },
+});
+```
+
+`endpoint` is where downloads read from; `uploader` and `deployer` are how `wvb upload` and `wvb deploy` publish. See [Remote configuration](/docs/references/configuration/remote) for every field.
+
+## Run a local remote
+
+Start a server backed by the same directory, and leave it running in its own terminal:
+
+```sh
+wvb remote local --base-dir .wvb/local
+```
+
+It serves `http://localhost:4313`. See [`wvb remote local`](/docs/references/cli/remote) for its flags.
+
+## Publish a version
+
+Pack, upload, and deploy in one command:
+
+```sh
+wvb upload app --version 1.0.0 --deploy
+```
+
+Without `--deploy`, the version is only staged; run [`wvb deploy app --version 1.0.0`](/docs/references/cli/remote-deploy) to make it current. Add `--channel beta` to publish to a channel.
+
+## Inspect and download
+
+Check what the remote serves:
+
+```sh
+wvb remote current app # current version and its metadata
+wvb remote list # every bundle on the remote
+wvb download app # download the current .wvb to disk
+```
+
+## Install in the app
+
+Downloading with the CLI verifies what the remote serves; your app installs updates through the updater, not the CLI. Point its remote at the same endpoint (`http://localhost:4313`) and run the check → download → install flow. See [Over-the-air](/docs/guide/core-concepts/over-the-air) for the client API and your platform's guide in the **Native** section for wiring it up.
diff --git a/content/docs/guide/spec/errors.ko.mdx b/content/docs/guide/spec/errors.ko.mdx
deleted file mode 100644
index 92f7ce5..0000000
--- a/content/docs/guide/spec/errors.ko.mdx
+++ /dev/null
@@ -1,93 +0,0 @@
----
-title: 오류
-description: Webview Bundle 원격 서버가 HTTP로 실패를 알리는 방식과, 클라이언트가 각 상태 코드를 오류로 매핑하는 방식을 설명합니다.
----
-
-원격 서버는 표준 HTTP 상태 코드로 실패를 알립니다. Webview Bundle 클라이언트는 모든 `2xx` 응답을 성공으로 처리하고, 문서에 명시된 `2xx` 외의 코드를 특정 오류로 매핑합니다. 클라이언트와 업데이터가 의도대로 반응하도록 이 응답들을 정확히 구현하세요. 이 오류들이 속한 전체 요청 계약은 [Remote HTTP 명세](/docs/guide/spec)와 [원격 서버 구축하기](/docs/guide/remote)를 참고하세요.
-
-## 상태 코드
-
-| 상태 | 의미 | 시점 |
-| ------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `403 Forbidden` | 특정 버전 다운로드 비활성화 | 클라이언트가 `GET /bundles/{name}/{version}`을 요청했지만 서버가 다른 버전의 다운로드를 비활성화한 경우입니다. 클라이언트는 이를 `RemoteForbidden`으로 매핑합니다. |
-| `404 Not Found` | 번들 또는 버전이 배포되지 않음 | 지정한 번들 또는 요청한 버전이 배포되지 않은 경우입니다. 클라이언트는 이를 `RemoteBundleNotFound`로 매핑합니다. |
-| 그 외 `2xx` 외 코드 | 원격 HTTP 오류 | 그 밖의 모든 실패입니다. 본문은 JSON `{ "message": string }`으로 파싱되어, 상태 코드와 메시지를 담은 원격 HTTP 오류로 표면화됩니다. |
-
-클라이언트는 모든 `2xx` 상태를 성공으로 처리합니다. 따라서 원격 서버는 실제 실패에만 `2xx` 외의 코드를 사용해야 합니다.
-
-## Forbidden (403)
-
-`403`은 특정 버전 다운로드가 비활성화되었음을 알리는 의도적인 신호입니다. 프로바이더는 이를 `allowOtherVersions` 옵션으로 노출하며, 기본값은 `false`입니다. 비활성화된 동안에는 해당 버전이 서버에 존재하더라도 `GET /bundles/{name}/{version}`이 `403`을 반환합니다. 현재 버전 경로(`GET /bundles/{name}`과 `HEAD /bundles/{name}`)는 영향을 받지 않습니다.
-
-```http
-HTTP/1.1 403 Forbidden
-```
-
-클라이언트는 이 상태에 대해 `RemoteForbidden`을 발생시킵니다.
-
-## Not Found (404)
-
-`404`는 지정한 번들 또는 요청한 버전이 배포되지 않았음을 의미합니다. 목록 응답은 배포되지 않은 번들을 제외하므로, 그중 하나를 이어서 요청하면 `404`가 반환됩니다.
-
-```http
-HTTP/1.1 404 Not Found
-```
-
-클라이언트는 이 상태에 대해 `RemoteBundleNotFound`를 발생시킵니다.
-
-## 그 밖의 오류
-
-그 밖의 `2xx` 외 상태에는 `{ "message": string }` 형태의 JSON 본문을 반환하세요. 클라이언트는 이 본문을 파싱하여, 상태 코드와 메시지를 모두 담은 원격 HTTP 오류를 발생시킵니다.
-
-```http
-HTTP/1.1 500 Internal Server Error
-Content-Type: application/json
-```
-
-```json
-{ "message": "failed to read bundle from storage" }
-```
-
-
- 이 응답들에는 `{ "message": string }` 본문을 보내세요. 클라이언트가 표면화하는 값이 바로 이 `message`이므로,
- 실패를 진단할 수 있을 만큼 설명적으로 작성하세요.
-
-
-## 필수 헤더 누락
-
-성공적인 다운로드나 메타데이터 응답이라도 필수 헤더는 반드시 담고 있어야 합니다. 클라이언트는 `Webview-Bundle-Name` 또는 `Webview-Bundle-Version`이 빠진 `2xx` 응답을 거부하며, 이를 전송 오류가 아니라 클라이언트 쪽의 잘못된 번들로 처리합니다. 이는 서버가 반환하는 상태가 아니라 클라이언트 쪽 실패이므로, 원격 서버는 다운로드와 메타데이터 응답에 두 헤더를 항상 설정해야 합니다. 전체 헤더 목록은 [GET /bundles/{name}](/docs/guide/spec/get-current)과 [HEAD /bundles/{name}](/docs/guide/spec/head-current)을 참고하세요.
-
-## 다음으로 볼 내용
-
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/errors.mdx b/content/docs/guide/spec/errors.mdx
deleted file mode 100644
index 0c37035..0000000
--- a/content/docs/guide/spec/errors.mdx
+++ /dev/null
@@ -1,93 +0,0 @@
----
-title: Errors
-description: How the Webview Bundle remote signals failures over HTTP and how the client maps each status to an error.
----
-
-A remote signals failure through standard HTTP status codes. The Webview Bundle client treats any `2xx` response as success and maps the documented non-`2xx` codes to specific errors. Implement these responses exactly so the client and updater react the way they should. For the full request contract these errors sit alongside, see the [Remote HTTP Spec](/docs/guide/spec) and [Building a remote](/docs/guide/remote).
-
-## Status codes
-
-| Status | Meaning | When |
-| --------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
-| `403 Forbidden` | Version-specific download disabled | A client requests `GET /bundles/{name}/{version}` but the server disables other-version downloads. The client maps this to `RemoteForbidden`. |
-| `404 Not Found` | Bundle or version not deployed | The named bundle or the requested version is not deployed. The client maps this to `RemoteBundleNotFound`. |
-| Other non-`2xx` | Remote HTTP error | Any other failure. The body is parsed as JSON `{ "message": string }` and surfaced as a remote HTTP error carrying the status and message. |
-
-The client treats every `2xx` status as success, so a remote should reserve non-`2xx` codes for genuine failures.
-
-## Forbidden (403)
-
-`403` is the deliberate signal that a version-specific download is disabled. Providers expose this as an `allowOtherVersions` option, which defaults to `false`. While disabled, `GET /bundles/{name}/{version}` returns `403` even when that version exists on the server. The current-version routes (`GET /bundles/{name}` and `HEAD /bundles/{name}`) are unaffected.
-
-```http
-HTTP/1.1 403 Forbidden
-```
-
-The client raises `RemoteForbidden` for this status.
-
-## Not Found (404)
-
-`404` means the named bundle or the requested version is not deployed. List responses exclude undeployed bundles, so a follow-up request for one of them returns `404`.
-
-```http
-HTTP/1.1 404 Not Found
-```
-
-The client raises `RemoteBundleNotFound` for this status.
-
-## Other errors
-
-For any other non-`2xx` status, return a JSON body of the shape `{ "message": string }`. The client parses this body and raises a remote HTTP error that carries both the status and the message.
-
-```http
-HTTP/1.1 500 Internal Server Error
-Content-Type: application/json
-```
-
-```json
-{ "message": "failed to read bundle from storage" }
-```
-
-
- Send a `{ "message": string }` body on these responses. The `message` is what the client surfaces,
- so make it descriptive enough to diagnose the failure.
-
-
-## Missing required headers
-
-A successful download or metadata response must still carry the required headers. The client rejects a `2xx` response that omits `Webview-Bundle-Name` or `Webview-Bundle-Version`, treating it as an invalid bundle on the client side rather than a transport error. This is a client-side failure, not a status the server returns, so a remote must always set both headers on download and metadata responses. See [GET /bundles/{name}](/docs/guide/spec/get-current) and [HEAD /bundles/{name}](/docs/guide/spec/head-current) for the full header set.
-
-## Where to go next
-
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/index.ko.mdx b/content/docs/guide/spec/index.ko.mdx
deleted file mode 100644
index e6027ed..0000000
--- a/content/docs/guide/spec/index.ko.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: 원격 HTTP 명세
-description: Webview Bundle 클라이언트와 업데이터가 번들을 나열하고 조회하고 다운로드할 수 있도록 서버가 구현하는 HTTP 계약입니다.
----
-
-여기서 설명하는 엔드포인트를 구현하는 HTTP 서버는 모두 유효한 Webview Bundle 원격입니다. 클라이언트와 업데이터는 이 계약을 통해 배포된 번들을 나열하고, 번들의 현재 메타데이터를 읽고, 번들 바이트를 다운로드합니다. 직접 구현하거나, 이미 이 계약을 구현한 프로바이더를 사용하세요. [Local](/docs/guide/providers/local), [AWS](/docs/guide/providers/aws), [Cloudflare](/docs/guide/providers/cloudflare) 프로바이더는 모두 정확히 이 계약을 제공합니다. 서버를 구축하고 프로바이더를 선택하려면 [원격 구축](/docs/guide/remote)을 참고하세요. 이 호출들을 이끄는 업데이트 흐름의 개념은 [원격 번들 및 업데이트](/docs/guide/remote-bundles)를 참고하세요.
-
-## Base URL
-
-원격은 단일 `endpoint` base URL로 설정합니다. 모든 라우트는 이 base 아래의 경로이므로, `endpoint`가 `https://cdn.example.com`이면 `GET https://cdn.example.com/bundles`, `HEAD https://cdn.example.com/bundles/{name}` 등을 제공합니다. 클라이언트는 설정된 `endpoint`에 라우트 경로와 쿼리 문자열을 결합해 각 요청 URL을 만듭니다.
-
-## 규약
-
-이 규약은 모든 엔드포인트에 공통으로 적용됩니다. 각 엔드포인트 페이지는 해당 라우트에 고유한 내용만 반복합니다.
-
-### 응답 헤더
-
-메타데이터는 다운로드 및 메타데이터 응답의 `Webview-Bundle-*` 응답 헤더에 담겨 전달됩니다. 클라이언트는 대소문자를 구분하지 않고 읽으며, 여기 표시한 것이 표준 표기입니다.
-
-| 헤더 | 필수 | 의미 |
-| -------------------------- | ------ | -------------------------------- |
-| `Webview-Bundle-Name` | 예 | 번들 이름 |
-| `Webview-Bundle-Version` | 예 | 번들 버전 |
-| `Webview-Bundle-Integrity` | 아니오 | 무결성 문자열 `":"` |
-| `Webview-Bundle-Signature` | 아니오 | Base64 서명 |
-| `ETag` | 아니오 | 표준 검증자 |
-| `Last-Modified` | 아니오 | 표준 검증자 |
-
-클라이언트는 `Webview-Bundle-Name` 또는 `Webview-Bundle-Version`이 없는 응답을 모두 거부합니다.
-
-### Content type
-
-다운로드 응답은 `Content-Type: application/webview-bundle`을 설정합니다. 목록 응답은 `Content-Type: application/json`을 설정합니다.
-
-### 채널
-
-목록, 메타데이터, 현재 버전 다운로드 라우트는 선택적 `?channel=` 쿼리를 받아 해당 채널의 배포를 선택합니다. 버전별 다운로드는 채널을 받지 않습니다. 기본 배포를 대상으로 하려면 쿼리를 생략하세요.
-
-### 요청 타임아웃
-
-클라이언트는 모든 호출에 기본 전체 요청 타임아웃 120초를 적용하므로, 서버는 그 안에 여유 있게 응답해야 합니다.
-
-
- 120초 타임아웃은 클라이언트의 HTTP 설정에서 재정의할 수 있지만, 변경하지 않으면 기본값이 모든
- 요청에 적용됩니다.
-
-
-## 엔드포인트
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/index.mdx b/content/docs/guide/spec/index.mdx
deleted file mode 100644
index a2cb3e8..0000000
--- a/content/docs/guide/spec/index.mdx
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: Remote HTTP Spec
-description: The HTTP contract a server implements so the Webview Bundle client and updater can list, inspect, and download bundles.
----
-
-Any HTTP server that implements the endpoints described here is a valid Webview Bundle remote. The client and updater speak this contract to list deployed bundles, read a bundle's current metadata, and download bundle bytes. Implement it directly, or use a provider that already does: the [Local](/docs/guide/providers/local), [AWS](/docs/guide/providers/aws), and [Cloudflare](/docs/guide/providers/cloudflare) providers all serve this exact contract. To stand up a server and choose a provider, see [Building a remote](/docs/guide/remote); for the conceptual update flow that drives these calls, see [Remote bundles & updates](/docs/guide/remote-bundles).
-
-## Base URL
-
-A remote is configured with a single `endpoint` base URL. Every route is a path under that base, so an `endpoint` of `https://cdn.example.com` serves `GET https://cdn.example.com/bundles`, `HEAD https://cdn.example.com/bundles/{name}`, and so on. The client builds each request URL by joining the configured `endpoint` with the route path and any query string.
-
-## Conventions
-
-These conventions hold across every endpoint. Each endpoint page repeats only what is specific to its route.
-
-### Response headers
-
-Metadata travels in `Webview-Bundle-*` response headers on download and metadata responses. The client reads them case-insensitively; the canonical casing is shown here.
-
-| Header | Required | Meaning |
-| -------------------------- | -------- | ----------------------------------- |
-| `Webview-Bundle-Name` | yes | Bundle name |
-| `Webview-Bundle-Version` | yes | Bundle version |
-| `Webview-Bundle-Integrity` | no | Integrity string `":"` |
-| `Webview-Bundle-Signature` | no | Base64 signature |
-| `ETag` | no | Standard validator |
-| `Last-Modified` | no | Standard validator |
-
-The client rejects any response that omits `Webview-Bundle-Name` or `Webview-Bundle-Version`.
-
-### Content type
-
-Download responses set `Content-Type: application/webview-bundle`. The list response sets `Content-Type: application/json`.
-
-### Channels
-
-The list, metadata, and current-download routes accept an optional `?channel=` query that selects a channel's deployment. The version-specific download does not accept a channel. Omit the query to target the default deployment.
-
-### Request timeout
-
-The client applies a default total request timeout of 120s to every call, so a server should respond well within it.
-
-
- The 120s timeout can be overridden in the client's HTTP configuration, but the default applies to
- every request unless changed.
-
-
-## Endpoints
-
-
-
-
-
-
-
-
diff --git a/content/docs/guide/spec/meta.json b/content/docs/guide/spec/meta.json
deleted file mode 100644
index f8023a5..0000000
--- a/content/docs/guide/spec/meta.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "Spec",
- "pages": ["index", "list-bundles", "head-current", "get-current", "get-version", "errors"]
-}
diff --git a/content/docs/guide/spec/meta.ko.json b/content/docs/guide/spec/meta.ko.json
deleted file mode 100644
index f208535..0000000
--- a/content/docs/guide/spec/meta.ko.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "title": "명세",
- "pages": ["index", "list-bundles", "head-current", "get-current", "get-version", "errors"]
-}
diff --git a/content/docs/guide/why-webview-bundle.ko.mdx b/content/docs/guide/why-webview-bundle.ko.mdx
deleted file mode 100644
index 7a29c4d..0000000
--- a/content/docs/guide/why-webview-bundle.ko.mdx
+++ /dev/null
@@ -1,189 +0,0 @@
----
-title: Webview Bundle을 선택하는 이유
-description: 웹 앱을 오프라인 우선 .wvb 번들로 출시하고 무선(OTA) 업데이트로 배포하는 방법을 설명합니다.
----
-
-Webview Bundle은 빌드된 웹 앱을 하나의 압축된, 무결성이 검증되는 아카이브(`.wvb`)로 묶고, 이를 커스텀 URL 스킴을 통해 웹뷰에 제공합니다. 웹뷰는 HTML, JS, CSS, 미디어를 네트워크가 아닌 로컬 저장소에서 읽습니다. 원격을 설정하면 앱이 더 새로운 번들을 무선으로 다운로드하며, 네이티브 앱스토어 출시 없이도 갱신된 코드를 배포합니다.
-
-팀이 Webview Bundle을 선택하는 이유는 세 가지입니다. 오프라인 우선 전달, 무선(OTA) 업데이트, 그리고 모든 웹뷰 플랫폼을 아우르는 하나의 워크플로입니다.
-
-## 기본으로 오프라인 우선
-
-원격 URL을 사용하면 첫 화면이 네트워크를 기다려야 합니다. 캐시가 비어 있거나, 캡티브 포털에 걸리거나, 연결이 끊기면 빈 화면이 나타납니다.
-
-Webview Bundle은 앱과 함께 출시되는 로컬 `.wvb`에서 자산을 곧바로 제공합니다. 프로토콜은 각 요청을 번들 안의 파일로 매핑하고 그 파일을 반환합니다.
-
-```text
-bundle://app/index.html -> index.html inside the app bundle
-bundle://app/assets/x.js -> assets/x.js inside the app bundle
-```
-
-```ts title="번들을 로컬에서 제공"
-import { BundleSource, BundleProtocol } from '@wvb/node';
-
-const source = new BundleSource({
- builtinDir: '/path/to/builtin',
- remoteDir: '/path/to/remote',
-});
-
-const protocol = new BundleProtocol(source);
-
-// 플랫폼 통합이 스킴 요청을 여기로 라우팅합니다.
-const res = await protocol.handle('get', 'bundle://app/index.html');
-// res: { status, headers, body }
-```
-
-첫 화면은 절대 네트워크를 기다리지 않습니다. 지하철에서든, 비행기에서든, 세션 도중 연결이 끊겨도 앱은 로컬 번들에서 화면을 그립니다. 네트워크는 최적화 수단이 됩니다.
-
-
- 번들 프로토콜은 `GET`과 `HEAD`를 지원하고, 각 파일에 저장된 헤더를 그대로 재현하며, 미디어에 대한
- `Range` 요청에는 `206`으로 응답합니다. 전체 요청 매핑은 [프로토콜
- 처리](/docs/guide/protocol-handling)를 참고하세요.
-
-
-## 무선(OTA) 업데이트
-
-앱스토어 출시는 느리고 심사에 막힙니다. 문구 오타 하나, 동작하지 않는 버튼 하나가 며칠에 걸친 심사에 묶일 수 있습니다. 앱 코드가 `.wvb` 안에 있기 때문에, 새 번들 버전을 배포하고 설치된 앱이 이를 무선(OTA)으로 다운로드하게 하면 문제를 고칠 수 있습니다. 네이티브 바이너리는 전혀 바뀌지 않습니다.
-
-두 개의 소스가 업데이트 모델을 뒷받침합니다. `builtin` 소스는 출시 시 함께 담은 읽기 전용 번들입니다. `remote` 소스는 다운로드한 번들을 담으며 우선권을 가집니다. 다운로드나 활성화가 실패하면 `builtin`으로 되돌아가므로, OTA가 기기를 동작하는 앱이 없는 상태로 만드는 일은 결코 없습니다.
-
-```ts title="두 소스: remote가 우선, builtin이 최후의 보루"
-new BundleSource({
- builtinDir: '/path/to/builtin', // 앱에 함께 담긴 읽기 전용
- remoteDir: '/path/to/remote', // OTA 다운로드, 우선권을 가짐
-});
-```
-
-업데이트는 두 개의 명시적인 단계로 실행되어, 다운로드가 절반만 된 번들이 제공되는 일이 없습니다. `download`는 원격 소스에 스테이징하고 검증합니다. `install`은 스테이징된 버전을 활성화합니다.
-
-```ts title="확인, 다운로드, 설치"
-import { Updater } from '@wvb/node';
-
-const updater = new Updater(source, remote);
-
-const info = await updater.getUpdate('app');
-if (info.isAvailable) {
- await updater.download('app'); // 스테이징 + 검증
- await updater.install('app', info.version); // 활성화
-}
-```
-
-### 채널을 이용한 단계적 롤아웃
-
-채널은 목록 및 다운로드 요청에 `?channel=`로 전달되는 선택적 문자열입니다. `UpdaterOptions`를 통해 전달하면 특정 기기 집단을 `beta`로 지정한 뒤 승격할 수 있습니다. 생략하면 이 파라미터는 빠지며, 하드코딩된 채널 이름은 없습니다.
-
-```ts title="이 기기를 beta 채널에 고정"
-const updater = new Updater(source, remote, { channel: 'beta' });
-```
-
-```http
-GET /bundles/app?channel=beta
-```
-
-### 무결성과 서명을 통한 진위 확인
-
-모든 다운로드는 활성화 전에 검증됩니다. 무결성은 SHA-2(`sha256` 기본값, 그 외 `sha384`, `sha512`)를 사용하며 `":"` 형식으로 직렬화됩니다.
-
-```text
-sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-```
-
-기본 정책은 `optional`(값이 있으면 검증)입니다. `strict`로 설정하면 무결성을 필수로 요구합니다. 여기에 선언적 `signatureVerifier`를 추가하면 누가 게시했는지 증명할 수 있습니다. 서명은 무결성 문자열의 바이트를 대상으로 합니다.
-
-```ts title="무결성 요구, Ed25519 서명 검증"
-const updater = new Updater(source, remote, {
- integrityPolicy: 'strict',
- signatureVerifier: {
- algorithm: 'ed25519',
- key: { format: 'raw', data: publicKeyBytes }, // 32바이트 Ed25519 키
- },
-});
-```
-
-검증은 ECDSA(`ecdsaSecp256R1`, `ecdsaSecp384R1`), `ed25519`, RSA(`rsaPkcs1V15`, `rsaPss`)를 지원합니다.
-
-
- 무결성은 SHA-3가 아니라 SHA-2입니다. 서명은 원시 아카이브 바이트가 아니라 무결성 문자열을 대상으로
- 하며, 무결성 값이 반드시 존재해야 합니다. 검증 흐름은 [원격 번들](/docs/guide/remote-bundles)을,
- 스키마는 [원격, 무결성 및 서명 설정](/docs/config/remote)을 참고하세요.
-
-
-## 웹 개발자를 위해
-
-이미 알고 있는 방식 그대로 앱을 작성하세요. Webview Bundle은 소스가 아니라 번들러의 산출물을 대상으로 동작하므로 React, Vue, Svelte, 순수 HTML 모두 그대로 동작합니다. 커스텀 프레임워크도, 특별한 빌드 단계도 필요 없습니다. `wvb pack`을 빌드 디렉터리로 향하게 하세요.
-
-```sh title="빌드 산출물을 하나의 .wvb로 패킹"
-npm install --save-dev @wvb/cli
-npx wvb pack ./dist
-```
-
-패킹된 파일은 기본적으로 `.wvb/`에 생성되며 `.wvb` 확장자는 자동으로 붙습니다. `outFile` 필드(단일 경로)로 이를 재정의할 수 있습니다.
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- pack: {
- outFile: '.wvb/app',
- },
-});
-```
-
-웹뷰 안에서 코드는 `@wvb/bridge`를 통해 네이티브 호스트와 통신합니다. 하나의 `invoke()` API가 소스, 원격, 업데이터 명령을 노출하고, 그 아래에서 Electron, Tauri, Android, iOS에 맞는 전송 방식을 선택합니다. 같은 업데이트 코드가 어디서나 실행됩니다.
-
-```ts title="웹 코드에서 업데이트 실행"
-import { updater } from '@wvb/bridge';
-
-const update = await updater.getUpdate('app');
-if (update.isAvailable) {
- await updater.download('app');
- await updater.install('app', update.version);
-}
-```
-
-브리지는 호출 시점에 플랫폼을 확인하므로, 직접 플랫폼별로 분기할 필요가 없습니다.
-
-```ts title="플랫폼이 자동으로 감지됩니다"
-import { platform } from '@wvb/bridge';
-
-platform.type; // 'electron' | 'tauri' | 'android' | 'ios'
-```
-
-### 다른 방식과의 비교
-
-흔히 쓰이는 두 가지 대안이 같은 문제의 일부를 해결합니다.
-
-- **플랫폼별 네이티브 자산 번들링.** 웹 빌드를 각 셸에 임베드하고, 서빙 및 업데이트 로직을 플랫폼마다 한 번씩 작성하는 방식입니다. 동작은 하지만 툴체인, 자산 포맷, 업데이트 코드가 플랫폼마다 다르고 그 모두를 직접 유지보수해야 합니다. Webview Bundle은 공유된 Rust 코어를 기반으로 Electron, Tauri, Android, iOS 전반에 하나의 `.wvb`와 하나의 사고 모델을 제공합니다.
-- **CodePush 방식의 OTA.** 호스팅형 OTA 서비스는 스토어 심사 없이 JS 업데이트를 배포합니다. 원격 소스와 같은 이점을 줍니다. 다만 절충점이 다릅니다. Webview Bundle은 관리형 서드파티 서비스가 아니라, builtin 폴백, 채널 롤아웃, 무결성과 선택적 서명을 갖춘, 직접 호스팅하는 검증 가능한 번들 포맷입니다. 서버와 아티팩트를 여러분이 소유합니다.
-
-솔직한 절충점은 이렇습니다. 번들 포맷과 패킹 단계를 도입하고 원격을 직접 운영해야 합니다. 그 대가로 오프라인 우선 전달, 스토어 심사 없는 OTA, 그리고 플랫폼을 추가해도 변하지 않는 하나의 워크플로를 얻습니다.
-
-
- Android와 iOS는 구현되어 출시된 상태입니다. iOS 최소 버전은 iOS 16입니다. Deno Desktop은
- 실험적입니다. 현재 상태는 [플랫폼 지원](/docs/guide/platform-support)을 참고하세요.
-
-
-## 다음으로 볼 내용
-
-
-
-
-
-
-
diff --git a/content/docs/guide/why-webview-bundle.mdx b/content/docs/guide/why-webview-bundle.mdx
deleted file mode 100644
index 332774d..0000000
--- a/content/docs/guide/why-webview-bundle.mdx
+++ /dev/null
@@ -1,190 +0,0 @@
----
-title: Why Webview Bundle?
-description: The case for shipping your web app as an offline-first .wvb bundle with over-the-air updates.
----
-
-Webview Bundle packs your built web app into one compressed, integrity-checked archive (`.wvb`) and serves it to the webview over a custom URL scheme. The webview reads HTML, JS, CSS, and media from local storage instead of the network. Configure a remote and the app downloads newer bundles over the air, shipping updated code without a native app-store release.
-
-Three reasons teams reach for it: offline-first delivery, over-the-air updates, and one workflow across every webview platform.
-
-## Offline-first by default
-
-A remote URL makes first paint wait on the network. A cold cache, a captive portal, or a dropped connection becomes a blank screen.
-
-Webview Bundle serves your assets straight from the local `.wvb` that ships with the app. The protocol maps each request to a file inside the bundle and returns it:
-
-```text
-bundle://app/index.html -> index.html inside the app bundle
-bundle://app/assets/x.js -> assets/x.js inside the app bundle
-```
-
-```ts title="Serving a bundle locally"
-import { BundleSource, BundleProtocol } from '@wvb/node';
-
-const source = new BundleSource({
- builtinDir: '/path/to/builtin',
- remoteDir: '/path/to/remote',
-});
-
-const protocol = new BundleProtocol(source);
-
-// Your platform integration routes scheme requests here.
-const res = await protocol.handle('get', 'bundle://app/index.html');
-// res: { status, headers, body }
-```
-
-First paint never waits on the network. The app paints from the local bundle on the subway, on a plane, or mid-session when the connection drops; the network becomes an optimization.
-
-
- The bundle protocol supports `GET` and `HEAD`, replays the headers stored for each file, and
- answers `Range` requests for media with `206`. See [Protocol
- handling](/docs/guide/protocol-handling) for the full request mapping.
-
-
-## Over-the-air updates
-
-An app-store release is slow and gated: a copy typo or a broken button can sit behind a multi-day review. Because your app code lives in a `.wvb`, you fix it by deploying a new bundle version and letting installed apps download it over the air (OTA). The native binary never changes.
-
-Two sources back the update model. The `builtin` source is the read-only bundle you shipped; the `remote` source holds downloaded bundles and takes priority. A failed download or activation falls back to `builtin`, so OTA can never leave a device without a working app.
-
-```ts title="Two sources: remote wins, builtin is the floor"
-new BundleSource({
- builtinDir: '/path/to/builtin', // shipped in the app, read-only
- remoteDir: '/path/to/remote', // OTA downloads, takes priority
-});
-```
-
-Updates run in two explicit steps so a half-downloaded bundle is never served: `download` stages into the remote source and verifies; `install` activates the staged version.
-
-```ts title="Check, download, install"
-import { Updater } from '@wvb/node';
-
-const updater = new Updater(source, remote);
-
-const info = await updater.getUpdate('app');
-if (info.isAvailable) {
- await updater.download('app'); // stage + verify
- await updater.install('app', info.version); // activate
-}
-```
-
-### Staged rollouts with channels
-
-A channel is an optional string sent as `?channel=` on list and download requests. Pass it through `UpdaterOptions` to point a device cohort at `beta`, then promote. Omit it and the parameter is dropped — there is no hardcoded channel name.
-
-```ts title="Pin this device to the beta channel"
-const updater = new Updater(source, remote, { channel: 'beta' });
-```
-
-```http
-GET /bundles/app?channel=beta
-```
-
-### Authenticity with integrity and signatures
-
-Every download is verified before activation. Integrity uses SHA-2 (`sha256` default, plus `sha384` and `sha512`), serialized as `":"`:
-
-```text
-sha256:n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=
-```
-
-The default policy is `optional` (verify if present). Set `strict` to require integrity, and add a declarative `signatureVerifier` to prove who published — the signature covers the integrity string bytes:
-
-```ts title="Require integrity, verify an Ed25519 signature"
-const updater = new Updater(source, remote, {
- integrityPolicy: 'strict',
- signatureVerifier: {
- algorithm: 'ed25519',
- key: { format: 'raw', data: publicKeyBytes }, // 32-byte Ed25519 key
- },
-});
-```
-
-Verification supports ECDSA (`ecdsaSecp256R1`, `ecdsaSecp384R1`), `ed25519`, and RSA (`rsaPkcs1V15`, `rsaPss`).
-
-
- Integrity is SHA-2, not SHA-3. The signature covers the integrity string, not the raw archive
- bytes, and requires an integrity value to be present. See [Remote
- bundles](/docs/guide/remote-bundles) for the verification flow and [Remote, integrity & signature
- config](/docs/config/remote) for the schema.
-
-
-## For web developers
-
-Keep writing the app you already know. Webview Bundle operates on your bundler's output, not your source, so React, Vue, Svelte, or plain HTML all work — no custom framework, no special build step. Point `wvb pack` at your build directory:
-
-```sh title="Pack your build output into one .wvb"
-npm install --save-dev @wvb/cli
-npx wvb pack ./dist
-```
-
-The packed file defaults to `.wvb/`; the `.wvb` extension is appended for you. Override it with the `outFile` field (a single path):
-
-```ts title="wvb.config.ts"
-import { defineConfig } from '@wvb/config';
-
-export default defineConfig({
- pack: {
- outFile: '.wvb/app',
- },
-});
-```
-
-Inside the webview, your code talks to the native host through `@wvb/bridge`. One `invoke()` API exposes the source, remote, and updater commands and picks the transport for Electron, Tauri, Android, or iOS underneath. The same update code runs everywhere:
-
-```ts title="Driving updates from web code"
-import { updater } from '@wvb/bridge';
-
-const update = await updater.getUpdate('app');
-if (update.isAvailable) {
- await updater.download('app');
- await updater.install('app', update.version);
-}
-```
-
-The bridge resolves the platform at call time, so you never branch on it yourself:
-
-```ts title="Platform is detected for you"
-import { platform } from '@wvb/bridge';
-
-platform.type; // 'electron' | 'tauri' | 'android' | 'ios'
-```
-
-### How this compares
-
-Two common alternatives solve part of the same problem:
-
-- **Per-platform native asset bundling.** Embed your web build in each shell and write serving and update logic once per platform. It works, but the toolchain, asset format, and update code differ on each platform and you maintain all of them. Webview Bundle gives you one `.wvb` and one mental model across Electron, Tauri, Android, and iOS, backed by a shared Rust core.
-- **CodePush-style OTA.** Hosted OTA services ship JS updates without store review — the same payoff as the remote source. The tradeoff differs: Webview Bundle is a verifiable bundle format you host yourself, with a builtin fallback, channel rollouts, and integrity plus optional signatures, rather than a managed third-party service. You own the server and the artifact.
-
-The honest tradeoff: you adopt a bundle format and a pack step, and you run the remote. In return you get offline-first delivery, OTA without store review, and one workflow that does not change as you add platforms.
-
-
- Android and iOS are implemented and shipping. The iOS minimum is iOS 16. Deno Desktop is
- experimental. See [Platform support](/docs/guide/platform-support) for current status.
-
-
-## Where to go next
-
-
-
-
-
-
-
diff --git a/content/docs/meta.json b/content/docs/meta.json
index 5c003f3..9501dae 100644
--- a/content/docs/meta.json
+++ b/content/docs/meta.json
@@ -1,3 +1,3 @@
{
- "pages": ["guide", "references", "config", "changelog"]
+ "pages": ["guide", "references", "changelog"]
}
diff --git a/content/docs/meta.ko.json b/content/docs/meta.ko.json
index 5c003f3..9501dae 100644
--- a/content/docs/meta.ko.json
+++ b/content/docs/meta.ko.json
@@ -1,3 +1,3 @@
{
- "pages": ["guide", "references", "config", "changelog"]
+ "pages": ["guide", "references", "changelog"]
}
diff --git a/content/docs/references/api/bridge/bridge-error-code.mdx b/content/docs/references/api/bridge/bridge-error-code.mdx
new file mode 100644
index 0000000..28d0785
--- /dev/null
+++ b/content/docs/references/api/bridge/bridge-error-code.mdx
@@ -0,0 +1,48 @@
+---
+title: 'BridgeErrorCode'
+description: 'BridgeErrorCode — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/error.ts#L9)
+
+## Properties
+
+
+
+---
+
+## Type
+
+```ts
+type BridgeErrorCode = (typeof BridgeErrorCode)[keyof typeof BridgeErrorCode];
+```
diff --git a/content/docs/references/api/bridge/bridge-error-data.mdx b/content/docs/references/api/bridge/bridge-error-data.mdx
new file mode 100644
index 0000000..23488bf
--- /dev/null
+++ b/content/docs/references/api/bridge/bridge-error-data.mdx
@@ -0,0 +1,16 @@
+---
+title: 'BridgeErrorData'
+description: 'Bridge error data that respond from native.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Bridge error data that respond from native.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/error.ts#L4)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/bridge-error.mdx b/content/docs/references/api/bridge/bridge-error.mdx
new file mode 100644
index 0000000..b826849
--- /dev/null
+++ b/content/docs/references/api/bridge/bridge-error.mdx
@@ -0,0 +1,52 @@
+---
+title: 'BridgeError'
+description: 'Error thrown by @wvb/bridge when an invoke() command rejects, regardless of platform.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Error thrown by `@wvb/bridge` when an `invoke()` command rejects, regardless of
+platform.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/error.ts#L28)
+
+**Extends:** `Error`
+
+## Constructor
+
+```ts
+new BridgeError(data: BridgeErrorData);
+```
+
+
+
+## Properties
+
+
+
+## Methods
+
+ BridgeError',
+ required: true,
+ parameters: [{ name: 'value', description: 'unknown' }],
+ returns: 'BridgeError',
+ },
+ of: {
+ type: "(code: string, message: string = '') => BridgeError",
+ required: true,
+ parameters: [
+ { name: 'code', description: 'string' },
+ { name: 'message', description: 'string' },
+ ],
+ returns: 'BridgeError',
+ },
+ }}
+/>
diff --git a/content/docs/references/api/bridge/bundle-manifest-metadata.mdx b/content/docs/references/api/bridge/bundle-manifest-metadata.mdx
new file mode 100644
index 0000000..2eefc8f
--- /dev/null
+++ b/content/docs/references/api/bridge/bundle-manifest-metadata.mdx
@@ -0,0 +1,33 @@
+---
+title: 'BundleManifestMetadata'
+description: 'Bundle manifest metadata.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Bundle manifest metadata.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/source.ts#L12)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/bundle-source-type.mdx b/content/docs/references/api/bridge/bundle-source-type.mdx
new file mode 100644
index 0000000..6d218f4
--- /dev/null
+++ b/content/docs/references/api/bridge/bundle-source-type.mdx
@@ -0,0 +1,17 @@
+---
+title: 'BundleSourceType'
+description: 'BundleSourceType — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+- builtin: Built-in bundle which is included in the application.
+- remote: Remote bundle which is downloaded from a remote server.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/source.ts#L7)
+
+## Type
+
+```ts
+type BundleSourceType = 'builtin' | 'remote';
+```
diff --git a/content/docs/references/api/bridge/bundle-source-version.mdx b/content/docs/references/api/bridge/bundle-source-version.mdx
new file mode 100644
index 0000000..8d03036
--- /dev/null
+++ b/content/docs/references/api/bridge/bundle-source-version.mdx
@@ -0,0 +1,23 @@
+---
+title: 'BundleSourceVersion'
+description: 'Bundle source version.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Bundle source version.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/source.ts#L50)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/bundle-update-info.mdx b/content/docs/references/api/bridge/bundle-update-info.mdx
new file mode 100644
index 0000000..ccb97ee
--- /dev/null
+++ b/content/docs/references/api/bridge/bundle-update-info.mdx
@@ -0,0 +1,49 @@
+---
+title: 'BundleUpdateInfo'
+description: 'Information of an available bundle update.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Information of an available bundle update.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/updater.ts#L9)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/index.mdx b/content/docs/references/api/bridge/index.mdx
new file mode 100644
index 0000000..dbdf4e1
--- /dev/null
+++ b/content/docs/references/api/bridge/index.mdx
@@ -0,0 +1,94 @@
+---
+title: '@wvb/bridge'
+description: 'WebView bridge'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+## Classes
+
+
+
+
+
+## Functions
+
+
+
+
+
+
+
+## Interfaces
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Type Aliases
+
+
+
+
+
+
+
+## Variables
+
+
+
+
+
+
+
diff --git a/content/docs/references/api/bridge/invoke-params.mdx b/content/docs/references/api/bridge/invoke-params.mdx
new file mode 100644
index 0000000..c29461c
--- /dev/null
+++ b/content/docs/references/api/bridge/invoke-params.mdx
@@ -0,0 +1,14 @@
+---
+title: 'InvokeParams'
+description: 'InvokeParams — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/invoke.ts#L15)
+
+## Index Signature
+
+```ts
+[key: string | number]: any;
+```
diff --git a/content/docs/references/api/bridge/invoke.mdx b/content/docs/references/api/bridge/invoke.mdx
new file mode 100644
index 0000000..ea6c1e7
--- /dev/null
+++ b/content/docs/references/api/bridge/invoke.mdx
@@ -0,0 +1,30 @@
+---
+title: 'invoke'
+description: 'Invokes a native bridge command and resolves its result.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Invokes a native bridge command and resolves its result.
+Before using the bridge, make sure native supports webview-bundle.
+
+Throws a [`BridgeError`](/docs/references/api/bridge/bridge-error) on failure.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/invoke.ts#L27)
+
+```ts
+function invoke(name: string, params?: InvokeParams): Promise;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`Promise`
diff --git a/content/docs/references/api/bridge/is-bridge-error-data.mdx b/content/docs/references/api/bridge/is-bridge-error-data.mdx
new file mode 100644
index 0000000..d8eefe0
--- /dev/null
+++ b/content/docs/references/api/bridge/is-bridge-error-data.mdx
@@ -0,0 +1,20 @@
+---
+title: 'isBridgeErrorData'
+description: 'isBridgeErrorData — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/error.ts#L62)
+
+```ts
+function isBridgeErrorData(value: unknown): value is BridgeErrorData;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`value is BridgeErrorData`
diff --git a/content/docs/references/api/bridge/is-bridge-error.mdx b/content/docs/references/api/bridge/is-bridge-error.mdx
new file mode 100644
index 0000000..2d0dfbc
--- /dev/null
+++ b/content/docs/references/api/bridge/is-bridge-error.mdx
@@ -0,0 +1,20 @@
+---
+title: 'isBridgeError'
+description: 'isBridgeError — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/error.ts#L58)
+
+```ts
+function isBridgeError(e: unknown): e is BridgeError;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`e is BridgeError`
diff --git a/content/docs/references/api/bridge/list-bundle-item.mdx b/content/docs/references/api/bridge/list-bundle-item.mdx
new file mode 100644
index 0000000..613ffac
--- /dev/null
+++ b/content/docs/references/api/bridge/list-bundle-item.mdx
@@ -0,0 +1,19 @@
+---
+title: 'ListBundleItem'
+description: 'List item of bundles.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+List item of bundles.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/source.ts#L40)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/list-bundle-manifest-item.mdx b/content/docs/references/api/bridge/list-bundle-manifest-item.mdx
new file mode 100644
index 0000000..d72ccec
--- /dev/null
+++ b/content/docs/references/api/bridge/list-bundle-manifest-item.mdx
@@ -0,0 +1,29 @@
+---
+title: 'ListBundleManifestItem'
+description: 'List item of bundle manifests.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+List item of bundle manifests.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/source.ts#L26)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/list-remote-bundle-info.mdx b/content/docs/references/api/bridge/list-remote-bundle-info.mdx
new file mode 100644
index 0000000..96e4328
--- /dev/null
+++ b/content/docs/references/api/bridge/list-remote-bundle-info.mdx
@@ -0,0 +1,19 @@
+---
+title: 'ListRemoteBundleInfo'
+description: 'List item of remote bundles.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+List item of remote bundles.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/remote.ts#L4)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/meta.json b/content/docs/references/api/bridge/meta.json
new file mode 100644
index 0000000..8c58dfb
--- /dev/null
+++ b/content/docs/references/api/bridge/meta.json
@@ -0,0 +1,33 @@
+{
+ "title": "@wvb/bridge",
+ "pages": [
+ "---Classes---",
+ "bridge-error",
+ "---Functions---",
+ "invoke",
+ "is-bridge-error",
+ "is-bridge-error-data",
+ "---Interfaces---",
+ "bridge-error-data",
+ "bundle-manifest-metadata",
+ "bundle-source-version",
+ "bundle-update-info",
+ "invoke-params",
+ "list-bundle-item",
+ "list-bundle-manifest-item",
+ "list-remote-bundle-info",
+ "remote-api",
+ "remote-bundle-info",
+ "source-api",
+ "updater-api",
+ "---Type Aliases---",
+ "bridge-error-code",
+ "bundle-source-type",
+ "platform-type",
+ "---Variables---",
+ "platform",
+ "remote",
+ "source",
+ "updater"
+ ]
+}
diff --git a/content/docs/references/api/bridge/meta.ko.json b/content/docs/references/api/bridge/meta.ko.json
new file mode 100644
index 0000000..3947637
--- /dev/null
+++ b/content/docs/references/api/bridge/meta.ko.json
@@ -0,0 +1,33 @@
+{
+ "title": "@wvb/bridge",
+ "pages": [
+ "---클래스---",
+ "bridge-error",
+ "---함수---",
+ "invoke",
+ "is-bridge-error",
+ "is-bridge-error-data",
+ "---인터페이스---",
+ "bridge-error-data",
+ "bundle-manifest-metadata",
+ "bundle-source-version",
+ "bundle-update-info",
+ "invoke-params",
+ "list-bundle-item",
+ "list-bundle-manifest-item",
+ "list-remote-bundle-info",
+ "remote-api",
+ "remote-bundle-info",
+ "source-api",
+ "updater-api",
+ "---타입---",
+ "bridge-error-code",
+ "bundle-source-type",
+ "platform-type",
+ "---변수---",
+ "platform",
+ "remote",
+ "source",
+ "updater"
+ ]
+}
diff --git a/content/docs/references/api/bridge/platform-type.mdx b/content/docs/references/api/bridge/platform-type.mdx
new file mode 100644
index 0000000..9114235
--- /dev/null
+++ b/content/docs/references/api/bridge/platform-type.mdx
@@ -0,0 +1,14 @@
+---
+title: 'PlatformType'
+description: 'PlatformType — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/platform.ts#L5)
+
+## Type
+
+```ts
+type PlatformType = 'electron' | 'tauri' | 'deno' | 'android' | 'ios';
+```
diff --git a/content/docs/references/api/bridge/platform.mdx b/content/docs/references/api/bridge/platform.mdx
new file mode 100644
index 0000000..6b16a0f
--- /dev/null
+++ b/content/docs/references/api/bridge/platform.mdx
@@ -0,0 +1,21 @@
+---
+title: 'platform'
+description: 'platform — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/platform.ts#L74)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/remote-api.mdx b/content/docs/references/api/bridge/remote-api.mdx
new file mode 100644
index 0000000..c3e6333
--- /dev/null
+++ b/content/docs/references/api/bridge/remote-api.mdx
@@ -0,0 +1,48 @@
+---
+title: 'RemoteApi'
+description: 'RemoteApi — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/remote.ts#L43)
+
+## Methods
+
+ Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'channel?', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ downloadVersion: {
+ type: '(bundleName: string, version: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ getInfo: {
+ type: '(bundleName: string, channel?: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'channel?', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ listBundles: {
+ type: '(channel?: string) => Promise',
+ required: true,
+ parameters: [{ name: 'channel?', description: 'string' }],
+ returns: 'Promise',
+ },
+ }}
+/>
diff --git a/content/docs/references/api/bridge/remote-bundle-info.mdx b/content/docs/references/api/bridge/remote-bundle-info.mdx
new file mode 100644
index 0000000..7f66c2f
--- /dev/null
+++ b/content/docs/references/api/bridge/remote-bundle-info.mdx
@@ -0,0 +1,35 @@
+---
+title: 'RemoteBundleInfo'
+description: 'Information of a remote bundle.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Information of a remote bundle.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/remote.ts#L16)
+
+## Properties
+
+
diff --git a/content/docs/references/api/bridge/remote.mdx b/content/docs/references/api/bridge/remote.mdx
new file mode 100644
index 0000000..af7fb93
--- /dev/null
+++ b/content/docs/references/api/bridge/remote.mdx
@@ -0,0 +1,12 @@
+---
+title: 'remote'
+description: 'remote — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/remote.ts#L50)
+
+**Type:** [`RemoteApi`](/docs/references/api/bridge/remote-api)
+
+See [`RemoteApi`](/docs/references/api/bridge/remote-api) for the full member list.
diff --git a/content/docs/references/api/bridge/source-api.mdx b/content/docs/references/api/bridge/source-api.mdx
new file mode 100644
index 0000000..4f3065c
--- /dev/null
+++ b/content/docs/references/api/bridge/source-api.mdx
@@ -0,0 +1,104 @@
+---
+title: 'SourceApi'
+description: 'SourceApi — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/source.ts#L117)
+
+## Methods
+
+ Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ getRemoteBundleFilepath: {
+ type: '(bundleName: string, version: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ listBundles: {
+ type: '() => Promise',
+ required: true,
+ returns: 'Promise',
+ },
+ loadBuiltinMetadata: {
+ type: '(bundleName: string, version: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ loadRemoteMetadata: {
+ type: '(bundleName: string, version: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ loadVersion: {
+ type: '(bundleName: string) => Promise',
+ required: true,
+ parameters: [{ name: 'bundleName', description: 'string' }],
+ returns: 'Promise',
+ },
+ pruneRemoteBundles: {
+ type: '(bundleName: string) => Promise',
+ required: true,
+ parameters: [{ name: 'bundleName', description: 'string' }],
+ returns: 'Promise',
+ },
+ remoteRetainedVersions: {
+ type: '(bundleName: string) => Promise',
+ required: true,
+ parameters: [{ name: 'bundleName', description: 'string' }],
+ returns: 'Promise',
+ },
+ removeRemoteBundle: {
+ type: '(bundleName: string, version: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ resolveFilepath: {
+ type: '(bundleName: string) => Promise',
+ required: true,
+ parameters: [{ name: 'bundleName', description: 'string' }],
+ returns: 'Promise',
+ },
+ unloadDescriptor: {
+ type: '(bundleName: string) => Promise',
+ required: true,
+ parameters: [{ name: 'bundleName', description: 'string' }],
+ returns: 'Promise',
+ },
+ updateVersion: {
+ type: '(bundleName: string, version: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ }}
+/>
diff --git a/content/docs/references/api/bridge/source.mdx b/content/docs/references/api/bridge/source.mdx
new file mode 100644
index 0000000..b0353b3
--- /dev/null
+++ b/content/docs/references/api/bridge/source.mdx
@@ -0,0 +1,12 @@
+---
+title: 'source'
+description: 'source — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/source.ts#L132)
+
+**Type:** [`SourceApi`](/docs/references/api/bridge/source-api)
+
+See [`SourceApi`](/docs/references/api/bridge/source-api) for the full member list.
diff --git a/content/docs/references/api/bridge/updater-api.mdx b/content/docs/references/api/bridge/updater-api.mdx
new file mode 100644
index 0000000..53e827c
--- /dev/null
+++ b/content/docs/references/api/bridge/updater-api.mdx
@@ -0,0 +1,44 @@
+---
+title: 'UpdaterApi'
+description: 'UpdaterApi — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/updater.ts#L40)
+
+## Methods
+
+ Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version?', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ getUpdate: {
+ type: '(bundleName: string) => Promise',
+ required: true,
+ parameters: [{ name: 'bundleName', description: 'string' }],
+ returns: 'Promise',
+ },
+ install: {
+ type: '(bundleName: string, version: string) => Promise',
+ required: true,
+ parameters: [
+ { name: 'bundleName', description: 'string' },
+ { name: 'version', description: 'string' },
+ ],
+ returns: 'Promise',
+ },
+ listRemotes: {
+ type: '() => Promise',
+ required: true,
+ returns: 'Promise',
+ },
+ }}
+/>
diff --git a/content/docs/references/api/bridge/updater.mdx b/content/docs/references/api/bridge/updater.mdx
new file mode 100644
index 0000000..4bfb50d
--- /dev/null
+++ b/content/docs/references/api/bridge/updater.mdx
@@ -0,0 +1,12 @@
+---
+title: 'updater'
+description: 'updater — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/bridge/src/updater.ts#L47)
+
+**Type:** [`UpdaterApi`](/docs/references/api/bridge/updater-api)
+
+See [`UpdaterApi`](/docs/references/api/bridge/updater-api) for the full member list.
diff --git a/content/docs/references/api/cli/define-config.mdx b/content/docs/references/api/cli/define-config.mdx
new file mode 100644
index 0000000..af1d1bb
--- /dev/null
+++ b/content/docs/references/api/cli/define-config.mdx
@@ -0,0 +1,90 @@
+---
+title: 'defineConfig'
+description: 'defineConfig — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+## Overload 1
+
+```ts
+function defineConfig(config: Config): Config;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`Config`
+
+## Overload 2
+
+```ts
+function defineConfig(config: Promise): Promise;
+```
+
+## Parameters
+
+', required: true } }} />
+
+## Returns
+
+`Promise`
+
+## Overload 3
+
+```ts
+function defineConfig(config: ConfigInputFnObj): ConfigInputFnObj;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`ConfigInputFnObj`
+
+## Overload 4
+
+```ts
+function defineConfig(config: ConfigInputFnPromise): ConfigInputFnPromise;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`ConfigInputFnPromise`
+
+## Overload 5
+
+```ts
+function defineConfig(config: ConfigInputFn): ConfigInputFn;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`ConfigInputFn`
+
+## Overload 6
+
+```ts
+function defineConfig(config: ConfigInput): ConfigInput;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`ConfigInput`
diff --git a/content/docs/references/api/cli/index.mdx b/content/docs/references/api/cli/index.mdx
new file mode 100644
index 0000000..d833892
--- /dev/null
+++ b/content/docs/references/api/cli/index.mdx
@@ -0,0 +1,21 @@
+---
+title: '@wvb/cli'
+description: 'API reference for @wvb/cli.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+## Functions
+
+
+
+
+
+
+
+## Interfaces
+
+
+
+
+
diff --git a/content/docs/references/api/cli/inline-config.mdx b/content/docs/references/api/cli/inline-config.mdx
new file mode 100644
index 0000000..c1d74cf
--- /dev/null
+++ b/content/docs/references/api/cli/inline-config.mdx
@@ -0,0 +1,29 @@
+---
+title: 'InlineConfig'
+description: 'InlineConfig — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/cli/src/config.ts#L174)
+
+**Extends:** `Config`
+
+## Properties
+
+
diff --git a/content/docs/references/api/cli/load-config-file.mdx b/content/docs/references/api/cli/load-config-file.mdx
new file mode 100644
index 0000000..35dab16
--- /dev/null
+++ b/content/docs/references/api/cli/load-config-file.mdx
@@ -0,0 +1,25 @@
+---
+title: 'loadConfigFile'
+description: 'loadConfigFile — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/cli/src/config.ts#L26)
+
+```ts
+function loadConfigFile(filePath?: string, cwd: string = ...): Promise<{ config: Config; configFile: string; configFileDependencies: string[] } | null>;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`Promise<{ config: Config; configFile: string; configFileDependencies: string[] } | null>`
diff --git a/content/docs/references/api/cli/meta.json b/content/docs/references/api/cli/meta.json
new file mode 100644
index 0000000..82860db
--- /dev/null
+++ b/content/docs/references/api/cli/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "@wvb/cli",
+ "pages": [
+ "---Functions---",
+ "define-config",
+ "load-config-file",
+ "resolve-config",
+ "---Interfaces---",
+ "inline-config",
+ "resolved-config"
+ ]
+}
diff --git a/content/docs/references/api/cli/meta.ko.json b/content/docs/references/api/cli/meta.ko.json
new file mode 100644
index 0000000..05b96ba
--- /dev/null
+++ b/content/docs/references/api/cli/meta.ko.json
@@ -0,0 +1,12 @@
+{
+ "title": "@wvb/cli",
+ "pages": [
+ "---함수---",
+ "define-config",
+ "load-config-file",
+ "resolve-config",
+ "---인터페이스---",
+ "inline-config",
+ "resolved-config"
+ ]
+}
diff --git a/content/docs/references/api/cli/resolve-config.mdx b/content/docs/references/api/cli/resolve-config.mdx
new file mode 100644
index 0000000..a63f1ee
--- /dev/null
+++ b/content/docs/references/api/cli/resolve-config.mdx
@@ -0,0 +1,20 @@
+---
+title: 'resolveConfig'
+description: 'resolveConfig — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/cli/src/config.ts#L189)
+
+```ts
+function resolveConfig(inlineConfig: InlineConfig): Promise;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`Promise`
diff --git a/content/docs/references/api/cli/resolved-config.mdx b/content/docs/references/api/cli/resolved-config.mdx
new file mode 100644
index 0000000..b760505
--- /dev/null
+++ b/content/docs/references/api/cli/resolved-config.mdx
@@ -0,0 +1,26 @@
+---
+title: 'ResolvedConfig'
+description: 'ResolvedConfig — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/cli/src/config.ts#L178)
+
+**Extends:** `Readonly & { configFile: string | undefined; configFileDependencies: string[] | undefined; inlineConfig: InlineConfig; packageJson: PackageJson | undefined; root: string }>`
+
+## Properties
+
+
diff --git a/content/docs/references/api/config/builtin-bundle-matches.mdx b/content/docs/references/api/config/builtin-bundle-matches.mdx
new file mode 100644
index 0000000..1e1b213
--- /dev/null
+++ b/content/docs/references/api/config/builtin-bundle-matches.mdx
@@ -0,0 +1,18 @@
+---
+title: 'BuiltinBundleMatches'
+description: 'BuiltinBundleMatches — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/builtin.ts#L5)
+
+## Type
+
+```ts
+type BuiltinBundleMatches =
+ | string
+ | RegExp
+ | (string | RegExp)[]
+ | ((info: { name: string; version: string }) => boolean | Promise);
+```
diff --git a/content/docs/references/api/config/builtin-config.mdx b/content/docs/references/api/config/builtin-config.mdx
new file mode 100644
index 0000000..c678d73
--- /dev/null
+++ b/content/docs/references/api/config/builtin-config.mdx
@@ -0,0 +1,43 @@
+---
+title: 'BuiltinConfig'
+description: 'BuiltinConfig — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/builtin.ts#L54)
+
+## Properties
+
+
diff --git a/content/docs/references/api/config/builtin-download-config.mdx b/content/docs/references/api/config/builtin-download-config.mdx
new file mode 100644
index 0000000..9984571
--- /dev/null
+++ b/content/docs/references/api/config/builtin-download-config.mdx
@@ -0,0 +1,21 @@
+---
+title: 'BuiltinDownloadConfig'
+description: 'BuiltinDownloadConfig — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/builtin.ts#L11)
+
+## Properties
+
+
diff --git a/content/docs/references/api/config/builtin-local-target-config.mdx b/content/docs/references/api/config/builtin-local-target-config.mdx
new file mode 100644
index 0000000..55bda17
--- /dev/null
+++ b/content/docs/references/api/config/builtin-local-target-config.mdx
@@ -0,0 +1,34 @@
+---
+title: 'BuiltinLocalTargetConfig'
+description: 'Install builtin bundles from a local target.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Install builtin bundles from a local target.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/builtin.ts#L29)
+
+## Properties
+
+ string[] | Promise)',
+ description:
+ 'Workspace directory path to install builtin bundles. Glob pattern is supported. Automatically resolve the workspace directory which includes a config file inside. (e.g. wvb.config.ts)',
+ required: true,
+ },
+ }}
+/>
diff --git a/content/docs/references/api/config/builtin-remote-target-config.mdx b/content/docs/references/api/config/builtin-remote-target-config.mdx
new file mode 100644
index 0000000..0169f94
--- /dev/null
+++ b/content/docs/references/api/config/builtin-remote-target-config.mdx
@@ -0,0 +1,21 @@
+---
+title: 'BuiltinRemoteTargetConfig'
+description: 'Install builtin bundles from a remote target.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Install builtin bundles from a remote target.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/builtin.ts#L22)
+
+**Extends:** `Pick`
+
+## Properties
+
+
diff --git a/content/docs/references/api/config/builtin-target.mdx b/content/docs/references/api/config/builtin-target.mdx
new file mode 100644
index 0000000..362a2ae
--- /dev/null
+++ b/content/docs/references/api/config/builtin-target.mdx
@@ -0,0 +1,16 @@
+---
+title: 'BuiltinTarget'
+description: 'BuiltinTarget — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/builtin.ts#L50)
+
+## Type
+
+```ts
+type BuiltinTarget =
+ | ({ type: 'remote' } & BuiltinRemoteTargetConfig)
+ | ({ type: 'local' } & BuiltinLocalTargetConfig);
+```
diff --git a/content/docs/references/api/config/bundle-info-resolver-params.mdx b/content/docs/references/api/config/bundle-info-resolver-params.mdx
new file mode 100644
index 0000000..7764668
--- /dev/null
+++ b/content/docs/references/api/config/bundle-info-resolver-params.mdx
@@ -0,0 +1,26 @@
+---
+title: 'BundleInfoResolverParams'
+description: 'BundleInfoResolverParams — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/common.ts#L3)
+
+## Properties
+
+
diff --git a/content/docs/references/api/config/bundle-name-resolver.mdx b/content/docs/references/api/config/bundle-name-resolver.mdx
new file mode 100644
index 0000000..86bd093
--- /dev/null
+++ b/content/docs/references/api/config/bundle-name-resolver.mdx
@@ -0,0 +1,25 @@
+---
+title: 'BundleNameResolver'
+description: 'Bundle name resolver to determine the name of the bundle to be used in remote or builtin.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+Bundle name resolver to determine the name of the bundle to be used in remote or builtin.
+
+From "package.json", will use the "name" field from "package.json",
+if not specified, will be throw error.
+If "name" is with a scope prefix, it will be removed.
+
+Or, specify a custom bundle name string or a function that returns a bundle name string.
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/common.ts#L47)
+
+## Type
+
+```ts
+type BundleNameResolver =
+ | { from: 'package.json' }
+ | string
+ | ((params: BundleInfoResolverParams) => string | Promise);
+```
diff --git a/content/docs/references/api/config/config-input-fn-obj.mdx b/content/docs/references/api/config/config-input-fn-obj.mdx
new file mode 100644
index 0000000..c711acb
--- /dev/null
+++ b/content/docs/references/api/config/config-input-fn-obj.mdx
@@ -0,0 +1,14 @@
+---
+title: 'ConfigInputFnObj'
+description: 'ConfigInputFnObj — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/config.ts#L6)
+
+## Type
+
+```ts
+type ConfigInputFnObj = () => Config;
+```
diff --git a/content/docs/references/api/config/config-input-fn-promise.mdx b/content/docs/references/api/config/config-input-fn-promise.mdx
new file mode 100644
index 0000000..4ef9813
--- /dev/null
+++ b/content/docs/references/api/config/config-input-fn-promise.mdx
@@ -0,0 +1,14 @@
+---
+title: 'ConfigInputFnPromise'
+description: 'ConfigInputFnPromise — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/config.ts#L7)
+
+## Type
+
+```ts
+type ConfigInputFnPromise = () => Promise;
+```
diff --git a/content/docs/references/api/config/config-input-fn.mdx b/content/docs/references/api/config/config-input-fn.mdx
new file mode 100644
index 0000000..addd04f
--- /dev/null
+++ b/content/docs/references/api/config/config-input-fn.mdx
@@ -0,0 +1,14 @@
+---
+title: 'ConfigInputFn'
+description: 'ConfigInputFn — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/config.ts#L8)
+
+## Type
+
+```ts
+type ConfigInputFn = () => Config | Promise;
+```
diff --git a/content/docs/references/api/config/config-input.mdx b/content/docs/references/api/config/config-input.mdx
new file mode 100644
index 0000000..50b0b21
--- /dev/null
+++ b/content/docs/references/api/config/config-input.mdx
@@ -0,0 +1,19 @@
+---
+title: 'ConfigInput'
+description: 'ConfigInput — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/config.ts#L10)
+
+## Type
+
+```ts
+type ConfigInput =
+ | Config
+ | Promise
+ | ConfigInputFnObj
+ | ConfigInputFnPromise
+ | ConfigInputFn;
+```
diff --git a/content/docs/references/api/config/config.mdx b/content/docs/references/api/config/config.mdx
new file mode 100644
index 0000000..08f3f4a
--- /dev/null
+++ b/content/docs/references/api/config/config.mdx
@@ -0,0 +1,26 @@
+---
+title: 'Config'
+description: 'Config — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/config.ts#L27)
+
+## Properties
+
+
diff --git a/content/docs/references/api/config/define-config.mdx b/content/docs/references/api/config/define-config.mdx
new file mode 100644
index 0000000..16ee46c
--- /dev/null
+++ b/content/docs/references/api/config/define-config.mdx
@@ -0,0 +1,92 @@
+---
+title: 'defineConfig'
+description: 'defineConfig — API reference.'
+---
+
+{/* This page is auto-generated from TypeScript + JSDoc by scripts/generate-references.ts — do not edit by hand. */}
+
+[View source ↗](https://github.com/webview-bundle/webview-bundle/blob/262b4d90c5f036ab9d52086913752b990875ac84/packages/config/src/config.ts#L17)
+
+## Overload 1
+
+```ts
+function defineConfig(config: Config): Config;
+```
+
+## Parameters
+
+
+
+## Returns
+
+`Config`
+
+## Overload 2
+
+```ts
+function defineConfig(config: Promise): Promise;
+```
+
+## Parameters
+
+