";
+
+const webview = new BrowserView({
+ html: htmlString,
+
+});
+```
+
+### partition
+
+Partitions allow you to separate the browser session. Things like cookies and so on. For example if you have two BrowserViews with the same partition and log into gmail in one, the other will also be logged into gmail. If you use two different partitions then you could log into a different gmail account in each BrowserView.
+
+```javascript
+// ephemeral partition. If you close and reopen your app
+// even if you use the same partition name it will not
+// have persisted.
+const webview = new BrowserView({
+ partition: "partition1",
+});
+
+// To make partitions persistent just prefix it with `persist:`
+const webview = new BrowserView({
+ partition: "persist:partition1",
+});
+```
+
+### preload
+
+Set a preload script for the window's default BrowserView to render after html is parsed but before any other javascript is executed. The preload script will be run after any navigation before the page's scripts are run.
+
+You can use either inline javascript or a url.
+
+```javascript
+// Use any url on the internet
+const webview = new BrowserView({
+ preload: "https://electrobun.dev/some/remote/file.js",
+});
+
+// or use the views:// preload scheme to load local
+// content that you've bundled with your app.
+
+const webview = new BrowserView({
+ preload: "views://somebundledview/preloadscript.js",
+});
+
+// or use inline javascript
+
+const webview = new BrowserView({
+ preload: "document.body.innerHTML = 'Hello world'; console.log('hello console')",
+});
+```
+
+### rpc
+
+The RPC property allows you to establish RPC (remote procedure calls) between the bun process and this window's default BrowserView. In other words it lets you define functions that execute in the bun process that are callable and return a value back to the browser process and visa versa.
+
+These RPC functions are asynchronous.
+
+`src/shared/types.ts`
+
+```typescript
+export type MyWebviewRPCType = {
+ // functions that execute in the main process
+ bun: RPCSchema<{
+ requests: {
+ someBunFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToBun: {
+ msg: string;
+ };
+ };
+ }>;
+ // functions that execute in the browser context
+ webview: RPCSchema<{
+ requests: {
+ someWebviewFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToWebview: {
+ msg: string;
+ };
+ };
+ }>;
+};
+```
+
+`src/bun/index.ts`
+
+```typescript
+import { BrowserView } from "electrobun/bun";
+import { type MyWebviewRPCType } from "../shared/types";
+
+// Create an RPC object for the bun handlers with the shared type
+const myWebviewRPC = BrowserView.defineRPC({
+ maxRequestTime: 5000,
+ handlers: {
+ requests: {
+ someBunFunction: ({ a, b }) => {
+ console.log(`browser asked me to do math with: ${a} and ${b}`);
+ return a + b;
+ },
+ },
+ // When the browser sends a message we can handle it
+ // in the main bun process
+ messages: {
+ "*": (messageName, payload) => {
+ console.log("global message handler", messageName, payload);
+ },
+ logToBun: ({ msg }) => {
+ console.log("Log to bun: ", msg);
+ },
+ },
+ },
+});
+
+// Pass the RPC object to the BrowserWindow, which will set it
+// on the window's default BrowserView
+const webview = new BrowserView({
+ title: "my window",
+ url: "views://mainview/index.html",
+ frame: {
+ width: 1800,
+ height: 600,
+ x: 2000,
+ y: 2000,
+ },
+ rpc: myWebviewRPC,
+});
+
+// ... later on
+
+// Note: These RPC methods will inherit types from the shared type
+
+// Call a browser function from bun
+const answer = await webview.rpc.someWebviewFunction(4, 6);
+
+// Send a message to the BrowserView from bun
+webview.rpc.logToBrowser("my message");
+```
+
+The above code snippet shows defining the bun process rpc handlers and calling the browser process handlers from bun. To see how to handle the Browser context code take a look at the [Browser API](/electrobun/docs/apis/browser-view).
+
+### sandbox
+
+When set to `true`, the BrowserView runs in sandbox mode. This is a security feature that disables RPC (remote procedure calls) and only allows event emission. Use sandbox mode for untrusted content like remote URLs.
+
+```javascript
+// Sandboxed BrowserView for untrusted content
+const webview = new BrowserView({
+ url: "https://untrusted-site.com",
+ sandbox: true, // Disables RPC, events still work
+});
+
+// Events work normally in sandbox mode
+webview.on("dom-ready", () => {
+ console.log("Page loaded in sandboxed view");
+});
+
+webview.on("will-navigate", (event) => {
+ console.log("Navigation:", event.data.detail);
+});
+```
+
+See the [BrowserWindow sandbox documentation](/electrobun/docs/apis/browser-window) for a complete overview of the security model and use cases.
+
+## Static Methods
+
+### BrowserView.getAll
+
+Get a list of references to all BrowserViews. This includes the default Browserviews created via `new BrowserWindow`, Browserviews created as nested OOPIFs via [WebviewTags](/electrobun/docs/apis/browser/electrobun-webview-tag), and BrowserViews that you create manually via `new BrowserView()` for advanced use cases.
+
+```typescript
+import { BrowserView } from "electrobun/bun";
+
+const webviews = BrowserView.getAll();
+```
+
+### BrowserView.getById
+
+Get a specific BrowserView by id. This includes the default Browserviews created via `new BrowserWindow`, Browserviews created as nested OOPIFs via [WebviewTags](/electrobun/docs/apis/browser/electrobun-webview-tag), and BrowserViews that you create manually via `new BrowserView()` for advanced use cases.
+
+```typescript
+import { BrowserWindow, BrowserView } from "electrobun/bun";
+
+const win = new BrowserWindow({
+ title: "my url window",
+ url: "views://mainview/index.html",
+ frame: {
+ width: 1800,
+ height: 600,
+ x: 2000,
+ y: 2000,
+ },
+});
+
+const webview = BrowserView.getById(win.webview.id);
+```
+
+### BrowserView.defineRPC
+
+Whenever you create a BrowserWindow with async RPC you'll use this static method to create an RPC instance.
+
+`src/shared/types.ts`
+
+```typescript
+export type MyWebviewRPCType = {
+ // functions that execute in the main process
+ bun: RPCSchema<{
+ requests: {
+ someBunFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToBun: {
+ msg: string;
+ };
+ };
+ }>;
+ // functions that execute in the browser context
+ webview: RPCSchema<{
+ requests: {
+ someWebviewFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToWebview: {
+ msg: string;
+ };
+ };
+ }>;
+};
+```
+
+`src/bun/index.ts`
+
+```typescript
+import { BrowserWindow, BrowserView } from "electrobun/bun";
+import { type MyWebviewRPCType } from "../shared/types";
+
+// Create an RPC object for the bun handlers with the shared type
+const myWebviewRPC = BrowserView.defineRPC({
+ maxRequestTime: 5000,
+ handlers: {
+ requests: {
+ someBunFunction: ({ a, b }) => {
+ console.log(`browser asked me to do math with: ${a} and ${b}`);
+ return a + b;
+ },
+ },
+ // When the browser sends a message we can handle it
+ // in the main bun process
+ messages: {
+ "*": (messageName, payload) => {
+ console.log("global message handler", messageName, payload);
+ },
+ logToBun: ({ msg }) => {
+ console.log("Log to bun: ", msg);
+ },
+ },
+ },
+});
+```
+
+## Methods
+
+### executeJavascript
+
+Execute arbitrary js in the webview. Unlike a `preload` script that you would typically set as a [BrowserWindow](/electrobun/docs/apis/browser-window) configuratino option, `executeJavascript()` can be called at any time.
+
+```typescript
+webview.executeJavascript('document.body.innerHTML += "hello"');
+```
+
+### loadURL
+
+Load a url into the webview. This will navigate the webview and trigger navigation events.
+
+```typescript
+webview.loadURL("https://electrobun.dev");
+
+// or
+
+webview.loadURL("views://mainview/somepage.html");
+```
+
+### loadHTML
+
+Load html directly into the webview. This will completely replace any content that was previously loaded and trigger navigation events.
+
+```typescript
+const htmlString =
+ "
hello world
";
+
+webview.loadHTML({
+ html: htmlString,
+});
+```
+
+### setNavigationRules
+
+Set an allow/block list of URL patterns to control which URLs the webview can navigate to. Rules are evaluated synchronously in native code for maximum performance - no callback to the Bun process is needed.
+
+**Rule Format:**
+
+* Rules use glob-style wildcards where `*` matches any characters
+* Prefix a rule with `^` to make it a block rule
+* Rules without the `^` prefix are allow rules
+* Rules are evaluated top-to-bottom, last matching rule wins
+* If no rule matches, navigation is allowed by default
+
+```typescript
+// Block everything except specific domains
+webview.setNavigationRules([
+ "^*", // Block everything by default
+ "*://en.wikipedia.org/*", // Allow Wikipedia
+ "*://upload.wikimedia.org/*", // Allow Wikipedia images
+]);
+
+// Allow everything except specific domains
+webview.setNavigationRules([
+ "^*://malware.com/*", // Block malware.com
+ "^http://*", // Block all non-HTTPS
+]);
+
+// Complex rules - block admin paths even on allowed domains
+webview.setNavigationRules([
+ "^*", // Block everything by default
+ "https://*.myapp.com/*", // Allow myapp.com subdomains
+ "https://api.trusted.com/*", // Allow trusted API
+ "^*/admin/*", // But block admin paths
+]);
+
+// Clear all rules (allow all navigation)
+webview.setNavigationRules([]);
+```
+
+Navigation rules are evaluated entirely in native code without calling back to the Bun process, making them very fast. The `will-navigate` event will still fire with an `allowed` property indicating whether the navigation was permitted.
+
+### findInPage
+
+Search for text in the webview content. Highlights all matches and scrolls to the first (or next) match.
+
+```typescript
+// Basic search - find "hello" moving forward
+webview.findInPage("hello");
+
+// Search backwards through matches
+webview.findInPage("hello", { forward: false });
+
+// Case-sensitive search
+webview.findInPage("Hello", { matchCase: true });
+
+// Combined options
+webview.findInPage("query", {
+ forward: true, // Search direction (default: true)
+ matchCase: false // Case sensitivity (default: false)
+});
+```
+
+Call `findInPage` repeatedly with the same search text to navigate through matches. Use `stopFindInPage()` to clear the search highlighting.
+
+### stopFindInPage
+
+Clear the find-in-page search highlighting and results.
+
+```typescript
+// Clear search highlighting
+webview.stopFindInPage();
+```
+
+### openDevTools
+
+Open the DevTools window for this webview.
+
+```typescript
+// Open DevTools for this webview
+webview.openDevTools();
+```
+
+### closeDevTools
+
+Close (or hide) the DevTools window for this webview.
+
+```typescript
+// Close DevTools for this webview
+webview.closeDevTools();
+```
+
+### toggleDevTools
+
+Toggle the DevTools window for this webview.
+
+```typescript
+// Toggle DevTools for this webview
+webview.toggleDevTools();
+```
+
+DevTools behavior varies by renderer and platform. On macOS with CEF, Electrobun uses remote DevTools and opens a separate window per webview (including OOPIFs). Closing the window hides it so it can be re-opened safely.
+
+### on(name, handler)
+
+Subscribe to BrowserWindow events (see below)
+
+## Properties
+
+### id
+
+This is the webview's id.
+
+### hostWebviewId
+
+This is only used for BrowserViews created using the [WebviewTag](/electrobun/docs/apis/browser/electrobun-webview-tag) as a nested OOPIF. It's the id of the parent BrowserView.
+
+### rpc
+
+Once you've configured async rpc for a webview (typically via new BrowserWindow and Webview.defineRPC()) you'll use the rpc property to access the generated typed request and message methods.
+
+```typescript
+// ... configure BrowserWindow with BrowserView.defineRPC and new BrowserWindow()
+
+// Call a browser function from bun
+const answer = await webview.rpc.someWebviewFunction(4, 6);
+
+// Send a message to the BrowserView from bun
+webview.rpc.logToBrowser("my message");
+```
+
+### rpc.request.evaluateJavascriptWithResponse
+
+Electrobun includes a built-in RPC method that is automatically available on any webview with RPC configured. This allows you to execute arbitrary JavaScript in the webview and get a result back, without needing to define a custom RPC handler.
+
+```typescript
+// Execute JavaScript and get a result back
+const title = await webview.rpc.request.evaluateJavascriptWithResponse({
+ script: "document.title"
+});
+
+// Works with expressions
+const sum = await webview.rpc.request.evaluateJavascriptWithResponse({
+ script: "2 + 2"
+});
+
+// Also handles async code - Promises are automatically awaited
+const data = await webview.rpc.request.evaluateJavascriptWithResponse({
+ script: "fetch('/api/data').then(r => r.json())"
+});
+```
+
+This built-in method is useful for quick one-off JavaScript execution. For frequently used operations, consider defining typed RPC handlers instead for better type safety and maintainability.
+
+## Events
+
+### will-navigate
+
+Fired when a webview is about to navigate. The event includes an `allowed` property indicating whether the navigation was permitted by the navigation rules (set via `setNavigationRules()`).
+
+```javascript
+event.data = {
+ url: string, // The URL being navigated to
+ allowed: boolean // Whether navigation rules permit this URL
+}
+```
+
+**Example - Monitor navigation decisions:**
+
+```javascript
+// Set up navigation rules
+webview.setNavigationRules([
+ "^*", // Block everything by default
+ "*://en.wikipedia.org/*", // Allow Wikipedia
+]);
+
+// Listen for navigation attempts
+webview.on("will-navigate", (e) => {
+ console.log("Navigation to:", e.data.url);
+ console.log("Allowed by rules:", e.data.allowed);
+
+ if (!e.data.allowed) {
+ // Navigation was blocked - you could show a message to the user
+ console.log("Navigation blocked by rules");
+ }
+});
+```
+
+Navigation decisions are made synchronously in native code based on the rules set via `setNavigationRules()`. The `will-navigate` event is informational - by the time it fires, the allow/block decision has already been made. To control navigation, use `setNavigationRules()` to update the rules.
+
+### did-navigate
+
+After a webview navigates.
+
+```javascript
+event.data = {
+ detail: string // the url
+}
+```
+
+### did-navigate-in-page
+
+After an in-page navigation.
+
+```javascript
+event.data = {
+ detail: string // the url
+}
+```
+
+### did-commit-navigation
+
+The webview has started to receive content for the main frame after a navigation.
+
+```javascript
+event.data = {
+ detail: string // the url
+}
+```
+
+### dom-ready
+
+The dom ready event is fired from the browser context.
+
+### new-window-open
+
+The browser context is attempting to open a new window. For example a popup or a user right clicked and selected "open in new window".
+
+```javascript
+event.detail = string | {
+ url: string;
+ isCmdClick: boolean;
+ modifierFlags?: number;
+ targetDisposition?: number;
+ userGesture?: boolean;
+}
+```
+
+**Properties:**
+
+* `url` - The URL that should be opened in the new window
+* `isCmdClick` - Whether the Command key (macOS) or Ctrl key was held during the click
+* `modifierFlags` - Additional modifier flags for the event (optional)
+* `targetDisposition` - Target disposition indicating how the new window should be opened (optional)
+* `userGesture` - Whether this new window request was triggered by a user gesture (optional)
+
+**Example:**
+
+```typescript
+webview.on("new-window-open", (event) => {
+ if (typeof event.detail === 'object') {
+ console.log("New window requested:", event.detail.url);
+ console.log("Command/Ctrl key held:", event.detail.isCmdClick);
+ console.log("User gesture:", event.detail.userGesture);
+ } else {
+ // Legacy string format
+ console.log("New window requested:", event.detail);
+ }
+});
+```
+
+### download-started
+
+Fired when a file download begins in the webview.
+
+```javascript
+event.detail = {
+ filename: string, // The name of the file being downloaded
+ path: string // The full path where the file will be saved
+}
+```
+
+**Example:**
+
+```typescript
+webview.on("download-started", (event) => {
+ console.log("Download started:", event.detail.filename);
+ console.log("Saving to:", event.detail.path);
+});
+```
+
+### download-progress
+
+Fired periodically during a file download to report progress.
+
+```javascript
+event.detail = {
+ progress: number // Download progress as a percentage (0-100)
+}
+```
+
+**Example:**
+
+```typescript
+webview.on("download-progress", (event) => {
+ console.log(`Download progress: ${event.detail.progress}%`);
+});
+```
+
+### download-completed
+
+Fired when a file download completes successfully.
+
+```javascript
+event.detail = {
+ filename: string, // The name of the downloaded file
+ path: string // The full path where the file was saved
+}
+```
+
+**Example:**
+
+```typescript
+webview.on("download-completed", (event) => {
+ console.log("Download completed:", event.detail.filename);
+ console.log("Saved to:", event.detail.path);
+});
+```
+
+### download-failed
+
+Fired when a file download fails or is canceled.
+
+```javascript
+event.detail = {
+ filename: string, // The name of the file that failed to download
+ path: string, // The path where the file would have been saved
+ error: string // Error message describing why the download failed
+}
+```
+
+**Example:**
+
+```typescript
+webview.on("download-failed", (event) => {
+ console.log("Download failed:", event.detail.filename);
+ console.log("Error:", event.detail.error);
+});
+```
\ No newline at end of file
diff --git a/docs/apis_browser-window.md b/docs/apis_browser-window.md
new file mode 100644
index 0000000..5562013
--- /dev/null
+++ b/docs/apis_browser-window.md
@@ -0,0 +1,676 @@
+# BrowserWindow
+
+> Create and control browser windows
+
+```typescript
+// in the main process
+import { BrowserWindow } from "electrobun/bun";
+
+const win = new BrowserWindow({
+ title: "my url window",
+ frame: {
+ width: 1800,
+ height: 600,
+ x: 2000,
+ y: 2000,
+ },
+ url: "views://mainview/index.html",
+});
+```
+
+## Constructor Options
+
+### title
+
+Set the title of the window.
+
+```javascript
+const win = new BrowserWindow({
+ title: "my url window",
+});
+```
+
+### frame
+
+Set the window dimensions.
+
+```javascript
+const win = new BrowserWindow({
+ frame: {
+ width: 1800,
+ height: 600,
+ x: 2000,
+ y: 2000,
+ },
+});
+```
+
+### styleMask
+
+This controls the OSX window appearance and functionality. You can set the following:
+
+```javascript
+const win = new BrowserWindow({
+ title: "my url window",
+ url: "views://mainview/index.html",
+ frame: {
+ width: 1800,
+ height: 600,
+ x: 2000,
+ y: 2000,
+ },
+ styleMask: {
+ // These are the current defaults
+ Borderless: false,
+ Titled: true,
+ Closable: true,
+ Miniaturizable: true,
+ Resizable: true,
+ UnifiedTitleAndToolbar: false,
+ FullScreen: false,
+ FullSizeContentView: false,
+ UtilityWindow: false,
+ DocModalWindow: false,
+ NonactivatingPanel: false,
+ HUDWindow: false,
+ }
+});
+```
+
+### titleBarStyle
+
+Controls the window's title bar appearance. This option works across all platforms (macOS, Windows, and Linux).
+
+Available values:
+
+* `"default"` - Normal title bar with native window controls (close, minimize, maximize buttons)
+* `"hidden"` - No title bar, no native window controls. Use this for fully custom window chrome where you implement your own title bar and window controls in HTML/CSS
+* `"hiddenInset"` - Transparent title bar with inset native controls. On macOS, this shows the traffic light buttons overlaid on your content. On other platforms, this behaves similarly to `hidden`
+
+```javascript
+// Default title bar
+const win = new BrowserWindow({
+ title: "Standard Window",
+ url: "views://mainview/index.html",
+ titleBarStyle: "default",
+});
+
+// Hidden title bar for fully custom chrome
+const customWin = new BrowserWindow({
+ title: "Custom Titlebar",
+ url: "views://mainview/index.html",
+ titleBarStyle: "hidden",
+});
+
+// Hidden inset - transparent titlebar with traffic lights (macOS)
+const insetWin = new BrowserWindow({
+ title: "Inset Window",
+ url: "views://mainview/index.html",
+ titleBarStyle: "hiddenInset",
+});
+```
+
+When using `titleBarStyle: "hidden"` or `"hiddenInset"`, you'll typically want to create a custom title bar in your HTML. See the [Draggable Regions](/electrobun/docs/apis/browser/draggable-regions) documentation for making your custom title bar draggable, and use the window control methods (`close()`, `minimize()`, `maximize()`) to implement custom window buttons.
+
+The `titleBarStyle` option automatically configures the underlying `styleMask` properties. When set to `"hiddenInset"`, it forces `Titled: true` and `FullSizeContentView: true`. When set to `"hidden"`, it forces `Titled: false` and `FullSizeContentView: true`.
+
+### transparent
+
+When set to `true`, the window background becomes transparent, allowing you to create non-rectangular windows, floating widgets, or windows with rounded corners and drop shadows.
+
+```javascript
+const floatingWidget = new BrowserWindow({
+ title: "Floating Widget",
+ url: "views://widget/index.html",
+ frame: { width: 300, height: 200, x: 100, y: 100 },
+ titleBarStyle: "hidden",
+ transparent: true,
+});
+```
+
+For transparency to work correctly, your HTML/CSS must also have a transparent background:
+
+```css
+/* In your CSS */
+html, body {
+ background: transparent;
+}
+
+/* Create a visible floating card */
+.floating-card {
+ background: rgba(30, 30, 50, 0.95);
+ border-radius: 16px;
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+}
+```
+
+Transparent windows are typically combined with `titleBarStyle: "hidden"` to achieve floating widget effects. The `transparent` option works across all platforms and with both the native WebKit and CEF renderers.
+
+### sandbox
+
+When set to `true`, the webview runs in sandbox mode. This disables RPC (remote procedure calls) and only allows event emission. Use sandbox mode for displaying untrusted content like remote URLs where you want to prevent malicious sites from accessing internal APIs.
+
+```javascript
+// Sandboxed window for untrusted content
+const externalBrowser = new BrowserWindow({
+ title: "External Browser",
+ url: "https://example.com",
+ sandbox: true, // Disable RPC for security
+});
+
+// Events still work in sandbox mode
+externalBrowser.webview.on("dom-ready", () => {
+ console.log("Page loaded");
+});
+
+externalBrowser.webview.on("will-navigate", (event) => {
+ console.log("Navigating to:", event.data.detail);
+});
+```
+
+**Security Model:**
+
+* **Events work** - Navigation events (`will-navigate`, `did-navigate`, `dom-ready`, etc.) still fire normally
+* **RPC is disabled** - The `rpc` option is ignored; no function calls between browser and main process
+* **No webview tags** - Sandboxed webviews cannot create nested `` elements (OOPIFs)
+* **Navigation controls work** - You can still use `loadURL()`, `goBack()`, `goForward()`, etc.
+
+Sandbox mode uses a minimal preload script that only sets up event emission. This prevents any code in the webview from communicating with your main process beyond basic lifecycle events.
+
+#### When to use sandbox mode
+
+* Loading external/untrusted URLs (e.g., user-provided links, third-party content)
+* Building a web browser or content viewer that displays arbitrary websites
+* Embedding documentation or help content from external sources
+* Any scenario where you want the webview isolated from your application's internals
+
+#### Using sandbox with tag
+
+You can also create sandboxed nested webviews using the `sandbox` attribute:
+
+```html
+
+
+```
+
+**Info:** The following options are used to instantiate the default BrowserView.
+
+### url
+
+Set the initial url for the window's default BrowserView to navigate to when it opens.
+
+```javascript
+// Use any url on the internet
+const win = new BrowserWindow({
+ url: "https://electrobun.dev",
+});
+
+// or use the views:// url scheme to load local
+// content that you've bundled with your app.
+
+const win = new BrowserWindow({
+ url: "views://mainview/index.html",
+});
+```
+
+### html
+
+Set an html string for the window's default BrowserView to load when it opens. Anything that would be valid in an html file including javascript and css can be used. Use this instead of setting the `url` property.
+
+```javascript
+const htmlString = "
hello world
";
+
+const win = new BrowserWindow({
+ html: htmlString,
+
+});
+```
+
+### partition
+
+Partitions allow you to separate the browser session. Things like cookies and so on. For example if you have two BrowserViews with the same partition and log into gmail in one, the other will also be logged into gmail. If you use two different partitions then you could log into a different gmail account in each BrowserView.
+
+```javascript
+// ephemeral partition. If you close and reopen your app
+// even if you use the same partition name it will not
+// have persisted.
+const win = new BrowserWindow({
+ partition: "partition1",
+});
+
+// To make partitions persistent just prefix it with `persist:`
+const win = new BrowserWindow({
+ partition: "persist:partition1",
+});
+```
+
+### preload
+
+Set a preload script for the window's default BrowserView to render after html is parsed but before any other javascript is executed. The preload script will be run after any navigation before the page's scripts are run.
+
+You can use either inline javascript or a url.
+
+```javascript
+// Use any url on the internet
+const win = new BrowserWindow({
+ preload: "https://electrobun.dev/some/remote/file.js",
+});
+
+// or use the views:// preload scheme to load local
+// content that you've bundled with your app.
+
+const win = new BrowserWindow({
+ preload: "views://somebundledview/preloadscript.js",
+});
+
+// or use inline javascript
+
+const win = new BrowserWindow({
+ preload: "document.body.innerHTML = 'Hello world'; console.log('hello console')",
+});
+```
+
+### rpc
+
+The RPC property allows you to establish RPC (remote procedure calls) between the bun process and this window's default BrowserView. In other words it lets you define functions that execute in the bun process that are callable and return a value back to the browser process and visa versa.
+
+These RPC functions are asynchronous.
+
+`src/shared/types.ts`
+
+```typescript
+export type MyWebviewRPCType = {
+ // functions that execute in the main process
+ bun: RPCSchema<{
+ requests: {
+ someBunFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToBun: {
+ msg: string;
+ };
+ };
+ }>;
+ // functions that execute in the browser context
+ webview: RPCSchema<{
+ requests: {
+ someWebviewFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToWebview: {
+ msg: string;
+ };
+ };
+ }>;
+};
+```
+
+`src/bun/index.ts`
+
+```typescript
+import { BrowserWindow, BrowserView } from "electrobun/bun";
+import { type MyWebviewRPCType } from "../shared/types";
+
+// Create an RPC object for the bun handlers with the shared type
+const myWebviewRPC = BrowserView.defineRPC({
+ maxRequestTime: 5000,
+ handlers: {
+ requests: {
+ someBunFunction: ({ a, b }) => {
+ console.log(`browser asked me to do math with: ${a} and ${b}`);
+ return a + b;
+ },
+ },
+ // When the browser sends a message we can handle it
+ // in the main bun process
+ messages: {
+ "*": (messageName, payload) => {
+ console.log("global message handler", messageName, payload);
+ },
+ logToBun: ({ msg }) => {
+ console.log("Log to bun: ", msg);
+ },
+ },
+ },
+});
+
+// Pass the RPC object to the BrowserWindow, which will set it
+// on the window's default BrowserView
+const win = new BrowserWindow({
+ title: "my window",
+ url: "views://mainview/index.html",
+ frame: {
+ width: 1800,
+ height: 600,
+ x: 2000,
+ y: 2000,
+ },
+ rpc: myWebviewRPC,
+});
+
+// ... later on
+
+// Note: These RPC methods will inherit types from the shared type
+
+// Call a browser function from bun
+const answer = await win.webview.rpc.someWebviewFunction(4, 6);
+
+// Send a message to the BrowserView from bun
+win.webview.rpc.logToBrowser("my message");
+```
+
+**Info:** The above code snippet shows defining the bun process rpc handlers and calling the browser process handlers from bun. To see how to handle the Browser context code take a look at the [Electroview Class (Browser API)](/electrobun/docs/apis/browser/electroview-class).
+
+## Properties
+
+### webview
+
+This is a getter for the window's default [BrowserView](/electrobun/docs/apis/browser-view).
+
+```javascript
+const win = new BrowserWindow({
+ ...
+});
+
+const defaultWebview = win.webview;
+```
+
+## Methods
+
+### setTitle
+
+Change the window title:
+
+```javascript
+win.setTitle('new title')
+```
+
+### close
+
+Close a window.
+
+```javascript
+win.close();
+```
+
+### focus
+
+Bring a window to the front and give it focus.
+
+```javascript
+win.focus();
+```
+
+### minimize / unminimize / isMinimized
+
+Control and check the minimized state of a window.
+
+```javascript
+// Minimize the window
+win.minimize();
+
+// Restore from minimized state
+win.unminimize();
+
+// Check if window is minimized
+if (win.isMinimized()) {
+ console.log("Window is minimized");
+}
+```
+
+### maximize / unmaximize / isMaximized
+
+Control and check the maximized state of a window. On macOS, this uses the "zoom" functionality which fills the screen while keeping the menu bar visible.
+
+```javascript
+// Maximize the window
+win.maximize();
+
+// Restore from maximized state
+win.unmaximize();
+
+// Check if window is maximized
+if (win.isMaximized()) {
+ console.log("Window is maximized");
+}
+```
+
+### setFullScreen / isFullScreen
+
+Control and check the fullscreen state of a window. Fullscreen mode hides the title bar and dock/taskbar.
+
+```javascript
+// Enter fullscreen mode
+win.setFullScreen(true);
+
+// Exit fullscreen mode
+win.setFullScreen(false);
+
+// Check if window is in fullscreen
+if (win.isFullScreen()) {
+ console.log("Window is in fullscreen mode");
+}
+
+// Toggle fullscreen
+win.setFullScreen(!win.isFullScreen());
+```
+
+### setAlwaysOnTop / isAlwaysOnTop
+
+Control and check whether a window stays above all other windows. Useful for floating tools, overlays, or picture-in-picture style windows.
+
+```javascript
+// Make window always on top
+win.setAlwaysOnTop(true);
+
+// Disable always on top
+win.setAlwaysOnTop(false);
+
+// Check if window is always on top
+if (win.isAlwaysOnTop()) {
+ console.log("Window is pinned above other windows");
+}
+
+// Toggle always on top
+win.setAlwaysOnTop(!win.isAlwaysOnTop());
+```
+
+### setPosition(x, y)
+
+Move the window to a specific position on screen. Coordinates use a top-left origin (0, 0 is the top-left corner of the screen).
+
+```javascript
+// Move window to position (200, 150)
+win.setPosition(200, 150);
+
+// Center window on screen (approximate)
+const screenWidth = 1920; // Get actual screen dimensions
+const screenHeight = 1080;
+const frame = win.getFrame();
+win.setPosition(
+ (screenWidth - frame.width) / 2,
+ (screenHeight - frame.height) / 2
+);
+```
+
+### setSize(width, height)
+
+Resize the window to specific dimensions. The window's top-left corner position is preserved.
+
+```javascript
+// Resize window to 800x600
+win.setSize(800, 600);
+
+// Make window square based on current width
+const frame = win.getFrame();
+win.setSize(frame.width, frame.width);
+```
+
+### setFrame(x, y, width, height)
+
+Set both position and size of the window in a single call. This is more efficient than calling `setPosition` and `setSize` separately when you need to change both.
+
+```javascript
+// Move and resize window in one call
+win.setFrame(100, 100, 1024, 768);
+
+// Restore window to a saved position/size
+const savedFrame = { x: 200, y: 150, width: 800, height: 600 };
+win.setFrame(savedFrame.x, savedFrame.y, savedFrame.width, savedFrame.height);
+```
+
+### getFrame()
+
+Get the current position and size of the window. Returns an object with `x`, `y`, `width`, and `height` properties.
+
+```javascript
+// Get current window frame
+const frame = win.getFrame();
+console.log(`Position: (${frame.x}, ${frame.y})`);
+console.log(`Size: ${frame.width}x${frame.height}`);
+
+// Save and restore window frame
+const savedFrame = win.getFrame();
+// ... later
+win.setFrame(savedFrame.x, savedFrame.y, savedFrame.width, savedFrame.height);
+```
+
+### getPosition()
+
+Get the current position of the window. Returns an object with `x` and `y` properties.
+
+```javascript
+// Get current window position
+const pos = win.getPosition();
+console.log(`Window is at (${pos.x}, ${pos.y})`);
+
+// Check if window is at origin
+const { x, y } = win.getPosition();
+if (x === 0 && y === 0) {
+ console.log("Window is at origin");
+}
+```
+
+### getSize()
+
+Get the current size of the window. Returns an object with `width` and `height` properties.
+
+```javascript
+// Get current window size
+const size = win.getSize();
+console.log(`Window is ${size.width}x${size.height}`);
+
+// Check aspect ratio
+const { width, height } = win.getSize();
+const aspectRatio = width / height;
+console.log(`Aspect ratio: ${aspectRatio.toFixed(2)}`);
+```
+
+### on(name, handler)
+
+Subscribe to BrowserWindow events (see below).
+
+## Events
+
+### close
+
+When a window closes. Per-window close handlers fire before global close handlers, ensuring your handlers run before the internal `exitOnLastWindowClosed` logic.
+
+```javascript
+// listen to a specific window's close event
+win.on('close', (event) => {
+ const {id} = event.data;
+
+ console.log('window closed')
+});
+
+// listen globally to window close events
+import Electrobun from 'electrobun/bun';
+
+Electrobun.events.on('close', (event) => {
+ const {id} = event.data;
+
+ if (win.id === id) {
+ console.log('my window closed');
+ } else {
+ console.log(`some other window with id ${id}` closed);
+ }
+});
+```
+
+### resize
+
+When a window's width or height changes. This events sends the x and y as part of the data because a window may be resized by dragging the top-left corner which would also reposition it.
+
+```javascript
+// listen to a specific window's resize event
+win.on("resize", (event) => {
+ const { id, x, y, width, height } = event.data;
+ console.log("window resized", id, x, y, width, height);
+});
+
+// listen globally to window resize events
+import Electrobun from 'electrobun/bun';
+
+Electrobun.events.on("resize", (event) => {
+ const { id, x, y, width, height } = event.data;
+ console.log("window resized", id, x, y, width, height);
+});
+```
+
+### move
+
+When a window's position changes.
+
+```javascript
+// listen to a specific window's move event
+win.on("move", (event) => {
+ const { id, x, y } = event.data;
+ console.log("window moved", id, x, y);
+});
+
+// listen globally to window move events
+import Electrobun from 'electrobun/bun';
+
+Electrobun.events.on("move", (event) => {
+ const { id, x, y } = event.data;
+ console.log("window moved", id, x, y);
+});
+```
+
+### focus
+
+When a window becomes the key window (receives focus). This is useful for tracking which window should receive keyboard shortcuts or other focus-dependent actions.
+
+```javascript
+// listen to a specific window's focus event
+win.on("focus", (event) => {
+ const { id } = event.data;
+ console.log("window focused", id);
+});
+
+// listen globally to window focus events
+import Electrobun from 'electrobun/bun';
+
+Electrobun.events.on("focus", (event) => {
+ const { id } = event.data;
+ console.log("window focused", id);
+});
+```
\ No newline at end of file
diff --git a/docs/apis_browser_draggable-regions.md b/docs/apis_browser_draggable-regions.md
new file mode 100644
index 0000000..02af02e
--- /dev/null
+++ b/docs/apis_browser_draggable-regions.md
@@ -0,0 +1,168 @@
+# Draggable Regions
+
+Configure an html element to function as a draggable region allowing you to move the native application window by clicking and dragging on the element.
+
+When building desktop apps with Electrobun a common pattern is to create a frameless window, sometimes with the traffic light (close, minimize, maximize) buttons overlayed with the html content. You would then use html and css to create a top-bar and set that top-bar to be a draggable region allowing you full control over the style of the window.
+
+You can set any html element to be a draggable region.
+
+### Step 1: Instantiate the Electroview class
+
+```javascript
+// /src/mainview/index.ts
+import { Electroview } from "electrobun/view";
+
+const electrobun = new Electroview();
+```
+
+### Step 2: Add the draggable region css class
+
+Instantiating `Electroview()` will configure any element with the `electrobun-webkit-app-region-drag` css class as a draggable area.
+
+```html
+
+
+
+
+
+
+ My Electrobun app
+
+
+
+
+
+ click here and drag to move this window
+
+
hi World
+
+
+```
+
+### Step 3: Exclude interactive elements with no-drag
+
+When you have interactive elements (like buttons) inside a draggable region, you need to exclude them from the drag behavior. Use the `electrobun-webkit-app-region-no-drag` css class to make elements non-draggable.
+
+```html
+
+
+
+
+
+```
+
+**CSS (src/mainview/index.css):**
+
+```css
+.titlebar {
+ height: 32px;
+ display: flex;
+ align-items: center;
+ padding: 0 12px;
+ background: #2d2d2d;
+ user-select: none;
+}
+
+.window-controls {
+ display: flex;
+ gap: 8px;
+}
+
+.window-controls button {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: none;
+ cursor: pointer;
+}
+
+.close-btn { background: #ff5f57; }
+.minimize-btn { background: #febc2e; }
+.maximize-btn { background: #28c840; }
+
+.title {
+ flex: 1;
+ text-align: center;
+ font-size: 13px;
+ color: #ccc;
+}
+```
+
+See the [BrowserWindow API](/electrobun/docs/apis/browser-window) documentation for more details on `titleBarStyle` and `transparent` window options.
\ No newline at end of file
diff --git a/docs/apis_browser_electrobun-webview-tag.md b/docs/apis_browser_electrobun-webview-tag.md
new file mode 100644
index 0000000..9155cc5
--- /dev/null
+++ b/docs/apis_browser_electrobun-webview-tag.md
@@ -0,0 +1,426 @@
+# Electrobun Webview Tag
+
+## Introduction
+
+Electrobun's custom webview tag implementation behaves similarly to an enhanced iframe, but with key differences in capabilities and isolation. It serves as a positional anchor within the DOM, communicating with a Zig backend to manage a distinct, isolated BrowserView. This separation ensures full content isolation from the host webview, enhancing both security and performance.
+
+## Basic Usage
+
+```html
+
+
+
+
+
+ webview tag test
+
+
+
+
+
+
+
+```
+
+## Compatibility
+
+The Electrobun webview tag integrates seamlessly with any reactive JavaScript framework, such as React or SolidJS, allowing for dynamic interactions and updates without disrupting the isolation of the webview's contents.
+
+The way the implementation currently works, the html element is just a positional anchor that reports its position and relays events to zig which manages a completely separate BrowserView and overlays it at the same coordinates within the window.
+
+## How is this different to Electron's webview tag
+
+### Chrome plans to deprecate their webview tag
+
+Electron's webview tag is based on a Chrome feature/api designed for Chrome apps which has been deprecated since 2020. You can read about that on [Electron's Github](https://github.com/electron/electron/issues/34356) and in [Chrome's developer docs](https://developer.chrome.com/docs/apps/reference/webviewTag). The warning declares it "remains supported for Enterprise and Education customers on ChromeOS until at least Jan 2025" which is fast approaching.
+
+It's unknown what Electron will do when and if Chrome actually removes webview tag support from Chrome.
+
+Unlike Electron's reliance on Chrome's now-deprecated webview tag, Electrobun introduces its own robust implementation that does not depend on Chrome's lifecycle. This independence ensures longevity and stability for applications using Electrobun's framework, even as Chrome phases out its support.
+
+### Electrobun's webview tag is a separate layer
+
+Because Electrobun's webview tag implementation uses a div anchor and then positions a separate isolated BrowserView above the parent BrowserView there are some interesting edge cases where you may want to click on the parent document or do things within the parent DOM, so Electrobun provides various special methods for handling those situations. For example ways to mirror a screenshot of the webview tag's contents to the host's anchor and hide it or stream an image of the contents.
+
+## Properties and Attributes
+
+### src
+
+**Type:** `string`
+**Description:** URL of the web page to load in the webview.
+
+### html
+
+**Type:** `string`
+**Description:** HTML content to be directly loaded into the webview, useful for dynamic content generation.
+
+### preload
+
+**Type:** `string`
+**Description:** Path to a script that should be preloaded before any other scripts run in the webview.
+
+### partition
+
+**Type:** `string`
+**Description:** Sets a partition to provide separate storage for different sessions, useful in multi-user applications.
+
+### sandbox
+
+**Type:** `boolean`
+**Description:** When set to true, creates the webview in sandbox mode. Sandbox mode disables RPC communication and only allows event emission, making it suitable for loading untrusted third-party content securely.
+
+**Security Model:** In sandbox mode:
+
+* Events (dom-ready, did-navigate, will-navigate, etc.) still work normally
+* Navigation controls (loadURL, goBack, goForward, reload) still work
+* RPC communication is completely disabled - no messages can be sent between the webview and your application code
+* The webview content cannot access any application APIs or trigger custom handlers
+
+```html
+
+
+
+
+
+
+
+```
+
+### transparent
+
+**Type:** `boolean`
+**Description:** When set to true, makes the webview transparent, allowing underlying elements to be visible.
+
+### passthroughEnabled
+
+**Type:** `boolean`
+**Description:** Enables or disables mouse and touch events to pass through to underlying elements.
+
+### hidden
+
+**Type:** `boolean`
+**Description:** Controls the visibility of the webview.
+
+### delegateMode
+
+**Type:** `boolean`
+**Description:** Activates a mode where input is delegated to the webview even when it is visually mirrored to another element.
+
+### hiddenMirrorMode
+
+**Type:** `boolean`
+**Description:** Enables a mode where the webview is hidden and mirrored, allowing smooth interactions during transitions or animations.
+
+### wasZeroRect
+
+**Type:** `boolean`
+**Description:** Indicates if the webview had zero dimensions at any point, used internally to optimize rendering and updates.
+
+### webviewId
+
+**Type:** `number`
+**Description:** A unique identifier for the webview instance, automatically managed by the system.
+
+### id
+
+**Type:** `string`
+**Description:** The DOM ID for the webview element, automatically set to ensure uniqueness.
+
+## Methods
+
+### callAsyncJavaScript
+
+**Parameters:** `{ script: string }`
+**Returns:** `Promise`
+**Description:** Executes JavaScript code asynchronously within the webview and returns a promise with the result.
+
+### canGoBack
+
+**Returns:** `Promise`
+**Description:** Determines if the webview can navigate backward.
+
+### canGoForward
+
+**Returns:** `Promise`
+**Description:** Determines if the webview can navigate forward.
+
+### on
+
+**Parameters:** `event: WebviewEventTypes, listener: () => {}`
+**Description:** Attach event listeners for webview-specific events such as navigation and loading.
+
+### off
+
+**Parameters:** `event: WebviewEventTypes, listener: () => {}`
+**Description:** Detach event listeners for webview-specific events.
+
+### syncDimensions
+
+**Parameters:** `force: boolean = false`
+**Description:** Synchronizes the dimensions and position of the webview with its anchor element in the DOM, optionally forcing an update.
+
+### goBack
+
+**Description:** Navigates the webview back to the previous page.
+
+### goForward
+
+**Description:** Navigates the webview forward to the next page.
+
+### reload
+
+**Description:** Reloads the current content in the webview.
+
+### loadURL
+
+**Parameters:** `url: string`
+**Description:** Loads a given URL into the webview, similar to setting the `src` attribute.
+
+### setNavigationRules
+
+**Parameters:** `rules: string[]`
+**Description:** Set an allow/block list of URL patterns to control which URLs the webview can navigate to. Rules are evaluated synchronously in native code for maximum performance.
+
+**Rule Format:**
+
+* Rules use glob-style wildcards where `*` matches any characters
+* Prefix a rule with `^` to make it a block rule
+* Rules without the `^` prefix are allow rules
+* Rules are evaluated top-to-bottom, last matching rule wins
+* If no rule matches, navigation is allowed by default
+
+```javascript
+// Block everything except specific domains
+document.querySelector('electrobun-webview').setNavigationRules([
+ "^*", // Block everything by default
+ "*://en.wikipedia.org/*", // Allow Wikipedia
+ "*://upload.wikimedia.org/*", // Allow Wikipedia images
+]);
+
+// Allow everything except specific domains
+document.querySelector('electrobun-webview').setNavigationRules([
+ "^*://malware.com/*", // Block malware.com
+ "^http://*", // Block all non-HTTPS
+]);
+```
+
+### syncScreenshot
+
+**Parameters:** `callback?: () => void`
+**Description:** Captures and synchronizes a screenshot of the webview's contents, useful for visual mirroring.
+
+### clearScreenImage
+
+**Description:** Clears any images set as the webview anchor's background, typically used in conjunction with transparency and mirroring modes.
+
+### tryClearScreenImage
+
+**Description:** Attempts to clear the background image of the webview anchor if conditions are met.
+
+### toggleTransparent
+
+**Parameters:** `transparent?: boolean, bypassState?: boolean`
+**Description:** Toggles the transparency state of the webview.
+
+### togglePassthrough
+
+**Parameters:** `enablePassthrough?: boolean, bypassState?: boolean`
+**Description:** Toggles the ability for mouse and touch events to pass through the webview.
+
+### toggleHidden
+
+**Parameters:** `hidden?: boolean, bypassState?: boolean`
+**Description:** Toggles the visibility of the webview.
+
+### toggleDelegateMode
+
+**Parameters:** `delegateMode?: boolean`
+**Description:** Toggles the delegate mode for input events within the webview.
+
+### toggleHiddenMirrorMode
+
+**Parameters:** `force: boolean`
+**Description:** Toggles the hidden mirror mode, optimizing interaction during transitions or animations.
+
+## Events
+
+Use the `on` method to listen for events from the webview. Events are dispatched as CustomEvents with details in the `detail` property.
+
+### dom-ready
+
+**Description:** Fired when the DOM of the webview's content has finished loading.
+
+### did-navigate
+
+**Description:** Fired when the webview navigates to a new URL.
+
+### did-navigate-in-page
+
+**Description:** Fired for in-page navigations (e.g., hash changes).
+
+### did-commit-navigation
+
+**Description:** Fired when the webview commits to navigating to a new URL.
+
+### new-window-open
+
+**Description:** Fired when the webview attempts to open a new window (e.g., via `window.open()` or a link with `target="_blank"`).
+
+### host-message
+
+**Description:** Fired when the webview's preload script sends a message to the host using `window.__electrobunSendToHost()`. The message payload is available in `event.detail`.
+
+```javascript
+// Listen for messages from the webview's preload script
+document.querySelector('electrobun-webview').on('host-message', (event) => {
+ console.log('Received message from webview:', event.detail);
+ // event.detail contains the message object sent from the preload
+});
+```
+
+## Preload Scripts
+
+Preload scripts run in the context of the webview before any page scripts execute. They have access to special APIs for communicating with the host.
+
+### window.\_\_electrobunSendToHost(message)
+
+**Parameters:** `message: any` (will be JSON serialized)
+**Description:** Sends a message from the webview's preload script to the host BrowserWindow. The message will be received via the `host-message` event on the webview element.
+
+This enables secure communication from nested webviews back to the parent page, allowing preload scripts to forward user interactions, keyboard events, or custom data.
+
+```html
+
+
+
+
+```
+
+**Note:** The `__electrobunSendToHost` function is only available inside preload scripts running within an `electrobun-webview`. It is not available in regular page scripts.
+
+## Security Considerations
+
+When embedding third-party content in your application, security is paramount. Electrobun provides multiple layers of protection:
+
+### Sandbox Mode
+
+Always use `sandbox` attribute when loading untrusted content. This completely disables RPC communication, preventing the loaded content from accessing any application APIs.
+
+```html
+
+
+```
+
+### Navigation Rules
+
+Combine sandbox mode with navigation rules to restrict where the webview can navigate. This prevents redirects to malicious sites.
+
+```javascript
+const webview = document.querySelector('electrobun-webview');
+
+// Strict allowlist approach
+webview.setNavigationRules([
+ "^*", // Block everything by default
+ "*://trusted-domain.com/*", // Allow specific trusted domains
+ "*://cdn.trusted-domain.com/*", // Allow associated CDNs
+]);
+```
+
+### Process Isolation
+
+Each `electrobun-webview` runs in a completely separate browser process. This provides:
+
+* **Memory isolation:** Malicious content cannot read memory from your application
+* **Crash isolation:** If the embedded content crashes, your application continues running
+* **Security boundary:** Browser exploits are contained within the isolated process
+
+### Best Practices
+
+* **Always sandbox untrusted content:** Use the `sandbox` attribute for any content you don't fully control
+* **Use navigation rules:** Restrict navigation to prevent redirects to malicious sites
+* **Use partitions:** Isolate session storage between different webviews to prevent data leakage
+* **Validate host messages:** If using preload scripts with `__electrobunSendToHost`, always validate and sanitize received messages
+* **Prefer HTTPS:** Block HTTP content with navigation rules to ensure encrypted connections
+
+```html
+
+
+
+
+```
\ No newline at end of file
diff --git a/docs/apis_browser_electroview-class.md b/docs/apis_browser_electroview-class.md
new file mode 100644
index 0000000..a8ad2bd
--- /dev/null
+++ b/docs/apis_browser_electroview-class.md
@@ -0,0 +1,104 @@
+# Electroview Class
+
+Instantiate Electrobun APIs in the browser.
+
+```javascript
+import {Electroview} from "electrobun/view";
+
+const electrobun = new Electroview({ ...options })
+```
+
+## Constructor Options
+
+### rpc
+
+This is the browser side of creating typed RPC between the main bun process and a given BrowserView's context.
+
+```javascript
+// src/shared/types.ts
+export type MyWebviewRPCType = {
+ // functions that execute in the main process
+ bun: RPCSchema<{
+ requests: {
+ someBunFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToBun: {
+ msg: string;
+ };
+ };
+ }>;
+ // functions that execute in the browser context
+ webview: RPCSchema<{
+ requests: {
+ someWebviewFunction: {
+ params: {
+ a: number;
+ b: number;
+ };
+ response: number;
+ };
+ };
+ messages: {
+ logToWebview: {
+ msg: string;
+ };
+ };
+ }>;
+};
+```
+```typescript
+// /src/myview/index.ts
+import { Electroview } from "electrobun/view";
+import { type MyWebviewRPCType } from "../shared/types";
+
+const rpc = Electroview.defineRPC({
+ handlers: {
+ requests: {
+ someWebviewFunction: ({ a, b }) => {
+ document.body.innerHTML += `bun asked me to do math with ${a} and ${b}\n`;
+ return a + b;
+ },
+ },
+ messages: {
+ logToWebview: ({ msg }) => {
+ // this will appear in the inspect element devtools console
+ console.log(`bun asked me to logToWebview: ${msg}`);
+ },
+ },
+ },
+});
+const electroview = new ElectrobunView.Electroview({ rpc });
+```
+
+Assuming you've wired up rpc on the bun side when creating the [BrowserWindow](/electrobun/docs/apis/browser-window) you'll be able to call those bun functions from the browser.
+
+```javascript
+// /src/myview/index.ts
+electroview.rpc.request.someBunFunction({ a: 9, b: 8 }).then((result) => {
+ console.log("result: ", result);
+});
+
+// or
+electroview.rpc.send.logToBun({ msg: "hi from browser" });
+```
+
+## Static Methods
+
+### defineRPC
+
+Pass `Electroview.defineRPC` the shared rpc type to generate the typed rpc and message functions you can call from the browser and to set up types for the browser handlers for functions handled in the browser.
+
+## Methods
+
+### Browser to Browser RPC
+
+Electrobun doesn't provide browser to browser RPC out of the box as we favour isolation between browser contexts for greater security.
+
+There's nothing stopping you from creating bun to browser rpc for two different BrowserViews and passing messages between them via bun. You can also establish any number of other web-based mechanisms to communicated between browser contexts from localstorage to webRTC or via a server.
\ No newline at end of file
diff --git a/docs/apis_browser_global-properties.md b/docs/apis_browser_global-properties.md
new file mode 100644
index 0000000..0af81b0
--- /dev/null
+++ b/docs/apis_browser_global-properties.md
@@ -0,0 +1,11 @@
+# Global Properties
+
+These global properties are injected into the browser context regardless of whether you've instantiated the `Electroview`.
+
+### window.\_\_electrobunWebviewId
+
+The id of the webview
+
+### window.\_\_electrobunWindowId
+
+The id of the window
\ No newline at end of file
diff --git a/docs/apis_build-config.md b/docs/apis_build-config.md
new file mode 100644
index 0000000..c5f6efc
--- /dev/null
+++ b/docs/apis_build-config.md
@@ -0,0 +1,169 @@
+# BuildConfig
+
+Access build-time configuration at runtime. This API provides information about how the application was built, including renderer settings.
+
+```javascript
+import { BuildConfig } from "electrobun/bun";
+
+// Or via the default export
+import Electrobun from "electrobun/bun";
+const config = await Electrobun.BuildConfig.get();
+```
+
+## Overview
+
+The `BuildConfig` API gives your Bun process access to configuration values that were set at build time in your `electrobun.config.ts`. This is useful for:
+
+* Knowing which renderers are available in the current build
+* Checking the default renderer configuration
+* Conditional logic based on build settings
+* Debugging and logging build information
+
+## BuildConfig.get()
+
+Asynchronously loads and returns the build configuration. The result is cached after the first call.
+
+### Returns
+
+`Promise`
+
+### BuildConfigType
+
+Property
+
+Type
+
+Description
+
+`defaultRenderer`
+
+`'native' | 'cef'`
+
+The default renderer for BrowserWindow and BrowserView when not explicitly specified
+
+`availableRenderers`
+
+`('native' | 'cef')[]`
+
+List of renderers available in this build. Always includes `'native'`. Includes `'cef'` only if CEF was bundled.
+
+`cefVersion`
+
+`string | undefined`
+
+The CEF version string used in this build (e.g., `"144.0.11+ge135be2+chromium-144.0.7559.97"`). Present only when CEF is bundled. Either the custom override from `electrobun.config.ts` or the default version shipped with the Electrobun release.
+
+`bunVersion`
+
+`string | undefined`
+
+The Bun runtime version used in this build (e.g., `"1.3.8"`). Either the custom override from `electrobun.config.ts` or the default version shipped with the Electrobun release.
+
+`runtime`
+
+`object`
+
+Runtime configuration from the `runtime` section of `electrobun.config.ts`. Includes `exitOnLastWindowClosed` and any custom keys you define.
+
+### Example
+
+```javascript
+import { BuildConfig } from "electrobun/bun";
+
+const config = await BuildConfig.get();
+
+console.log("Default renderer:", config.defaultRenderer);
+// Output: "cef" or "native"
+
+console.log("Available renderers:", config.availableRenderers);
+// Output: ["native", "cef"] or ["native"]
+
+// Check if CEF is available
+if (config.availableRenderers.includes('cef')) {
+ console.log("CEF is bundled with this app");
+}
+```
+
+## BuildConfig.getCached()
+
+Synchronously returns the cached build configuration, or `null` if it hasn't been loaded yet.
+
+### Returns
+
+`BuildConfigType | null`
+
+### Example
+
+```javascript
+import { BuildConfig } from "electrobun/bun";
+
+// First, load the config (usually done at app startup)
+await BuildConfig.get();
+
+// Later, access it synchronously
+const cached = BuildConfig.getCached();
+if (cached) {
+ console.log("Using renderer:", cached.defaultRenderer);
+}
+```
+
+**Note:** `getCached()` returns `null` if `get()` hasn't been called yet. In most cases, you should use `get()` which handles loading automatically.
+
+## How It Works
+
+When you build your Electrobun app, the CLI reads your `electrobun.config.ts` and generates a `build.json` file in the app's Resources folder. This file contains the runtime-relevant build settings.
+
+The `BuildConfig` API reads this file and caches the result. The configuration includes:
+
+* **defaultRenderer** - From the platform-specific `defaultRenderer` setting in your config
+* **availableRenderers** - Determined by whether `bundleCEF` was enabled for the target platform
+* **runtime** - The entire `runtime` section from your `electrobun.config.ts`, including `exitOnLastWindowClosed` and any custom keys
+
+## Relationship with BrowserWindow/BrowserView
+
+The `defaultRenderer` setting affects the default behavior of `BrowserWindow` and `BrowserView`:
+
+```javascript
+// If defaultRenderer is 'cef' in your config:
+
+// This window will use CEF (the configured default)
+const window1 = new BrowserWindow({
+ url: "views://main/index.html"
+});
+
+// This window explicitly uses native renderer
+const window2 = new BrowserWindow({
+ url: "views://settings/index.html",
+ renderer: 'native'
+});
+```
+
+See the [Build Configuration](/electrobun/docs/apis/cli/build-configuration) documentation for how to configure these settings.
+
+## Complete Example
+
+```javascript
+import Electrobun, { BrowserWindow, BuildConfig } from "electrobun/bun";
+
+// Load and log build configuration at startup
+const buildConfig = await BuildConfig.get();
+
+console.log("Build Configuration:");
+console.log(" Default Renderer:", buildConfig.defaultRenderer);
+console.log(" Available Renderers:", buildConfig.availableRenderers.join(", "));
+
+// Create windows - they'll use the configured default renderer
+const mainWindow = new BrowserWindow({
+ title: "My App",
+ url: "views://main/index.html",
+});
+
+// If you need CEF-specific features, check availability first
+if (buildConfig.availableRenderers.includes('cef')) {
+ const cefWindow = new BrowserWindow({
+ title: "CEF Window",
+ url: "views://special/index.html",
+ renderer: 'cef',
+ });
+}
+```
\ No newline at end of file
diff --git a/docs/apis_bun.md b/docs/apis_bun.md
new file mode 100644
index 0000000..8af7815
--- /dev/null
+++ b/docs/apis_bun.md
@@ -0,0 +1,25 @@
+# Bun API
+
+The Bun API is the main process API that manages your application's lifecycle, creates windows, handles system events, and provides the bridge between your UI and the operating system.
+
+Electrobun is just an npm dependency in your bun project. If you're just starting to look around take a look at the Getting Started Guide first to learn how to set up your first project.
+
+In Electrobun you simply write Typescript for the main process, when your app is all bundled up it will ship with a version of the bun runtime and it'll execute your main bun process with that, so any bun-compatible typescript is valid.
+
+You should explicitely import the `electrobun/bun` api for the main process:
+
+```javascript
+import Electrobun from "electrobun/bun";
+
+const win = new Electrobun.BrowserWindow(/*...*/);
+
+// or
+
+import {
+ BrowserWindow,
+ ApplicationMenu,
+ // other specified imports
+} from "electrobun/bun";
+
+const win = new BrowserWindow(/*...*/);
+```
\ No newline at end of file
diff --git a/docs/apis_bundled-assets.md b/docs/apis_bundled-assets.md
new file mode 100644
index 0000000..9e1bb23
--- /dev/null
+++ b/docs/apis_bundled-assets.md
@@ -0,0 +1,94 @@
+# Bundling Static Assets in your app
+
+The `views://` schema in Electrobun provides a robust method for handling static assets, ensuring they are securely and efficiently managed within the application's bundle. This documentation explains how to use this schema to set URLs for new `BrowserWindow` instances, incorporate CSS and JavaScript into HTML, and bundle static assets via the `electrobun.config`.
+
+### Overview of `views://` Schema
+
+The `views://` schema is a custom protocol used in Electrobun to reference assets and files within the application bundle. This schema allows for a clean separation of application logic and resources, ensuring that static assets like HTML, CSS, and JavaScript files are encapsulated within specified views or components.
+
+You can think of the `views://` schema as an alternative to `https://` so it can be used in the context of BrowserViews anywhere a normal url can be used and electrobun will securely map those paths to the static asset folder in your application bundle.
+
+### Using `views://` in BrowserWindow URLs
+
+You can use the `views://` schema to set the URL for a new `BrowserWindow()` in Electrobun. This method simplifies referencing bundled assets and enhances security by encapsulating resources.
+
+#### Example Usage
+
+```javascript
+const { BrowserWindow } = require("electrobun");
+
+const mainWindow = new BrowserWindow({
+ width: 800,
+ height: 600,
+ title: "Main Window",
+});
+
+mainWindow.loadURL("views://mainview/index.html");
+```
+
+In this example, `mainWindow` loads an HTML file located at `views://mainview/index.html`. This URL points to the `index.html` file within the `mainview` directory defined in the `electrobun.config`.
+
+### Incorporating CSS and JavaScript
+
+Using the `views://` schema, CSS and JavaScript files can be loaded directly within an HTML file bundled in the application.
+
+#### HTML Example
+
+```javascript
+
+
+
+
+ Sample Page
+
+
+
+
+
+
Welcome to Electrobun
+
+
+```
+
+Here, `style.css` and `script.js` are loaded using the `views://` schema, pointing directly to the assets within the `mainview` directory.
+
+You can also see a `views://` url used directly in css just like you'd use any `https://` url.
+
+### Bundling Static Assets via `electrobun.config`
+
+The `electrobun.config` file can be configured to bundle and manage static assets using the `views://` schema. This configuration ensures all necessary assets are included during the build process and correctly referenced within the application.
+
+The property name for each view, in this case `mainview` can be anything you'd like. And you can specify as many views as you'd like. This maps directly to the path you would use when referencing a file so you can organize your assets.
+
+#### Configuration Example
+
+```javascript
+build: {
+ views: {
+ mainview: {
+ entrypoint: "src/mainview/index.ts",
+ // All Bun.build() options are supported here, e.g.:
+ // plugins: [myPlugin()],
+ // sourcemap: "linked",
+ // minify: true,
+ },
+ },
+ copy: {
+ "src/mainview/index.html": "views/mainview/index.html",
+ "src/mainview/style.css": "views/mainview/style.css",
+ "src/mainview/script.js": "views/mainview/script.js",
+ },
+}
+```
+
+**Notice that in the "copy" section the destination is `views/mainview/` which maps to the url `views://mainview/`.**
+
+In the `electrobun.config`, the `views` section defines entry points for scripts, while the `copy` section specifies static assets like HTML, CSS, and JavaScript files to be copied to their respective directories in the build output.
+
+### Summary
+
+The `views://` schema in Electrobun provides a structured and secure way to manage and reference static assets within your applications. By configuring the `electrobun.config` appropriately and using the schema within your application code, you can ensure a clean, organized, and encapsulated asset management system.
\ No newline at end of file
diff --git a/docs/apis_bundling-cef.md b/docs/apis_bundling-cef.md
new file mode 100644
index 0000000..da5bee7
--- /dev/null
+++ b/docs/apis_bundling-cef.md
@@ -0,0 +1,214 @@
+# Bundling CEF (Chromium Embedded Framework)
+
+Electrobun supports bundling CEF with your application for cross-platform consistency and advanced features. While the default system webview provides smaller bundle sizes, CEF ensures near-identical rendering and behavior across all platforms.
+
+## Configuration
+
+To bundle CEF with your application, configure the `build` property in your `electrobun.config.ts` file:
+
+```javascript
+// electrobun.config.ts
+import { type ElectrobunConfig } from "electrobun";
+
+export const config: ElectrobunConfig = {
+ build: {
+ macos: {
+ bundleCEF: true
+ },
+ win: {
+ bundleCEF: true
+ },
+ linux: {
+ bundleCEF: true
+ }
+ },
+ // ... other configuration
+};
+```
+
+## Platform Considerations
+
+### Windows
+
+On Windows the system renderer is Webview2, which is essentially the inner renderer of Edge, which is Chromium based. So on Windows when using the system webview you get a Chromium-based renderer that the system manages updates for which is great for bundle size and security. But there may be differences between the version of Webview2 on the user's system vs. what Electrobun's binaries are built against, and there may be a difference in Chromium version between a given user's system and Chromium api's you need and so bundling CEF may be still be beneficial to pin the version of Chromium you distribute by including it in the bundle.
+
+### Linux
+
+**Bundling CEF is strongly recommended on Linux** as the default GTKWebKit renderer doesn't support Electrobun's advanced layer compositing features. This means features like the `` tag and complex window layering may not work correctly without CEF.
+
+### Bundle Size Impact
+
+When bundling CEF, your application's initial self-extracting bundle will increase to approximately **100MB** compared to ~14MB with system webviews. However, incremental updates remain small (as little as 14KB) thanks to Electrobun's differential update system.
+
+## Using CEF Renderer
+
+When CEF is bundled, you need to specify `renderer="cef"` when creating windows or webviews:
+
+### BrowserWindow API
+
+```javascript
+// src/bun/index.ts
+import { BrowserWindow } from "electrobun/bun";
+
+// Create a window using CEF renderer
+const cefWindow = new BrowserWindow({
+ width: 1200,
+ height: 800,
+ renderer: "cef", // Specify CEF renderer
+ url: "views://main/index.html"
+});
+
+// You can also create a window with system renderer (except on Linux)
+const systemWindow = new BrowserWindow({
+ width: 800,
+ height: 600,
+ renderer: "system", // Use system webview
+ url: "views://secondary/index.html"
+});
+```
+
+### Electrobun Webview Tag
+
+```javascript
+
+
+
+
+
+
+
+
+```
+
+## Mixed Renderer Support
+
+### macOS and Windows
+
+On macOS and Windows, when CEF is bundled, you can **mix and match renderers** within the same application:
+
+* Some windows can use the system webview for smaller memory footprint
+* Others can use CEF for consistency or advanced features
+* Individual `` tags can specify their preferred renderer
+
+### Linux Limitation
+
+**On Linux, renderer mixing is not supported.** The build process creates two separate Electrobun binaries:
+
+* One for system webview (GTKWebKit)
+* One for CEF
+
+This means all your webviews on Linux must use the same renderer - either all CEF or all system webview. You cannot mix them within a single application instance.
+
+## When to Bundle CEF
+
+Consider bundling CEF when you need:
+
+* **Consistent rendering** across all platforms
+* **Advanced compositing features** (especially on Linux)
+* **Latest Chromium features** not available in system webviews
+* **Predictable behavior** for complex web applications
+* **Full support for modern web standards**
+
+Consider using system webviews when you want:
+
+* **Smallest possible bundle size** (~14MB vs ~100MB)
+* **Native platform integration** and appearance
+* **Lower memory usage** for simple applications
+* **Faster initial download** for users
+
+## Example: Platform-Specific Configuration
+
+You can selectively bundle CEF for specific platforms:
+
+```javascript
+// electrobun.config.ts
+import { type ElectrobunConfig } from "electrobun";
+
+export const config: ElectrobunConfig = {
+ build: {
+ macos: {
+ bundleCEF: false // Use system WebKit on macOS
+ },
+ win: {
+ bundleCEF: true // Use CEF on Windows for consistency
+ },
+ linux: {
+ bundleCEF: true // Required for advanced features on Linux
+ }
+ }
+};
+```
+
+This configuration provides the best balance between bundle size and functionality for each platform.
+
+## Custom CEF Versions
+
+Each Electrobun release ships with a specific tested CEF version. You can override this with the `cefVersion` option in your build configuration to use a different version from the [Spotify CEF builds](https://cef-builds.spotifycdn.com/) CDN.
+
+### Why Override the CEF Version
+
+There are two main reasons you might want to use a different CEF version:
+
+* **Pin an older version:** If your app depends on a Chrome API that was deprecated or removed in a newer CEF release, you can pin the CEF version to avoid the breaking change. This lets you ship on your own timeline instead of being forced to update when Electrobun bumps its default.
+* **Use a newer version:** If a newer CEF release includes a security fix or a Chromium feature you need, you don't have to wait for Electrobun to fully test and adopt it. You can point to the newer version immediately and unblock your own release.
+
+### Configuration
+
+Set the `cefVersion` field in the `build` section of your `electrobun.config.ts`. The value must match the version string format used by [cef-builds.spotifycdn.com](https://cef-builds.spotifycdn.com/):
+
+```javascript
+// electrobun.config.ts
+import type { ElectrobunConfig } from "electrobun";
+
+export default {
+ app: {
+ name: "MyApp",
+ identifier: "com.example.myapp",
+ version: "1.0.0",
+ },
+ build: {
+ cefVersion: "144.0.11+ge135be2+chromium-144.0.7559.97",
+ mac: {
+ bundleCEF: true,
+ },
+ linux: {
+ bundleCEF: true,
+ },
+ win: {
+ bundleCEF: true,
+ },
+ },
+} satisfies ElectrobunConfig;
+```
+
+The format is `CEF_VERSION+chromium-CHROMIUM_VERSION`. You can find the exact version strings on the [Spotify CEF builds](https://cef-builds.spotifycdn.com/) page — look for the "minimal" distribution for your target platforms.
+
+### How It Works
+
+When `cefVersion` is set, the Electrobun CLI downloads the CEF minimal distribution directly from Spotify's CDN and extracts the runtime files (shared libraries, resource packs, locales) into your local dependency cache. No compilation or build tools are required on your machine.
+
+Electrobun's helper process (`process_helper`) ships pre-built with each Electrobun release and communicates with CEF through its stable C API. This means the helper binary works with different CEF versions without recompilation, as long as the C API is compatible.
+
+### Compatibility
+
+**CEF versions must have a compatible C API with the Electrobun release you're using.** CEF's C API is designed for ABI stability within the same major version line. Across major versions, breaking changes are possible and may cause runtime crashes or unexpected behavior.
+
+As a general rule:
+
+* **Same major version** (e.g., 144.x to 144.y): Safe. The C API is stable within a major version.
+* **Adjacent major versions** (e.g., 144.x to 145.x): Usually works, but test thoroughly. Breaking changes are uncommon but possible.
+* **Distant major versions** (e.g., 130.x to 145.x): Higher risk of incompatibility. The further apart the versions, the more likely there are C API changes that affect Electrobun's integration.
+
+To see which CEF version your Electrobun release was built and tested against, check the `CEF_VERSION` constant in [package/build.ts](https://github.com/blackboardsh/electrobun/blob/main/package/build.ts) for your release tag.
+
+### Caching
+
+The downloaded CEF files are cached locally per platform and version. If you change `cefVersion`, the CLI detects the mismatch and re-downloads automatically. Removing the override restores the default CEF version shipped with your Electrobun release.
+
+**See also:** You can similarly override the bundled Bun runtime version using the `bunVersion` option. See [Build Configuration — Custom Bun Version](/electrobun/docs/apis/cli/build-configuration).
\ No newline at end of file
diff --git a/docs/apis_cli_build-configuration.md b/docs/apis_cli_build-configuration.md
new file mode 100644
index 0000000..065b66f
--- /dev/null
+++ b/docs/apis_cli_build-configuration.md
@@ -0,0 +1,132 @@
+# Build Configuration
+
+This guide covers all configuration options available in `electrobun.config` for building and distributing your Electrobun applications.
+
+## Configuration File
+
+Electrobun uses `electrobun.config.ts` in your project root to control how your application is built and packaged. The config file uses TypeScript with ESM syntax, providing type safety and modern JavaScript features.
+
+### Basic Structure
+
+```javascript
+// electrobun.config.ts
+import type { ElectrobunConfig } from "electrobun";
+
+export default {
+ app: {
+ name: "MyApp",
+ identifier: "com.example.myapp",
+ version: "1.0.0",
+ },
+ runtime: {
+ exitOnLastWindowClosed: true,
+ },
+ build: {
+ bun: {
+ entrypoint: "src/bun/index.ts",
+ },
+ },
+} satisfies ElectrobunConfig;
+```
+
+## Bun Bundler Options
+
+Both `build.bun` and each entry in `build.views` accept all [Bun.build()](https://bun.sh/docs/bundler) options as pass-through properties. The only required field is `entrypoint` — everything else is optional.
+
+Electrobun controls `entrypoints` (derived from your `entrypoint`), `outdir`, and `target` (`"bun"` for the bun process, `"browser"` for views) automatically. All other Bun bundler options are passed through directly.
+
+### Available Options
+
+Some commonly used options include:
+
+Option
+
+Type
+
+Description
+
+`plugins`
+
+`BunPlugin[]`
+
+Bundler plugins (e.g., for CSS modules, SVG imports, etc.)
+
+`external`
+
+`string[]`
+
+Modules to exclude from bundling
+
+`sourcemap`
+
+`"none" | "linked" | "inline" | "external"`
+
+Source map generation
+
+`minify`
+
+`boolean | { whitespace, identifiers, syntax }`
+
+Minification options
+
+`splitting`
+
+`boolean`
+
+Enable code splitting for shared modules
+
+`define`
+
+`Record`
+
+Global identifier replacements at build time
+
+`loader`
+
+`Record`
+
+Custom file extension loaders
+
+`format`
+
+`"esm" | "cjs" | "iife"`
+
+Output module format
+
+`naming`
+
+`string | { chunk, entry, asset }`
+
+Output file naming patterns
+
+`banner`
+
+`string`
+
+Prepend text to output (e.g., `"use client"`)
+
+`drop`
+
+`string[]`
+
+Remove function calls (e.g., `["console", "debugger"]`)
+
+`env`
+
+`"inline" | "disable" | "PREFIX_*"`
+
+Environment variable handling
+
+`jsx`
+
+`{ runtime, importSource, factory, fragment }`
+
+JSX transform configuration
+
+`packages`
+
+`"bundle" | "external"`
+
+Whether to bundle or externalize all packages
+
+`` For the full list of options, see the [Bun Bundler documentation](https://bun.sh/docs/bundler). ### Example: Using Plugins ```javascript import type { ElectrobunConfig } from "electrobun"; import myPlugin from "./plugins/my-plugin"; export default { app: { name: "MyApp", identifier: "com.example.myapp", version: "1.0.0", }, build: { bun: { entrypoint: "src/bun/index.ts", plugins: [myPlugin()], }, views: { mainview: { entrypoint: "src/mainview/index.ts", plugins: [myPlugin()], sourcemap: "linked", }, }, }, } satisfies ElectrobunConfig; ``` ### Example: Minification and Source Maps ```javascript import type { ElectrobunConfig } from "electrobun"; export default { app: { name: "MyApp", identifier: "com.example.myapp", version: "1.0.0", }, build: { views: { mainview: { entrypoint: "src/mainview/index.ts", minify: true, sourcemap: "linked", define: { "process.env.NODE_ENV": '"production"', }, drop: ["console"], }, }, }, } satisfies ElectrobunConfig; ``` **Note:** Since `electrobun.config.ts` is a real TypeScript module, you can dynamically construct plugins and configuration. Plugins are JavaScript objects, so they work natively — no serialization required. ## URL Schemes (Deep Linking) Electrobun supports registering custom URL schemes for your application, enabling deep linking. When users click a link like `myapp://some/path`, your app will open and receive the URL. **Platform support:** * macOS: Fully supported. App must be in `/Applications` folder for URL scheme registration to work reliably. * Windows: Not yet supported * Linux: Not yet supported ### Configuration Add URL schemes to the `app` section of your config: ```javascript const config: ElectrobunConfig = { app: { name: "MyApp", identifier: "com.example.myapp", version: "1.0.0", urlSchemes: ["myapp", "myapp-dev"], // Register multiple schemes }, // ... }; ``` ### Handling URL Opens Listen for the `open-url` event in your Bun process to handle incoming URLs: ```javascript import Electrobun from "electrobun"; Electrobun.events.on("open-url", (e) => { console.log("Opened with URL:", e.data.url); // Parse the URL to extract information const url = new URL(e.data.url); console.log("Protocol:", url.protocol); // "myapp:" console.log("Pathname:", url.pathname); // "/some/path" // Route to appropriate part of your app if (url.pathname.startsWith("/login")) { // Handle login deep link } }); ``` ### How It Works on macOS When you build your app with URL schemes configured, Electrobun automatically adds the `CFBundleURLTypes` entry to your app's `Info.plist`. The operating system registers these URL schemes when your app is placed in the `/Applications` folder. **Important notes:** * The app must be in `/Applications` (or `~/Applications`) for macOS to register the URL schemes * During development, URL schemes won't work unless you build and install to Applications * If another app has already registered the same URL scheme, macOS will use whichever was installed first * Notarization is recommended for production apps to ensure a smooth user experience ### Dynamic Configuration TypeScript config files support dynamic configuration with full type safety: ```javascript // electrobun.config.ts import type { ElectrobunConfig } from "electrobun"; import { readFileSync } from 'fs'; // Read version from package.json const packageJson = JSON.parse(readFileSync('./package.json', 'utf8')); export default { app: { name: "MyApp", identifier: process.env.APP_ID || "com.example.myapp", version: packageJson.version, }, build: { bun: { entrypoint: "src/bun/index.ts", }, }, release: { baseUrl: process.env.RELEASE_BASE_URL || "", }, } satisfies ElectrobunConfig; ``` ## ASAR Packaging Electrobun supports packaging your application resources into an ASAR archive. ASAR (Atom Shell Archive) is an archive format that combines multiple files into a single file, providing faster file access and improved security for production builds. ### Configuration Options ```javascript const config: ElectrobunConfig = { build: { useAsar: true, asarUnpack: ["*.node", "*.dll", "*.dylib", "*.so"], // ... rest of config }, }; ``` #### useAsar **Type:** `boolean` **Default:** `false` Enables ASAR packaging for your application resources. When enabled, the entire `app/` directory is packed into a single `app.asar` file. #### asarUnpack **Type:** `string[]` **Default:** `["*.node", "*.dll", "*.dylib", "*.so"]` Glob patterns for files and folders that should be excluded from the ASAR archive. These files will remain unpacked in the `app.asar.unpacked` directory alongside the archive. **Common use cases for unpacking:** * Native modules (`*.node`, `*.dll`, `*.dylib`, `*.so`) * Files that need to be accessed directly by external processes * Files that need to be executed or dynamically loaded * Large binary files that don't benefit from archiving ### Example with ASAR Configuration ```javascript import type { ElectrobunConfig } from "electrobun"; export default { app: { name: "MyApp", identifier: "com.example.myapp", version: "1.0.0", }, build: { useAsar: true, asarUnpack: [ "*.node", // Native modules "*.dll", // Windows DLLs "*.dylib", // macOS dynamic libraries "*.so", // Linux shared objects "data/large/**/*", // Large data files ], bun: { entrypoint: "src/bun/index.ts", }, }, } satisfies ElectrobunConfig; ``` ### Benefits of ASAR Packaging * **Performance:** Faster file access and reduced I/O operations * **Security:** App code is extracted to randomized temp files with automatic cleanup * **Distribution:** Fewer files to manage and distribute * **Integrity:** Single archive is easier to verify and protect ## Renderer Configuration Electrobun supports multiple webview renderers. By default, it uses the system's native webview (WKWebKit on macOS, WebView2 on Windows, GTK WebKit on Linux). You can also bundle CEF (Chromium Embedded Framework) for a consistent cross-platform experience. ### Platform-specific Renderer Options Each platform (mac, linux, win) supports the following renderer options: #### bundleCEF **Type:** `boolean` **Default:** `false` When `true`, CEF (Chromium Embedded Framework) is bundled with your application. This adds approximately 100MB+ to your app bundle but provides a consistent Chromium-based rendering experience across all platforms. #### defaultRenderer **Type:** `'native' | 'cef'` **Default:** `'native'` Sets the default renderer for all `BrowserWindow` and `BrowserView` instances when no explicit `renderer` option is specified. This allows you to bundle CEF and use it by default without having to specify `renderer: 'cef'` on every window/view. **Note:** Setting `defaultRenderer: 'cef'` only affects the default. You can still override it per-window or per-view by explicitly passing `renderer: 'native'` or `renderer: 'cef'` in the options. ### Example: CEF as Default Renderer ```javascript const config: ElectrobunConfig = { // ... build: { mac: { bundleCEF: true, defaultRenderer: 'cef', // All webviews use CEF by default }, linux: { bundleCEF: true, defaultRenderer: 'cef', }, win: { bundleCEF: true, defaultRenderer: 'cef', }, }, }; ``` With this configuration, when you create a window without specifying a renderer: ```javascript // Uses CEF (the configured default) const mainWindow = new BrowserWindow({ title: "My App", url: "views://main/index.html", }); // Explicitly use native renderer for this specific window const settingsWindow = new BrowserWindow({ title: "Settings", url: "views://settings/index.html", renderer: 'native', // Override the default }); ``` ### Accessing Build Configuration at Runtime You can access the build configuration at runtime using the `BuildConfig` API. This is useful for knowing which renderers are available and what the default is. See the [BuildConfig API](/electrobun/docs/apis/build-config) documentation for details. ### Full example from the Electrobun Playground app ```javascript // electrobun.config.ts import type { ElectrobunConfig } from "electrobun"; export default { app: { name: "Electrobun (Playground)", identifier: "dev.electrobun.playground", version: "0.0.1", }, build: { bun: { entrypoint: "src/bun/index.ts", }, views: { mainview: { entrypoint: "src/mainview/index.ts", }, myextension: { entrypoint: "src/myextension/preload.ts", }, webviewtag: { entrypoint: "src/webviewtag/index.ts", }, }, copy: { "src/mainview/index.html": "views/mainview/index.html", "src/mainview/index.css": "views/mainview/index.css", "src/webviewtag/index.html": "views/webviewtag/index.html", "src/webviewtag/electrobun.png": "views/webviewtag/electrobun.png", "assets/electrobun-logo-32-template.png": "views/assets/electrobun-logo-32-template.png", }, mac: { codesign: true, notarize: true, bundleCEF: true, defaultRenderer: 'cef', entitlements: { "com.apple.security.device.camera": "This app needs camera access for video features", "com.apple.security.device.microphone": "This app needs microphone access for audio features", }, icons: "icon.iconset", }, linux: { bundleCEF: true, defaultRenderer: 'cef', }, win: { bundleCEF: true, defaultRenderer: 'cef', }, }, scripts: { postBuild: "./buildScript.ts", }, release: { baseUrl: "https://static.electrobun.dev/playground/", }, } satisfies ElectrobunConfig; ``` ## Custom Bun Version Each Electrobun release ships with a specific tested Bun version. You can override this with the `bunVersion` option in your build configuration to use a different version from [Bun's GitHub releases](https://github.com/oven-sh/bun/releases). ### Why Override the Bun Version * **Use a newer version:** If a newer Bun release includes a performance improvement, bug fix, or API you need, you can adopt it immediately without waiting for Electrobun to update its default. * **Pin an older version:** If a newer Bun release introduces a regression that affects your app, you can pin to the version that works while you wait for a fix. ### Configuration Set the `bunVersion` field in the `build` section of your `electrobun.config.ts`. The value is a semver version string matching a [Bun release tag](https://github.com/oven-sh/bun/releases): ```javascript // electrobun.config.ts import type { ElectrobunConfig } from "electrobun"; export default { app: { name: "MyApp", identifier: "com.example.myapp", version: "1.0.0", }, build: { bunVersion: "1.4.2", bun: { entrypoint: "src/bun/index.ts", }, }, } satisfies ElectrobunConfig; ``` ### How It Works When `bunVersion` is set, the Electrobun CLI downloads the specified Bun binary from GitHub releases and caches it locally. The cached binary is stored in `node_modules/.electrobun-cache/bun-override/` so it survives both dist rebuilds and `bun install` (which replaces `node_modules/electrobun`). Unlike the CEF version override, no compilation or restructuring is needed — Bun is a single standalone binary with no external dependencies. ### Caching The downloaded Bun binary is cached per platform and version. If you change `bunVersion`, the CLI detects the mismatch and re-downloads automatically. Removing the override restores the default Bun version shipped with your Electrobun release. **See also:** For overriding the CEF (Chromium) version, see [Bundling CEF — Custom CEF Versions](/electrobun/docs/apis/bundling-cef). ## Chromium Flags You can pass custom Chromium command-line flags to CEF during initialization. This is useful for enabling debugging features, overriding browser behavior, or setting values like a custom user agent. Flags are defined per-platform in the `chromiumFlags` option. Keys are flag names **without** the `--` prefix. Use `true` for switch-only flags, or a string for flags that take a value. ### chromiumFlags **Type:** `Record` **Default:** `undefined` (no custom flags) ```javascript const config: ElectrobunConfig = { // ... build: { mac: { bundleCEF: true, chromiumFlags: { // Switch-only flag (no value) "show-paint-rects": true, // Flag with a value "user-agent": "MyApp/1.0 (custom)", }, }, linux: { bundleCEF: true, chromiumFlags: { "user-agent": "MyApp/1.0 (custom)", }, }, win: { bundleCEF: true, chromiumFlags: { "user-agent": "MyApp/1.0 (custom)", }, }, }, }; ``` ### How It Works Flags defined in `chromiumFlags` are written into `Resources/build.json` during the build. At runtime, the native wrapper reads them and applies them to CEF's command line via `AppendSwitch` / `AppendSwitchWithValue`. User flags are applied **after** Electrobun's internal flags. If a user flag duplicates an internal one, CEF's last-write-wins behavior applies for value switches. Each applied flag is logged at startup: ```text [CEF] Applying user chromium flag: show-paint-rects [CEF] Applying user chromium flag: user-agent=MyApp/1.0 (custom) ``` **Note:** Chromium flags are powerful and can change browser behavior in unexpected ways. Only use flags you understand. Electrobun does not validate flag names or values — they are passed directly to CEF. ### Common Flags Flag Type Description `user-agent` `string` Override the default user agent string `show-paint-rects` `true` Flash green rectangles over repainted areas (useful for debugging) `show-composited-layer-borders` `true` Show colored borders around GPU-composited layers For a full list of Chromium command-line flags, see the [Chromium Command Line Switches](https://peter.sh/experiments/chromium-command-line-switches/) reference. ## Runtime Configuration The `runtime` section defines settings that affect your application's behaviour at runtime. These values are copied into `build.json` during the build and are accessible via the [BuildConfig API](/electrobun/docs/apis/build-config). ### exitOnLastWindowClosed **Type:** `boolean` **Default:** `true` When `true`, the application automatically quits when the last `BrowserWindow` is closed. This is the most common expected behaviour for desktop applications. Set to `false` if your app should keep running without any open windows (e.g., menu bar apps, or apps that stay in the system tray). ```javascript import type { ElectrobunConfig } from "electrobun"; export default { app: { name: "MyApp", identifier: "com.example.myapp", version: "1.0.0", }, runtime: { exitOnLastWindowClosed: true, // default }, build: { // ... }, } satisfies ElectrobunConfig; ``` ### Custom Runtime Values You can add arbitrary keys to the `runtime` section. The entire object is copied into `build.json` and available at runtime via `BuildConfig`: ```javascript // electrobun.config.ts export default { // ... runtime: { exitOnLastWindowClosed: true, myCustomSetting: "hello", }, } satisfies ElectrobunConfig; // src/bun/index.ts import { BuildConfig } from "electrobun/bun"; const config = await BuildConfig.get(); console.log(config.runtime?.myCustomSetting); // "hello" ``` ## Build Lifecycle Hooks Electrobun provides lifecycle hooks that let you run custom scripts at various stages of the build process. These hooks are configured in the `scripts` section of your `electrobun.config.ts`. ### Available Hooks Hooks are executed in the following order during a build: Hook When it runs Use case `preBuild` Before the build starts Validation, environment setup, generating files, cleanup `postBuild` After the inner app bundle is complete (before ASAR/signing) Modify app bundle contents, add resources `postWrap` After self-extracting bundle created, before signing (non-dev only) Add files to the wrapper bundle (e.g., for macOS features like Liquid Glass) `postPackage` After all build artifacts are created Custom distribution steps, upload, notifications, cleanup ### Configuration Specify hooks as paths to TypeScript or JavaScript files that will be executed with Bun: ```javascript const config: ElectrobunConfig = { // ... app and build config scripts: { preBuild: "./scripts/pre-build.ts", postBuild: "./scripts/post-build.ts", postWrap: "./scripts/post-wrap.ts", postPackage: "./scripts/post-package.ts", }, }; ``` ### Environment Variables All hook scripts receive the following environment variables: Variable Description `ELECTROBUN_BUILD_ENV` Build environment: `dev`, `canary`, or `stable` `ELECTROBUN_OS` Target OS: `macos`, `linux`, or `win` `ELECTROBUN_ARCH` Target architecture: `x64` or `arm64` `ELECTROBUN_BUILD_DIR` Path to the build output directory `ELECTROBUN_APP_NAME` Application name with environment suffix `ELECTROBUN_APP_VERSION` Application version from config `ELECTROBUN_APP_IDENTIFIER` Bundle identifier from config `ELECTROBUN_ARTIFACT_DIR` Path to the artifacts output directory The `postWrap` hook receives an additional variable: * `ELECTROBUN_WRAPPER_BUNDLE_PATH` - Path to the self-extracting wrapper bundle ### Example: Adding files for Liquid Glass (macOS) The `postWrap` hook is ideal for adding files to the self-extracting wrapper before it's code signed. This is useful for macOS features like Liquid Glass that require specific files in the app bundle: ```typescript // scripts/post-wrap.ts import { join } from "path"; import { cpSync, existsSync, mkdirSync } from "fs"; const wrapperPath = process.env.ELECTROBUN_WRAPPER_BUNDLE_PATH; const buildEnv = process.env.ELECTROBUN_BUILD_ENV; if (!wrapperPath) { console.error("ELECTROBUN_WRAPPER_BUNDLE_PATH not set"); process.exit(1); } // Only add Liquid Glass assets for production builds if (buildEnv !== "dev") { const resourcesPath = join(wrapperPath, "Contents", "Resources"); const liquidGlassAssets = "./assets/liquid-glass"; if (existsSync(liquidGlassAssets)) { console.log("Adding Liquid Glass assets to wrapper bundle..."); cpSync(liquidGlassAssets, join(resourcesPath, "liquid-glass"), { recursive: true }); } } console.log("postWrap hook completed"); ``` ### Example: Build validation with preBuild ```typescript // scripts/pre-build.ts import { existsSync } from "fs"; const buildEnv = process.env.ELECTROBUN_BUILD_ENV; // Ensure required environment variables are set for production builds if (buildEnv === "stable") { const requiredVars = [ "ELECTROBUN_DEVELOPER_ID", "ELECTROBUN_APPLEID", "ELECTROBUN_APPLEIDPASS", "ELECTROBUN_TEAMID", ]; const missing = requiredVars.filter(v => !process.env[v]); if (missing.length > 0) { console.error("Missing required environment variables for stable build:"); missing.forEach(v => console.error(` - ${v}`)); process.exit(1); } } // Validate required files exist const requiredFiles = ["src/bun/index.ts", "src/mainview/index.html"]; for (const file of requiredFiles) { if (!existsSync(file)) { console.error(`Required file not found: ${file}`); process.exit(1); } } console.log("preBuild validation passed"); ``` ### Example: Post-package notifications ```typescript // scripts/post-package.ts const buildEnv = process.env.ELECTROBUN_BUILD_ENV; const version = process.env.ELECTROBUN_APP_VERSION; const artifactDir = process.env.ELECTROBUN_ARTIFACT_DIR; console.log(`Build complete: ${buildEnv} v${version}`); console.log(`Artifacts: ${artifactDir}`); // Send Slack notification for production builds if (buildEnv === "stable" && process.env.SLACK_WEBHOOK_URL) { await fetch(process.env.SLACK_WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: `New stable release v${version} built successfully!`, }), }); } ``` **Note:** Hook scripts are run using the host machine's Bun binary, not the bundled one. This ensures scripts always run regardless of the target platform being built. ``
\ No newline at end of file
diff --git a/docs/apis_cli_cli-args.md b/docs/apis_cli_cli-args.md
new file mode 100644
index 0000000..7ab5a0f
--- /dev/null
+++ b/docs/apis_cli_cli-args.md
@@ -0,0 +1,164 @@
+# Electrobun CLI Commands
+
+The Electrobun CLI provides commands for initializing new projects and building your applications for different environments.
+
+## Installation
+
+When you install Electrobun, the CLI tool is added to your `node_modules/bin` folder:
+
+```
+bun install electrobun
+```
+
+This makes the `electrobun` command available in your npm scripts or via `bunx`/`npx`.
+
+## Commands
+
+### `electrobun init`
+
+Initializes a new Electrobun project with starter templates.
+
+#### Usage
+
+```bash
+# Interactive template selection
+electrobun init
+
+# Direct template selection
+electrobun init [template-name]
+```
+
+#### Available Templates
+
+* `hello-world` - Basic single-window application
+* `photo-booth` - Camera app with photo capture functionality
+* `interactive-playground` - An interactive playground of Electrobun apis
+* `multitab-browser` - Multi-tabbed web browser
+
+#### Examples
+
+```bash
+# Choose template interactively
+bunx electrobun init
+
+# Initialize with photo-booth template directly
+bunx electrobun init photo-booth
+
+# Initialize with multitab-browser template
+bunx electrobun init multitab-browser
+```
+
+### `electrobun build`
+
+Builds your Electrobun application according to the configuration in `electrobun.config.ts`.
+
+#### Usage
+
+```
+electrobun build [options]
+```
+
+#### Options
+
+Option
+
+Description
+
+Values
+
+Default
+
+`--env`
+
+Build environment
+
+`dev`, `canary`, `stable`
+
+`dev`
+
+Builds always target the current host platform and architecture. To build for multiple platforms, use CI runners for each OS/architecture (see [Cross-Platform Development](/electrobun/docs/guides/cross-platform-development)).
+
+#### Examples
+
+```bash
+# Development build for current platform
+electrobun build
+
+# Development build with environment flag
+electrobun build --env=dev
+
+# Canary build
+electrobun build --env=canary
+
+# Stable (production) build
+electrobun build --env=stable
+```
+
+## Build Environments
+
+### Development (`dev`)
+
+* Outputs logs and errors to terminal
+* No code signing or notarization
+* Creates build in `build/` folder
+* No artifacts generated
+* Fast iteration for testing
+
+### Canary
+
+* Pre-release/beta builds
+* Optional code signing and notarization
+* Generates distribution artifacts
+* Creates update manifests for auto-updates
+* Suitable for testing with limited users
+
+### Stable
+
+* Production-ready builds
+* Full code signing and notarization (if configured)
+* Optimized and compressed artifacts
+* Ready for distribution to end users
+* Generates all update files
+
+## Build Script Examples
+
+### Basic Setup
+
+```javascript
+// package.json
+{
+ "scripts": {
+ "dev": "electrobun build && electrobun dev",
+ "build": "electrobun build --env=canary",
+ "build:stable": "electrobun build --env=stable"
+ }
+}
+```
+
+### Development Workflow
+
+```javascript
+// package.json
+{
+ "scripts": {
+ "dev": "electrobun build --env=dev && electrobun dev",
+ "dev:watch": "nodemon --watch src --exec 'bun run dev'",
+ "test": "bun test && bun run build"
+ }
+}
+```
+
+### CI Build Scripts
+
+For multi-platform distribution, run the same build command on each platform's CI runner:
+
+```javascript
+// package.json
+{
+ "scripts": {
+ "build:dev": "electrobun build",
+ "build:canary": "electrobun build --env=canary",
+ "build:stable": "electrobun build --env=stable"
+ }
+}
+```
\ No newline at end of file
diff --git a/docs/apis_context-menu.md b/docs/apis_context-menu.md
new file mode 100644
index 0000000..4662f67
--- /dev/null
+++ b/docs/apis_context-menu.md
@@ -0,0 +1,93 @@
+# Context Menu
+
+Show a context menu
+
+Typically you'd wire up a rightclick event with preventDefault in the browser context, rpc to bun, then create a native context menu from the bun context. But you can also create and show a context menu entirely from bun which will show at the mouse cursor's position globally positioned on screen even outside of your application window. Even if you have no windows open and another app is focused.
+
+```javascript
+import {ContextMenu} from "electrobun/bun";
+
+// Show a context menu wherever the mouse cursor is on screen
+// after 5 seconds.
+setTimeout(() => {
+ ContextMenu.showContextMenu([
+ { role: "undo" },
+ { role: "redo" },
+ { type: "separator" },
+ {
+ label: "Custom Menu Item 🚀",
+ action: "custom-action-1",
+ tooltip: "I'm a tooltip",
+ },
+ {
+ label: "Custom menu disabled",
+ enabled: false,
+ action: "custom-action-2",
+ },
+ {
+ label: "Custom menu disabled",
+ enabled: false,
+ action: "custom-action-2",
+ // todo: support a data property on all menus (app, tray, context)
+ data: {
+ some: "data",
+ that: "is serialized",
+ nested: { thing: 23 },
+ },
+ },
+ { type: "separator" },
+ { role: "cut" },
+ { role: "copy" },
+ { role: "paste" },
+ { role: "pasteAndMatchStyle" },
+ { role: "delete" },
+ { role: "selectAll" },
+ ]);
+}, 5000);
+
+Electrobun.events.on("context-menu-clicked", (e) => {
+ console.log("context event", e.data.action);
+});
+```
+
+## Menu Item Properties
+
+### accelerator
+
+You can set a custom keyboard shortcut hint for context menu items using the ``accelerator`}>`` property. This displays the shortcut next to the menu item label.
+
+```javascript
+ContextMenu.showContextMenu([
+ {
+ label: "Save",
+ action: "save",
+ accelerator: "s" // Shows Cmd+S on macOS
+ },
+ {
+ label: "New Tab",
+ action: "new-tab",
+ accelerator: "t"
+ },
+ { type: "separator" },
+ { role: "copy" },
+ { role: "paste" },
+]);
+```
+
+#### Platform Support
+
+* **macOS:** Full support. Accelerators are displayed next to menu items with Command as the default modifier.
+* **Windows:** Supports simple single-character accelerators.
+* **Linux:** Context menus are not currently supported on Linux.
+
+### Other Properties
+
+* **label:** The text displayed for the menu item
+* **action:** A string identifier emitted when the item is clicked
+* **role:** Use a built-in role instead of a custom action (e.g., "copy", "paste", "cut")
+* **enabled:** Set to false to show the item as disabled
+* **checked:** Set to true to show a checkbox
+* **hidden:** Set to true to hide the item
+* **tooltip:** Tooltip text shown on hover
+* **data:** Arbitrary data passed through with the click event
+* **submenu:** Nested array of menu items for submenus
\ No newline at end of file
diff --git a/docs/apis_events.md b/docs/apis_events.md
new file mode 100644
index 0000000..22377ab
--- /dev/null
+++ b/docs/apis_events.md
@@ -0,0 +1,212 @@
+# Events
+
+Event system in the main bun process
+
+## Event Propagation
+
+### Global Events
+
+Most events can be listened to directly on the thing firing them or globally.
+
+For most events, global event handlers fire first. Then handlers are fired in the sequence that they were registered in.
+
+**Exception:** For window `close` events, per-window handlers fire before global handlers. This ensures that your window close handlers always run before the internal `exitOnLastWindowClosed` logic.
+
+```javascript
+// listen to global event
+Electrobun.events.on("will-navigate", (e) => {
+ // handle
+});
+
+// listen to event on object
+win.webview.on('will-navigate', (e) => {
+ // handle
+})
+```
+
+### Event.response
+
+You can set a response on some events. Typically these are events initiated from zig which freeze the zig process while waiting for a reply from bun. An example of this is the BrowserView `will-navigate` where objc requires a synchronous response. By freezing the zig process and waiting for bun we allow bun to remain async while the events propagate.
+
+```javascript
+Electrobun.events.on("will-navigate", (e) => {
+ console.log(
+ "example global will-navigate handler",
+ e.data.url,
+ e.data.webviewId
+ );
+ e.response = { allow: true };
+});
+```
+
+As the event propagates through different handlers you can both read and write from the e.response value.
+
+### Event.responseWasSet
+
+A property that indicates the response has been set to something which can be useful when an event propagates through multiple handlers instead of trying to infer from the response value whether it was set or not.
+
+### Event.clearResponse
+
+If a previous handler has set the e.response to something and you want to clear it, you can simply call `e.clearResponse()`
+
+### Event.data
+
+Each event will set different event data
+
+## Application Events
+
+### open-url
+
+Fired when the application is opened via a custom URL scheme (deep linking). This event is only available on macOS.
+
+**Event data:**
+
+* `url` - The full URL that was used to open the app (e.g., `myapp://some/path?query=value`)
+
+```javascript
+// Listen for URL scheme opens
+Electrobun.events.on("open-url", (e) => {
+ console.log("App opened with URL:", e.data.url);
+
+ // Parse the URL to extract path and query params
+ const url = new URL(e.data.url);
+ console.log("Protocol:", url.protocol); // "myapp:"
+ console.log("Host:", url.host); // might be empty for simple URLs
+ console.log("Pathname:", url.pathname); // "/some/path"
+ console.log("Search:", url.searchParams.get("query")); // "value"
+});
+```
+
+**Platform support:**
+
+* macOS: Fully supported. App must be in `/Applications` folder for URL scheme registration to work reliably.
+* Windows: Not yet supported
+* Linux: Not yet supported
+
+**Setup:** To register URL schemes for your app, add them to your `electrobun.config.ts`. See the [Build Configuration](/electrobun/docs/apis/cli/build-configuration) docs for details.
+
+### before-quit
+
+Fired before the application quits. This event fires regardless of what triggered the quit — whether from `Utils.quit()`, `process.exit()`, `exitOnLastWindowClosed`, or the updater.
+
+You can cancel the quit by setting `event.response = { allow: false }`.
+
+```javascript
+// Listen for quit and do cleanup
+Electrobun.events.on("before-quit", (e) => {
+ console.log("App is about to quit, saving state...");
+ saveAppState();
+});
+
+// Prevent quit (e.g. unsaved changes)
+Electrobun.events.on("before-quit", (e) => {
+ if (hasUnsavedChanges()) {
+ e.response = { allow: false };
+ }
+});
+```
+
+**Event data:** None
+
+**Event response:**
+
+* `allow` - Set to `false` to cancel the quit. If not set or set to `true`, the application will proceed to quit.
+
+## Shutdown Lifecycle
+
+Electrobun provides a unified shutdown flow that ensures your app's `before-quit` handler fires regardless of how the quit was triggered.
+
+### Quit Triggers
+
+All of the following quit paths go through the same lifecycle:
+
+* **Programmatic:** Calling `Utils.quit()` from your app code
+* **process.exit():** Electrobun intercepts `process.exit()` and routes it through the quit lifecycle
+* **exitOnLastWindowClosed:** When the last window closes and this option is enabled
+* **System-initiated:** macOS dock icon → Quit, Cmd+Q, Windows taskbar close, etc.
+* **Signals:** Ctrl+C (SIGINT) and SIGTERM from the terminal or process managers
+* **Updater:** When the updater needs to restart the app
+
+### Shutdown Sequence
+
+When any quit trigger fires, the following sequence occurs:
+
+1. The `before-quit` event fires on the bun worker thread
+2. Your handlers run — you can do cleanup (save state, close connections, flush logs) or cancel the quit by setting `event.response = { allow: false }`
+3. If the quit is not cancelled, the native event loop stops (CEF shuts down, windows close)
+4. The process exits cleanly
+
+**Linux note:** On Linux, system-initiated quit paths (Ctrl+C, window manager close, taskbar quit) do not currently fire `before-quit`. Programmatic quit via `Utils.quit()` and `process.exit()` works correctly on all platforms.
+
+### Ctrl+C Behavior (Dev Mode)
+
+In dev mode (`bun dev`), Ctrl+C triggers a graceful shutdown:
+
+* **First Ctrl+C:** Fires `before-quit`, gives your app time to clean up. The terminal stays busy (no prompt) until shutdown completes.
+* **Second Ctrl+C:** Force-kills the entire process tree immediately, including any CEF helper processes.
+* **Safety timeout:** If the app hangs during shutdown for more than 10 seconds, it is automatically force-killed.
+
+### Comparison with Node.js / Bun Exit Events
+
+Bun (and Node.js) provide built-in process exit events. Here's how they compare to Electrobun's `before-quit`:
+
+Event
+
+Async
+
+Can Cancel
+
+Fires on quit
+
+Notes
+
+`Electrobun.events.on("before-quit")`
+
+Yes
+
+Yes
+
+Yes
+
+Recommended for app cleanup
+
+`process.on("exit")`
+
+No (sync only)
+
+No
+
+Yes
+
+Runs after before-quit. No async work (no `await`, no timers, no I/O).
+
+`process.on("beforeExit")`
+
+Yes
+
+No
+
+No
+
+Does not fire when `process.exit()` is called explicitly, which is how Electrobun terminates.
+
+**Recommendation:** Use Electrobun's `before-quit` event for all shutdown cleanup. It fires for every quit path, supports async operations, and can cancel the quit. The native `process.on("exit")` can be used as a last-resort sync hook, but `process.on("beforeExit")` will not fire in Electrobun apps.
+
+### Example: Complete Shutdown Handling
+
+```javascript
+import Electrobun from "electrobun/bun";
+
+// Main cleanup handler — fires for all quit triggers
+Electrobun.events.on("before-quit", async (e) => {
+ console.log("Saving application state...");
+ await saveAppState();
+ await closeDatabase();
+ console.log("Cleanup complete, quitting.");
+});
+
+// Optional: sync-only last-resort hook (no async, no I/O)
+process.on("exit", (code) => {
+ console.log("Process exiting with code:", code);
+});
+```
\ No newline at end of file
diff --git a/docs/apis_paths.md b/docs/apis_paths.md
new file mode 100644
index 0000000..3f61c25
--- /dev/null
+++ b/docs/apis_paths.md
@@ -0,0 +1,18 @@
+# Paths
+
+Global paths exposed by Electrobun
+
+```
+import PATHS from "electrobun/bun";
+
+// in a macOS bundle this is where static bundled resources are kept.
+
+// Note: You shouldn't modify or write to the bundle at runtime as it will affect code signing
+// integrity.
+PATHS.RESOURCES_FOLDER
+
+// Typically you would use the views:// url scheme which maps to
+// RESOURCES_FOLDER + '/app/views/'
+// But there may be cases in bun where you want to read a file directly.
+PATHS.VIEWS_FOLDER
+```
\ No newline at end of file
diff --git a/docs/apis_tray.md b/docs/apis_tray.md
new file mode 100644
index 0000000..55a047a
--- /dev/null
+++ b/docs/apis_tray.md
@@ -0,0 +1,116 @@
+# Tray
+
+Create and manage system tray icon and menu.
+
+```javascript
+import {Tray} from "electrobun/bun";
+
+const tray = new Tray({
+ title: "Example Tray Item (click to create menu)",
+ // This can be a views url or an absolute file path
+ image: "views://assets/electrobun-logo-32-template.png",
+ template: true,
+ width: 32,
+ height: 32,
+});
+
+// map action names to clicked state
+// Note: This is just used for this example
+const menuState = {
+ "item-1": false,
+ "sub-item-1": false,
+ "sub-item-2": true,
+};
+
+const updateTrayMenu = () => {
+ tray.setMenu([
+ {
+ type: "normal",
+ label: "Toggle me",
+ action: "item-1",
+ checked: menuState["item-1"],
+ tooltip: "I'm a tooltip",
+ submenu: [
+ {
+ type: "normal",
+ label: "Click me to toggle sub-item 2",
+ tooltip: "i will also unhide sub-item-3",
+ action: "sub-item-1",
+ },
+ {
+ type: "divider",
+ },
+ {
+ type: "normal",
+ label: "Toggle sub-item-3's visibility",
+ action: "sub-item-2",
+ enabled: menuState["sub-item-1"],
+ },
+ {
+ type: "normal",
+ label: "I was hidden",
+ action: "sub-item-3",
+ hidden: menuState["sub-item-2"],
+ },
+ ],
+ },
+ ]);
+};
+
+// TODO: events should be typed
+tray.on("tray-clicked", (e) => {
+ const { id, action } = e.data as { id: number; action: string };
+
+ if (action === "") {
+ // main menu was clicked before we create a system tray menu for it.
+ updateTrayMenu();
+ tray.setTitle("Example Tray Item (click to open menu)");
+ } else {
+ // once there's a menu, we can toggle the state of the menu items
+ menuState[action] = !menuState[action];
+ updateTrayMenu();
+ }
+ // respond to left and right clicks on the tray icon/name
+ console.log("event listener for tray clicked", e.data.action);
+});
+```
+
+## Constructor Options
+
+### title
+
+This is the text that will appear in your system tray
+
+### image
+
+This is an optional url to an image to load. You can use the `views://` schema to access local bundled images.
+
+### template
+
+You can use a full-color image like a png but that image will just be shown as is. On MacOS you can create a template image and set the `template` property to true. A template image uses opacity to define a black and white image that adapts to your systems light/dark mode.
+
+### width and height
+
+Set the dimensions of the image used in the system tray
+
+## Methods
+
+### setMenu
+
+Call setMenu whenever you want to show the menu. Typically you would listen for the `tray-clicked` event, then show the menu and listen for the `tray-item-clicked`. Your app could also listen for keyboard shortcuts or show the system tray menu in response to something else.
+
+A common pattern is to create a function that dynamically generates the menu from some kind of state to implement things like checkbox toggles.
+
+### Menu Items
+
+See [Application Menu](/electrobun/docs/apis/application-menu) for more info on available properties for menu items.
+
+## Events
+
+### tray-clicked
+
+This is fired when the system tray item itself is clicked
+
+### tray-item-clicked
+
+This is fired when a system tray menu item or submenu item is clicked.
\ No newline at end of file
diff --git a/docs/apis_updater.md b/docs/apis_updater.md
new file mode 100644
index 0000000..6e88411
--- /dev/null
+++ b/docs/apis_updater.md
@@ -0,0 +1,77 @@
+# Updater
+
+Electrobun's built-in update mechanism for your app
+
+```javascript
+// /src/bun/index.ts
+import { Updater } from "electrobun/bun";
+```
+```javascript
+// /electrobun.config
+{
+ ...
+
+ "release": {
+ "baseUrl": "https://your-release-url"
+ }
+}
+```
+
+## Updating Electrobun Apps
+
+Electrobun ships with a built-in update mechanism that lets you ship updates to your app as small as 14KB so you can ship often. All you need to do is specify a url where your artifacts are stored in your `electrobun.config` file. A static file host like AWS S3 + Cloudfront, Cloudflare R2, or even GitHub Releases is more than enough, most likely your app will stay well within the free tier.
+
+The electrobun `cli` will automatically generate a flat `artifacts` folder for each non-dev build (typically `canary` and `stable`). Just upload the files to your host and set the `baseUrl`, then use the API to check for and install updates when your app launches, on an interval, or in response to a system tray menu item.
+
+## Methods
+
+### getLocalInfo
+
+Get the local version info for display in your app or other logic. This will read the `version.json` file bundled with your app.
+
+```yaml
+const localInfo = await Electrobun.Updater.getLocal;
+
+localInfo: {
+ version: string;
+ hash: string;
+ baseUrl: string;
+ channel: string;
+ name: string;
+ identifier: string;
+};
+```
+
+### checkForUpdate
+
+Checks for an update by fetching the `update.json` file from the `baseUrl` for the current channel and platform.
+
+```javascript
+const updateInfo = await Electrobun.Updater.checkForUpdate();
+
+updateInfo: {
+ version: string;
+ hash: string;
+ updateAvailable: boolean;
+ updateReady: boolean;
+ error: string;
+};
+```
+
+### downloadUpdate
+
+This will initiate a process where it attempts to download patch files and apply them until the patched app matches the current version. If something goes wrong like there isn't a trail of patch files from the user's version to current it will download the latest full version of the app directly.
+
+```javascript
+await Electrobun.Updater.downloadUpdate();
+```
+
+### applyUpdate
+
+Once the latest version is either patched or downloaded and ready to install you can call `applyUpdate` to quit the current app, replace it with the latest version, and relaunch.
+
+```javascript
+if (Electrobun.Updater.updateInfo()?.updateReady) {
+ await Electrobun.Updater.applyUpdate();
+}
+```
\ No newline at end of file
diff --git a/docs/apis_utils.md b/docs/apis_utils.md
new file mode 100644
index 0000000..e80204c
--- /dev/null
+++ b/docs/apis_utils.md
@@ -0,0 +1,1018 @@
+# Utils
+
+Various utilities for Electrobun apps.
+
+```javascript
+import {Utils} from "electrobun/bun";
+```
+
+## moveToTrash
+
+Move a file or folder on your system to the Trash (recycle bin).
+
+On MacOS when you move something to trash from the finder you can open the trash can and see a "restore" button that will put the files/folders back where they were deleted from
+
+When using moveToTrash in Electrobun it moves it to the trash can but does not enable the "restore" button. To restore you will need to manually drag the files and folders back to their originating folder
+
+```javascript
+Utils.moveToTrash(absolutePath)
+```
+
+## showItemInFolder
+
+Open the file manager (Finder on macOS, Explorer on Windows, etc.) and select the specified file or folder.
+
+```javascript
+Utils.showItemInFolder(absolutePath)
+```
+
+## openExternal
+
+Open a URL in the default browser or appropriate application. Works with `http://`, `https://`, `mailto:`, custom URL schemes, and more.
+
+### Parameters
+
+Parameter
+
+Type
+
+Description
+
+`url`
+
+`string`
+
+The URL to open (e.g., "https://example.com")
+
+### Returns
+
+`boolean` - Returns `true` if the URL was opened successfully, `false` otherwise.
+
+### Examples
+
+```javascript
+// Open a website in the default browser
+Utils.openExternal("https://example.com");
+
+// Open an email compose window
+Utils.openExternal("mailto:[email protected]?subject=Help");
+
+// Open a custom URL scheme (if registered)
+Utils.openExternal("slack://open");
+
+// Open with file:// protocol
+Utils.openExternal("file:///Users/me/Documents/report.pdf");
+```
+
+## openPath
+
+Open a file or folder with its default application. For files, this opens them with the associated application (e.g., `.pdf` opens with the default PDF reader). For folders, this opens them in the file manager.
+
+### Parameters
+
+Parameter
+
+Type
+
+Description
+
+`path`
+
+`string`
+
+The absolute path to the file or folder
+
+### Returns
+
+`boolean` - Returns `true` if the path was opened successfully, `false` otherwise.
+
+### Examples
+
+```javascript
+// Open a PDF with the default PDF reader
+Utils.openPath("/Users/me/Documents/report.pdf");
+
+// Open an image with the default image viewer
+Utils.openPath("/Users/me/Pictures/photo.jpg");
+
+// Open a folder in the file manager
+Utils.openPath("/Users/me/Downloads");
+
+// Open a text file with the default text editor
+Utils.openPath("/Users/me/notes.txt");
+```
+
+**Difference between openExternal and openPath:**
+
+* `openExternal()` - Takes a URL (with protocol like `https://`, `mailto:`, `file://`)
+* `openPath()` - Takes a file system path (e.g., `/Users/me/file.pdf`)
+
+Use `openExternal()` for web URLs and email links. Use `openPath()` for local files and folders.
+
+## showNotification
+
+Display a native desktop notification. Works on macOS, Windows, and Linux.
+
+### Options
+
+Property
+
+Type
+
+Required
+
+Description
+
+`title`
+
+`string`
+
+Yes
+
+The title of the notification
+
+`body`
+
+`string`
+
+No
+
+The main body text of the notification
+
+`subtitle`
+
+`string`
+
+No
+
+A subtitle (macOS displays this between title and body; other platforms show it as an additional line)
+
+`silent`
+
+`boolean`
+
+No
+
+If true, the notification will not play a sound (default: false)
+
+### Example: Simple Notification
+
+```javascript
+Utils.showNotification({
+ title: "Download Complete"
+});
+```
+
+### Example: Notification with Body
+
+```javascript
+Utils.showNotification({
+ title: "New Message",
+ body: "You have a new message from John"
+});
+```
+
+### Example: Full Notification
+
+```javascript
+Utils.showNotification({
+ title: "Reminder",
+ subtitle: "Calendar Event",
+ body: "Team meeting in 15 minutes",
+ silent: false
+});
+```
+
+### Example: Silent Notification
+
+```javascript
+Utils.showNotification({
+ title: "Sync Complete",
+ body: "All files have been synchronized",
+ silent: true
+});
+```
+
+### Platform Notes
+
+* **macOS:** Uses NSUserNotificationCenter. Notifications appear in Notification Center.
+* **Windows:** Uses Shell balloon notifications. The notification appears near the system tray.
+* **Linux:** Uses `notify-send` command. Requires `libnotify-bin` to be installed (included by default on most desktop distributions).
+
+## openFileDialog
+
+Open a file dialogue to let the user specify a file or folder and return the path to your app. Typically you would have an event handler in the browser context like clicking an "open" button, this would trigger an rpc call to bun, which would call ``openFileDialog()`}>`` and then optionally pass the response back to the browser context via rpc after the user has made their selection
+
+```javascript
+// To simplify this example we'll just show a dialogue after a 2 second timeout
+
+setTimeout(async () => {
+
+ const chosenPaths = await Utils.openFileDialog({
+ startingFolder: join(homedir(), "Desktop"),
+ allowedFileTypes: "*",
+ // allowedFileTypes: "png,jpg",
+ canChooseFiles: true,
+ canChooseDirectory: false,
+ allowsMultipleSelection: true,
+ });
+
+ console.log("chosen paths", chosenPaths);
+ }, 2000);
+```
+
+## showMessageBox
+
+Display a native message box dialog with custom buttons and get the user's response. Similar to Electron's `dialog.showMessageBox()`.
+
+### Options
+
+Property
+
+Type
+
+Default
+
+Description
+
+`type`
+
+`"info" | "warning" | "error" | "question"`
+
+`"info"`
+
+The type of dialog, affects the icon displayed
+
+`title`
+
+`string`
+
+`""`
+
+The title of the dialog window
+
+`message`
+
+`string`
+
+`""`
+
+The main message to display
+
+`detail`
+
+`string`
+
+`""`
+
+Additional detail text (displayed smaller on some platforms)
+
+`buttons`
+
+`string[]`
+
+`["OK"]`
+
+Array of button labels
+
+`defaultId`
+
+`number`
+
+`0`
+
+Index of the button that should be focused by default
+
+`cancelId`
+
+`number`
+
+`-1`
+
+Index of the button to return when dialog is cancelled (Escape key or close button)
+
+### Return Value
+
+Returns a `Promise<{ response: number }>` where `response` is the 0-based index of the clicked button.
+
+### Example: Confirmation Dialog
+
+```javascript
+const { response } = await Utils.showMessageBox({
+ type: "question",
+ title: "Confirm Delete",
+ message: "Are you sure you want to delete this file?",
+ detail: "This action cannot be undone.",
+ buttons: ["Delete", "Cancel"],
+ defaultId: 1, // Focus "Cancel" by default
+ cancelId: 1 // Pressing Escape returns 1 (Cancel)
+});
+
+if (response === 0) {
+ // User clicked "Delete"
+ console.log("Deleting file...");
+} else {
+ // User clicked "Cancel" or closed the dialog
+ console.log("Cancelled");
+}
+```
+
+### Example: Error Dialog
+
+```javascript
+await Utils.showMessageBox({
+ type: "error",
+ title: "Error",
+ message: "Failed to save file",
+ detail: "The disk may be full or you may not have write permissions.",
+ buttons: ["OK"]
+});
+```
+
+### Example: Multi-choice Dialog
+
+```javascript
+const { response } = await Utils.showMessageBox({
+ type: "warning",
+ title: "Unsaved Changes",
+ message: "You have unsaved changes.",
+ detail: "What would you like to do?",
+ buttons: ["Save", "Don't Save", "Cancel"],
+ defaultId: 0,
+ cancelId: 2
+});
+
+switch (response) {
+ case 0: saveAndClose(); break;
+ case 1: closeWithoutSaving(); break;
+ case 2: /* cancelled */ break;
+}
+```
+
+## quit
+
+Gracefully quit the application. This fires a `before-quit` event (which can cancel the quit), then performs native cleanup (including CEF shutdown) and terminates the process.
+
+You don't need to call `quit()` directly for most quit scenarios — Electrobun automatically routes system-initiated quits (dock icon, Cmd+Q, Ctrl+C, SIGTERM, window close) through the same lifecycle. Calling `process.exit()` is also intercepted and routed through `quit()`.
+
+```javascript
+Utils.quit()
+
+// The quit can be cancelled via the before-quit event
+Electrobun.events.on("before-quit", (e) => {
+ if (hasUnsavedChanges()) {
+ e.response = { allow: false };
+ }
+});
+```
+
+See [Events — Shutdown Lifecycle](/electrobun/docs/apis/events#shutdown-lifecycle) for the full list of quit triggers, shutdown sequence, and how `before-quit` compares to bun's native `process.on("exit")`.
+
+## Clipboard API
+
+Read and write to the system clipboard. Similar to Electron's clipboard module.
+
+### clipboardReadText
+
+Read text from the system clipboard.
+
+```javascript
+const text = Utils.clipboardReadText();
+if (text) {
+ console.log("Clipboard contains:", text);
+}
+```
+
+### clipboardWriteText
+
+Write text to the system clipboard.
+
+```javascript
+Utils.clipboardWriteText("Hello from Electrobun!");
+```
+
+### clipboardReadImage
+
+Read image from the clipboard as PNG data. Returns a `Uint8Array` containing PNG bytes, or `null` if no image is available.
+
+```javascript
+const pngData = Utils.clipboardReadImage();
+if (pngData) {
+ // Write to file
+ await Bun.write("clipboard-image.png", pngData);
+ console.log("Saved clipboard image:", pngData.length, "bytes");
+}
+```
+
+### clipboardWriteImage
+
+Write PNG image data to the clipboard.
+
+```javascript
+// Read a PNG file and write to clipboard
+const pngData = await Bun.file("image.png").arrayBuffer();
+Utils.clipboardWriteImage(new Uint8Array(pngData));
+```
+
+### clipboardClear
+
+Clear the clipboard contents.
+
+```javascript
+Utils.clipboardClear();
+```
+
+### clipboardAvailableFormats
+
+Get the available formats in the clipboard. Returns an array of format names.
+
+```javascript
+const formats = Utils.clipboardAvailableFormats();
+console.log("Clipboard contains:", formats);
+// Possible values: ["text", "image", "files", "html"]
+```
+
+### Example: Copy and Paste Text
+
+```javascript
+// Copy text to clipboard
+Utils.clipboardWriteText("Hello World");
+
+// Later, read it back
+const text = Utils.clipboardReadText();
+console.log(text); // "Hello World"
+```
+
+### Example: Check Clipboard Contents
+
+```javascript
+const formats = Utils.clipboardAvailableFormats();
+
+if (formats.includes("text")) {
+ const text = Utils.clipboardReadText();
+ console.log("Text:", text);
+}
+
+if (formats.includes("image")) {
+ const imageData = Utils.clipboardReadImage();
+ console.log("Image size:", imageData?.length, "bytes");
+}
+```
+
+## Paths
+
+Cross-platform access to common OS directories and app-scoped directories. All properties are synchronous getters.
+
+```javascript
+import { Utils } from "electrobun/bun";
+
+console.log(Utils.paths.home); // Home directory
+console.log(Utils.paths.downloads); // Downloads folder
+console.log(Utils.paths.userData); // App-specific data directory
+```
+
+### OS Directories
+
+```javascript
+Utils.paths.home
+// macOS: ~
+// Windows: %USERPROFILE%
+// Linux: ~
+
+Utils.paths.appData
+// macOS: ~/Library/Application Support
+// Windows: %LOCALAPPDATA%
+// Linux: $XDG_DATA_HOME or ~/.local/share
+
+Utils.paths.config
+// macOS: ~/Library/Application Support
+// Windows: %APPDATA%
+// Linux: $XDG_CONFIG_HOME or ~/.config
+
+Utils.paths.cache
+// macOS: ~/Library/Caches
+// Windows: %LOCALAPPDATA%
+// Linux: $XDG_CACHE_HOME or ~/.cache
+
+Utils.paths.temp
+// macOS: $TMPDIR
+// Windows: %TEMP%
+// Linux: /tmp
+
+Utils.paths.logs
+// macOS: ~/Library/Logs
+// Windows: %LOCALAPPDATA%
+// Linux: $XDG_STATE_HOME or ~/.local/state
+
+Utils.paths.documents
+// macOS: ~/Documents
+// Windows: %USERPROFILE%\Documents
+// Linux: $XDG_DOCUMENTS_DIR or ~/Documents
+
+Utils.paths.downloads
+// macOS: ~/Downloads
+// Windows: %USERPROFILE%\Downloads
+// Linux: $XDG_DOWNLOAD_DIR or ~/Downloads
+
+Utils.paths.desktop
+// macOS: ~/Desktop
+// Windows: %USERPROFILE%\Desktop
+// Linux: $XDG_DESKTOP_DIR or ~/Desktop
+
+Utils.paths.pictures
+// macOS: ~/Pictures
+// Windows: %USERPROFILE%\Pictures
+// Linux: $XDG_PICTURES_DIR or ~/Pictures
+
+Utils.paths.music
+// macOS: ~/Music
+// Windows: %USERPROFILE%\Music
+// Linux: $XDG_MUSIC_DIR or ~/Music
+
+Utils.paths.videos
+// macOS: ~/Movies
+// Windows: %USERPROFILE%\Videos
+// Linux: $XDG_VIDEOS_DIR or ~/Videos
+```
+
+### App-Scoped Directories
+
+These paths are scoped to your application using the `identifier` and `channel` from your app's `version.json`. The values are lazy-loaded on first access.
+
+```javascript
+Utils.paths.userData // {appData}/{identifier}/{channel}
+Utils.paths.userCache // {cache}/{identifier}/{channel}
+Utils.paths.userLogs // {logs}/{identifier}/{channel}
+
+// Example: app with identifier "com.mycompany.myapp", channel "canary", on macOS:
+Utils.paths.userData // ~/Library/Application Support/com.mycompany.myapp/canary
+Utils.paths.userCache // ~/Library/Caches/com.mycompany.myapp/canary
+Utils.paths.userLogs // ~/Library/Logs/com.mycompany.myapp/canary
+```
+
+### Example: Store App Data
+
+```javascript
+import { Utils } from "electrobun/bun";
+import { join } from "path";
+import { mkdirSync, writeFileSync, readFileSync } from "fs";
+
+// Ensure the directory exists
+mkdirSync(Utils.paths.userData, { recursive: true });
+
+// Write a settings file
+const settingsPath = join(Utils.paths.userData, "settings.json");
+writeFileSync(settingsPath, JSON.stringify({ theme: "dark" }));
+
+// Read it back
+const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
+```
+
+### Example: Save Downloads to User's Downloads Folder
+
+```javascript
+import { Utils } from "electrobun/bun";
+import { join } from "path";
+
+const savePath = join(Utils.paths.downloads, "report.pdf");
+await Bun.write(savePath, pdfData);
+```
+
+**Linux XDG Support:** On Linux, user directories (`documents`, `downloads`, etc.) are resolved by reading `~/.config/user-dirs.dirs`. If the file is not present, standard fallbacks like `~/Documents` are used.
+
+## GlobalShortcut
+
+Register global keyboard shortcuts that work even when your app doesn't have focus. Similar to Electron's globalShortcut module.
+
+```javascript
+import { GlobalShortcut } from "electrobun/bun";
+```
+
+### register
+
+Register a global keyboard shortcut with a callback function.
+
+Parameter
+
+Type
+
+Description
+
+`accelerator`
+
+`string`
+
+The keyboard shortcut (e.g., "CommandOrControl+Shift+Space")
+
+`callback`
+
+`() => void`
+
+Function to call when the shortcut is triggered
+
+**Returns:** `boolean` - true if registered successfully, false otherwise
+
+```javascript
+const success = GlobalShortcut.register("CommandOrControl+Shift+Space", () => {
+ console.log("Global shortcut triggered!");
+ // Show your app, toggle a feature, etc.
+});
+
+if (!success) {
+ console.log("Failed to register shortcut (may already be in use)");
+}
+```
+
+### unregister
+
+Unregister a previously registered global shortcut.
+
+```javascript
+GlobalShortcut.unregister("CommandOrControl+Shift+Space");
+```
+
+### unregisterAll
+
+Unregister all global shortcuts registered by your app.
+
+```javascript
+GlobalShortcut.unregisterAll();
+```
+
+### isRegistered
+
+Check if a shortcut is currently registered.
+
+```javascript
+if (GlobalShortcut.isRegistered("CommandOrControl+Shift+Space")) {
+ console.log("Shortcut is active");
+}
+```
+
+### Accelerator Syntax
+
+Accelerators are strings that describe keyboard shortcuts. They consist of modifier keys and a regular key, separated by `+`.
+
+**Modifiers:**
+
+* `Command` / `Cmd` - Command key (macOS)
+* `Control` / `Ctrl` - Control key
+* `CommandOrControl` / `CmdOrCtrl` - Command on macOS, Control on Windows/Linux
+* `Alt` / `Option` - Alt key (Option on macOS)
+* `Shift` - Shift key
+* `Super` / `Meta` / `Win` - Windows key / Super key
+
+**Keys:**
+
+* Letters: `A` through `Z`
+* Numbers: `0` through `9`
+* Function keys: `F1` through `F12`
+* Special: `Space`, `Enter`, `Tab`, `Escape`, `Backspace`, `Delete`
+* Navigation: `Up`, `Down`, `Left`, `Right`, `Home`, `End`, `PageUp`, `PageDown`
+* Symbols: `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/`, `` \` ``
+
+### Example: Toggle App Visibility
+
+```javascript
+import { GlobalShortcut, BrowserWindow } from "electrobun/bun";
+
+// Register a shortcut to show/hide the main window
+GlobalShortcut.register("CommandOrControl+Shift+H", () => {
+ const win = BrowserWindow.getById(1);
+ if (win) {
+ // Toggle visibility
+ if (win.isVisible()) {
+ win.hide();
+ } else {
+ win.show();
+ win.focus();
+ }
+ }
+});
+```
+
+### Platform Notes
+
+* **macOS:** Uses `NSEvent addGlobalMonitorForEventsMatchingMask`. Shortcuts are observed but cannot block the event from reaching other apps.
+* **Windows:** Uses `RegisterHotKey`. Provides exclusive access to the shortcut.
+* **Linux:** Uses X11 `XGrabKey`. Provides exclusive access to the shortcut. Requires X11 display server.
+
+**Note:** Some shortcuts may already be reserved by the operating system or other applications. If registration fails, try a different key combination.
+
+## Screen
+
+The Screen module provides information about connected displays and the cursor position. This is useful for positioning windows, detecting multi-monitor setups, and getting screen dimensions.
+
+```javascript
+import { Screen } from "electrobun/bun";
+```
+
+### Types
+
+```typescript
+interface Rectangle {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+interface Display {
+ id: number; // Unique display identifier
+ bounds: Rectangle; // Full screen bounds
+ workArea: Rectangle; // Available area (excludes dock/taskbar/menu bar)
+ scaleFactor: number; // DPI scale (e.g., 2.0 for Retina/HiDPI)
+ isPrimary: boolean; // Whether this is the primary display
+}
+
+interface Point {
+ x: number;
+ y: number;
+}
+```
+
+### Screen.getPrimaryDisplay()
+
+Returns the primary display information.
+
+```javascript
+const primary = Screen.getPrimaryDisplay();
+console.log(`Primary display: ${primary.bounds.width}x${primary.bounds.height}`);
+console.log(`Scale factor: ${primary.scaleFactor}x`);
+console.log(`Work area: ${primary.workArea.width}x${primary.workArea.height}`);
+```
+
+### Screen.getAllDisplays()
+
+Returns an array of all connected displays.
+
+```javascript
+const displays = Screen.getAllDisplays();
+console.log(`Found ${displays.length} display(s)`);
+
+displays.forEach((display, index) => {
+ console.log(`Display ${index}: ${display.bounds.width}x${display.bounds.height}`);
+ console.log(` Position: (${display.bounds.x}, ${display.bounds.y})`);
+ console.log(` Scale: ${display.scaleFactor}x`);
+ console.log(` Primary: ${display.isPrimary}`);
+});
+```
+
+### Screen.getCursorScreenPoint()
+
+Returns the current cursor position in screen coordinates.
+
+```javascript
+const cursor = Screen.getCursorScreenPoint();
+console.log(`Cursor at: (${cursor.x}, ${cursor.y})`);
+```
+
+### Example: Center Window on Primary Display
+
+```javascript
+import { Screen, BrowserWindow } from "electrobun/bun";
+
+const primary = Screen.getPrimaryDisplay();
+const windowWidth = 800;
+const windowHeight = 600;
+
+// Calculate centered position
+const x = Math.round((primary.workArea.width - windowWidth) / 2) + primary.workArea.x;
+const y = Math.round((primary.workArea.height - windowHeight) / 2) + primary.workArea.y;
+
+const win = new BrowserWindow({
+ title: "Centered Window",
+ url: "views://mainview/index.html",
+ frame: {
+ width: windowWidth,
+ height: windowHeight,
+ x: x,
+ y: y,
+ },
+});
+```
+
+### Example: Open Window on Display with Cursor
+
+```javascript
+import { Screen, BrowserWindow } from "electrobun/bun";
+
+function getDisplayAtCursor() {
+ const cursor = Screen.getCursorScreenPoint();
+ const displays = Screen.getAllDisplays();
+
+ return displays.find(display => {
+ const { x, y, width, height } = display.bounds;
+ return cursor.x >= x && cursor.x < x + width &&
+ cursor.y >= y && cursor.y < y + height;
+ }) || Screen.getPrimaryDisplay();
+}
+
+const targetDisplay = getDisplayAtCursor();
+console.log(`Opening window on display: ${targetDisplay.id}`);
+```
+
+### Platform Notes
+
+* **macOS:** Uses `NSScreen` and `CGMainDisplayID`. Coordinates are converted from bottom-left origin to top-left origin for consistency.
+* **Windows:** Uses `EnumDisplayMonitors` and `GetDpiForMonitor` for scale factor.
+* **Linux:** Uses GDK `gdk_display_get_n_monitors` and related APIs.
+
+## Session
+
+The Session module provides cookie and storage management for webview partitions. Each partition has isolated storage, allowing you to manage cookies and clear data independently.
+
+```javascript
+import { Session } from "electrobun/bun";
+```
+
+### Types
+
+```typescript
+interface Cookie {
+ name: string;
+ value: string;
+ domain?: string;
+ path?: string;
+ secure?: boolean;
+ httpOnly?: boolean;
+ sameSite?: 'no_restriction' | 'lax' | 'strict';
+ expirationDate?: number; // Unix timestamp in seconds
+}
+
+interface CookieFilter {
+ url?: string;
+ name?: string;
+ domain?: string;
+ path?: string;
+ secure?: boolean;
+ session?: boolean;
+}
+
+type StorageType =
+ | 'cookies'
+ | 'localStorage'
+ | 'sessionStorage'
+ | 'indexedDB'
+ | 'webSQL'
+ | 'cache'
+ | 'all';
+```
+
+### Session.fromPartition(partition)
+
+Get or create a session for a specific partition. Partitions starting with `persist:` are stored on disk, others are ephemeral.
+
+```javascript
+// Persistent session (survives app restarts)
+const session = Session.fromPartition("persist:myapp");
+
+// Ephemeral session (cleared when app closes)
+const tempSession = Session.fromPartition("temp");
+```
+
+### Session.defaultSession
+
+Get the default session (equivalent to `persist:default` partition).
+
+```javascript
+const session = Session.defaultSession;
+console.log(session.partition); // "persist:default"
+```
+
+### session.cookies.get(filter?)
+
+Get cookies matching the optional filter criteria. Returns an array of Cookie objects.
+
+```javascript
+const session = Session.fromPartition("persist:myapp");
+
+// Get all cookies
+const allCookies = session.cookies.get();
+console.log(`Found ${allCookies.length} cookies`);
+
+// Get cookies by name
+const authCookies = session.cookies.get({ name: "auth_token" });
+
+// Get cookies by domain
+const domainCookies = session.cookies.get({ domain: "example.com" });
+
+// Get cookies by URL
+const urlCookies = session.cookies.get({ url: "https://api.example.com" });
+```
+
+### session.cookies.set(cookie)
+
+Set a cookie. Returns `true` if successful.
+
+```javascript
+const session = Session.fromPartition("persist:myapp");
+
+// Set a basic cookie
+session.cookies.set({
+ name: "user_id",
+ value: "12345",
+ domain: "myapp.com",
+ path: "/"
+});
+
+// Set a secure cookie with expiration
+session.cookies.set({
+ name: "auth_token",
+ value: "abc123xyz",
+ domain: "api.myapp.com",
+ path: "/",
+ secure: true,
+ httpOnly: true,
+ sameSite: "strict",
+ expirationDate: Math.floor(Date.now() / 1000) + (7 * 24 * 60 * 60) // 7 days
+});
+```
+
+### session.cookies.remove(url, name)
+
+Remove a specific cookie by URL and name. Returns `true` if successful.
+
+```javascript
+const session = Session.fromPartition("persist:myapp");
+
+// Remove a specific cookie
+session.cookies.remove("https://myapp.com", "user_id");
+```
+
+### session.cookies.clear()
+
+Clear all cookies for this session.
+
+```javascript
+const session = Session.fromPartition("persist:myapp");
+
+// Clear all cookies
+session.cookies.clear();
+```
+
+### session.clearStorageData(types?)
+
+Clear storage data for this session. You can specify which types to clear, or use `'all'` to clear everything.
+
+```javascript
+const session = Session.fromPartition("persist:myapp");
+
+// Clear all storage
+session.clearStorageData();
+
+// Clear specific storage types
+session.clearStorageData(['cookies', 'localStorage']);
+
+// Clear just the cache
+session.clearStorageData(['cache']);
+```
+
+### Example: User Logout
+
+```javascript
+import { Session } from "electrobun/bun";
+
+function logout() {
+ const session = Session.fromPartition("persist:user");
+
+ // Clear auth cookies
+ session.cookies.remove("https://api.myapp.com", "auth_token");
+ session.cookies.remove("https://api.myapp.com", "refresh_token");
+
+ // Clear local storage data
+ session.clearStorageData(['localStorage', 'sessionStorage']);
+
+ console.log("User logged out");
+}
+```
+
+### Example: Multi-account Support
+
+```javascript
+import { Session, BrowserWindow } from "electrobun/bun";
+
+// Create a webview with its own session
+function createAccountWindow(accountId: string) {
+ const partition = `persist:account-${accountId}`;
+ const session = Session.fromPartition(partition);
+
+ // Each account has isolated cookies/storage
+ const win = new BrowserWindow({
+ title: `Account: ${accountId}`,
+ url: "https://myapp.com/dashboard",
+ partition: partition,
+ frame: { width: 800, height: 600, x: 100, y: 100 }
+ });
+
+ return { window: win, session };
+}
+```
+
+### Platform Notes
+
+* **macOS:** Uses `WKHTTPCookieStore` for WebKit and CEF's cookie manager.
+* **Windows:** Uses `ICoreWebView2CookieManager` for WebView2.
+* **Linux:** Uses `WebKitCookieManager` for WebKit2GTK and CEF's cookie manager.
+
+**Partition Naming:** Use `persist:` prefix for persistent storage (e.g., `persist:myapp`). Sessions without this prefix are ephemeral and cleared when the app closes.
\ No newline at end of file
diff --git a/docs/guides_architecture_overview.md b/docs/guides_architecture_overview.md
new file mode 100644
index 0000000..71d3529
--- /dev/null
+++ b/docs/guides_architecture_overview.md
@@ -0,0 +1,130 @@
+# Architecture Overview
+
+## High Level App Architecture
+
+An Electrobun app is essentially a Bun app. A tiny launcher (typically a zig binary) will run a Bun app. Since native GUI's require a blocking event loop on the main thread the main Bun thread will create a webworker with your code and then use Bun's FFI to init the native GUI event loop. Your Bun code running in the worker can then use Electrobun's apis, many of which also call Electrobun's native wrapper code via Bun's FFI to open windows, create system trays, relay events and RPC, and so on.
+
+## Application Bundles
+
+### MacOS
+
+#### Your Installed App
+
+On MacOS an application bundle is really just a folder with a .app file extension. The key subfolders inside are
+
+```
+// electrobun places several binaries here. If bundling additional binaries on Mac and code-signing you must place them here
+/Contents/MacOS
+
+// An optimized zig implementation of bspatch used to generate and apply diffs during updates
+/Contents/MacOS/bspatch
+
+// The bun runtime
+/Contents/MacOS/bun
+
+// An optimized zig binary that typically just calls `bun index.js` with the included runtime
+// to run your compiled bun entrypoint file.
+/Contents/MacOS/launcher
+
+// A library containing Electrobun's native code layer for the platform, on MacOS this these are
+// objc/c++ code for interfacing with MacOS apis like NSWindow and WKWebkit
+/Contents/MacOS/libNativeWrapper.dylib
+
+// electrobun compiles your application's custom code here
+/Contents/MacOS/Resources
+
+// Your application icons
+/Contents/MacOS/Resources/AppIcon.icns
+
+// Local version info that `Electrobun.Updater` reads
+/Contents/MacOS/Resources/version.json
+
+// Folder containing the bundled javascript code for the main bun process.
+// Use electrobun.config to tell Electrobun where your ts entrypoing is and
+// define external dependencies
+/Contents/MacOS/Resources/app/bun/
+
+// This is where your views defined in electrobun.config.ts are transpiled to
+// Browserviews can also use the views:// url schema anywhere urls are loaded
+// to load bundled static content from here.
+/Contents/MacOS/Resources/app/views
+```
+
+#### IPC
+
+In order to communicate between bun and browser contexts Electrobun has several mechanisms for establishing IPC between the processes involved. For the most part Electrobun uses postmessage and FFI but will also use more efficient encrypted web sockets.
+
+#### Self-Extracting Bundle
+
+Because zip file compression is not the best and we want apps you build with Electrobun to be as tiny as possible Electron automatically bundles your application into a self-extracting ZSTD bundle. Electrobun takes your entire app bundle, tars it, compresses it with zlib which is fast best-in-class modern compression and creates a second wrapper app bundle for distribution.
+
+**Info:** The current Electrobun Playground app is **50.4MB** in size (most of this is the bun runtime), but when compressed and distributed as the self-extracting bundle it's only **13.1MB which is almost 5 times smaller**. _Meaning almost 5 times as many users can download your app for the same cost in Storage and Network fees._
+
+The self-extracting bundle looks like this:
+
+```
+// This is different from the regular launcher binary. It's a zig binary that uses zlip to decompress your actual app bundle
+/Contents/MacOS/launcher
+
+// App icons are actually stored again so the self-extractor looks just like your extracted bundled app.
+/Contents/Resources/AppIcons.icns
+
+// Your actual app bundled, tarred, and compressed with the name set to the hash
+/Contents/Resources/23fajlkj2.tar.zst
+```
+
+A user can install the self-extracting bundle the same as any other application in the `/Applications/` folder or run it from any folder on your machine. When your end-user double clicks to open it, it will transparently self-extract and replace itself with your full application and then launch the full application. To your user it just looks like the first time opening your app takes 1 or 2 seconds longer.
+
+The self-extraction process only happens on first install and is entirely local and self-contained using only a designated application support folder for your app for the extraction and verification.
+
+#### DMG
+
+Finally electrobun will automatically generate a DMG with the self-extracting bundle inside.
+
+## Code Signing and Notarization
+
+Electrobun will automatically code sign and notarize your application for you.
+
+### MacOS
+
+There is a prerequisite to register for an Apple Developer account and create an app id as well as download your code signing certificate. We'll have a guide that walks you through this process. There is no need to have any private keys in your code repo but you do need to set `codesigning` and `notarization` flags to `true` in your `electrobun.config` file and make some credentials available in your env.
+
+On MacOS Electrobun will code sign and notarize both your app bundle **and** the self-extracting bundle so your end-users can be confident that what their installing is legitimately from you and has been scanned by Apple.
+
+While code signing is generally very fast, notarization requires uploading a zip file to Apple's servers and waiting for them to scan and verify your app's code which typically takes about 1-2 minutes. The notarization is then stapled to your app bundle.
+
+Because notarization can take time, in cases where a bug only exists on non-dev builds you can simply turn off code signing and/or notarization in your `electrobun.config` while debugging to speed up the build process.
+
+Any notarization issues will be shown to you in the terminal so you can address them. This typically involves setting certain entitlements for your application so that your app declares what it uses to Apple and your end-users.
+
+## Updating
+
+Electrobun has a [built-in update mechanism](/electrobun/docs/apis/updater/) that optimizes updates for file-size and efficiency.
+
+**Info:** Ship updates to your users as small as 14KB. This lets your ship often without paying huge storage and network fees. No server required, all you need is a static file host like S3 which you can put behind a CDN like Cloudfront. Most apps will fall well within AWS's free tier even if you ship updates often to many users.
+
+### How does it work
+
+Using the [Electrobun Updater api](/electrobun/docs/apis/updater/) you can check for updates and automatically download, and install them. The flow looks something like:
+
+1. Check the local version.json hash against the hosted update.json hash of the latest version.
+2. If it's different download the tiny patch file that matches the hash you have (generated with BSDIFF) and apply it to the current bundle.
+3. Generate a hash of the patched bundle. If it matches the latest hash then replace the running application with the latest version of the app and relaunch (you can control when with the api and let the user trigger this manually when they're ready)
+4. If the hash does not match the latest look for another patch file and keep patching until it does.
+5. If for some reason the algorithm can't patch its way to the latest version it will download a zlib compressed bundle from your static host and complete the update that way.
+
+**Info:** Whenever you build a non-dev build of your app the electrobun cli will automatically generate a patch from the current hosted version to the newly built version. It's completely up to you how many patches you make available on your static host.
+
+## CLI and development builds
+
+The Electrobun cli is automatically installed locally to your project when you `bun install electrobun`. You can then add npm scripts and an `electrobun.config` file to build your app.
+
+### Development Builds
+
+When building a `dev` build of your app instead of the optimized `launcher` binary the cli uses a special dev launcher binary which routes any bun, zig, and native output to your terminal.
+
+Dev builds are not meant to be distributed and so the cli does not generate artifacts for dev builds.
+
+### Distribution
+
+When building `canary` and `stable` builds of your app Electrobun will generate an `artifacts` folder that contains everything you need to upload to a static host for distribution and updates.
\ No newline at end of file
diff --git a/docs/guides_architecture_webview-tag.md b/docs/guides_architecture_webview-tag.md
new file mode 100644
index 0000000..e4adbce
--- /dev/null
+++ b/docs/guides_architecture_webview-tag.md
@@ -0,0 +1,77 @@
+# The `` Tag
+
+## Overview
+
+The `` tag is Electrobun's implementation of an Out-Of-Process IFrame (OOPIF), providing a secure, isolated, and performant way to embed web content within your application. Unlike traditional iframes or Electron's deprecated `` tag, Electrobun's approach offers full process isolation while maintaining seamless DOM integration.
+
+**Info:** For a deep dive into the technical implementation and philosophy behind Electrobun's OOPIF approach, check out our blog post: [Building a Better OOPIF](/blog/building-a-better-oopif)
+
+## Why Not Regular IFrames?
+
+Standard iframes come with significant limitations for desktop applications:
+
+* **Security restrictions**: Modern browsers prevent iframes from loading cross-domain content
+* **Limited control**: Cannot fully customize behavior or bypass same-origin policies
+* **Performance constraints**: Share the same process as the parent page
+* **Feature limitations**: Restricted access to native APIs and advanced browser features
+
+## The OOPIF Advantage
+
+An Out-Of-Process IFrame (OOPIF) solves these limitations by:
+
+* **Process isolation**: Each webview runs in its own isolated process
+* **Security boundary**: Complete separation between host and embedded content
+* **Performance**: Independent resource allocation and crash protection
+* **Flexibility**: Full control over content loading and permissions
+
+## How It Works
+
+The `` tag functions as a layer positioned above the main window, synchronized with the DOM element's position and size. This architecture provides:
+
+1. **DOM Integration**: The webview behaves like any other DOM element - you can style, animate, and position it using CSS
+2. **Process Separation**: Content runs in a completely isolated process, preventing cross-contamination
+3. **Transparent Layering**: Support for transparency and layering effects without breaking the host design
+4. **Native Performance**: Direct rendering without the overhead of iframe restrictions
+
+## Key Features
+
+### Full Isolation
+
+Each `` runs in its own process, ensuring:
+
+* Crash protection (one webview crash doesn't affect others)
+* Memory isolation
+* Security boundaries between different content sources
+
+### Seamless Communication
+
+Fast inter-process communication (IPC) between:
+
+* The Bun main process
+* The host webview
+* Individual OOPIF webviews
+
+### Not Deprecated
+
+Unlike Electron's `` tag (which relies on Chromium's deprecated implementation scheduled for removal in January 2025), Electrobun's implementation is built from the ground up and will continue to be supported and improved.
+
+## Usage Example
+
+```
+
+
+```
+
+The webview integrates seamlessly with your application's DOM while maintaining complete process isolation for security and performance.
+
+## Architecture Benefits
+
+* **Security**: Complete process isolation prevents cross-site scripting and other attacks
+* **Reliability**: Crash isolation ensures one failing webview doesn't bring down your app
+* **Performance**: Independent resource allocation and rendering
+* **Flexibility**: Full control over content without iframe limitations
+* **Future-proof**: Not dependent on deprecated Chromium features
+
+For more technical details and the evolution of this implementation, read our [Building a Better OOPIF](/blog/building-a-better-oopif) blog post.
\ No newline at end of file
diff --git a/docs/guides_bundling-and-distribution.md b/docs/guides_bundling-and-distribution.md
new file mode 100644
index 0000000..95c87f8
--- /dev/null
+++ b/docs/guides_bundling-and-distribution.md
@@ -0,0 +1,232 @@
+# Bundling & Distribution
+
+Continuing on from the [Creating UI](/electrobun/docs/guides/creating-ui) guide.
+
+Let's add two more scripts to `package.json` to get our app ready for distribution: `build:canary` and `build:stable`.
+
+```json
+{
+ "name": "my-app",
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "dependencies": {
+ "electrobun": "^0.0.1"
+ },
+ "scripts": {
+ "start": "bun run build:dev && electrobun dev",
+ "build:dev": "bun install && electrobun build",
+ "build:canary": "electrobun build --env=canary",
+ "build:stable": "electrobun build --env=stable"
+ }
+}
+```
+
+In your terminal you can now run:
+
+```bash
+bun run build:canary
+
+# or
+
+bun run build:stable
+```
+
+Both of these non-dev builds will:
+
+* Build an optimized app bundle
+* Tar and compress it using state-of-the-art ZSTD compression
+* Generate a self-extracting app bundle
+* Create an `artifacts` folder for distribution
+
+All you need to distribute your app is a static file host like S3 or Google Cloud Storage. There's no need to run a server beyond that.
+
+Assuming you've set up a Google Cloud Storage bucket with a subfolder for this application, add it to `electrobun.config.ts`:
+
+```typescript
+export default {
+ app: {
+ name: "My App",
+ identifier: "dev.my.app",
+ version: "0.0.1",
+ },
+ build: {
+ bun: {
+ entrypoint: "src/bun/index.ts",
+ },
+ views: {
+ "main-ui": {
+ entrypoint: "src/main-ui/index.ts",
+ },
+ },
+ copy: {
+ "src/main-ui/index.html": "views/main-ui/index.html",
+ },
+ },
+ release: {
+ baseUrl: "https://storage.googleapis.com/mybucketname/myapp/",
+ },
+};
+```
+
+You can make your app available by uploading the contents of the `artifacts` folder to your release host (S3, R2, GitHub Releases, etc.).
+
+The artifacts folder contains flat files with a `channel-os-arch` prefix (for example `canary-macos-arm64-update.json`). This flat structure works with any host, including GitHub Releases which don't support folders. The Electrobun CLI builds for the current machine's platform and automatically downloads the core/CEF files during bundling.
+
+To produce artifacts for all platforms, run the same build command on CI runners for each OS/architecture. See the [Cross-Platform Development](/electrobun/docs/guides/cross-platform-development) guide for details.
+
+Once you've uploaded artifacts to your host, the next time you run a non-dev build (like `bun run build:canary`) the Electrobun CLI will download the current version of your app using `release.baseUrl` and generate a patch file using our optimized BSDIFF implementation. That patch file gets added to your artifacts folder.
+
+It's recommended to keep older patch files in your storage. Users on older versions can download successive patches, each as small as 14KB. If patching can't get the user to the latest version, Electrobun's Updater falls back to downloading the full latest build.
+
+Visit the [Updater API docs](/electrobun/docs/apis/updater) to learn how to make your app check for and install updates.
+
+## Build Lifecycle Hooks
+
+Electrobun provides lifecycle hooks that let you run custom scripts at various stages of the build process. This is useful for tasks like:
+
+* Validating your environment before building
+* Transforming compiled code
+* Adding custom files to the app bundle or wrapper
+* Sending notifications when builds complete
+
+Available hooks (in execution order): `preBuild`, `postBuild`, `postWrap`, `postPackage`
+
+See the [Build Configuration docs](/electrobun/docs/apis/cli/build-configuration#build-lifecycle-hooks) for detailed information about each hook and example scripts.
+
+## Artifacts Folder Structure
+
+When you build your app, Electrobun creates a flat `artifacts` folder. All files are prefixed with `{channel}-{os}-{arch}-`:
+
+```
+artifacts/
+├── canary-macos-arm64-update.json
+├── canary-macos-arm64-MyCoolApp-canary.dmg
+├── canary-macos-arm64-MyCoolApp-canary.app.tar.zst
+├── canary-macos-arm64-a1b2c3d4.patch
+├── canary-win-x64-update.json
+├── canary-win-x64-MyCoolApp-Setup-canary.zip
+├── canary-win-x64-MyCoolApp-canary.tar.zst
+├── canary-win-x64-a1b2c3d4.patch
+├── canary-linux-x64-update.json
+├── canary-linux-x64-MyCoolAppSetup-canary.tar.gz
+├── canary-linux-x64-MyCoolApp-canary.tar.zst
+├── canary-linux-x64-a1b2c3d4.patch
+└── ...
+```
+
+This flat structure works with any host, including GitHub Releases which don't support folders.
+
+### Artifact Naming Conventions
+
+App names are **sanitized** by removing spaces. For example, an app named "My Cool App" becomes "MyCoolApp" in all artifact filenames.
+
+For **stable** builds, channel suffixes are omitted. For other channels (canary, beta, etc.), the channel is appended.
+
+Windows and Linux installers are distributed as archives (`.zip` and `.tar.gz` respectively). The archive filenames are sanitized (no spaces), but the installer files inside the archives preserve spaces for a user-friendly experience.
+
+### macOS Artifacts
+
+```
+# Canary:
+canary-macos-arm64-update.json # Version metadata for the Updater API
+canary-macos-arm64-MyCoolApp-canary.dmg # Installer DMG for first-time installs
+canary-macos-arm64-MyCoolApp-canary.app.tar.zst # Compressed app bundle for updates
+canary-macos-arm64-a1b2c3d4.patch # Incremental patch from previous version
+
+# Stable (no channel suffix):
+stable-macos-arm64-MyCoolApp.dmg
+stable-macos-arm64-MyCoolApp.app.tar.zst
+```
+
+### Windows Artifacts
+
+```
+# Canary:
+canary-win-x64-update.json # Version metadata
+canary-win-x64-MyCoolApp-Setup-canary.zip # Zip containing the Setup .exe installer
+canary-win-x64-MyCoolApp-canary.tar.zst # Compressed app for updates
+canary-win-x64-a1b2c3d4.patch # Incremental patch
+
+# Stable:
+stable-win-x64-MyCoolApp-Setup.zip
+stable-win-x64-MyCoolApp.tar.zst
+```
+
+### Linux Artifacts
+
+```
+# Canary:
+canary-linux-x64-update.json # Version metadata
+canary-linux-x64-MyCoolAppSetup-canary.tar.gz # tar.gz containing the self-extracting setup
+canary-linux-x64-MyCoolApp-canary.tar.zst # Compressed app for updates
+canary-linux-x64-a1b2c3d4.patch # Incremental patch
+
+# Stable:
+stable-linux-x64-MyCoolAppSetup.tar.gz
+stable-linux-x64-MyCoolApp.tar.zst
+```
+
+### Constructing Download URLs
+
+Since artifacts are already prefixed, the download URL is simply `{baseUrl}/{artifact-filename}`:
+
+```
+# Examples (assuming baseUrl is "https://releases.example.com/myapp"):
+
+# macOS ARM (Apple Silicon)
+https://releases.example.com/myapp/canary-macos-arm64-MyCoolApp-canary.dmg
+
+# macOS Intel
+https://releases.example.com/myapp/canary-macos-x64-MyCoolApp-canary.dmg
+
+# Windows
+https://releases.example.com/myapp/canary-win-x64-MyCoolApp-Setup-canary.zip
+
+# Linux x64
+https://releases.example.com/myapp/canary-linux-x64-MyCoolAppSetup-canary.tar.gz
+
+# Linux ARM
+https://releases.example.com/myapp/canary-linux-arm64-MyCoolAppSetup-canary.tar.gz
+```
+
+### Platform Reference
+
+Platform
+
+OS Value
+
+Arch Values
+
+Installer Format
+
+macOS
+
+`macos`
+
+`arm64`, `x64`
+
+`.dmg`
+
+Windows
+
+`win`
+
+`x64`
+
+`.zip` (contains `-Setup.exe`)
+
+Linux
+
+`linux`
+
+`x64`, `arm64`
+
+`.tar.gz` (contains self-extracting setup)
+
+### Patch Files
+
+Patch files are named with the platform prefix and a hash representing the source version (e.g., `canary-macos-arm64-a1b2c3d4.patch`). When replacing the contents of your static file host, keep old patch files so users on older versions can step through incremental updates to reach the latest build.
\ No newline at end of file
diff --git a/docs/guides_code-signing.md b/docs/guides_code-signing.md
new file mode 100644
index 0000000..3aa5183
--- /dev/null
+++ b/docs/guides_code-signing.md
@@ -0,0 +1,67 @@
+# Code Signing
+
+## Mac
+
+Apple often ships machines with expired certificates which is a huge pain. You can easily end up in a loop of generating certificates in the developer portal, installing them, and seeing the certificate is not trusted.
+
+You can avoid a lot of headaches by installing the full Xcode via the app store. Open XCode, click the app menu and the Settings. Go to the Accounts tab and add your developer account. Click "Manage Certificates". Then click the + sign and add a "Developer ID Application" certificate. If you open Keychain Access you should be able to see it if you search the Login keychain for "Developer ID Application". You can also log into the Apple Developer portal and look at your certificates and you'll see it there as well.
+
+Now in the developer portal go to Identifiers and click the plus sign to add one for your app. Make sure "App Attest" is checked so Electrobun's CLI can code sign and notarize your app. You may need other services if you need them.
+
+Now in another tab outside the Apple developer portal log into your apple account [https://account.apple.com/sign-in](https://account.apple.com/sign-in). Go to "App Specific Passwords" and Create one for your Electrobun usage, this will be your `ELECTROBUN_APPLEIDPASS` that the Electrobun CLI will use to notarize your apps.
+
+Now we need to get some values that you will add to your .zshrc file. Here is the mapping of those values and where to find them
+
+```bash
+ELECTROBUN_DEVELOPER_ID: In Apple Dev Portal open the certificate you created. The certificate name (probably your company name). eg: "My Corp Inc."
+
+ELECTROBUN_TEAMID: In the Apple Dev Portal open the App Identifier you created for your app. Under "App ID Prefix" you'll see something like "BGU899NB8T (Team ID)" it's the "BGU899NB8T" part.
+
+ELECTROBUN_APPLEID: This is your apple id email address, likely your personal apple id email address
+
+ELECTROBUN_APPLEIDPASS: This is the app specific password you created for Electrobun code signing
+```
+
+Now open your .zshrc file and add the following lines so that they're in your env
+
+```bash
+export ELECTROBUN_DEVELOPER_ID="ELECTROBUN_DEVELOPER_ID: My Corp Inc. (BGU899NB8T)"
+export ELECTROBUN_TEAMID="BGU899NB8T"
+export ELECTROBUN_APPLEID="[email protected]"
+export ELECTROBUN_APPLEIDPASS="your-app-specific-password"
+```
+
+Now in your electrobun.config file make sure Build.mac.codesign and build.mac.notarize are set to true. eg:
+
+```javascript
+{
+ "build": {
+ "mac": {
+ "codesign": true,
+ "notarize": true,
+ }
+ }
+}
+```
+
+Restart your terminal. You can confirm your env is setup correctly by entering the following and hitting enter to see if it outputs the value in your .zshrc file. You may need to restart or add it to a different file if it doesn't.
+
+```bash
+echo $ELECTROBUN_TEAMID
+```
+
+The next time you build your app the Electrobun CLI will sign and notarize your app, then compress it into the self extractor and sign and notarize the self extractor for you.
+
+## Unsigned Apps
+
+If you distribute an unsigned app (with `codesign: false`), users who download it from the internet will see a "damaged and can't be opened" error when trying to launch it. This happens because macOS adds a quarantine attribute to downloaded files, and Gatekeeper blocks unsigned quarantined apps.
+
+To run an unsigned app that was downloaded from the internet, users need to remove the quarantine attribute:
+
+```bash
+xattr -cr /Applications/YourApp.app
+```
+
+After running this command, the app should open normally. Note that this is only necessary for apps downloaded from the internet - apps built and run locally don't have the quarantine attribute.
+
+For production apps intended for end users, it's strongly recommended to enable code signing and notarization for the best user experience.
\ No newline at end of file
diff --git a/docs/guides_compatability.md b/docs/guides_compatability.md
new file mode 100644
index 0000000..e79f722
--- /dev/null
+++ b/docs/guides_compatability.md
@@ -0,0 +1,119 @@
+# Compatibility
+
+## Dependencies and Versions
+
+Dependency
+
+Version
+
+Notes
+
+Bun
+
+1.3.0
+
+Zig
+
+0.13.0
+
+CEF
+
+125.0.22
+
+optionally bundled
+
+## Platform Support
+
+### Development Platform
+
+* **macOS**: Required for building Electrobun apps (Intel and Apple Silicon supported)
+* **Windows**: Development support available
+* **Linux**: Development support available
+
+### Target Platforms
+
+Apps built with Electrobun can be distributed to:
+
+Platform
+
+Architecture
+
+Status
+
+Notes
+
+macOS
+
+ARM64 (Apple Silicon)
+
+✅ Stable
+
+Full support with system WebKit
+
+macOS
+
+x64 (Intel)
+
+✅ Stable
+
+Full support with system WebKit
+
+Windows
+
+x64
+
+✅ Stable
+
+WebView2 (Edge) or bundled CEF
+
+Windows
+
+ARM64
+
+✅ Via Emulation
+
+Runs x64 binary through Windows emulation
+
+Linux
+
+x64
+
+✅ Stable
+
+WebKitGTK or bundled CEF
+
+Linux
+
+ARM64
+
+✅ Stable
+
+WebKitGTK or bundled CEF
+
+### Webview Engines
+
+Electrobun supports both system webviews and bundled engines:
+
+Platform
+
+System Webview
+
+Bundled Option
+
+macOS
+
+WebKit (WKWebView)
+
+CEF (Chromium) - Optional
+
+Windows
+
+WebView2 (Edge)
+
+CEF (Chromium) - Optional
+
+Linux
+
+WebKitGTK
+
+CEF (Chromium) - Optional
\ No newline at end of file
diff --git a/docs/guides_creating-ui.md b/docs/guides_creating-ui.md
new file mode 100644
index 0000000..1a2f3d1
--- /dev/null
+++ b/docs/guides_creating-ui.md
@@ -0,0 +1,139 @@
+# Creating UI
+
+Continuing on from the [Hello World](/electrobun/docs/guides/hello-world) guide we're going to add some UI.
+
+Currently our app is opening a browser window and just loading a url. Let's make a simple web browser.
+
+Create a new folder `src/main-ui/` and add an `index.ts` file. This is where our browser code will go. The Electrobun CLI will automatically transpile this into javascript and make it available at the url `views://main-ui/index.js`.
+
+```typescript
+import { Electroview } from "electrobun/view";
+
+// Instantiate the electrobun browser api
+const electrobun = new Electroview({ rpc: null });
+
+window.loadPage = () => {
+ const newUrl = document.querySelector("#urlInput").value;
+ const webview = document.querySelector(".webview");
+
+ webview.src = newUrl;
+};
+
+window.goBack = () => {
+ const webview = document.querySelector(".webview");
+ webview.goBack();
+};
+
+window.goForward = () => {
+ const webview = document.querySelector(".webview");
+ webview.goForward();
+};
+```
+
+Now create an HTML file to load into the BrowserView that will import the transpiled javascript above:
+
+```html
+
+
+
+
+
+ My Web Browser
+
+
+
+
My Web Browser
+
+
+
+
+
+
+
+
+
+```
+
+Update `electrobun.config.ts` so that it knows to transpile the new TypeScript and copy the HTML file for `main-ui`:
+
+```typescript
+export default {
+ app: {
+ name: "My App",
+ identifier: "dev.my.app",
+ version: "0.0.1",
+ },
+ build: {
+ bun: {
+ entrypoint: "src/bun/index.ts",
+ },
+ views: {
+ "main-ui": {
+ entrypoint: "src/main-ui/index.ts",
+ },
+ },
+ copy: {
+ "src/main-ui/index.html": "views/main-ui/index.html",
+ },
+ },
+};
+```
+
+Finally, update the bun process code to load the new HTML file:
+
+```typescript
+import { BrowserWindow } from "electrobun/bun";
+
+const win = new BrowserWindow({
+ title: "Hello Electrobun",
+ url: "views://main-ui/index.html",
+});
+```
+
+In your terminal press `ctrl+c` if the dev server is running and then `bun start` to rebuild and launch. You should now see a window with an input—type in `https://google.com` and hit go, then try the back and forward buttons.
+
+You'll notice that while you can right click on the text input and choose copy and paste from the default context menu, keyboard shortcuts like `cmd+c`, `cmd+v`, and `cmd+a` don't work. Let's update our main bun file to set up an Application Edit menu to enable those keyboard shortcuts.
+
+```typescript
+import { BrowserWindow, ApplicationMenu } from "electrobun/bun";
+
+ApplicationMenu.setApplicationMenu([
+ {
+ submenu: [{ label: "Quit", role: "quit" }],
+ },
+ {
+ label: "Edit",
+ submenu: [
+ { role: "undo" },
+ { role: "redo" },
+ { type: "separator" },
+ {
+ label: "Custom Menu Item 🚀",
+ action: "custom-action-1",
+ tooltip: "I'm a tooltip",
+ },
+ {
+ label: "Custom menu disabled",
+ enabled: false,
+ action: "custom-action-2",
+ },
+ { type: "separator" },
+ { role: "cut" },
+ { role: "copy" },
+ { role: "paste" },
+ { role: "pasteAndMatchStyle" },
+ { role: "delete" },
+ { role: "selectAll" },
+ ],
+ },
+]);
+
+const win = new BrowserWindow({
+ title: "Hello Electrobun",
+ url: "views://main-ui/index.html",
+});
+```
+
+Your app now shows an Edit menu when focused, and because we used roles for the cut/copy/paste/selectAll menu items those global keyboard shortcuts now work in your app's URL input.
+
+**Congratulations!** You just built a simple web browser in Electrobun!
\ No newline at end of file
diff --git a/docs/guides_cross-platform-development.md b/docs/guides_cross-platform-development.md
new file mode 100644
index 0000000..53e02b9
--- /dev/null
+++ b/docs/guides_cross-platform-development.md
@@ -0,0 +1,86 @@
+# Cross-Platform Development
+
+Electrobun enables you to build desktop applications that run on macOS, Windows, and Linux from a single codebase. This guide covers platform-specific considerations and best practices for cross-platform development.
+
+## Platform-Specific Issues
+
+### Window Management
+
+Some window options like frameless windows work differently on different OSes.
+
+### Webview Behavior
+
+Webview hiding and passthrough behavior varies between platforms:
+
+* **macOS**: Webviews can be set to hidden and passthrough separately. These are independent settings.
+* **Windows & Linux**: Setting a webview to hidden using also automatically enables passthrough behavior. There is no separate passthrough setting - clicks will pass through hidden webviews to underlying content.
+
+```
+// Hide a webview (behavior differs by platform)
+webviewSetHidden(webviewId, true);
+
+// On macOS: webview is hidden but still intercepts clicks (unless passthrough is also enabled)
+// On Windows/Linux: webview is hidden AND clicks pass through automatically
+
+// Enable click passthrough (macOS only - no effect on Windows/Linux)
+webviewSetPassthrough(webviewId, true);
+```
+
+### Linux
+
+By default on Linux we use GTK windows and GTKWebkit webviews. This is as close to a "system" webview on Linux that's managed/updated by the OS. Some distros don't have this installed by default so you will need to ask your end users to install those dependencies.
+
+In addition GTK and GTKWebkit have severe limitations and are unable to handle Electrobun's more advanced webview layering and masking functionality.
+
+So we strongly recommend bundling CEF (just set bundleCEF to true in your electrobun.config.ts file) for your app's linux distribution. And make sure you open `new BrowserWindow()`s and ``s with `renderer="cef"` which uses pure x11 windows.
+
+## Building for Multiple Platforms
+
+Electrobun builds for the current host platform. To produce builds for all platforms, use a CI service like GitHub Actions with a runner for each OS/architecture. GitHub Actions provides free CI runners for open-source projects covering all supported platforms.
+
+```
+# On each CI runner, just run:
+electrobun build --env=stable
+```
+
+Electrobun's [GitHub repository](https://github.com/blackboardsh/electrobun) includes a release workflow that builds natively on each platform using a build matrix. This is the recommended approach — each platform build runs on its native OS, avoiding cross-compilation complexity and ensuring platform-specific tools (code signing, icon utilities, etc.) work correctly.
+
+### Architecture Considerations
+
+Platform
+
+Architectures
+
+Notes
+
+macOS
+
+x64, ARM64
+
+Universal binaries supported
+
+Windows
+
+x64
+
+ARM64 runs via emulation
+
+Linux
+
+x64, ARM64
+
+Native support for both
+
+## Windows Console Output
+
+On Windows, Electrobun builds your app as a GUI application (Windows subsystem) so that no console window appears when end users launch it. Dev builds automatically attach to the parent console so you can see `console.log` output and debug information in your terminal.
+
+When you need to inspect console output from a **canary** or **stable** build (for example to debug an issue that only reproduces in a production build), set the `ELECTROBUN_CONSOLE` environment variable:
+
+```
+# Launch a canary/stable build with console output visible
+set ELECTROBUN_CONSOLE=1
+.\MyApp.exe
+```
+
+When `ELECTROBUN_CONSOLE=1` is set, the launcher will attach to the parent console and inherit standard output/error streams, just like a dev build. This has no effect on macOS or Linux where console output is always available.
\ No newline at end of file
diff --git a/docs/guides_hello-world.md b/docs/guides_hello-world.md
new file mode 100644
index 0000000..1d3e151
--- /dev/null
+++ b/docs/guides_hello-world.md
@@ -0,0 +1,90 @@
+# Hello World
+
+**Info:** Electrobun will install a specific version of bun as a dependency in `node_modules`.
+
+**Info:** This guide assumes you have bun installed globally, if you're using node.js or another package manager then you may need to adjust the terminal commands and package.json scripts accordingly.
+
+## Step 0: Init templates
+
+This guide will walk you through building a hello world from scratch.
+
+If you'd prefer to be up and running in seconds we offer starter templates including a `hello-world`, `photo-booth`, and multi-tabbed `web-browser`. If you have bun installed and want to get started in a few clicks.
+
+Just run **`bunx electrobun init`** and choose the starter template you want to explore.
+
+## Step 1: Initialize your project folder
+
+Create a new folder for your project. Let's call it `/electrobun-test`.
+
+In your `electrobun-test` folder run `bun init .` You'll be prompted to enter a package name, let's use `my-app` and an entrypoint which we don't need so just hit enter.
+
+## Step 2: Install Electrobun as a dependency
+
+Let's install electrobun. Just run: `bun install electrobun` to add it as a dependency.
+
+## Step 3: Add package.json scripts to build and run your app
+
+Open your `package.json` in a code editor and add a build:dev script and a start script so that your `package.json` looks like this:
+
+```javascript
+{
+ "name": "my-app",
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "dependencies": {
+ "electrobun": "^0.0.1"
+ },
+ "scripts": {
+ "start": "bun run build:dev && electrobun dev",
+ "build:dev": "bun install && electrobun build"
+ }
+}
+```
+
+**Note:** That we've modified the default package.json that bun creates
+
+* Removed the "type": "module" and "module": "index.ts" properties since we don't need them.
+* Added two npm scripts that will use the electrobun cli that should now be in your node\_modules/.bin folder.
+
+## Step 4: Hello World
+
+We now have electrobun installed and a way to build and run our hello world app, let's add some code.
+
+Create a file in `src/bun/index.ts` with the following contents:
+
+```javascript
+import { BrowserWindow } from "electrobun/bun";
+const win = new BrowserWindow({
+ title: "Hello Electrobun",
+ url: "https://electrobun.dev",
+});
+```
+
+## Step 5: Configure Electrobun
+
+One last thing, we need to a way to let the Electrobun cli know where our bun entrypoint file is and how to build it. We do that by creating an ``electrobun.config.ts`}>`` file in the root of the project.
+
+```javascript
+export default {
+ app: {
+ name: "My App",
+ identifier: "dev.my.app",
+ version: "0.0.1",
+ },
+ build: {
+ bun: {
+ entrypoint: "src/bun/index.ts",
+ },
+ },
+};
+```
+
+## Step 6: Run your app
+
+With this we can now go back to our terminal and run `bun start` and you should see a window pop up and load the site.
+
+To stop running the app just hit `cmd+c`
\ No newline at end of file
diff --git a/docs/guides_migrating-to-v1.md b/docs/guides_migrating-to-v1.md
new file mode 100644
index 0000000..a53441c
--- /dev/null
+++ b/docs/guides_migrating-to-v1.md
@@ -0,0 +1,117 @@
+# Migrating from 0.x to v1
+
+Electrobun v1 includes breaking changes to artifact naming and configuration. This guide covers what changed and how to migrate your existing apps.
+
+## Breaking Changes
+
+### 1\. Config: `bucketUrl` renamed to `baseUrl`
+
+The `release.bucketUrl` configuration option has been renamed to `release.baseUrl` to better reflect that it supports any HTTP host, not just S3-style buckets.
+
+#### Before:
+
+```javascript
+// electrobun.config.ts
+export default {
+ // ...
+ release: {
+ bucketUrl: "https://example.com/releases",
+ },
+};
+```
+
+#### After:
+
+```javascript
+// electrobun.config.ts
+export default {
+ // ...
+ release: {
+ baseUrl: "https://example.com/releases",
+ },
+};
+```
+
+### 2\. Artifact Naming: Folder-based to Prefix-based (Flat Structure)
+
+Artifact URLs have changed from a folder-based structure to a flat prefix-based structure. This enables hosting on GitHub Releases and other platforms that don't support folders.
+
+#### Before (folder-based):
+
+```
+https://example.com/releases/canary-macos-arm64/update.json
+https://example.com/releases/canary-macos-arm64/MyApp.app.tar.zst
+https://example.com/releases/canary-macos-arm64/abc123.patch
+```
+
+#### After (prefix-based / flat):
+
+```
+https://example.com/releases/canary-macos-arm64-update.json
+https://example.com/releases/canary-macos-arm64-MyApp.app.tar.zst
+https://example.com/releases/canary-macos-arm64-abc123.patch
+```
+
+The local `artifacts/` folder is now flat with prefixed filenames instead of containing platform subfolders.
+
+## Migration Steps
+
+If you have an app already deployed with the old folder-based structure, follow these steps to migrate your users:
+
+### Step 1: Build a Transitional Release
+
+Before upgrading to v1, build and release one final version using your current Electrobun version (pre-v1). This ensures all your users update to a version that can still read the old URL structure.
+
+```
+# Using your current Electrobun version (pre-v1)
+bun run build --env=canary # or stable
+# Deploy artifacts to your bucket with the OLD folder structure
+```
+
+### Step 2: Upgrade Electrobun
+
+Update your Electrobun dependency to v1:
+
+```
+bun add electrobun@latest
+```
+
+### Step 3: Update Your Config
+
+Rename `bucketUrl` to `baseUrl` in your `electrobun.config.ts`:
+
+```javascript
+release: {
+ baseUrl: "https://example.com/releases", // was: bucketUrl
+},
+```
+
+### Step 4: Switch Your Bucket to Flat Structure
+
+Once you're confident that most users have updated to the transitional release from Step 1, you can switch your bucket/hosting to use the new flat structure:
+
+1. Build your app with Electrobun v1 - artifacts will now be generated with the flat naming convention
+2. Upload the new flat-named artifacts to your bucket
+3. Optionally remove the old folder-based artifacts
+
+```
+# Using Electrobun v1
+bun run build --env=canary
+# Deploy the flat-named artifacts from artifacts/ folder
+```
+
+### Step 5: Release Updates
+
+From now on, all releases will use the new flat structure. Users who updated in Step 1 will seamlessly transition to fetching updates from the new URLs.
+
+## Why This Change?
+
+The folder-based structure worked well with S3 and R2 buckets, but GitHub Releases and many other hosting platforms don't support folder hierarchies. The new flat prefix-based naming works everywhere while maintaining clear organization through the prefix.
+
+## API Changes
+
+If you're using the Updater API directly, note that `Updater.localInfo.bucketUrl()` has been renamed to `Updater.localInfo.baseUrl()`.
+
+## Need Help?
+
+If you run into issues during migration, please open an issue on the [Electrobun GitHub repository](https://github.com/nicksellen/electrobun).
\ No newline at end of file
diff --git a/docs/guides_quick-start.md b/docs/guides_quick-start.md
new file mode 100644
index 0000000..ab140a0
--- /dev/null
+++ b/docs/guides_quick-start.md
@@ -0,0 +1,69 @@
+# Quick Start
+
+Welcome to Electrobun! This guide will help you create your first ultra-fast, tiny desktop application with TypeScript.
+
+## Prerequisites
+
+Before getting started, make sure you have:
+
+* [Bun](https://bun.sh) installed on your system
+* A text editor or IDE (Blackboard's own [co(lab)](/colab/) recommended)
+* Basic knowledge of TypeScript/JavaScript
+
+## Getting Started
+
+Create a new Electrobun project with a single command:
+
+```
+bunx electrobun init
+```
+
+It'll ask you which template project you want to get started with.
+
+This creates a new directory with the basic project structure:
+
+```
+my-app/
+├── src/
+│ ├── main.ts # Main process entry point
+│ └── renderer/
+│ ├── index.html # UI template
+│ ├── style.css # Styles
+│ └── script.ts # Frontend logic
+├── package.json # Project dependencies
+└── electrobun.config.ts # Build configuration
+```
+
+## Running Your App
+
+Navigate to your project directory and start development:
+
+```
+cd my-app
+bun install
+bun start
+```
+
+This will use the Electrobun cli:
+
+* Create a quick start project on your machine
+* Use the Electrobun cli to do a dev build of your app
+* Open your app in dev mode
+
+## Next Steps
+
+Now that you have a basic app running, explore these topics:
+
+* [Hello World](/electrobun/docs/guides/hello-world) - Create a hello world from scratch.
+* [Creating UI](/electrobun/docs/guides/creating-ui) - Build beautiful interfaces with web technologies
+* [Bun API](/electrobun/docs/apis/bun) - Learn about the main process APIs
+* [BrowserView](/electrobun/docs/apis/browser-view) - Manage multiple webviews
+* [Bundling & Distribution](/electrobun/docs/guides/bundling-and-distribution) - Package your app for distribution
+
+## Need Help?
+
+If you run into any issues:
+
+* Check the [GitHub repository](https://github.com/blackboardsh/electrobun)
+* Join our [Discord community](https://discord.gg/ueKE4tjaCE)
+* Read through the other documentation guides
\ No newline at end of file
diff --git a/docs/guides_updates.md b/docs/guides_updates.md
new file mode 100644
index 0000000..4461160
--- /dev/null
+++ b/docs/guides_updates.md
@@ -0,0 +1,110 @@
+# Updates
+
+## Introduction
+
+We've implemented a batteries included update mechanism. All you need is to bring your own static file host like AWS S3, Cloudflare R2, or GitHub Releases.
+
+* [Update API](/electrobun/docs/apis/updater) to check for, download, and update your apps.
+* [CLI](/electrobun/docs/apis/cli/cli-args) to build your app bundle, codesign, and generate artifacts.
+* A custom BSDIFF implementation in zig that takes advantage of SIMD operations for performance and lets you distribute updates as small as 14KB
+
+## Hosting on GitHub Releases
+
+GitHub Releases is a convenient option for hosting your update artifacts, especially for open source projects. Electrobun uses a flat, prefix-based naming scheme (e.g., `stable-macos-arm64-update.json`) that works with hosts that don't support folder structures.
+
+### Configuration
+
+Set your `baseUrl` in `electrobun.config` to point to your GitHub Releases:
+
+```javascript
+// electrobun.config.ts
+export default {
+ // ...
+ release: {
+ baseUrl: "https://github.com/YOUR_ORG/YOUR_REPO/releases/latest/download",
+ },
+};
+```
+
+### Example GitHub Action
+
+Here's an example workflow that builds and publishes releases when you push a tag:
+
+```yaml
+name: Build and Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ build-macos-arm64:
+ runs-on: macos-14 # Apple Silicon runner
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Install dependencies
+ run: bun install
+
+ - name: Determine build environment
+ id: build-env
+ run: |
+ if [[ "${{ github.ref_name }}" == *"-canary"* ]]; then
+ echo "env=canary" >> $GITHUB_OUTPUT
+ else
+ echo "env=stable" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Build app
+ env:
+ ELECTROBUN_DEVELOPER_ID: ${{ secrets.ELECTROBUN_DEVELOPER_ID }}
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ run: |
+ if [ "${{ steps.build-env.outputs.env }}" = "canary" ]; then
+ bun run build:canary
+ else
+ bun run build:stable
+ fi
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v1
+ with:
+ files: artifacts/*
+ draft: false
+ prerelease: ${{ steps.build-env.outputs.env == 'canary' }}
+ generate_release_notes: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+```
+
+The `generate_release_notes: true` option uses GitHub's automatic release notes feature, which lists merged PRs and contributors since the last release.
+
+## Limitations
+
+### Single Patch File
+
+Electrobun only generates a single patch file per build - from the immediately previous version to the current version. This means:
+
+* Users updating from the previous version get a small delta patch (often just a few KB)
+* Users more than one version behind will automatically fall back to downloading the full `.tar.zst` bundle
+
+This is a practical tradeoff that keeps the build process simple while still providing delta updates for users who update regularly.
+
+### Canary Builds on GitHub Releases
+
+GitHub's `/releases/latest/download` URL only resolves to non-prerelease builds. This means:
+
+* **Stable builds**: Auto-updates work correctly via `/releases/latest/download`
+* **Canary builds**: Will not auto-update when hosted on GitHub Releases because the `latest` URL won't point to prerelease versions
+
+If you need auto-updating canary builds, consider using a static file host like Cloudflare R2 or AWS S3 where you control the URL structure directly.
\ No newline at end of file
diff --git a/docs/guides_what-is-electrobun.md b/docs/guides_what-is-electrobun.md
new file mode 100644
index 0000000..c859c23
--- /dev/null
+++ b/docs/guides_what-is-electrobun.md
@@ -0,0 +1,139 @@
+# What is Electrobun?
+
+Electrobun is a desktop application framework that lets you build ultra fast, tiny, and cross-platform applications using TypeScript. It combines the best of native performance with web development simplicity.
+
+## The Problem
+
+Traditional desktop app frameworks force you to choose between developer experience and app performance:
+
+* **Electron:** Great DX but huge bundle sizes (150MB+), slow startup times (2-5s), and massive update downloads
+* **Native development:** Great performance but complex setup, platform-specific code, and limited web technology integration
+* **Tauri:** Better than Electron but still large updates and you have to learn Rust
+
+## The Solution
+
+Electrobun provides a third option that doesn't compromise:
+
+* **Ultra-small bundles:** ~14MB compressed (90%+ smaller than Electron)
+* **Lightning-fast startup:** <50ms cold start vs 2-5s for Electron
+* **Tiny updates:** 14KB patches using custom binary diff vs 100MB+ with Electron
+* **Pure TypeScript:** Write TypeScript for both main process and UI
+* **Web technologies:** HTML, CSS, JavaScript - use any frontend framework
+* **Native performance:** Zig bindings with Bun runtime for maximum speed
+* **Optional CEF:** bundle CEF (Chromium) when cross-platform consistency matters most.
+
+## Performance Comparison
+
+Metric
+
+Electron
+
+Tauri
+
+Electrobun
+
+Bundle Size
+
+150MB+
+
+25MB
+
+14MB
+
+Update Size
+
+100MB+
+
+10MB
+
+14KB
+
+Startup Time
+
+2-5s
+
+500ms
+
+<50ms
+
+Memory Usage
+
+100-200MB
+
+30-50MB
+
+15-30MB
+
+## Technical Architecture
+
+Electrobun achieves its performance through a carefully designed architecture:
+
+### Zig, and Native Bindings
+
+Native functionality like window management, system trays, and app menus writtin in C++ and Objc
+
+### Bun Runtime
+
+The main process runs on Bun, providing lightning-fast Typescript execution and built-in bundling without the overhead of Node.js and V8.
+
+### System WebView
+
+Instead of distributing Chromium, By default Electrobun uses your system's native WebView (WebKit on macOS, Edge WebView2 on Windows, WebKitGTK on Linux).
+
+### Custom Update System
+
+Binary diff updates using a SIMD optimized BSDIFF implementation written in zig to allow for incredibly small update patches - often just kilobytes instead of megabytes.
+
+### ZSTD self-extracting distributables
+
+The Electrobun cli bundles your app, then compresses it with state of the art compression making initial downloads as small as possible.
+
+### Custom OOPIF Implementation
+
+Use OOPIFs (super iframes) in your html for secure, isolated, webviews across browser engines and platforms.
+
+## Key Benefits
+
+### 🚀 Faster Development
+
+* Fast build times - Electrobun cli uses pre-built binaries for your target platform
+* Use any web framework (React, SolidJS, Vue, Svelte, etc.)
+* TypeScript throughout - no context switching
+* Built-in bundling and optimization
+
+### 📦 Better Distribution
+
+* 14MB bundles vs 150MB+ with Electron
+* Kilobyte updates instead of megabyte downloads
+* Built-in code signing and notarization
+* Cross-platform builds from any OS
+* Built-in ZSTD self-extractor
+
+### ⚡ Superior Performance
+
+* Sub-50ms startup times
+* Minimal memory footprint
+* Native-feeling UI responsiveness
+* Battery-efficient operation
+
+### 🔐 Security First
+
+* Process isolation by default
+* Secure, encrypted, and typed RPC between processes
+* custom views:// schema for loading bundled assets in webviews
+* Minimal attack surface
+
+## When to Use Electrobun
+
+Electrobun is perfect for:
+
+* **Startup MVPs:** Ship fast, iterate quickly with small updates
+* **Developer tools:** IDEs, terminals, productivity apps that need native performance
+* **Cross-platform apps:** One codebase, native feel everywhere
+* **High-performance apps:** When Electron is too slow but native development is too complex
+* **Bandwidth-conscious apps:** Frequent updates without user friction
+* **Multi-tab web browsers**Build multi-tab experiences and mix CEF and Webkit webviews
+
+## Getting Started
+
+Ready to build your first Electrobun app? Follow our [Hello World guide](/electrobun/docs/guides/hello-world) to create a new project in minutes.
\ No newline at end of file
diff --git a/docs/react-tailwind-vite/README.md b/docs/react-tailwind-vite/README.md
new file mode 100644
index 0000000..5e863fc
--- /dev/null
+++ b/docs/react-tailwind-vite/README.md
@@ -0,0 +1,61 @@
+# React + Tailwind + Vite Electrobun Template
+
+A fast Electrobun desktop app template with React, Tailwind CSS, and Vite for hot module replacement (HMR).
+
+## Getting Started
+
+```bash
+# Install dependencies
+bun install
+
+# Development without HMR (uses bundled assets)
+bun run dev
+
+# Development with HMR (recommended)
+bun run dev:hmr
+
+# Build for production
+bun run build
+
+# Build for production release
+bun run build:prod
+```
+
+## How HMR Works
+
+When you run `bun run dev:hmr`:
+
+1. **Vite dev server** starts on `http://localhost:5173` with HMR enabled
+2. **Electrobun** starts and detects the running Vite server
+3. The app loads from the Vite dev server instead of bundled assets
+4. Changes to React components update instantly without full page reload
+
+When you run `bun run dev` (without HMR):
+
+1. Electrobun starts and loads from `views://mainview/index.html`
+2. You need to rebuild (`bun run build`) to see changes
+
+## Project Structure
+
+```
+├── src/
+│ ├── bun/
+│ │ └── index.ts # Main process (Electrobun/Bun)
+│ └── mainview/
+│ ├── App.tsx # React app component
+│ ├── main.tsx # React entry point
+│ ├── index.html # HTML template
+│ └── index.css # Tailwind CSS
+├── electrobun.config.ts # Electrobun configuration
+├── vite.config.ts # Vite configuration
+├── tailwind.config.js # Tailwind configuration
+└── package.json
+```
+
+## Customizing
+
+- **React components**: Edit files in `src/mainview/`
+- **Tailwind theme**: Edit `tailwind.config.js`
+- **Vite settings**: Edit `vite.config.ts`
+- **Window settings**: Edit `src/bun/index.ts`
+- **App metadata**: Edit `electrobun.config.ts`
diff --git a/docs/react-tailwind-vite/bun.lock b/docs/react-tailwind-vite/bun.lock
new file mode 100644
index 0000000..8140d86
--- /dev/null
+++ b/docs/react-tailwind-vite/bun.lock
@@ -0,0 +1,659 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "electrobun-react-tailwind-vite",
+ "dependencies": {
+ "electrobun": "file:../../package",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.20",
+ "concurrently": "^9.1.0",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.16",
+ "typescript": "^5.7.2",
+ "vite": "^6.0.3",
+ },
+ },
+ },
+ "packages": {
+ "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
+
+ "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
+
+ "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
+
+ "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
+
+ "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
+
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
+
+ "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
+
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
+
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
+
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
+
+ "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
+
+ "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
+
+ "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
+
+ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
+
+ "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
+
+ "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
+
+ "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
+
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
+ "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="],
+
+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
+
+ "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
+
+ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+
+ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
+
+ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
+
+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="],
+
+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="],
+
+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="],
+
+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="],
+
+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="],
+
+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="],
+
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="],
+
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="],
+
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="],
+
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="],
+
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="],
+
+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="],
+
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="],
+
+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="],
+
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="],
+
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="],
+
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="],
+
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="],
+
+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="],
+
+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="],
+
+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="],
+
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="],
+
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="],
+
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="],
+
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="],
+
+ "@types/archiver": ["@types/archiver@6.0.4", "", { "dependencies": { "@types/readdir-glob": "*" } }, "sha512-ULdQpARQ3sz9WH4nb98mJDYA0ft2A8C4f4fovvUcFwINa1cgGjY36JCAYuP5YypRq4mco1lJp1/7jEMS2oR0Hg=="],
+
+ "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
+
+ "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
+
+ "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
+
+ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
+
+ "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
+
+ "@types/emscripten": ["@types/emscripten@1.41.5", "", {}, "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q=="],
+
+ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+ "@types/filesystem": ["@types/filesystem@0.0.36", "", { "dependencies": { "@types/filewriter": "*" } }, "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA=="],
+
+ "@types/filewriter": ["@types/filewriter@0.0.33", "", {}, "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g=="],
+
+ "@types/har-format": ["@types/har-format@1.2.16", "", {}, "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A=="],
+
+ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
+
+ "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
+
+ "@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="],
+
+ "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
+
+ "@types/readdir-glob": ["@types/readdir-glob@1.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg=="],
+
+ "@types/tar": ["@types/tar@6.1.13", "", { "dependencies": { "@types/node": "*", "minipass": "^4.0.0" } }, "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw=="],
+
+ "@types/webextension-polyfill": ["@types/webextension-polyfill@0.12.4", "", {}, "sha512-wK8YdSI0pDiaehSLDIvtvonYmLwUUivg4Z6JCJO8rkyssMAG82cFJgwPK/V7NO61mJBLg/tXeoXQL8AFzpXZmQ=="],
+
+ "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
+
+ "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
+
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
+
+ "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
+
+ "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="],
+
+ "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="],
+
+ "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
+
+ "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
+
+ "autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="],
+
+ "b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
+
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.9.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Mij6Lij93pTAIsSYy5cyBQ975Qh9uLEc5rwGTpomiZeXZL9yIS6uORJakb3ScHgfs0serMMfIbXzokPMuEiRyw=="],
+
+ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
+
+ "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
+
+ "browser-namespace": ["browser-namespace@1.4.0", "", { "dependencies": { "@types/filesystem": "*", "@types/har-format": "*", "@types/webextension-polyfill": "*" } }, "sha512-9b4yNTNs+8HVPssSq8RSZMRunf+G4cVQ2PMtOTn+uEVFOW5C0Uo+eGXuJ5LfxS1UDph5oAdWj92thPyxVhpqXg=="],
+
+ "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
+
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+
+ "buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],
+
+ "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
+
+ "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
+
+ "caniuse-lite": ["caniuse-lite@1.0.30001762", "", {}, "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw=="],
+
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
+
+ "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
+
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
+
+ "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="],
+
+ "concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="],
+
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
+ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
+
+ "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
+
+ "crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="],
+
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+ "cross-spawn-windows-exe": ["cross-spawn-windows-exe@1.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "is-wsl": "^2.2.0", "which": "^2.0.2" } }, "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw=="],
+
+ "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
+
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
+
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
+
+ "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
+
+ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
+
+ "electrobun": ["electrobun@file:../../package", { "dependencies": { "archiver": "^7.0.1", "png-to-ico": "^2.1.8", "rcedit": "^4.0.1", "rpc-anywhere": "1.5.0", "tar": "^6.2.1" }, "devDependencies": { "@types/archiver": "^6.0.3", "@types/bun": "1.1.9", "@types/tar": "^6.1.13", "typescript": "^5.9.3" }, "bin": { "electrobun": "./bin/electrobun.cjs" } }],
+
+ "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
+
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
+ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
+
+ "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
+
+ "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
+
+ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
+
+ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
+
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
+
+ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
+
+ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
+
+ "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
+
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
+ "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
+
+ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
+
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+ "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
+
+ "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
+
+ "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
+
+ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
+
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
+
+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+
+ "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+
+ "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
+
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
+
+ "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
+
+ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+ "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
+
+ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+
+ "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
+
+ "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
+
+ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
+
+ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
+
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
+
+ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+
+ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
+
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+
+ "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
+ "minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
+
+ "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
+
+ "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
+
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+ "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
+
+ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
+
+ "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
+
+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
+
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
+
+ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+
+ "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
+
+ "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
+
+ "png-to-ico": ["png-to-ico@2.1.8", "", { "dependencies": { "@types/node": "^17.0.36", "minimist": "^1.2.6", "pngjs": "^6.0.0" }, "bin": { "png-to-ico": "bin/cli.js" } }, "sha512-Nf+IIn/cZ/DIZVdGveJp86NG5uNib1ZXMiDd/8x32HCTeKSvgpyg6D/6tUBn1QO/zybzoMK0/mc3QRgAyXdv9w=="],
+
+ "pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
+
+ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
+
+ "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
+
+ "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
+
+ "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
+
+ "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
+
+ "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
+
+ "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
+
+ "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
+
+ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
+
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
+
+ "rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="],
+
+ "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
+
+ "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
+
+ "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
+
+ "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
+
+ "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
+
+ "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="],
+
+ "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
+
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
+ "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
+
+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
+
+ "rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="],
+
+ "rpc-anywhere": ["rpc-anywhere@1.5.0", "", { "dependencies": { "browser-namespace": "^1.4.0" } }, "sha512-ZYrB0foAM4oE7oBnUH3BL7LwtW9d6+RkzL/rFnjj8GCaFt5c81Rbw6oVl6u9AMsGONsKeJX0mL62TpbPXSO6og=="],
+
+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
+
+ "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
+
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
+
+ "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+ "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
+
+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+ "streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
+
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
+
+ "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+
+ "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="],
+
+ "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
+
+ "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
+
+ "text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
+
+ "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
+
+ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
+
+ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+
+ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
+
+ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
+
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
+
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+
+ "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
+
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
+ "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
+
+ "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
+ "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
+
+ "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+
+ "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
+
+ "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+
+ "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+
+ "electrobun/@types/bun": ["@types/bun@1.1.9", "", { "dependencies": { "bun-types": "1.1.27" } }, "sha512-SXJRejXpmAc3qxyN/YS4/JGWEzLf4dDBa5fLtRDipQXHqNccuMU4EUYCooXNTsylG0DmwFQsGgEDHxZF+3DqRw=="],
+
+ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+
+ "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+ "glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+
+ "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+
+ "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
+
+ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+ "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+
+ "png-to-ico/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
+
+ "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+ "tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
+
+ "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+
+ "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
+
+ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+
+ "electrobun/@types/bun/bun-types": ["bun-types@1.1.27", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-rHXAiIDefeMS/fleNM1rRDYqolJGNRdch3+AuCRwcZWaqTa1vjGBNsahH/HVV7Y82frllYhJomCVSEiHzLzkgg=="],
+
+ "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
+
+ "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
+
+ "electrobun/@types/bun/bun-types/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
+
+ "electrobun/@types/bun/bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+ }
+}
diff --git a/docs/react-tailwind-vite/electrobun.config.ts b/docs/react-tailwind-vite/electrobun.config.ts
new file mode 100644
index 0000000..8a28d6d
--- /dev/null
+++ b/docs/react-tailwind-vite/electrobun.config.ts
@@ -0,0 +1,25 @@
+import type { ElectrobunConfig } from "electrobun";
+
+export default {
+ app: {
+ name: "react-tailwind-vite",
+ identifier: "reacttailwindvite.electrobun.dev",
+ version: "0.0.1",
+ },
+ build: {
+ // Vite builds to dist/, we copy from there
+ copy: {
+ "dist/index.html": "views/mainview/index.html",
+ "dist/assets": "views/mainview/assets",
+ },
+ mac: {
+ bundleCEF: false,
+ },
+ linux: {
+ bundleCEF: false,
+ },
+ win: {
+ bundleCEF: false,
+ },
+ },
+} satisfies ElectrobunConfig;
diff --git a/docs/react-tailwind-vite/llms.txt b/docs/react-tailwind-vite/llms.txt
new file mode 100644
index 0000000..74e7715
--- /dev/null
+++ b/docs/react-tailwind-vite/llms.txt
@@ -0,0 +1,24 @@
+# Electrobun Project
+
+This is an Electrobun desktop application.
+
+IMPORTANT: Electrobun is NOT Electron. Do not use Electron APIs or patterns.
+
+## Documentation
+
+Full API reference: https://blackboard.sh/electrobun/llms.txt
+Getting started: https://blackboard.sh/electrobun/docs/
+
+## Quick Reference
+
+Import patterns:
+- Main process (Bun): `import { BrowserWindow } from "electrobun/bun"`
+- Browser context: `import { Electroview } from "electrobun/view"`
+
+Use `views://` URLs to load bundled assets (e.g., `url: "views://mainview/index.html"`).
+Views must be configured in `electrobun.config.ts` to be built and copied into the bundle.
+
+## About
+
+Electrobun is built by Blackboard (https://blackboard.sh), an innovation lab building
+tools and funding teams that define the next generation of technology.
diff --git a/docs/react-tailwind-vite/package.json b/docs/react-tailwind-vite/package.json
new file mode 100644
index 0000000..b197c03
--- /dev/null
+++ b/docs/react-tailwind-vite/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "electrobun-react-tailwind-vite",
+ "version": "1.0.0",
+ "description": "Electrobun app with React, Tailwind CSS, and Vite HMR",
+ "scripts": {
+ "dev": "bun run build && electrobun dev",
+ "dev:hmr": "concurrently \"bun run hmr\" \"bun run dev\"",
+ "hmr": "vite --port 5173",
+ "build": "vite build && electrobun build",
+ "build:prod": "vite build && electrobun build --channel prod",
+ "start": "bun run dev"
+ },
+ "dependencies": {
+ "electrobun": "latest",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.20",
+ "concurrently": "^9.1.0",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.16",
+ "typescript": "^5.7.2",
+ "vite": "^6.0.3"
+ }
+}
diff --git a/docs/react-tailwind-vite/postcss.config.js b/docs/react-tailwind-vite/postcss.config.js
new file mode 100644
index 0000000..e873f1a
--- /dev/null
+++ b/docs/react-tailwind-vite/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/docs/react-tailwind-vite/src/bun/index.ts b/docs/react-tailwind-vite/src/bun/index.ts
new file mode 100644
index 0000000..3789a0f
--- /dev/null
+++ b/docs/react-tailwind-vite/src/bun/index.ts
@@ -0,0 +1,42 @@
+import { BrowserWindow, Updater, Utils } from "electrobun/bun";
+
+const DEV_SERVER_PORT = 5173;
+const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;
+
+// Check if Vite dev server is running for HMR
+async function getMainViewUrl(): Promise {
+ const channel = await Updater.localInfo.channel();
+ if (channel === "dev") {
+ try {
+ await fetch(DEV_SERVER_URL, { method: "HEAD" });
+ console.log(`HMR enabled: Using Vite dev server at ${DEV_SERVER_URL}`);
+ return DEV_SERVER_URL;
+ } catch {
+ console.log(
+ "Vite dev server not running. Run 'bun run dev:hmr' for HMR support.",
+ );
+ }
+ }
+ return "views://mainview/index.html";
+}
+
+// Create the main application window
+const url = await getMainViewUrl();
+
+const mainWindow = new BrowserWindow({
+ title: "React + Tailwind + Vite",
+ url,
+ frame: {
+ width: 900,
+ height: 700,
+ x: 200,
+ y: 200,
+ },
+});
+
+// Quit the app when the main window is closed
+mainWindow.on("close", () => {
+ Utils.quit();
+});
+
+console.log("React Tailwind Vite app started!");
diff --git a/docs/react-tailwind-vite/src/mainview/App.tsx b/docs/react-tailwind-vite/src/mainview/App.tsx
new file mode 100644
index 0000000..594425d
--- /dev/null
+++ b/docs/react-tailwind-vite/src/mainview/App.tsx
@@ -0,0 +1,115 @@
+import { useState } from "react";
+
+function App() {
+ const [count, setCount] = useState(0);
+
+ return (
+
+
+
+ React + Tailwind + Vite
+
+
+ A fast Electrobun app with hot module replacement
+
+
+
+
+ Interactive Counter
+
+
+ Click the button below to test React state. With HMR enabled, you
+ can edit this component and see changes instantly without losing
+ state.
+
+
+
+
+
+
+
+
+
+ Getting Started
+
+
+
+ 1.
+
+ Run{" "}
+
+ bun run dev
+ {" "}
+ for development without HMR
+
+
+
+ 2.
+
+ Run{" "}
+
+ bun run dev:hmr
+ {" "}
+ for development with hot reload
+
+
+
+ 3.
+
+ Run{" "}
+
+ bun run build
+ {" "}
+ to build for production
+
+
+
+
+
+
+
Stack
+
+
+
⚡
+
Electrobun
+
+
+
⚛️
+
React
+
+
+
🎨
+
Tailwind
+
+
+
🔥
+
Vite HMR
+
+
+
+
+
+
+ Edit{" "}
+
+ src/mainview/App.tsx
+ {" "}
+ and save to see HMR in action
+