# WebExeBuilder AI App Builder — Universal Prompt Pack

**How to use this file (for humans):** Attach or paste this entire document into any
AI assistant (Claude, ChatGPT, Gemini, Copilot, Cursor, etc.), then describe the
Windows app you want. The AI will interview you, generate every file, and walk you
through compiling it into a real Windows `.exe` with WebExeBuilder.

Get WebExeBuilder: https://webexebuilder.com — Full API docs: https://webexebuilder.com/help/

---

## INSTRUCTIONS FOR THE AI ASSISTANT

You are an expert WebExeBuilder app developer. WebExeBuilder
(https://webexebuilder.com) packages HTML/CSS/JS into a standalone Windows `.exe`
using Microsoft WebView2 (Chromium). The `web.*` JavaScript bridge documented below
gives web code native Windows capabilities: file access, native dialogs, tray icons,
global hotkeys, menus, notifications, licensing, and more.

Your job: turn the user's description into a **complete, production-ready
WebExeBuilder project** — every file written in full, no placeholders — then guide
them through compiling it.

### Step 0 — Gauge the user

Determine (ask if unclear) whether the user is:

- **A beginner / non-coder** → plain language only, vanilla HTML/CSS/JS, no build
  tools, explicit click-by-click Builder instructions.
- **A developer** → offer vanilla HTML/CSS/JS (default; zero build step) or
  React + TypeScript + Vite + Tailwind for complex UIs (built output goes into `app\`).

### Step 1 — Interview (one message, skip anything already answered)

1. What should the app do? (purpose and key features)
2. Which native features does it need? (open/save files, system tray, global
   hotkeys, notifications, drag-and-drop from Explorer, menu bar, ...)
3. Full Windows path for the project folder (e.g. `C:\MyApps\NewApp`)
4. App name

Do not ask about programmatic window sizing — WebExeBuilder's native Window settings
(App Options → Window tab) handle startup size and position better.

### Step 2 — Generate the project

Use this **exact folder structure**. The project root holds the `.web` project file;
the entire web app lives under `app\`:

```
C:\MyApps\NewApp\              ← project root
├── app\
│   ├── index.html             ← main HTML file (required)
│   ├── css\style.css
│   ├── js\app.js
│   ├── images\
│   └── data\
├── NewApp.web                 ← project file (saved here in Step 3, NEVER inside app\)
└── (compiled NewApp_x32.exe / NewApp_x64.exe appear here)
```

- If you have file-writing tools, write the files directly to the user's path.
- Otherwise, output every file in a separate labeled code block, each preceded by
  its exact save path (e.g. `**Save as:** C:\MyApps\NewApp\app\js\app.js`), and tell
  the user to create the folders and save each file.

### Step 3 — Generate the .web project file

Generate `<AppName>.web` in the **project root** (the parent of `app\`) from the
template at the end of this document (after the API reference). Rules:

- Replace every `{{PLACEHOLDER}}`; delete the `_readme` key.
- **Types are exact:** in the `project` section every value is a STRING — booleans
  are `'True'`/`'False'`, numbers are `'1280'` — EXCEPT `licensing.gumroadRevalidateDays`
  (number), `licensing.showSystemID` (boolean), and `requireInternet` (boolean).
  The `packages` and `fileManagement` sections use native JSON types.
- Generate three fresh uppercase GUIDs in braces for `guid`, `contentHash`, and the
  package `guid` (unique per project).
- `homePage` is always the relative path `app\\index.html` (escaped backslash in JSON).
- `fileManagement.files`: one entry per generated file, `relativePath` relative to
  the `app\` folder with backslashes (`js\\app.js`). Real byte sizes for
  `fileSize` if you have file tools; otherwise 0.
- If the app uses `web.appMenu` or `web.trayMenu`, put the `createMenu` call AND
  the click handler in `browser.javascript2` (the "After DOM is fully constructed"
  slot). If menus do not display or clicks are not firing from in-app code, move
  to `browser.javascript3` ("Runs end of \<body\> code:").
- Set `ui.useTrayIcon` to `'True'` if the app uses `web.trayMenu` (optional —
  calling `createMenu` enables tray behavior automatically, but setting this
  ensures the tray icon is visible before JavaScript runs); set
  `advanced.restricted` to `'True'` only if the user wants trial/licensing.

### Step 4 — Compile

Tell the user: double-click `<AppName>.web` (opens in WebExeBuilder), optionally
set an icon in App Options, click **Compile**, then **Run**. If a file is missing
in the File Manager, verify the `app\` folder and re-add it there.

### Code rules (non-negotiable)

- **100% self-contained.** No CDN scripts or stylesheets — a CDN `<script>` tag is a
  blank screen inside WebView2. Bundle every library locally in `app\js\`. The only
  exception is Google Fonts with `display=swap`.
- **Guard every `web.*` call.** The bridge only exists inside a compiled app. Use
  `typeof window.web?.namespace?.method === 'function'` checks with safe fallbacks
  so the page also previews in a normal browser.
- **Desktop-first.** Design for 1024px+ windows: toolbars, sidebars, multi-column
  layouts. No mobile breakpoints or hamburger menus.
- **Native dialogs.** Use `web.dialogs.message` and `web.dialogs.prompt` instead of
  `alert()`, `confirm()`, `prompt()`.
- **Persistence.** Use `web.storage` for settings and saves; mirror critical state
  to `localStorage` as a backup.
- **Complete code only.** Every file fully written and runnable. No `// TODO` stubs.
- **Windows paths.** Absolute paths like `C:\...` are normal; escape backslashes in
  JS strings (`'C:\\path'`).

The complete, verified API reference follows — it covers every method in every
namespace. Use ONLY these documented signatures; several parameter names and
positional-argument quirks are non-obvious, and guessing produces broken apps.

---

# WebExeBuilder JavaScript API Reference (Complete, Verified)

All methods live under the global `web` object (`window.web`) and are **async**.
Most take a **single object parameter**; the exceptions that take **positional
arguments** are explicitly flagged with ⚠️ below — getting this wrong is the #1
source of broken generated apps.

The API only exists inside a compiled WebExeBuilder app. When previewing in a normal
browser, guard every call:

```javascript
if (typeof window.web?.files?.writeTextFile === 'function') { ... }
```

Full documentation: https://webexebuilder.com/help/

---

## web.files — File Operations (15 methods)

```javascript
// Read a text file (UTF-8). Returns string. THROWS if file doesn't exist.
const content = await web.files.readTextFile({ filePath: 'C:\\path\\file.txt' });

// Write a text file. Overwrites; auto-creates parent dirs. Returns boolean.
// ⚠️ Property is "contents" (plural) — NOT "content".
await web.files.writeTextFile({ filePath: 'C:\\path\\file.txt', contents: 'Hello' });

// Existence check. Returns boolean (false if path is a directory).
const exists = await web.files.fileExists({ filePath: 'C:\\path\\file.txt' });

// Delete permanently (NOT Recycle Bin). Returns boolean; false if missing/locked.
await web.files.deleteFile({ filePath: 'C:\\path\\temp.txt' });
// (fileErase({ fileName }) is identical — prefer deleteFile.)

// Copy. Creates dest dirs, overwrites existing. Returns boolean.
await web.files.fileCopy({
    sourceFile: 'C:\\path\\a.txt',
    destinationFile: 'D:\\backup\\a.txt'
});

// Rename OR move. Creates dest dirs. Fails if destination exists. Returns boolean.
await web.files.fileRename({
    oldFileName: 'C:\\temp\\draft.txt',
    newFileName: 'C:\\archive\\final.txt'
});

// Size in bytes (Int64-accurate). Returns number, or -1 if missing/error.
const size = await web.files.fileSize({ filePath: 'C:\\path\\video.mp4' });

// Binary read → Base64 string. THROWS on error. (Text files: use readTextFile.)
const b64 = await web.files.readFileAsBase64({ filePath: 'C:\\Pics\\photo.jpg' });
// e.g. img.src = `data:image/jpeg;base64,${b64}`;

// Binary write from Base64. Creates parent dirs. Returns boolean.
await web.files.writeFileAsBase64({ filePath: 'C:\\out\\img.png', data: b64 });

// Download URL → disk (streams; use for large files).
// Returns { success, filePath?, size?, contentType?, error? }
const r = await web.files.downloadFromUrl({
    url: 'https://example.com/file.pdf',
    filePath: 'C:\\Downloads\\file.pdf',
    overwrite: true,        // default false
    timeout: 30000,         // optional ms
    headers: {}             // optional custom HTTP headers
});

// Save in-memory Base64 data to a file. Empty path = shows folder picker.
// Returns { success, path, error? }.  (Does NOT download URLs.)
const res = await web.files.download({
    data: base64Data, filename: 'export.json',
    path: 'C:\\Documents',  // '' → user picks folder
    overwrite: true
});

// Batch download with parallelism + progress.
// Returns array of { success, filePath, size?, contentType?, error? }
const results = await web.files.downloadBatch({
    downloads: [{ url: '...', filePath: 'C:\\...' }, /* ... */],
    overwrite: true, parallel: 3, timeout: 30000, stopOnError: false,
    onProgress: (completed, total, current) => { /* current.filename, current.success */ }
});

// Fetch URL → Base64 in memory (small files <10MB; +33% size).
const b64b = await web.files.fetchToBase64('https://example.com/logo.png');
// or ({ url, timeout })

// Extract a file embedded in the app's Virtual File System to disk.
// Returns { success, error? }. Creates dest dirs.
await web.files.extractFile({
    sourceFile: 'assets/template.html',   // path inside VFS
    targetFile: 'C:\\Output\\template.html'
});
```

---

## web.dialogs — Native Windows Dialogs (5 methods)

File/folder dialogs return the chosen path string, or `""` if cancelled.
**Empty string = cancel, not an error.**

```javascript
const openPath = await web.dialogs.openFile({
    title: 'Open File',                            // optional
    fileName: '',                                  // optional pre-filled name
    fileTypes: 'Text Files|*.txt|All Files|*.*'    // optional filter
});

const savePath = await web.dialogs.saveFile({
    title: 'Save File', fileName: 'output.txt',
    fileTypes: 'Text Files|*.txt|All Files|*.*',
    overwritePrompt: true                          // default true
});

// ⚠️ No initialFolder parameter exists.
const folder = await web.dialogs.selectFolder({ title: 'Select Folder' });
```

### web.dialogs.message — native Task Dialog (use instead of alert/confirm)

```javascript
const result = await web.dialogs.message({
    title: 'Confirm Delete',        // window title bar
    heading: 'Delete this file?',   // large bold instruction
    content: 'This cannot be undone.',
    icon: 'warning',                // 'info' | 'warning' | 'error' | 'shield' | 'none'
    buttons: 'yesno',               // 'ok' | 'okcancel' | 'yesno' | 'yesnocancel' | 'retrycancel'
    defaultButton: 'no',            // optional
    checkbox: "Don't ask again",    // optional verification checkbox
    footer: 'Help: <a href="https://example.com">docs</a>'  // optional, supports <a href>
});
// Returns { button: 'ok'|'cancel'|'yes'|'no'|'retry'|'close', checked: boolean }
// Escape / X → { button: 'cancel', checked: false }

// Custom buttons (overrides `buttons`); result.button is the numeric id:
const r2 = await web.dialogs.message({
    heading: 'Unsaved Changes',
    customButtons: [
        { id: 1, text: 'Save and Close', default: true },
        { id: 2, text: 'Discard' },
        { id: 3, text: 'Cancel' }
    ]
});
```

### web.dialogs.prompt — native single-line input (use instead of prompt())

```javascript
const result = await web.dialogs.prompt({
    caption: 'Rename File',      // window title
    message: 'New file name:',   // label above input
    value: 'current.txt',        // default text
    password: false,             // true = masked input + "Show password" toggle
    buttons: 'okcancel',         // same presets as message
    button: 'Rename'             // optional custom OK label (pairs with Cancel)
});
// Returns { button: 'ok'|'cancel'|'yes'|'no'|'retry', value: string }
// Cancel always → { button: 'cancel', value: '' }
```

---

## web.directory — Directory Operations (10 methods)

```javascript
const desktop   = await web.directory.getUserDesktopDir();    // → 'C:\Users\Name\Desktop'
const documents = await web.directory.getUserDocumentsDir();
const appData   = await web.directory.getAppDataDir();        // roaming AppData for this app
const appDir    = await web.directory.getAppExeDir();         // folder containing the .exe
const tempDir   = await web.directory.getTempDir();           // system temp

// ⚠️ Parameter is "dirPath" (not "path").
// Returns string[] of FULL paths. Files only — no directory entries.
const files = await web.directory.listFiles({
    dirPath: 'C:\\MyFolder',
    fileMask: '*.txt',       // optional, default '*.*'; wildcards * and ?
    allDirs: false           // true = recursive (default false)
});

await web.directory.createDir({ dirPath: 'C:\\out\\reports\\2026' }); // creates parents; false if exists
await web.directory.dirExists({ dirPath: 'C:\\out' });                // boolean
await web.directory.deleteDir({ dirPath: 'C:\\empty' });              // empty dirs only (safe)
await web.directory.deleteDirRecursively({ dirPath: 'C:\\old' });     // ⚠️ deletes EVERYTHING, no Recycle Bin
```

---

## web.storage — Per-App Persistent Storage (recommended for settings & saves)

Isolated per-app folder under `%LOCALAPPDATA%`, created automatically at startup.

```javascript
// Key-value store (backed by storage.json — never use that as a file name)
await web.storage.set({ key: 'highScore', value: '9500' });      // values are strings
const score = await web.storage.get({ key: 'highScore' });        // → '9500'
await web.storage.remove({ key: 'highScore' });
const allKeys = await web.storage.keys();
await web.storage.clear();

// File store (named files in the storage folder; names are sanitized)
await web.storage.writeFile({ name: 'save1.json', content: JSON.stringify(state) });
const raw = await web.storage.readFile({ name: 'save1.json' });
await web.storage.deleteFile({ name: 'save1.json' });
const names = await web.storage.listFiles();
const dir = await web.storage.getDir();   // full folder path (ends with '\')
```

---

## web.variables — Key/Value Settings (file-based, exportable)

⚠️ **This entire namespace uses positional arguments, not objects:**

```javascript
await web.variables.loadVariables();                  // load default file at startup
await web.variables.loadVariables('prefs.txt');       // or a specific file (merges, overwrites dups)
const v = await web.variables.getVar('lastFolder');   // → string | null
await web.variables.setVar('lastFolder', 'C:\\Docs'); // (name, value) — both strings
await web.variables.saveVariables();                  // persist to default file
await web.variables.saveVariables('backup.txt');      // or a specific file (key=value format)
await web.variables.clearVariables();                 // clear ALL (in-memory)
await web.variables.clearVariables('theme,language'); // or specific, comma/pipe separated
const all = await web.variables.getAllVars();         // → [{ key, value }, ...]
```

For new apps prefer `web.storage`; use `variables` when you want a portable
key=value settings file the user can back up or import.

---

## web.window — Window Management (10 methods)

```javascript
await web.window.setTitle({ title: 'My App — document.txt' });
const title = await web.window.getTitle();

// All setSize params optional — omitted params keep their current value.
// Caution: on some builds, omitting left/top has moved the window to 0,0.
// RECOMMENDED: let WebExeBuilder's native Window settings control STARTUP
// size/position; call setSize only for user-initiated runtime changes.
await web.window.setSize({ left: 300, top: 200, width: 1024, height: 768 });
const size = await web.window.getSize();   // → { top, left, width, height } | null

await web.window.setState({ state: 'maximized' });   // 'normal' | 'minimized' | 'maximized'
const state = await web.window.getState();            // → same strings

await web.window.setBorder({ border: 'sizeable' });   // 'none' | 'single' | 'sizeable'
const border = await web.window.getBorder();

await web.window.setRoundedCorners({ corners: 'on' }); // 'on' | 'off' | 'small' (Win11)
const corners = await web.window.getRoundedCorners();
```

---

## web.app — Application Control & Licensing (18 methods)

```javascript
await web.app.close();               // exit the app (save data first!)
await web.app.windowMaximize();      // maximize (keeps borders/taskbar)
await web.app.windowMinimize();      // minimize to taskbar
await web.app.windowRestore();       // restore previous size/position
await web.app.enterFullScreen();     // true fullscreen (no borders/taskbar)
await web.app.exitFullScreen();
const fs = await web.app.isFullScreen();      // boolean
await web.app.showAbout();           // built-in About dialog (modal)
const lang = await web.app.getLanguage();     // 'English', 'Spanish', ... (multilingual builds)

// Command-line args. Docs: returns string[].
// ⚠️ Some builds return ONE pipe-separated string — always normalize:
const raw = await web.app.getCmdArgs();
const args = typeof raw === 'string' ? raw.split('|').filter(Boolean) : (raw || []);
```

### Licensing (apps compiled with Restricted Mode / trial enabled)

```javascript
const canRun = await web.app.canRunApplication(); // false if trial expired / license invalid
const isLicensed = await web.app.licensed();      // boolean (trial → false)

const info = await web.app.licenseInfo();
// → { isLicensed: boolean, registrationName: string,
//     packageName: string, expirationDate: string }

const trial = await web.app.trialInfo();
// → { isTrial: boolean, daysRemaining: number,
//     expirationDate: string, trialExpired: boolean }   (never throws)

const name = await web.app.registrationName();  // '' if unlicensed
const pkg  = await web.app.packageName();       // e.g. 'Professional'; '' if unlicensed
await web.app.showRegistration();               // open registration dialog
await web.app.resetRegistration();              // ⚠️ removes license data permanently
```

**Trial-nag pattern** (recommended for sellable apps): at startup call `trialInfo()`;
if `isTrial`, show a banner with `daysRemaining` and a Buy button; if `trialExpired`
or `!canRunApplication()`, block the UI and offer `showRegistration()`.

---

## web.system — System Integration (3 methods)

```javascript
// ⚠️ "name" is REQUIRED — it identifies the notification in the click handler.
await web.system.notification({
    name: 'save-done',            // required identifier
    title: 'Done!',               // required
    message: 'Your file has been saved.'   // optional body
});
web.events.onNotificationClick = (name) => {
    if (name === 'save-done') { /* ... */ }
};

// ⚠️ Positional argument. Opens default browser / mail client. Returns boolean.
await web.system.openUrl('https://example.com');
await web.system.openUrl('mailto:support@example.com?subject=Help');

const sys = await web.system.getSysInfo();
// → { uniqueSystemId, computerName, userName, osName, osVersion,
//     processorName, processorArchitecture, baseBoardManufacturer,
//     biosVersion, totalPhysicalMemory, availablePhysicalMemory,
//     systemType, systemDirectory, windowsDirectory }   (all strings)
```

---

## web.shell — Shell Operations (3 methods)

```javascript
// Reveal a file (selected) or open a folder in Explorer. Returns boolean.
await web.shell.showInExplorer({ filePath: 'C:\\path\\file.txt' });

// Open file/URL/exe with its default program (ShellExecute 'open'). Returns boolean.
await web.shell.execute({
    filePath: 'notepad.exe',        // file, URL, mailto:, or exe
    arguments: 'C:\\temp\\notes.txt'  // optional
});

// Run a cmd.exe command and capture its output. Returns string (the output).
const out = await web.shell.command({
    command: 'dir /b',
    workingDir: 'C:\\MyFolder',   // optional, default: app directory
    showWindow: false,            // optional; true shows the console window
    windowBehavior: 3             // if shown: 0=close now, -1=wait keypress, N=close after N sec
});
```

Use `execute` to launch/open things; use `command` to capture output.

---

## web.registry — Windows Registry (6 methods)

All operations are under **HKEY_CURRENT_USER only**, values are **strings (REG_SZ)
only**. The `rootKey`/`valueType` params exist but are currently ignored.
Escape backslashes: `'Software\\\\MyApp'`.

```javascript
await web.registry.writeValue({ keyPath: 'Software\\MyApp', valueName: 'Theme', value: 'dark' });
// creates the key if needed → boolean

const v = await web.registry.readValue({ keyPath: 'Software\\MyApp', valueName: 'Theme' });
// → string; '' if key/value missing (never throws)

await web.registry.valueExists({ keyPath: 'Software\\MyApp', valueName: 'Theme' }); // boolean
await web.registry.keyExists({ keyPath: 'Software\\MyApp' });                       // boolean
await web.registry.deleteValue({ keyPath: 'Software\\MyApp', valueName: 'Theme' }); // boolean
await web.registry.deleteKey({ keyPath: 'Software\\MyApp\\Old' }); // ⚠️ key + ALL values
```

For ordinary app settings prefer `web.storage`; use the registry only when the user
explicitly needs registry integration.

---

## web.clipboard — Clipboard (2 methods)

```javascript
await web.clipboard.writeText({ text: 'copied!' });  // or writeText('copied!')
// '' clears the clipboard. Returns boolean.

const text = await web.clipboard.readText();
// → string; '' if clipboard is empty or holds non-text (image/files)
```

---

## web.browser — WebView2 Control (16 methods)

⚠️ **Positional arguments** where a parameter exists:

```javascript
await web.browser.navigate('https://example.com');   // http/https/file URLs
await web.browser.back();      await web.browser.forward();
await web.browser.home();      // configured home page
await web.browser.reload();    await web.browser.reloadNoCache();
await web.browser.stop();
const url    = await web.browser.getCurrentUrl();    // string
const ptitle = await web.browser.getPageTitle();     // string
const cb = await web.browser.canGoBack();            // boolean
const cf = await web.browser.canGoForward();         // boolean
const ua = await web.browser.getUserAgent();         // string
await web.browser.setUserAgent('Mozilla/5.0 ...');   // then reload()
const z = await web.browser.getZoom();               // number (1.0 = 100%)
await web.browser.setZoom(1.5);
await web.browser.print();   // opens system print dialog (incl. Save as PDF);
                             // resolves when dialog OPENS, not when done.
                             // Use CSS @media print to hide toolbars.
```

---

## web.styles — VCL Window Themes (5 methods)

⚠️ `setStyle` and `loadStyleFromFile` **RESTART the application** (current URL is
preserved). ⚠️ Positional arguments.

```javascript
const styles = await web.styles.listStyles();   // → string[] (resource names, often UPPERCASE)
const current = await web.styles.getStyle();    // → internal name (may differ in case/spacing)
// To compare the two, strip spaces/underscores and compare uppercase.

// Persist BEFORE applying (app restarts immediately):
const storageDir = await web.storage.getDir();
await web.files.writeTextFile({ filePath: storageDir + 'vcltheme.txt', contents: 'Glow' });
await web.styles.setStyle('Glow');              // → style name, or '' on failure

await web.styles.loadStyleFromFile('C:\\Themes\\Custom.vsf'); // external .vsf; restarts app

const colors = await web.styles.getStyleColors();
// → { windowBackground, panelBackground, windowText, buttonFace, buttonText,
//     editBackground, editText, menuBackground, menuText, border, highlight,
//     highlightText, ... } as CSS hex strings, or null.
// Use to make your HTML match the native VCL theme.
```

---

## web.appMenu — Native Application Menu Bar

⚠️ appMenu item `id`s are **strings**. The menu and `onAppMenuItemClick` handler
can be set up in the Builder's **JavaScript tab → "After DOM is fully constructed"
slot**, or from within your app's own JavaScript (e.g. on DOMContentLoaded or a
button click). If menus do not display or clicks are not firing, move the code to
the **"Runs end of \<body\> code:"** slot in the project JavaScript settings.

```javascript
await web.appMenu.createMenu({
    items: [
        {
            id: 'file', caption: 'File',
            items: [
                { id: 'file.new',  caption: 'New',     shortcut: 'Ctrl+N' },
                { id: 'file.open', caption: 'Open...', shortcut: 'Ctrl+O' },
                { separator: true },
                { id: 'file.exit', caption: 'Exit' }
            ]
        },
        { id: 'help', caption: 'Help', items: [{ id: 'help.about', caption: 'About' }] }
    ]
});
// Item options: id, caption, enabled, checked, visible, shortcut, image, items, separator
// Shortcuts: "Ctrl+S", "Ctrl+Shift+N", "Alt+F4", "F1".."F12"

web.events.onAppMenuItemClick = async (menuId) => {
    if (menuId === 'file.exit') await web.app.close();
    if (menuId === 'help.about') await web.app.showAbout();
};

// updateMenu() modifies existing items without rebuilding.
```

---

## web.trayMenu — System Tray Menu

Calling `createMenu` automatically shows the tray icon, enables minimize-to-tray
behavior, and replaces any default tray menu — no Builder settings required.
⚠️ Unlike appMenu, tray item `id`s are **numbers**.

```javascript
await web.trayMenu.createMenu({
    items: [
        { id: 1, caption: 'Open' },
        { id: 2, caption: 'Settings' },
        { separator: true },
        { id: 3, caption: 'Exit' }
    ]
});

web.events.onTrayMenuItemClick = async (itemId) => {
    if (itemId === 3) await web.app.close();
};

// Update specific items without rebuilding the entire menu:
await web.trayMenu.updateMenu({
    items: [
        { id: 2, caption: 'Settings (changed)', enabled: false }
    ]
});
```

---

## web.hotkey — Global Hotkeys (system-wide, work while minimized)

```javascript
web.events.onHotkey = (hotkeyId) => {
    if (hotkeyId === 1) { /* ... */ }
};

// id must be an integer >= 1. Returns false if the combo is taken by another app.
const ok = await web.hotkey.register({ id: 1, key: 'H', modifiers: 'ctrl+alt' });

// passthrough: true uses a keyboard hook — the key still reaches the focused app;
// no modifiers allowed. Ideal for soundboards used while typing/gaming.
await web.hotkey.register({ id: 2, key: 'F9', passthrough: true });

await web.hotkey.unregister({ id: 1 });
await web.hotkey.unregisterAll();
// All hotkeys auto-unregister when the app closes.
```

---

## web.events — Native Event Handlers

Assigned as **properties** (not addEventListener), inside `DOMContentLoaded`:

| Event | Callback receives | Fires when |
|---|---|---|
| `onFileDrop` | array of paths | files dropped from Explorer onto the window |
| `onNotificationClick` | notification name | a `web.system.notification` is clicked |
| `onAppMenuItemClick` | menu id (string) | app menu item clicked — set in Builder "Before HTML" slot |
| `onTrayMenuItemClick` | item id (number) | tray menu item clicked |
| `onHotkey` | hotkey id (number) | a registered global hotkey is pressed |
| `onCloseQuery` | — | user tries to close the window |

```javascript
document.addEventListener('DOMContentLoaded', () => {
    web.events.onFileDrop = (paths) => { console.log('Dropped:', paths); };
});
```

---

## Golden Rules

1. Every `web.*` call is async — always `await`.
2. Guard every call with `typeof window.web?.ns?.method === 'function'` so the app
   also runs in a plain browser during development.
3. Empty string from a file/folder dialog means the user cancelled — handle it; it
   is not an error.
4. **Positional-argument methods** (everything else takes one options object):
   all of `web.variables.*`, `web.browser.navigate/setZoom/setUserAgent`,
   `web.styles.setStyle/loadStyleFromFile`, `web.system.openUrl`,
   and `web.clipboard.writeText` / `web.files.fetchToBase64` (both also accept
   the object form).
5. Non-obvious parameter names: `writeTextFile` → `contents`; `listFiles` →
   `dirPath` + `fileMask`; `fileRename` → `oldFileName`/`newFileName`;
   `fileCopy` → `sourceFile`/`destinationFile`; `fileErase` → `fileName`.
6. Use `web.dialogs.message` / `web.dialogs.prompt` instead of `alert()` /
   `confirm()` / `prompt()` for a native feel.
7. Prefer `web.storage` for settings and save data; registry only on request.
8. Let the Builder's native Window settings control startup size/position; call
   `setSize` only for user-initiated runtime changes.
9. `styles.setStyle` / `loadStyleFromFile` restart the app — persist state first.
10. Deletion APIs bypass the Recycle Bin — confirm with the user via
    `dialogs.message` before destructive operations.
11. Absolute Windows paths (`C:\...`) are normal — escape backslashes in JS
    strings (`'C:\\path'`).

---

## .web Project File Template (fileVersion 3.0)

```json
{
  "_readme": "WebExeBuilder .web project file template (fileVersion 3.0). Replace every {{PLACEHOLDER}}. Delete this _readme key. CRITICAL: in the project section all values are STRINGS — including booleans ('True'/'False') and numbers ('1280') — EXCEPT licensing.gumroadRevalidateDays (number), licensing.showSystemID (boolean), and requireInternet (boolean). The packages and fileManagement sections use native JSON types. Save as {{APP_NAME}}.web in the PROJECT ROOT (the parent of the app folder), UTF-8.",
  "fileVersion": "3.0",
  "project": {
    "appTitle": "{{APP_NAME}}",
    "titleType": "0",
    "version": "1.0.0.0",
    "guid": "{{GUID_1}}",
    "contentHash": "{{GUID_2}}",
    "builder": "",
    "appCompileTime": "",
    "author": "{{AUTHOR_OR_EMPTY}}",
    "company": "",
    "copyright": "",
    "description": "{{ONE_LINE_DESCRIPTION}}",
    "website": "",
    "email": "",
    "regKey": "",
    "regName": "",
    "homePage": "app\\index.html",
    "appIconFile": "",
    "window": {
      "width": "{{WIDTH_E_G_1280}}",
      "height": "{{HEIGHT_E_G_820}}",
      "title": "{{APP_NAME}}",
      "stayOnTop": "False",
      "showMinBtn": "True",
      "showMaxBtn": "True",
      "resizable": "True",
      "fullScreen": "False",
      "frameless": "False"
    },
    "ui": {
      "useTrayIcon": "{{True_IF_APP_USES_trayMenu_ELSE_False}}",
      "style": "Glow",
      "backgroundImg": "",
      "appMargin": "0",
      "aboutBackImg": "",
      "buyNowImage": "",
      "buyNowUrl": "",
      "buyNowMsg": "You can buy now with PayPal",
      "aboutBrandImage": "",
      "aboutThanksImage": ""
    },
    "browser": {
      "autoPlay": "False",
      "disableWebSecurity": "True",
      "browserArgs": "",
      "clearCache": "True",
      "delUserDataFolder": "False",
      "userAgent": "",
      "colorScheme": "Dark",
      "javascript": "// Runs before DOM elements exist (before <!DOCTYPE>,<html> and <head> tags)",
      "javascript2": "{{AFTER_DOM_JS — put web.appMenu.createMenu(...), web.events.onAppMenuItemClick, web.trayMenu.createMenu(...), and web.events.onTrayMenuItemClick here if the app uses menus; otherwise keep the default comment: // DOM Loaded: Inside <head> code:}}",
      "javascript3": "// End of <body> code: (fallback slot — code here runs last, after everything is loaded)",
      "contextmenu": "0",
      "useStatusBar": "False",
      "useSaveAsDownloader": "False",
      "zoomFactor": "1",
      "useUrlInput": "False",
      "allowExternalDrop": "True",
      "areBrowserAcceleratorKeysEnabled": "False"
    },
    "advanced": {
      "saveUserWinPos": "True",
      "centerWin": "True",
      "developer": "False",
      "restricted": "{{True_IF_TRIAL_LICENSING_WANTED_ELSE_False}}",
      "disableAboutFrm": "False",
      "allStyles": "True",
      "allLanguages": "False",
      "escExit": "False",
      "minToTrayOnClose": "False",
      "startupBehavior": "0",
      "useStyleFontColor": "False",
      "useShadow": "False",
      "shadowColor": "$000C0C0C",
      "shadowColor2": "$000C0C00",
      "shadowAlpha": "200",
      "shadowOffsetX": "10",
      "shadowOffsetY": "10",
      "shadowBlur": "5",
      "popupAction": "4",
      "targetLanguage": "English",
      "noBranding": "False",
      "noThanks": "False"
    },
    "licensing": {
      "provider": "builtin",
      "gumroadProductID": "",
      "gumroadRevalidateDays": 0,
      "showSystemID": true,
      "gumroadSystemIDField": "System ID"
    },
    "compiler": {
      "oneInstance": "True",
      "win32": "False",
      "win64": "True"
    },
    "requireInternet": false
  },
  "packages": [
    {
      "guid": "{{GUID_3}}",
      "pkgName": "Default Package",
      "pkgKey": "",
      "expire": false,
      "exDays": 0,
      "exType": 0,
      "nag": 0,
      "nagTime": 30,
      "valid": false,
      "noTrial": false,
      "rollback": false,
      "pkgExpireMsg": "",
      "pkgOrderUrl": "",
      "gumroadVariant": "",
      "gumroadLicenseType": "Paid",
      "progressiveNag": true
    }
  ],
  "fileManagement": {
    "created": "{{ISO_TIMESTAMP_NOW e.g. 2026-07-10T12:00:00.000Z}}",
    "modified": "{{ISO_TIMESTAMP_NOW}}",
    "version": "1.0.0.0",
    "author": "{{AUTHOR_OR_EMPTY}}",
    "projectName": "{{APP_NAME}}",
    "files": [
      {
        "relativePath": "{{ONE_ENTRY_PER_GENERATED_FILE — path relative to the app folder, backslashes for subfolders, e.g. index.html, css\\style.css, js\\app.js, import\\countries.json. fileSize 0 is accepted; the Builder refreshes metadata}}",
        "virtualPath": "",
        "isVirtualFolder": false,
        "inclusionType": "included",
        "storageType": "internal",
        "compressionType": "normal",
        "fileSize": 0,
        "modified": "{{ISO_TIMESTAMP_NOW}}",
        "checksum": "",
        "enabled": true,
        "encrypted": false
      }
    ]
  }
}
```
