# Capacitor plugins - JavaScript bridge API

> The Capacitor plugins the app hosts. A hand-written page reaches them as window.Capacitor.Plugins.<Name>; an AI-built app uses the @/lib/appmintNative wrapper each example names.

- **Applies to:** AppMint
- **Source:** extracted from the app runtime and its per-method example files; this page is generated from them.
- **HTML:** https://freewebtoapk.com/docs/api/capacitor

### `Capacitor.ActionSheet`

```js
Capacitor.Plugins.ActionSheet — showActions({ title, message, options })
```

**Example**

Shows a native bottom sheet with a list of choices and tells you which one was tapped (`@capacitor/action-sheet` 8.1.1).

**Returns:** Promise `{ index, canceled }` - `index` is the tapped option; when the sheet is dismissed `canceled` is `true` and `index` is outside the list (`-1`). **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

Each option is `{ title, style? }` with `style` `'DEFAULT'`, `'DESTRUCTIVE'` or `'CANCEL'`.

```js
var ActionSheet = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ActionSheet;

async function photoMenu() {
  if (!ActionSheet) return;
  var opts = [
    { title: 'Share', style: 'DEFAULT' },
    { title: 'Edit', style: 'DEFAULT' },
    { title: 'Delete', style: 'DESTRUCTIVE' }
  ];
  var r = await ActionSheet.showActions({ title: 'Photo', message: 'Choose an action', options: opts });
  if (r.canceled || r.index < 0 || r.index >= opts.length) return;   // dismissed
  if (opts[r.index].title === 'Delete') deletePhoto();
}
```

**In an AI-built app** the Capacitor-shaped wrapper returns `{ index: -1 }` on dismiss; the short form `actionSheet()` returns `null`:

```ts
import { ActionSheet, actionSheet } from '@/lib/appmintNative';

const { index } = await ActionSheet.showActions({
  title: 'Photo',
  options: [{ title: 'Share' }, { title: 'Delete', destructive: true }],
});
if (index === 1) deletePhoto();

const i = await actionSheet('Sort by', [{ title: 'Name' }, { title: 'Date' }]);   // number | null
```

**Notes:** Always check the index bound - never treat any index as a real choice. The wrapper's option shape is `{ title, destructive? }`; it maps `destructive` to the plugin's `'DESTRUCTIVE'` style.

### `Capacitor.App`

```js
Capacitor.Plugins.App — getInfo(), getState(), exitApp(), minimizeApp(), addListener(...)
```

**Example**

App identity and lifecycle: name, package id, version, whether the app is in front, and pause/resume events (`@capacitor/app` 8.1.0).

**Returns:** `getInfo()` → `{ name, id, build, version }`; `getState()` → `{ isActive }`; `getLaunchUrl()` → `{ url }` or nothing; `getAppLanguage()` → `{ value }`; `exitApp()` / `minimizeApp()` → `void`. `addListener()` → Promise of a handle with `remove()`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var App = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.App;
if (App) {
  App.getInfo().then(function (i) {
    document.getElementById('about').textContent = i.name + ' ' + i.version + ' (' + i.build + ')';
  });
}
```

**Pause and resume** - save a draft when the user leaves, refresh when they come back:

```js
if (App) {
  App.addListener('appStateChange', function (s) {
    if (!s.isActive) saveDraft();      // went to the background
    else refreshInbox();               // back in front
  });
  App.addListener('pause', function () { console.log('paused'); });
  App.addListener('resume', function () { console.log('resumed'); });
}
```

**In an AI-built app:**

```ts
import { App } from '@/lib/appmintNative';

const info = await App.getInfo();   // { name, id, build, version }
App.exitApp();                      // closes the app on Android
```

**Notes:** The `backButton` event never fires: the shell keeps the back button (the plugin runs with `disableBackButtonHandler`). Handle back with `AppMint.setBackHandler(fn)` or the `onBackGesture` wrapper. For deep links use `WebToApk.getLaunchUrl()` / the `appmint:deep-link` event (or `launchUrl()` / `onDeepLink()` in AI-built apps). `minimizeApp()` is Android-only. Never make an Exit button the only way forward.

### `Capacitor.AppLauncher`

```js
Capacitor.Plugins.AppLauncher — canOpenUrl({ url }), openUrl({ url })
```

**Example**

Opens another app by link (`whatsapp://send?text=…`, `tel:`, `geo:`, an https link) and asks whether one can handle it (`@capacitor/app-launcher` 8.0.1).

**Returns:** `canOpenUrl()` → Promise `{ value: boolean }`. `openUrl()` → Promise `{ completed: boolean }` (`false` when no app took the link). **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var AL = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppLauncher;

async function chatOnWhatsApp(text) {
  if (!AL) return;
  var url = 'whatsapp://send?text=' + encodeURIComponent(text);
  var can = await AL.canOpenUrl({ url: url });
  if (!can.value) { alert('WhatsApp is not installed'); return; }
  var r = await AL.openUrl({ url: url });
  if (!r.completed) alert('Could not open WhatsApp');
}
```

**In an AI-built app:**

```ts
import { appLauncher } from '@/lib/appmintNative';

if (await appLauncher.canOpen('tel:+15551234567')) await appLauncher.open('tel:+15551234567');
```

**Notes:** On Android 11+ an app can only "see" other apps it declares. Generated apps declare `https`, `geo`, `google.navigation`, `tel`, `mailto`, `sms`, `whatsapp`, `intent` and `upi` links, so `canOpenUrl` answers `false` for other schemes (for example `spotify:`) even when the app is installed. `openUrl` still works for them - call it and check `completed`. On Android `canOpenUrl` also accepts a package name (`'com.whatsapp'`), with the same visibility limit.

### `Capacitor.AppUpdate`

```js
Capacitor.Plugins.AppUpdate — getAppUpdateInfo(), performImmediateUpdate(), startFlexibleUpdate(), completeFlexibleUpdate(), openAppStore()
```

**Example**

Checks Google Play for a newer version of the app and starts Play's in-app update. Vendored `@capawesome/capacitor-app-update` (`template/plugins/app-update`).

**Returns:** `getAppUpdateInfo()` → `{ currentVersionName, currentVersionCode, availableVersionCode, updateAvailability, updatePriority, immediateUpdateAllowed, flexibleUpdateAllowed, clientVersionStalenessDays?, installStatus }` (version codes are strings; `updateAvailability` `2` = an update is available). `performImmediateUpdate()` / `startFlexibleUpdate()` → `{ code }`: `0` OK, `1` cancelled, `2` failed, `3` not available, `4` not allowed, `5` call `getAppUpdateInfo()` first. `openAppStore()` → `void`. **Needs:** nothing to switch on; the app must be installed from Google Play. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var AU = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppUpdate;

async function checkForUpdate() {
  if (!AU) return;
  try {
    var info = await AU.getAppUpdateInfo();          // must come first
    if (info.updateAvailability !== 2) return;       // up to date
    if (info.immediateUpdateAllowed) {
      var r = await AU.performImmediateUpdate();     // Play's full-screen update
      if (r.code === 1) console.log('user said not now');
    } else {
      await AU.openAppStore();                       // the Play listing
    }
  } catch (e) {
    console.log('Play update check failed:', e.message);  // e.g. no Play services
  }
}
```

**Flexible update** - download in the background, then restart when the user agrees:

```js
async function flexibleUpdate() {
  if (!AU) return;
  var info = await AU.getAppUpdateInfo();
  if (info.updateAvailability !== 2 || !info.flexibleUpdateAllowed) return;
  await AU.addListener('onFlexibleUpdateStateChange', function (s) {
    if (s.installStatus === 2) console.log(s.bytesDownloaded + ' / ' + s.totalBytesToDownload);
    if (s.installStatus === 11) showRestartButton();   // 11 = DOWNLOADED
  });
  await AU.startFlexibleUpdate();
}
function restartNow() { if (AU) AU.completeFlexibleUpdate(); }
```

**In an AI-built app:**

```ts
import { appUpdate } from '@/lib/appmintNative';

const info = await appUpdate.check();          // null on the web / without Play
if (info?.inPlace) await appUpdate.start();    // Android: immediate update
```

**Notes:** Use `updateAvailability === 2` as the only "update available" test. The wrapper's `check()` compares `currentVersionName` with `availableVersionCode`, which are never equal on Android, so its `updateAvailable` is not reliable there - test `inPlace` instead. `start()` resolves `true` even when the user cancels.

### `Capacitor.AppwrightDocScanner`

```js
Capacitor.Plugins.AppwrightDocScanner — scan({ pageLimit, pdf, quality }), isSupported()
```

**Example**

Opens the Google ML Kit document scanner: the user photographs one or more pages (or imports them), the edges are found and straightened, and the pages come back as JPEG images. Appwright's own plugin (`AppwrightDocScannerPlugin.kt`).

**Returns:** `scan()` → Promise that always RESOLVES: `{ ok: true, pages: string[] (JPEG data URLs), pdfDataUrl?, pdfPath?, pdfError? }` on success, `{ ok: false, cancelled: true }` when the user backs out, `{ ok: false, error }` when the scanner cannot start. `isSupported()` → `{ value: boolean }` (Google Play services present). **Needs:** nothing to switch on and no camera permission (the scanner runs inside Play services, which downloads its model on first use).

```js
var Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightDocScanner;

async function scanReceipt() {
  if (!Scanner) { alert('Scanning is not available here'); return; }
  var s = await Scanner.isSupported();
  if (!s.value) { alert('This phone has no Google Play services'); return; }
  var r = await Scanner.scan({ pageLimit: 1, pdf: false, quality: 0.8 });
  if (r.cancelled) return;
  if (!r.ok) { alert('Scanner error: ' + r.error); return; }
  document.getElementById('preview').src = r.pages[0];
}
```

`pageLimit` `0` = no limit. `quality` is 0.1-1 for the JPEG. With `pdf: true` the scanner also writes a PDF: `pdfDataUrl` is it as a `data:application/pdf;base64,…` URL the page can download, upload or show, and `pdfPath` is its copy in the app's cache folder. If the PDF could not be read, the pages still return and `pdfError` says why.

**In an AI-built app** (`null` on cancel, error, or the web):

```ts
import { docScanner, can } from '@/lib/appmintNative';

if (can('docScanner') !== 'none') {
  const res = await docScanner.scan({ pageLimit: 5, pdf: true });
  if (res) setPages(res.pages);          // res.pdfPath when pdf: true
}
```

**Notes:** Pages are full data URLs and can be large - shrink them before saving many in `localStorage`. Use `pdfDataUrl`, not `pdfPath` - a device file path is not a URL the page can load. A page that cannot be read is skipped; the rest still return.

### `Capacitor.AppwrightGallery`

```js
Capacitor.Plugins.AppwrightGallery — save({ source, fileName, album, kind })
```

**Example**

Saves a photo or a video into the phone's gallery (Pictures/<album> or Movies/<album>). The app shell's own plugin (`AppwrightGalleryPlugin.kt`).

**Returns:** Promise of `{ uri }` of the saved item. Rejects with a `code`: `bad-source`, `download-failed`, `permission-denied` (Android 7-9, storage refused) or `failed`. **Needs:** nothing to switch on; no permission on Android 10+ (Android 7-9 asks for storage the first time).

```js
const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

async function saveCard(canvas) {
  const dataUrl = canvas.toDataURL('image/png');
  if (!Gallery) { const a = document.createElement('a'); a.href = dataUrl; a.download = 'card.png'; a.click(); return; }
  try {
    await Gallery.save({ source: dataUrl, fileName: 'birthday-card.png', album: 'Cards' });
    toast('Saved to your gallery');
  } catch (e) {
    toast('Could not save: ' + e.message);
  }
}
```

**Notes:** `source` is a `data:` URL, an http(s) URL or a `content://`/`file://` URI; `kind` (`'image'`/`'video'`) is read from the MIME type when omitted. AI-built apps use `saveToGallery()` from `@/lib/appmintNative`.

### `Capacitor.AppwrightHaptics`

```js
Capacitor.Plugins.AppwrightHaptics
```

**Example**

AppMint's own plugin for rich vibration patterns: a list of beats, each with its own start time, length and strength.

**Returns:** `play({ events })` resolves `{ ok: true }` or `{ ok: false, error }` (`'unsupported'` = no vibrator, `'empty'` = no events, or the system's message). It never rejects. `isSupported()` resolves `{ value: true/false }`. **Needs:** nothing to switch on - every app declares the vibration permission, whether or not **Vibrate** is ticked in Step 4 (Access). `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

Each event is `{ at, duration, intensity, sharpness }`: `at` = start in ms, `duration` = length in ms (under 20 becomes a 30 ms tap), `intensity` = 0-1 (default 0.8). `sharpness` is accepted but ignored on Android.

```js
var AH = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.AppwrightHaptics : null;

async function heartbeat() {
  if (!AH) return false;
  var r = await AH.play({
    events: [
      { at: 0,   intensity: 0.8 },
      { at: 140, intensity: 0.5 },
      { at: 700, intensity: 0.8 },
      { at: 840, intensity: 0.5 }
    ]
  });
  if (!r.ok) console.log('pattern not played:', r.error);
  return r.ok;
}

document.getElementById('like').addEventListener('click', heartbeat);
```

Hide the button on phones with no vibrator:

```js
(async function () {
  var btn = document.getElementById('feel-it');
  var ok = false;
  if (AH) { try { ok = (await AH.isSupported()).value; } catch (e) {} }
  btn.hidden = !ok;
})();
```

**Notes:** Strength works only on phones with amplitude control; other phones keep the timing at one fixed strength. If the system refuses the vibration, `play` answers `{ ok: false, error: '<the system message>' }`. In AI-built apps use `hapticPlay` from `@/lib/appmintNative`, which also has named patterns.

### `Capacitor.AppwrightLiveProgress`

```js
Capacitor.Plugins.AppwrightLiveProgress — start / update / end
```

**Example**

AppMint's own Android plugin (its JS name is `AppwrightLiveProgress`) for a sticky, silent, live-updating status notification (progress bar, spinner or running clock) that opens a route when tapped.

**Returns:** `start(opts)` / `update(opts)` → `Promise<{ ok: true, id }>`, or `{ ok: false, error }` with nothing posted - `'permission'` when notifications are not allowed (Android 13+: not granted yet, or switched off for the app), `'channel_disabled'` when the user turned off the "Live status" category; `end({ id })` → `Promise<void>`. **Needs:** the Android app and the notification permission (Android 13+). Not available on the web.

**In an AI-built app** use the wrapper `liveProgress` from `@/lib/appmintNative`:

```ts
import { liveProgress } from '@/lib/appmintNative';
await liveProgress.start({ id: 'order', title: 'Order #42', text: 'Being prepared', indeterminate: true });
await liveProgress.update({ id: 'order', title: 'Order #42', text: 'On the way', progress: 0.6, route: '/orders/42' });
await liveProgress.end('order');
```

**In a hand-written page:**

```js
async function showDelivery(step, total) {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (!P) return;                                   // not in the Android app
  if (window.Notification && Notification.permission !== 'granted') {
    if (await Notification.requestPermission() !== 'granted') return;
  }
  const r = await P.update({
    id: 'delivery',
    title: 'Your delivery',
    text: 'Stop ' + step + ' of ' + total,
    progress: step / total,                         // 0–1; or indeterminate: true
    route: '/track'                                 // opened when the card is tapped
  });
  if (!r.ok) console.warn('live card not posted:', r.error);
}

async function deliveryDone() {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (P) await P.end({ id: 'delivery' });
}
```

**Options:** `id` (default `'live'`), `title`, `text`, `progress` (0-1, omit for no bar), `indeterminate`, `startedAt` (epoch ms, shows a running clock), `route`.

**Notes:** `start` and `update` do the same thing (post or replace the card with that id). The card cannot be swiped away - always call `end`. Ask for the permission first: on Android 13+ without it `start` answers `error: 'permission'` and posts nothing.

### `Capacitor.AppwrightSystemPalette`

```js
Capacitor.Plugins.AppwrightSystemPalette — get()
```

**Example**

Returns the phone's Material You colours (the palette Android 12+ builds from the wallpaper), so the app can match the system apps. AppMint's own plugin (`AppwrightSystemPalettePlugin.kt`).

**Returns:** Promise `{ available: false }` below Android 12, else `{ available: true, accent1, accent2, accent3, neutral1, neutral2 }` - each an object from tone to hex colour, with tones `'0'`, `'10'`, `'50'`, `'100'` … `'900'`, `'1000'` (e.g. `accent1['600'] === '#3F5F90'`). **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Palette = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightSystemPalette;

async function useWallpaperColours() {
  if (!Palette) return;
  var p = await Palette.get();
  if (!p.available) return;                 // keep the app's own colours
  var root = document.documentElement.style;
  root.setProperty('--accent', p.accent1['600']);
  root.setProperty('--surface', p.neutral1['50']);
}
useWallpaperColours();
```

**In an AI-built app:**

```ts
import { systemPalette } from '@/lib/appmintNative';

const p = await systemPalette();
if (p.available && p.accent1) document.documentElement.style.setProperty('--accent', p.accent1['600']);
```

**Notes:** Check text contrast before you apply palette colours - a wallpaper tone is not guaranteed to read well on your background. Browsers expose no wallpaper colours: on the web the wrapper answers `{ available: false }`. The colours change when the user changes the wallpaper; call `get()` again on resume.

### `Capacitor.AppwrightWidget`

```js
Capacitor.Plugins.AppwrightWidget — set(card), clear(), placedCount(), requestPin()
```

**Example**

Fills the app's home-screen widget: one card with a title, a big value, up to six lines, your colours and a tap route. The card is saved on the phone and shown even after a reboot. AppMint's own plugin (`AppwrightWidgetPlugin.kt`).

**Returns:** `set({ title?, value?, lines?, route?, bg?, ink?, accent? })` → Promise `{ ok: true, placed }` (`placed` = widgets currently on the home screen). `clear()` → `void`. `placedCount()` → `{ value }`. `requestPin()` → `{ value: boolean }` (`true` when the launcher's "add widget" sheet was opened). **Needs:** nothing to switch on - every app declares the widget. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Widget = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightWidget;

async function updateWidget(tasks) {
  if (!Widget) return;
  await Widget.set({
    title: 'Today',
    value: tasks.length + ' tasks',
    lines: tasks.slice(0, 6).map(function (t) { return t.title; }),
    route: '/today',                 // opened when the widget is tapped
    bg: '#0B0F1A', ink: '#FFFFFF', accent: '#22D3EE'
  });
}
```

**Offer to place it** (only when none is placed yet):

```js
async function offerWidget() {
  if (!Widget) return;
  var n = (await Widget.placedCount()).value;
  if (n > 0) return;
  var r = await Widget.requestPin();
  if (!r.value) alert('Long-press the home screen, tap Widgets and pick this app.');
}
```

**In an AI-built app:**

```ts
import { widget } from '@/lib/appmintNative';

await widget.set({ title: 'Today', value: '3 tasks', lines: ['Gym', 'Call mum'], route: '/today' });
if ((await widget.placedCount()) === 0) await widget.requestPin();
```

**Notes:** Colours are hex strings; empty ones use a dark card with a cyan value. The tap opens the app at `route` as a deep link (read it with `WebToApk.getLaunchUrl()` or the `appmint:deep-link` event). Before any `set()` the widget says "Nothing to show yet"; `clear()` returns it to that state. In a browser `can('widget')` is `'none'`.

### `Capacitor.Badge`

```js
Capacitor.Plugins.Badge — the launcher-icon count
```

**Example**

The Badge plugin (capawesome) that sets the number on the app's launcher icon.

**Returns:** Promises: `set({ count })`, `clear()`, `increase()`, `decrease()` → `void`; `get()` → `{ count }`; `isSupported()` → `{ isSupported }`. The Android plugin has no permission methods. **Needs:** nothing on Android. Not available on the web.

**In an AI-built app** use the wrapper `badge` from `@/lib/appmintNative`:

```ts
import { badge } from '@/lib/appmintNative';
await badge.set(3);
await badge.clear();
```

**In a hand-written page:**

```js
async function setUnread(count) {
  const B = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Badge;
  if (!B) return false;                       // not inside the app
  try {
    const s = await B.isSupported();
    if (!s.isSupported) return false;         // this launcher shows no badges
    if (count > 0) await B.set({ count: count });
    else await B.clear();
    return true;
  } catch (e) {
    return false;
  }
}

window.addEventListener('appmint:push', function () {
  const inbox = JSON.parse(window.WebToApk ? WebToApk.getPushInbox() : '[]');
  setUnread(inbox.filter(function (m) { return !m.read; }).length);
});
```

**Notes:** on Android the look depends on the launcher (a number, a dot, or nothing). `navigator.setAppBadge(n)` in the app writes to the same badge (it exists only on launchers that can show a count).

### `Capacitor.Browser`

```js
Capacitor.Plugins.Browser — open({ url, toolbarColor }), close(), addListener('browserFinished')
```

**Example**

Opens a web page in an in-app browser tab (Chrome Custom Tabs), so the user reads terms, docs or a sign-in page and comes back with one gesture (`@capacitor/browser` 8.0.3).

**Returns:** `open()` / `close()` → Promise `void`. Events: `browserFinished` (the user closed the tab) and `browserPageLoaded`. **Needs:** nothing - every app hosts the plugin (a browser that supports Custom Tabs, such as Chrome, must be installed). `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Browser = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser;

async function openTerms() {
  if (!Browser) return;
  var h = await Browser.addListener('browserFinished', function () {
    console.log('user closed the terms');
    h.remove();
  });
  await Browser.open({ url: 'https://example.com/terms', toolbarColor: '#0B0F1A' });
}
```

**In an AI-built app** (a new tab on the web):

```ts
import { browser } from '@/lib/appmintNative';

const off = browser.onClosed(() => refreshAccount());
await browser.open('https://example.com/terms', { toolbarColor: '#0B0F1A' });
// call off() when you no longer care
```

**Notes:** `url` must not be empty. `toolbarColor` is a hex colour; an invalid one is ignored. `close()` also works on Android in this plugin version (it finishes the tab's host activity) - useful after a sign-in link returns to the app. The tab is a separate browser: it does not share the app's cookies or `localStorage`.

### `Capacitor.Calendar`

```js
Capacitor.Plugins.Calendar — createEvent, findEvents, deleteEvent, listCalendars, openCalendar
```

**Example**

Adds, finds and deletes events in the phone's own calendar (Android CalendarContract), and opens the calendar app at a date. Vendored `@capacitor/calendar` (`template/plugins/calendar`).

**Returns:** `createEvent()` → `{ id }`; `findEvents()` → `{ events: [{ id, title, location?, notes?, startDate, endDate, isAllDay, calendarId, calendarName?, attendees? }] }`; `listCalendars()` → `{ calendars: [{ id, name, displayName?, isPrimary }] }`; `deleteEvent()`, `openCalendar()` → `void`. Dates are epoch milliseconds. **Needs:** tick **Calendar** in Step 4 (Access) when you build. For HTML, ZIP and AI-built apps the build also adds the calendar permission by itself when your files contain a `Calendar.<method>(` call written exactly so (as below) or the wrapper's `calendar.createEvent(`. The plugin asks the user for calendar access on first use. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Calendar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Calendar;

async function addDentist() {
  if (!Calendar) return;
  var start = new Date('2026-10-02T10:00:00').getTime();
  try {
    var r = await Calendar.createEvent({
      title: 'Dentist', startDate: start, endDate: start + 60 * 60 * 1000,
      location: 'Main St 5', notes: 'Bring the card', isAllDay: false,
      firstReminderMinutes: 30
    });
    console.log('event id', r.id);
  } catch (e) {
    // e.code: OS-PLUG-CLDR-0020 permission denied, -0001 bad arguments
    alert('Could not add the event: ' + e.message);
  }
}
```

**Read this week, delete one, open the calendar:**

```js
async function thisWeek() {
  if (!Calendar) return;
  var now = Date.now();
  var found = await Calendar.findEvents({ startDate: now, endDate: now + 7 * 864e5 });
  found.events.forEach(function (ev) { console.log(ev.title, new Date(ev.startDate)); });
  if (found.events.length) await Calendar.deleteEvent({ id: found.events[0].id });
  await Calendar.openCalendar({ date: now });
}
```

**In an AI-built app** (on the web `createEvent` downloads an `.ics` file; reading returns `[]`):

```ts
import { calendar } from '@/lib/appmintNative';

if ((await calendar.requestPermission()) === 'granted') {
  const id = await calendar.createEvent({ title: 'Dentist', start: new Date('2026-10-02T10:00'), end: new Date('2026-10-02T11:00') });
  const week = await calendar.listEvents({ from: Date.now(), to: Date.now() + 7 * 864e5 });
  if (id) await calendar.deleteEvent(id);
  await calendar.openCalendar(new Date());
}
```

**Notes:** Without the calendar permission in the app, no dialog appears and every event call rejects (missing permission); a user who says no gets `OS-PLUG-CLDR-0020`. `recurrence: { frequency: 'daily' | 'weekly' | 'monthly' | 'yearly', interval?, count?, endDate? }` makes a repeating event. `createEventInteractively({ title, startDate, endDate })` opens the calendar app's own editor instead (no id comes back). The wrapper returns `null` / `[]` / `false` instead of throwing.

### `Capacitor.Camera`

```js
Capacitor.Plugins.Camera
```

**Example**

The official Capacitor Camera plugin, built into the app: take a photo with the camera or choose photos from the gallery.

**Returns:** Promises. `getPhoto(opts)` → `{ dataUrl | base64String | webPath, format, saved }` (which field depends on `resultType`), and REJECTS when the user cancels; `pickImages(opts)` → `{ photos: [{ webPath, format }] }`; `checkPermissions()` / `requestPermissions({ permissions })` → `{ camera, photos }`. **Needs:** tick **Camera** in Step 4 (Access). For HTML, ZIP and AI-built apps the build also adds the camera permission by itself when your files contain `Camera.getPhoto(` written exactly so (as below), `getUserMedia` with video, or `<input capture>`. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

**Take a photo and show it.**

```js
async function takePhoto() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) { showMessage('Open the app to use the camera.'); return; }
  try {
    var perm = await Camera.requestPermissions({ permissions: ['camera'] });
    if (perm.camera !== 'granted') { showMessage('Camera permission was refused.'); return; }

    var photo = await Camera.getPhoto({
      source: 'CAMERA',          // 'CAMERA' | 'PHOTOS' | 'PROMPT'
      resultType: 'dataUrl',     // 'dataUrl' | 'base64' | 'uri'
      quality: 80,
      direction: 'REAR',         // 'REAR' | 'FRONT'
      correctOrientation: true,
      saveToGallery: false
    });
    document.getElementById('photo').src = photo.dataUrl;
  } catch (err) {
    // The user closed the camera (or no camera app). Nothing to do.
  }
}
```

**Choose up to 5 photos from the gallery.**

```js
async function choosePhotos() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) return;
  try {
    var result = await Camera.pickImages({ quality: 80, limit: 5 });
    result.photos.forEach(function (p) {
      var img = document.createElement('img');
      img.src = p.webPath;       // a URL the page can load
      document.getElementById('gallery').appendChild(img);
    });
  } catch (err) { /* cancelled */ }
}
```

**In an AI-built app** use the wrapper, which also works on the web (and resolves `null` instead of throwing on cancel):

```ts
import { camera } from '@/lib/appmintNative';
const photo = await camera.take({ source: 'camera' });
```

**Notes:** `pickImages` needs no camera permission. If the camera permission is not in the app (the box was off and the build found no camera call), the camera permission is refused with no prompt. An `<input type="file" accept="image/*" capture>` also opens the camera. Camera is sensitive: declare it in your Play Data safety form.

### `Capacitor.Clipboard`

```js
Capacitor.Plugins.Clipboard — write(options), read()
```

**Example**

Copies text (or a link) to the system clipboard and reads it back (`@capacitor/clipboard` 8.0.1).

**Returns:** `write({ string?, url?, image?, label? })` → Promise `void`. `read()` → Promise `{ value, type }` (`type` is `'text/plain'` for text). **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
async function copyCode(code) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) { alert('Copy is not available here'); return; }
  await Clipboard.write({ string: code, label: 'Invite code' });
}

async function pasteCode(input) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) return;
  try {
    var r = await Clipboard.read();
    input.value = r.value;
  } catch (e) {
    // An EMPTY clipboard is a rejection: "There is no data on the clipboard".
    if (/no data/i.test(e.message)) input.value = '';
    else alert('Could not read the clipboard: ' + e.message);
  }
}
```

**In an AI-built app** the Capacitor-shaped wrapper turns an empty clipboard into `''`:

```ts
import { Clipboard } from '@/lib/appmintNative';

await Clipboard.write({ string: 'ABC-123' });
const { value } = await Clipboard.read();   // '' when the clipboard is empty
```

**Notes:** `write` needs one of `string`, `url` or `image` (a data URL), else it rejects "No data provided". Android 12+ shows its own "pasted from…" notice on read - that is the OS, not the app. The wrapper's `write` sends only `string || url`.

### `Capacitor.Device`

```js
Capacitor.Plugins.Device
```

**Example**

The official Capacitor Device plugin, built into the app: phone model, OS version, battery, a stable ID and the language.

**Returns:** Promises. `getInfo()` → `{ model, platform, operatingSystem, osVersion, androidSDKVersion, manufacturer, isVirtual, webViewVersion, … }`; `getId()` → `{ identifier }`; `getBatteryInfo()` → `{ batteryLevel (0-1), isCharging }`; `getLanguageCode()` → `{ value }`. **Needs:** nothing to switch on. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

**Show the phone and battery.**

```js
async function showDevice() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) { document.getElementById('out').textContent = navigator.userAgent; return; }
  try {
    var info = await cap.getInfo();
    var battery = await cap.getBatteryInfo();
    document.getElementById('out').textContent =
      info.manufacturer + ' ' + info.model + ' - ' + info.operatingSystem + ' ' + info.osVersion +
      ' - battery ' + Math.round(battery.batteryLevel * 100) + '%' + (battery.isCharging ? ' (charging)' : '');
  } catch (err) {
    document.getElementById('out').textContent = 'Could not read device info';
  }
}
```

**A stable ID and the language.**

```js
async function helloServer() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) return;
  var id = (await cap.getId()).identifier;
  var lang = (await cap.getLanguageCode()).value;     // e.g. "en"
  await fetch('https://api.example.com/hello?lang=' + encodeURIComponent(lang), {
    method: 'POST', body: JSON.stringify({ device: id })
  });
}
```

**In an AI-built app** use the wrapper, which also works on the web:

```ts
import { Device } from '@/lib/appmintNative';
const info = await Device.getInfo();
```

**Notes:** For the Android build ID, security patch, screen, memory and network, the shell's `WebToApk.getDeviceInfo()` gives more detail. Plugin ids are not hardware ids; they can change after a factory reset.

### `Capacitor.Dialog`

```js
Capacitor.Plugins.Dialog — alert, confirm, prompt
```

**Example**

Shows a real Android system dialog: a message, a yes/no question, or a one-line text question (`@capacitor/dialog` 8.0.1).

**Returns:** `alert({ title?, message, buttonTitle? })` → Promise `void`. `confirm({ title?, message, okButtonTitle?, cancelButtonTitle? })` → `{ value: boolean }`. `prompt({ title?, message, okButtonTitle?, cancelButtonTitle?, inputPlaceholder?, inputText? })` → `{ value: string, cancelled: boolean }`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Dialog = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Dialog;

async function deleteNote(id) {
  if (!Dialog) return;
  var r = await Dialog.confirm({
    title: 'Delete note?',
    message: 'This cannot be undone.',
    okButtonTitle: 'Delete',
    cancelButtonTitle: 'Keep'
  });
  if (r.value) removeNote(id);
}
```

**Alert and prompt:**

```js
async function renameFlow() {
  if (!Dialog) return;
  await Dialog.alert({ title: 'Saved', message: 'Your changes are stored.', buttonTitle: 'OK' });

  var p = await Dialog.prompt({ title: 'Rename', message: 'New name', inputText: 'Groceries', inputPlaceholder: 'List name' });
  if (!p.cancelled && p.value.trim()) renameList(p.value.trim());
}
```

**In an AI-built app** the wrapper never rejects: dismissal is `{ value: false }` / `{ cancelled: true }`, and on the web it uses the browser's own dialogs:

```ts
import { Dialog } from '@/lib/appmintNative';

const { value } = await Dialog.confirm({ title: 'Delete note?', message: 'This cannot be undone.' });
if (value) removeNote(id);
```

**Notes:** `message` is required - without it the plugin rejects "Please provide a message for the dialog". Dialogs are modal; use them only for decisions that must block.

### `Capacitor.Filesystem`

```js
Capacitor.Plugins.Filesystem
```

**Example**

The stock Capacitor Filesystem plugin: read and write files in the app's OWN storage (not a folder the user picked - for that use `WebToApkFS`).

**Returns:** every method returns a Promise and rejects on failure (for example reading a file that does not exist). `readFile` → `{ data }`, `writeFile` → `{ uri }`, `readdir` → `{ files: [{ name, type, size, mtime, uri }] }`, `mkdir` / `deleteFile` → nothing. **Needs:** nothing to switch on; `'DATA'` and `'CACHE'` need no permission.

A page has no `Directory` / `Encoding` imports, so pass the plain strings `'DATA'` (private app files, kept until uninstall) or `'CACHE'` (the system may clear it), and `'utf8'` for text:

```js
async function saveAndReadBack() {
  var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
  if (!Fs) { console.log('Filesystem plugin not available (not inside the app)'); return; }

  try {
    await Fs.mkdir({ path: 'notes', directory: 'DATA', recursive: true });
    await Fs.writeFile({ path: 'notes/today.txt', data: 'Hello', directory: 'DATA', encoding: 'utf8', recursive: true });

    var r = await Fs.readFile({ path: 'notes/today.txt', directory: 'DATA', encoding: 'utf8' });
    console.log('text:', r.data);

    var dir = await Fs.readdir({ path: 'notes', directory: 'DATA' });
    dir.files.forEach(function (f) { console.log(f.type, f.name, f.size); });   // type: 'file' | 'directory'

    await Fs.deleteFile({ path: 'notes/today.txt', directory: 'DATA' });
  } catch (e) {
    console.log('File error:', e.message);   // e.g. the file does not exist
  }
}
```

Leave out `encoding` to write or read binary data as base64:

```js
var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
var base64Data = btoa('raw bytes here');   // any base64 string, e.g. from a canvas or FileReader
if (Fs) {
  Fs.writeFile({ path: 'blobs/data.bin', data: base64Data, directory: 'CACHE', recursive: true })
    .then(function (w) { console.log('written to', w.uri); })
    .catch(function (e) { console.log('write failed:', e.message); });
}
```

In AI-built apps, the `@/lib/appmintNative` wrapper that uses this plugin is `fileTransfer` (it stages downloads and uploads in `'CACHE'`):

```ts
import { fileTransfer } from '@/lib/appmintNative';

const r = await fileTransfer.download('https://example.com/report.pdf', 'report.pdf', {
  onProgress: (p) => console.log(Math.round(p.fraction * 100) + '%'),
});
if (r.path) console.log('saved in the app at', r.path);   // on the web the browser saves it instead
```

**Notes:** `window.Capacitor` exists only inside the app - always feature-detect. Files in `'DATA'` are private to the app and deleted on uninstall; to put files where the user can see them, use `WebToApkFS` (a folder the user grants) or a download.

### `Capacitor.FileTransfer`

```js
Capacitor.Plugins.FileTransfer
```

**Example**

The official Capacitor File Transfer plugin: downloads a URL to a file on the phone, or uploads a file from the phone, natively, with progress events.

**Returns:** `downloadFile({url, path, headers?, progress?})` → `Promise<{path}>`; `uploadFile({url, path, fileKey?, mimeType?, method?, headers?, progress?})` → `Promise<{bytesSent, responseCode, response, headers}>`. Progress arrives as `'progress'` listener events `{type, url, bytes, contentLength, lengthComputable}`. Failures reject with `{message, code}`. **Needs:** nothing to switch on. `window.Capacitor` exists only in pages the app itself serves - always feature-detect.

Download into the app's cache with progress:

```js
async function download(url, name, onPct) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer || !P.Filesystem) return null;          // not in the app
  const target = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  const listener = await P.FileTransfer.addListener('progress', function (p) {
    if (p.url === url && p.lengthComputable) onPct(Math.round(p.bytes / p.contentLength * 100));
  });
  try {
    const r = await P.FileTransfer.downloadFile({ url: url, path: target.uri, progress: true });
    return r.path;
  } catch (e) {
    alert('Download failed: ' + e.message);
    return null;
  } finally {
    listener.remove();
  }
}
```

Upload a file that is already on the phone:

```js
const P = window.Capacitor && window.Capacitor.Plugins;
if (P && P.FileTransfer) {
  P.FileTransfer.uploadFile({
    url: 'https://example.com/api/upload', path: localFileUri,
    fileKey: 'file', mimeType: 'image/jpeg', headers: { Authorization: 'Bearer ' + token }
  }).then(function (r) { console.log('server said', r.responseCode, r.response); })
    .catch(function (e) { alert('Upload failed: ' + e.message); });
}
```

**Notes:** The path must be a file path or `file://` URI, not a Blob - write a Blob to the cache with `Filesystem.writeFile` first. On Android 10 and older, a path in public storage asks for storage permission. In AI-built apps prefer `fileTransfer` from `@/lib/appmintNative`, which does all of this and also works on the web.

### `Capacitor.FileViewer`

```js
Capacitor.Plugins.FileViewer
```

**Example**

The official Capacitor File Viewer plugin: opens a document or media file in the phone's viewer app, from a web address, a local file path, or a file bundled in the app.

**Returns:** each method returns a `Promise<void>` that rejects with `{message, code}` when nothing can open the file. **Needs:** nothing to switch on. `window.Capacitor` exists only in pages the app itself serves - always feature-detect.

```js
async function openPdf(url) {
  const FV = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.FileViewer;
  if (!FV) { window.open(url, '_blank'); return; }          // a normal browser: open a tab
  try {
    await FV.openDocumentFromUrl({ url: url });
  } catch (e) {
    alert('No app on this phone can open this file.');
  }
}
```

A file on the phone, for example one written with `Capacitor.Plugins.Filesystem`:

```js
async function openSaved(name) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileViewer || !P.Filesystem) return;
  const f = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  try {
    await P.FileViewer.openDocumentFromLocalPath({ path: f.uri.replace(/^file:\/\//, '') });
  } catch (e) {
    alert('Cannot open ' + name + ': ' + e.message);
  }
}
```

`openDocumentFromResources({path})` opens a file packed in the APK's own assets folder. The same three methods also exist as `previewMediaContentFromUrl`, `previewMediaContentFromLocalPath` and `previewMediaContentFromResources` for video and audio.

**Notes:** In AI-built apps prefer `fileViewer.open(target)` from `@/lib/appmintNative`, which picks the right method and also works on the web. A local path is a plain path (no `file://`).

### `Capacitor.Geolocation`

```js
Capacitor.Plugins.Geolocation
```

**Example**

The official Capacitor Geolocation plugin, built into the app: the phone's position once, or continuous updates.

**Returns:** Promises. `getCurrentPosition(opts)` → `{ timestamp, coords: { latitude, longitude, accuracy, altitude, speed, heading, … } }`; `watchPosition(opts, callback)` → the watch id (a string); `clearWatch({ id })`; `checkPermissions()` / `requestPermissions()` → `{ location, coarseLocation }` (`'granted' | 'denied' | 'prompt' | …`). **Needs:** **GPS** and/or **Location** in Step 4 (Access) - for HTML, ZIP and AI-built apps the build also turns them on by itself when your files call `getCurrentPosition` or `watchPosition`. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

**Position once, asking first.**

```js
async function whereAmI() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) { showMessage('Open the app to use location.'); return; }
  try {
    var perm = await geo.checkPermissions();
    if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
      perm = await geo.requestPermissions();
      if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
        showMessage('Location is off for this app.');
        return;
      }
    }
    var pos = await geo.getCurrentPosition({ enableHighAccuracy: true, timeout: 15000 });
    showMessage(pos.coords.latitude.toFixed(5) + ', ' + pos.coords.longitude.toFixed(5));
  } catch (err) {
    showMessage('No location: ' + err.message);   // location off in settings, timeout, …
  }
}
```

**Continuous updates.** The callback gets `(position, error)`.

```js
var watchId = null;

async function startTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) return;
  watchId = await geo.watchPosition({ enableHighAccuracy: true }, function (pos, err) {
    if (err || !pos) return;
    addPointToMap(pos.coords.latitude, pos.coords.longitude);
  });
}

async function stopTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (geo && watchId) { await geo.clearWatch({ id: watchId }); watchId = null; }
}
```

**In an AI-built app** use the wrapper, which also works on the web:

```ts
import { Geolocation } from '@/lib/appmintNative';
const pos = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });
```

**Notes:** Plain `navigator.geolocation` works in the app too. If the location switches were off at build time, the permission is refused with no prompt. On Android 12+ the user may allow only approximate location (`coarseLocation` granted). Location is personal data: declare it in your Play Data safety form.

### `Capacitor.Haptics`

```js
Capacitor.Plugins.Haptics
```

**Example**

The official Capacitor Haptics plugin (`@capacitor/haptics`), built into the app: light/medium/heavy taps, success/warning/error feedback, selection ticks and plain vibration.

**Returns:** every method returns a Promise that resolves with nothing. **Needs:** nothing to switch on - every app declares the vibration permission, whether or not **Vibrate** is ticked in Step 4 (Access). `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
function haptics() {
  return window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
}

async function tap(style) {            // 'LIGHT' | 'MEDIUM' | 'HEAVY'
  var h = haptics();
  if (!h) return;
  try { await h.impact({ style: style }); } catch (e) { console.log('haptics failed', e); }
}

async function result(type) {          // 'SUCCESS' | 'WARNING' | 'ERROR'
  var h = haptics();
  if (h) { try { await h.notification({ type: type }); } catch (e) {} }
}

document.getElementById('add').addEventListener('click', function () { tap('LIGHT'); });
document.getElementById('pay').addEventListener('click', function () { result('SUCCESS'); });
```

Selection ticks for a picker or slider, and a plain vibration:

```js
var h = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
var wheel = document.getElementById('wheel');

wheel.addEventListener('pointerdown', function () { if (h) h.selectionStart(); });
wheel.addEventListener('change', function () { if (h) h.selectionChanged(); });
wheel.addEventListener('pointerup', function () { if (h) h.selectionEnd(); });

document.getElementById('buzz').addEventListener('click', function () {
  if (h) h.vibrate({ duration: 300 });   // milliseconds
});
```

**Notes:** The strings are upper case (`'HEAVY'`, not `'heavy'`). In AI-built apps, `import { Haptics } from '@/lib/appmintNative'` gives the same names. For custom rhythms with strength per beat, use `Capacitor.Plugins.AppwrightHaptics`.

### `Capacitor.InAppReview`

```js
Capacitor.Plugins.InAppReview — requestReview()
```

**Example**

Asks Google Play to show its own "rate this app" sheet inside the app. Vendored `@capacitor-community/in-app-review` (`template/plugins/in-app-review`).

**Returns:** Promise `void` - it resolves when Play's flow ends, and Play never says whether the sheet was shown or what the user did. It rejects when Play could not start the flow. **Needs:** nothing to switch on; the app must be installed from Google Play for the sheet to appear. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Review = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.InAppReview;

async function afterThirdOrder() {
  if (!Review) return;
  try {
    await Review.requestReview();
  } catch (e) {
    console.log('review flow not available:', e.message);
  }
  // continue the app flow either way — you cannot know if the user rated
}
```

**In an AI-built app:**

```ts
import { requestReview } from '@/lib/appmintNative';

// after a success moment, not on launch
const asked = await requestReview();   // false where nothing could be asked (web)
```

**Notes:** Play limits how often the sheet appears and does not show it for every call - never tie a reward to it and never ask from a "Rate us" button expecting it to open (open the store listing for that). Ask after a success moment, not at startup. A sideloaded or debug install usually shows nothing.

### `Capacitor.KeepAwake`

```js
Capacitor.Plugins.KeepAwake — keepAwake(), allowSleep(), isKeptAwake(), isSupported()
```

**Example**

Stops the screen from dimming and locking while something must stay visible - a recipe, a route, a QR code, a timer. Vendored `@capacitor-community/keep-awake` (`template/plugins/keep-awake`).

**Returns:** `keepAwake()` / `allowSleep()` → Promise `void`. `isKeptAwake()` → `{ isKeptAwake: boolean }`. `isSupported()` → `{ isSupported: true }`. **Needs:** nothing - every app hosts the plugin; no permission. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var KA = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.KeepAwake;

async function startCooking() {
  if (KA) await KA.keepAwake();
  showSteps();
}

async function stopCooking() {
  if (KA) await KA.allowSleep();      // always release when the view closes
}

if (KA) KA.isKeptAwake().then(function (r) { console.log('awake?', r.isKeptAwake); });
```

**In an AI-built app** (the Screen Wake Lock API on the web):

```ts
import { keepAwake } from '@/lib/appmintNative';

useEffect(() => {
  void keepAwake(true);
  return () => { void keepAwake(false); };
}, []);
```

**Notes:** The flag belongs to the app window: it only works while the app is in front, and it stays on until `allowSleep()` - release it on every exit from the screen. `keepAwake()` returns `false` when nothing could keep the screen on.

### `Capacitor.Keyboard`

```js
Capacitor.Plugins.Keyboard — show(), hide(), addListener('keyboardDidShow' | ...)
```

**Example**

Opens or closes the software keyboard and tells the page when it appears and how tall it is (`@capacitor/keyboard` 8.0.5).

**Returns:** `show()` / `hide()` → Promise `void`. `addListener(event, fn)` → Promise of a handle with `remove()`; show events pass `{ keyboardHeight }` in CSS pixels. Events: `keyboardWillShow`, `keyboardDidShow`, `keyboardWillHide`, `keyboardDidHide`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Keyboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Keyboard;

if (Keyboard) {
  Keyboard.addListener('keyboardDidShow', function (info) {
    console.log('keyboard height', info.keyboardHeight);
    document.body.classList.add('typing');       // e.g. hide the bottom tab bar
  });
  Keyboard.addListener('keyboardDidHide', function () {
    document.body.classList.remove('typing');
  });
}

function onSend() {
  if (Keyboard) Keyboard.hide().catch(function () {});   // rejects if no field is focused
}
```

**In an AI-built app** use `onKeyboard()` for layout - it reports only the part of the keyboard the resized page does not already clear - and `hideKeyboard()` to close it:

```ts
import { onKeyboard, hideKeyboard, Keyboard } from '@/lib/appmintNative';

const off = onKeyboard((px) => { composer.style.paddingBottom = `${px}px`; });  // call off() on unmount
hideKeyboard();

const h = Keyboard.addListener('keyboardWillShow', ({ keyboardHeight }) => console.log(keyboardHeight));
h.remove();
```

**Notes:** The shell already resizes the page for the keyboard, so do NOT add the full `keyboardHeight` as padding - a bottom bar is already above the keys. `setAccessoryBarVisible`, `setStyle`, `setResizeMode` and `setScroll` are not implemented on Android - do not call them. `hide()` rejects "Can't close keyboard, not currently focused" when nothing has focus.

### `Capacitor.Network`

```js
Capacitor.Plugins.Network — getStatus(), addListener('networkStatusChange')
```

**Example**

Tells the page whether the phone is online and on which kind of connection, and when that changes (`@capacitor/network` 8.0.1).

**Returns:** `getStatus()` → Promise `{ connected: boolean, connectionType: 'wifi' | 'cellular' | 'none' | 'unknown' }`. The `networkStatusChange` event passes the same shape. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Network = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Network;

function showBanner(s) {
  document.getElementById('offline').hidden = s.connected;
}

if (Network) {
  Network.getStatus().then(showBanner);
  Network.addListener('networkStatusChange', function (s) {
    showBanner(s);
    if (s.connected) retryQueuedWrites();
    if (s.connectionType === 'cellular') console.log('on mobile data');
  });
}
```

**In an AI-built app:**

```ts
import { Network, networkStatus, onNetworkChange } from '@/lib/appmintNative';

const { connected, connectionType } = await Network.getStatus();
const now = networkStatus();                        // sync: { connected, type }
const off = onNetworkChange((s) => setOnline(s.connected));   // call off() on unmount
```

**Notes:** `connected` means the phone has a network, not that your server answers - still handle failed requests. The wrapper's `Network.getStatus()` and `networkStatus()` read `navigator.onLine` / `navigator.connection`, not the plugin, so their type can be `'unknown'` or a speed class like `'4g'`; `onNetworkChange()` does use the plugin's event in the app.

### `Capacitor.Preferences`

```js
Capacitor.Plugins.Preferences — get / set / remove / keys / clear
```

**Example**

A small native key-value store (Android SharedPreferences) for strings (`@capacitor/preferences` 8.0.1).

**Returns:** `set({ key, value })`, `remove({ key })`, `clear()` → Promise `void`; `get({ key })` → `{ value }` (`null` when the key is missing); `keys()` → `{ keys: string[] }`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Prefs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Preferences;

async function saveTheme(name) {
  if (!Prefs) return false;          // not inside the app
  await Prefs.set({ key: 'theme', value: name });
  return true;
}

async function loadTheme() {
  if (!Prefs) return null;
  var r = await Prefs.get({ key: 'theme' });
  return r.value;            // null if never saved
}
```

Objects must be turned into a string first:

```js
async function saveUser() {
  if (!Prefs) return;
  await Prefs.set({ key: 'user', value: JSON.stringify({ id: 7, name: 'Asha' }) });
  var u = JSON.parse((await Prefs.get({ key: 'user' })).value || 'null');
  var all = (await Prefs.keys()).keys;
  await Prefs.remove({ key: 'user' });
}
```

**Notes:** `@/lib/appmintNative` has NO Preferences wrapper, on purpose: `localStorage` is already real and persistent in the app, and a second store would split one app's data across two places. AI-built apps should keep using `localStorage` (or the data runtime). Use Preferences in a hand-written page only if you want all of your settings there, and pick one store per value. `configure({ group })` changes the store name (default `CapacitorStorage`).

### `Capacitor.PrivacyScreen`

```js
Capacitor.Plugins.PrivacyScreen — enable(), disable(), isEnabled()
```

**Example**

The official `@capacitor/privacy-screen` plugin: blocks screenshots and screen recording and hides the app's content in the recent-apps view. Baked so imported Capacitor projects that call it work.

**Returns:** `enable({android})` → `{success}`; `disable()` → `{success}`; `isEnabled()` → `{enabled}`. **Needs:** nothing to switch on.

```js
const Privacy = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PrivacyScreen;

async function openVault() {
  if (Privacy) await Privacy.enable({ android: { privacyModeOnActivityHidden: 'dim' } });
  showVault();
}
async function closeVault() {
  if (Privacy) await Privacy.disable();
}
```

**Notes:** `privacyModeOnActivityHidden`: `'none'`, `'dim'` or `'splash'`. Code written for the app itself should use `WebToApk.setSecureScreen(true)` / `privacyScreen.enable()` from `@/lib/appmintNative`, which follows the page (released when the app leaves it); mixing both, the last call wins.

### `Capacitor.ScreenBrightness`

```js
Capacitor.Plugins.ScreenBrightness — getBrightness(), setBrightness({ brightness })
```

**Example**

Sets the app window's screen brightness - full brightness for a boarding pass or QR code, then back. Vendored `@capacitor-community/screen-brightness` (`template/plugins/screen-brightness`).

**Returns:** `getBrightness()` → Promise `{ brightness }` (0-1, or `-1` when the app has not overridden it and the screen follows the system). `setBrightness({ brightness })` → Promise `void`. **Needs:** nothing - every app hosts the plugin; no permission (it changes only this app's window, not the system setting). `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var SB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenBrightness;

async function showTicket() {
  if (SB) await SB.setBrightness({ brightness: 1 });     // full brightness
  document.getElementById('qr').hidden = false;
}

async function hideTicket() {
  if (SB) await SB.setBrightness({ brightness: -1 });    // -1 = follow the system again
  document.getElementById('qr').hidden = true;
}
```

**In an AI-built app:**

```ts
import { brightness } from '@/lib/appmintNative';

await brightness.set(1);        // clamped to 0–1; false where not available (web)
// ... on leaving the screen
await brightness.restore();
const level = await brightness.get();   // -1 = following the system / not available
```

**Notes:** Always pass a number - `setBrightness({})` has no value to apply. Restore with `-1` when the view closes; the override also ends when the app closes. A web page cannot drive the backlight, so `can('brightness')` is `'none'` in a browser.

### `Capacitor.ScreenOrientation`

```js
Capacitor.Plugins.ScreenOrientation — orientation(), lock({ orientation }), unlock()
```

**Example**

Reads the screen orientation, pins it for one view (a game, a chart, a signature pad) and releases it (`@capacitor/screen-orientation` 8.0.1).

**Returns:** `orientation()` → Promise `{ type }` (e.g. `'portrait-primary'`). `lock()` / `unlock()` → Promise `void`. Event `screenOrientationChange` passes `{ type }`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

`orientation` values: `'portrait'`, `'landscape'`, `'portrait-primary'`, `'portrait-secondary'`, `'landscape-primary'`, `'landscape-secondary'`, `'natural'`, `'any'`.

```js
var SO = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenOrientation;

async function openChart() {
  if (SO) await SO.lock({ orientation: 'landscape' });
  showChart();
}

async function closeChart() {
  if (SO) await SO.unlock();          // always release when leaving the view
  hideChart();
}

if (SO) {
  SO.orientation().then(function (o) { console.log('now', o.type); });
  SO.addListener('screenOrientationChange', function (o) { console.log('rotated to', o.type); });
}
```

**In an AI-built app:**

```ts
import { lockOrientation, ScreenOrientation } from '@/lib/appmintNative';

const ok = await lockOrientation('landscape');   // false when refused (always on desktop)
await lockOrientation('any');                    // release

await ScreenOrientation.lock({ orientation: 'portrait' });
await ScreenOrientation.unlock();
```

**Notes:** The wrappers only know `portrait` / `landscape` / `any`: `ScreenOrientation.lock({ orientation: 'landscape-secondary' })` locks to plain `landscape`. Give the user a way out of a locked view.

### `Capacitor.ScreenReader`

```js
Capacitor.Plugins.ScreenReader — isEnabled(), speak({ value, language }), addListener('stateChange')
```

**Example**

Tells the page whether TalkBack is on, and speaks an announcement (`@capacitor/screen-reader` 8.0.1).

**Returns:** `isEnabled()` → Promise `{ value: boolean }`. `speak()` → Promise `void`. The `stateChange` event passes `{ value: boolean }` when TalkBack is switched on or off. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var SR = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenReader;

function setCalmMode(on) {
  document.documentElement.classList.toggle('no-motion', on);   // shorten animations
}

if (SR) {
  SR.isEnabled().then(function (r) { setCalmMode(r.value); });
  SR.addListener('stateChange', function (s) { setCalmMode(s.value); });
}

async function announceSaved() {
  if (SR) await SR.speak({ value: 'Order placed', language: 'en' });
}
```

**In an AI-built app** (`speak` uses text-to-speech on the web; `isEnabled()` is `false` there):

```ts
import { screenReader } from '@/lib/appmintNative';

const on = await screenReader.isEnabled();
const off = screenReader.onChange((enabled) => setCalmMode(enabled));
await screenReader.speak('Order placed', 'en');
```

**Notes:** `language` defaults to `'en'`. Prefer normal accessible markup (labels, `aria-live` regions) for most announcements; use `speak` for short, important state changes. There is no web API to detect a screen reader, so `can('screenReader')` is `'none'` in a browser.

### `Capacitor.Share`

```js
Capacitor.Plugins.Share — share(options), canShare()
```

**Example**

Opens the Android share sheet with a title, text and/or a link (`@capacitor/share` 8.0.1).

**Returns:** `share()` → Promise `{ activityType }` (the chosen app's component name, may be `''`); it REJECTS when the user closes the sheet. `canShare()` → Promise `{ value: boolean }`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
async function shareLink() {
  var Share = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Share;
  if (!Share) { alert('Sharing is not available here'); return; }
  // Only pass the fields you have. A present but empty `url` is rejected ("Unsupported url").
  var opts = { title: 'My list', text: 'Milk, eggs, bread' };
  var link = 'https://example.com/list/42';
  if (link) opts.url = link;
  try {
    var r = await Share.share(opts);
    console.log('shared to', r.activityType);
  } catch (e) {
    console.log('share cancelled or failed:', e.message);   // "Share canceled" on dismiss
  }
}
```

**In an AI-built app** the wrapper has the same shape and also rejects on cancel:

```ts
import { Share, can } from '@/lib/appmintNative';

if (can('share') !== 'none') {
  try {
    await Share.share({ title: 'My list', text: 'Milk, eggs, bread', url: 'https://example.com/list/42' });
  } catch {
    // the user closed the sheet — not an error to show
  }
}
```

**Notes:** `url` must be `http(s)://` or `file://`. `dialogTitle` sets the chooser heading. Sharing files needs `file://` paths the app owns; from a page, share files with `navigator.share({ files })` instead. Only one share can be open at a time ("Can't share while sharing is in progress").

### `Capacitor.SpeechRecognition`

```js
Capacitor.Plugins.SpeechRecognition
```

**Example**

Turns the user's speech into text with Android's speech recognizer (the community `@capacitor-community/speech-recognition` plugin, built into the app). Android WebView has no Web Speech recognition of its own, so this is the way to do dictation.

**Returns:** Promises. `available()` → `{ available }`. `checkPermissions()` / `requestPermissions()` → `{ speechRecognition: 'granted' | 'denied' | 'prompt' }`. `start({ partialResults: false })` → `{ status: 'success', matches: [...] }` after the user stops talking; it rejects on errors such as no speech or no match. **Needs:** tick **Mic** in Step 4 (Access) when you build (for HTML, ZIP and AI-built apps the build also adds the microphone permission by itself when your files mention `SpeechRecognition`), and the user must allow the microphone. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

One-shot dictation (the same way `@/lib/appmintNative` `speech` uses it):

```js
var SR = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.SpeechRecognition : null;

async function dictate() {
  var out = document.getElementById('text');
  if (!SR || !(await SR.available()).available) { out.textContent = 'Voice input is not available on this phone.'; return; }

  var p = (await SR.checkPermissions()).speechRecognition;
  if (p !== 'granted') p = (await SR.requestPermissions()).speechRecognition;
  if (p !== 'granted') { out.textContent = 'Microphone permission was not given.'; return; }

  try {
    var r = await SR.start({ language: 'en-US', maxResults: 3, partialResults: false, popup: false });
    out.textContent = r.matches && r.matches.length ? r.matches[0] : '';
  } catch (e) {
    out.textContent = 'Did not catch that. Try again.';
  }
}

document.getElementById('mic').addEventListener('click', dictate);
```

Live text while the user speaks (`partialResults: true`), and the listening state:

```js
// Add the listeners ONCE, when the page starts.
if (SR) {
  SR.addListener('partialResults', function (d) {
    // Words as they come, and then the final text, all arrive here.
    if (d.matches && d.matches.length) document.getElementById('text').textContent = d.matches[0];
  });
  SR.addListener('listeningState', function (d) {
    // 'started' = the user began speaking, 'stopped' = the user stopped speaking
    document.body.classList.toggle('listening', d.status === 'started');
  });
}

document.getElementById('mic-live').addEventListener('click', async function () {
  if (!SR) return;
  try {
    await SR.start({ language: 'en-US', partialResults: true, popup: false });   // resolves at once, no matches
  } catch (e) {
    document.getElementById('text').textContent = 'Could not start: ' + e.message;
  }
});

document.getElementById('stop').addEventListener('click', function () { if (SR) SR.stop(); });
```

**Notes:**
- In live mode (`partialResults: true`), the final text arrives **after** the `'stopped'` state, so do not remove the `partialResults` listener on `'stopped'`. An error after the start (no speech, no match) is not reported in this mode.
- `language` is a tag like `'en-US'` or `'ta-IN'` (default: the phone's language). `popup: true` shows Google's own listening dialog instead. `getSupportedLanguages()` → `{ languages }` and `isListening()` → `{ listening }` also exist.
- `start()` rejects with `'Missing permission'` if you did not ask first. Listening stops by itself after a pause in speech.

### `Capacitor.SplashScreen`

```js
Capacitor.Plugins.SplashScreen — show(), hide()
```

**Example**

The `@capacitor/splash-screen` API over the app's own branded launch splash, so imported Capacitor / AI Studio projects that call `SplashScreen.hide()` once their first screen is ready work in the Android app.

**Returns:** `hide()` → Promise (the splash goes once the creator's minimum splash time has passed); `show({showDuration, autoHide})` → Promise (the splash comes back over the page and, with `autoHide` - the default - goes after `showDuration` ms, default 3000). **Needs:** nothing to switch on.

```js
const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function boot() {
  await loadFirstScreen();
  if (Splash) await Splash.hide();     // otherwise the shell hides it at first paint
}
boot();
```

**Notes:** without a `hide()` call the splash still goes at the first paint, like every app. AI-built apps use `splash.hold()` / `splash.ready()` from `@/lib/appmintNative`.

### `Capacitor.StatusBar`

```js
Capacitor.Plugins.StatusBar — setStyle, setBackgroundColor, show, hide, getInfo
```

**Example**

Changes the status bar's icon style, colour and visibility (`@capacitor/status-bar` 8.0.2).

**Returns:** every method → Promise `void`, except `getInfo()` → `{ visible, style, color, overlays, height }`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

`style` names the BACKGROUND it suits: `'DARK'` = light icons (for a dark bar), `'LIGHT'` = dark icons, `'DEFAULT'` = follow the system.

```js
var StatusBar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.StatusBar;
if (StatusBar) {
  StatusBar.setStyle({ style: 'DARK' });          // light icons
  StatusBar.hide();                               // e.g. while a video plays
  // later
  StatusBar.show();
}
```

**Colour the bar with the shell bridge, not the plugin.** On Android 15+ the plugin's `setBackgroundColor` does nothing (edge-to-edge ignores it). The shell paints the strip itself and takes the icon style in the same call:

```js
if (window.WebToApk && window.WebToApk.setStatusBarColor) {
  window.WebToApk.setStatusBarColor('#0B0F1A', 'light');   // dark bar, light icons
} else if (StatusBar) {
  StatusBar.setBackgroundColor({ color: '#0B0F1A' });      // hex only, e.g. #RRGGBB
}
```

**In an AI-built app** use `setStatusBar(color, contentStyle)`; it prefers the shell bridge and sets colour and icons in one call:

```ts
import { setStatusBar, StatusBar } from '@/lib/appmintNative';

setStatusBar('#0B0F1A', 'light');                       // dark bar, light icons
await StatusBar.setBackgroundColor({ color: '#FFFFFF' }); // colour only; icons picked from luminance
```

**Notes:** `setBackgroundColor` rejects a colour that is not a hex string. In fullscreen / kiosk builds the bars are hidden by the shell. In the Android app the wrapper's `StatusBar.setStyle()` alone changes nothing (the bridge needs a colour to paint) - pass the style to `setStatusBar()` together with a colour.

### `Capacitor.TextToSpeech`

```js
Capacitor.Plugins.TextToSpeech — speak(), stop(), getSupportedLanguages(), getSupportedVoices()
```

**Example**

The community `@capacitor-community/text-to-speech` plugin over the phone's text-to-speech engine. Baked so imported Capacitor projects that call it work.

**Returns:** `speak({text, lang, rate, pitch, volume, voice})` → Promise that resolves when the sentence has been spoken; `stop()`; `getSupportedLanguages()` → `{languages}`; `getSupportedVoices()` → `{voices}`; `isLanguageSupported({lang})` → `{supported}`. **Needs:** nothing to switch on.

```js
const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function readAloud(text) {
  if (!Tts) { speechSynthesis.speak(new SpeechSynthesisUtterance(text)); return; }
  await Tts.speak({ text: text, lang: 'en-US', rate: 1.0, pitch: 1.0, volume: 1.0 });
}
document.getElementById('stop').onclick = () => Tts && Tts.stop();
```

**Notes:** `window.speechSynthesis` works in the app too (the shell's own engine) and is what AI-built apps use through `speak()`; this plugin is the Capacitor-shaped path.

### `Capacitor.TextZoom`

```js
Capacitor.Plugins.TextZoom — get(), getPreferred(), set({ value })
```

**Example**

Reads and changes the text size of the app's page, for an in-app "text size" control (`@capacitor/text-zoom` 8.0.1).

**Returns:** `get()` → Promise `{ value }` (current zoom, `1` = 100%). `getPreferred()` → `{ value }` (the size the user picked in Android settings). `set({ value })` → Promise `void`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var TZ = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextZoom;

async function biggerText() {
  if (!TZ) return;
  var now = (await TZ.get()).value;
  await TZ.set({ value: Math.min(2, now + 0.1) });
}

async function resetText() {
  if (!TZ) return;
  var pref = (await TZ.getPreferred()).value;   // e.g. 1.15 when the phone uses large text
  await TZ.set({ value: pref });
}
```

**In an AI-built app** (on the web it scales the root font size instead):

```ts
import { textZoom } from '@/lib/appmintNative';

const size = await textZoom.get();
await textZoom.set(size + 0.1);                     // clamped to 0.5–3
await textZoom.set(await textZoom.getPreferred());  // back to the system size
```

**Notes:** The app already follows the system text size; this is for an extra in-app control. Test your layout at large sizes - fixed heights on text controls will clip.

### `Capacitor.Toast`

```js
Capacitor.Plugins.Toast — show({ text, duration, position })
```

**Example**

Shows a short Android system toast such as "Saved" or "Copied" (`@capacitor/toast` 8.0.1).

**Returns:** Promise `void`. **Needs:** nothing to switch on - every app hosts the plugin. `window.Capacitor` exists only on pages the app serves itself (HTML, ZIP and AI-built apps). A **Website** mode app loads a remote site, and that site never gets `window.Capacitor` - feature-detect.

```js
var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;

function saved() {
  if (Toast) Toast.show({ text: 'Saved', duration: 'short', position: 'bottom' });
}
```

`duration` is `'short'` (about 2 s) or `'long'` (about 3.5 s). `position` is `'top'`, `'center'` or `'bottom'`.

**In an AI-built app** use `toast()` or the Capacitor-shaped `Toast`; on the web they draw a snackbar in the app's colours:

```ts
import { Toast, toast } from '@/lib/appmintNative';

await Toast.show({ text: 'Added to list', duration: 'long' });
toast('Copied');   // short form, same result
```

**Notes:** `text` is required ("Must provide text"). The system draws the toast in the phone maker's style, and newer Android versions ignore `position` for text toasts. A toast is easy to miss - never use it for an error the user must act on; use `Dialog.alert` for that.

### `Capacitor.Torch`

```js
Capacitor.Plugins.Torch — enable(), disable(), toggle(), isEnabled(), isAvailable()
```

**Example**

The phone's flashlight, with the `@capawesome/capacitor-torch` API. The app shell's own plugin (`TorchPlugin.kt`).

**Returns:** `enable()` / `disable()` / `toggle()` → Promise (rejects `CAMERA_IN_USE` while the app's own camera stream is open, `UNAVAILABLE` without a flash); `isEnabled()` → `{enabled}`; `isAvailable()` → `{available}`. **Needs:** nothing to switch on and no permission.

```js
const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;

async function setupLightButton() {
  if (!Torch || !(await Torch.isAvailable()).available) { lightBtn.hidden = true; return; }
  lightBtn.onclick = async () => {
    try {
      await Torch.toggle();
      lightBtn.classList.toggle('on', (await Torch.isEnabled()).enabled);
    } catch (e) {
      alert(e.message);
    }
  };
}
```

**Notes:** the light follows the system state (turning it off from the quick-settings tile reads back as off) and is switched off when the app closes. While a `getUserMedia` stream is open, light it through the track: `track.applyConstraints({ advanced: [{ torch: true }] })`. AI-built apps use `torch` from `@/lib/appmintNative`.

