[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M699MPAW0W1W8QDAH9DXJFEF":3,"skill-same:01M699MPAW0W1W8QDAH9DXJFEF":79},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":48,"linkOnly":49,"markdownContent":50,"markdownBody":51,"frontMatter":52,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":59,"repositoryHtmlUrl":60,"repositoryHomepageUrl":61,"ownerHtmlUrl":62,"ownerBlogUrl":63,"ownerDescription":64,"updatedAt":65,"install":66,"resources":78},"01M699MPAW0W1W8QDAH9DXJFEF","add-native",null,"Public entry point for native device capabilities and native controls — camera, image picker, barcode\u002FQR scanner, document picker, file picker, secure storage, file system, sharing, haptic feedback, PDF generation\u002Fviewing, pen\u002Fsignature capture, background GPS\u002Fgeolocation tracking, or supported local file workflows — in a Power Apps mobile app. Also owns routing to internal camera\u002FPDF\u002Fpen\u002Fgeolocation implementation helpers and the guidance boundary between native wrappers and Dataverse File\u002FImage host controls.","microsoft\u002Fpower-platform-skills","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M699JVYJFB75D9JM70BN4H0Q",1146,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fmobile-apps\u002Fskills\u002Fadd-native\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000002","文档处理","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"native","add","add native","read","qr","pdf generation","apps","host","app","local","scanner","power","PDF","Bash","capabilities","secure","power apps","dataverse","workflows","storage","image","2026-11-10 10:12:30",false,"---\nname: add-native\ndescription: Public entry point for native device capabilities and native controls — camera, image picker, barcode\u002FQR scanner, document picker, file picker, secure storage, file system, sharing, haptic feedback, PDF generation\u002Fviewing, pen\u002Fsignature capture, background GPS\u002Fgeolocation tracking, or supported local file workflows — in a Power Apps mobile app. Also owns routing to internal camera\u002FPDF\u002Fpen\u002Fgeolocation implementation helpers and the guidance boundary between native wrappers and Dataverse File\u002FImage host controls.\nuser-invocable: true\nallowed-tools: Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, Skill\nmodel: sonnet\n---\n\n> **Plugin check**: Run `node \"${PLUGIN_ROOT}\u002Fscripts\u002Fcheck-version.js\"` - if it outputs a message, show it to the user before proceeding.\n\n**? Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}\u002Fshared\u002Fshared-instructions.md)** — read first.\n\n**Working directory:** before any project read or command, execute\n[native-artifact-compatibility.md Step 0](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fnative-artifact-compatibility.md#0-bind-every-operation-to-the-app-root).\nResolve `--working-dir` \u002F owner context there; use the resolved absolute root for\nevery shell call and file tool, not a previous shell's cwd.\n\n# Add Native Capability\n\n**Entry routing:** use the shared [App feature entry points](..\u002F..\u002Fshared\u002Fshared-instructions.md#app-feature-entry-points)\npreflight before the workflow below.\n\nThe leaf generates typed wrappers under `src\u002Fnative\u002F` for native capabilities that\nthe upstream template already ships. Screens import the wrapper instead of touching\nExpo modules directly, so the discriminated-union result contract stays consistent.\n\n## Hard rules — do NOT cross these lines\n\n1. **Never run `npx expo install`, `npm install`, or `yarn add` for a native module.** The set of native modules in `package.json` is fixed by the upstream template. Adding a new one breaks the rewrap pipeline (the customer's binary is built from a pre-built base, not from their `package.json`).\n2. **Never edit `app.config.js`** — plugins, `ios.infoPlist`, `android.permissions`, or anything else. All native config the template ships is intentional and signed off; arbitrary additions cannot be honored at rewrap time.\n3. **Never edit `package.json` `dependencies` for native modules.** Native means the package ships platform source\u002Fprojects, a podspec, codegen, an Expo module\u002Fconfig plugin, or `react-native.config.js`; a package-name prefix alone is not proof. Pure-JavaScript dependencies are out of scope for `\u002Fadd-native` and are installed from an approved `JavaScript Dependencies` plan by `\u002Fcreate-mobile-app` or `\u002Fedit-app`.\n4. **If the requested module isn't actually present in `package.json` — STOP.** That means the upstream template hasn't shipped it yet; do not work around by installing it.\n\n## Routing — `\u002Fadd-native` is the public entry point\n\nSome capabilities have a dedicated implementation helper that does more than a plain wrapper (camera writes scanner\u002Fupload helpers; PDF report\u002Fviewer helpers enforce local-vs-HTTPS boundaries; pen has native-control-specific validation). Users should still call `\u002Fadd-native \u003Ccapability>` for native controls. When a dedicated implementation exists, **run it internally and do not ask the user to run that helper directly**.\n\n**Lookup convention:** after normalizing the capability, first check the internal-helper map below. For `camera`, `image-picker`, `barcode-scanner`, and `qr-scanner`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-camera\u002FSKILL.md` inside this `\u002Fadd-native` invocation. For `pdf-report`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-report\u002FSKILL.md`. For `pdf-viewer`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-viewer\u002FSKILL.md`. For `pen-input`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pen-input\u002FSKILL.md`. For `geolocation`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-geolocation\u002FSKILL.md`. If no helper exists, fall through to this skill's inline wrapper flow.\n\nCurrent dedicated implementations:\n\n| Capability | Dedicated skill | Why dedicated |\n|---|---|---|\n| `camera`, `take-photo`, `photo`, `expo-camera`, `image-picker`, `gallery`, `expo-image-picker`, `barcode-scanner`, `qr-scanner`, `scanner` | [`add-camera`](add-camera\u002FSKILL.md) internal helper | Owns photo capture, gallery image picking, and barcode\u002FQR scanner controls backed by `expo-camera` \u002F `expo-image-picker` |\n| `pdf-report`, `pdf-export`, `generate-pdf`, `print-report`, `evidence-packet` | [`add-pdf-report`](add-pdf-report\u002FSKILL.md) internal helper | Generates app-owned local PDFs with `expo-print` and shares them only when `expo-sharing` is present |\n| `pdf-viewer`, `native-pdf-viewer`, `pdf-control`, `open-pdf`, `@microsoft\u002Fpower-apps-native-pdf-viewer` | [`add-pdf-viewer`](add-pdf-viewer\u002FSKILL.md) internal helper | Enforces `https:\u002F\u002F` \u002F `file:\u002F\u002F` viewer inputs and native viewer result handling |\n| `pen-input`, `signature`, `ink`, `draw`, `@microsoft\u002Fpower-apps-native-pen-input` | [`add-pen-input`](add-pen-input\u002FSKILL.md) internal helper | Captures PNG data URI and documents Dataverse Image\u002FFile persistence |\n| `geolocation`, `location-tracking`, `background-location`, `gps-tracking`, `geo-tracking`, `@microsoft\u002Fpower-apps-native-bglocation` | [`add-geolocation`](add-geolocation\u002FSKILL.md) internal helper | Native background GPS tracking with durable storage and inline Dataverse sync; distinct from one-shot `expo-location` |\n\nFor every other capability listed below, this skill writes the wrapper directly.\n\n## Native capability gate\n\nBefore adding any native control or wrapper, apply every gate: classify the intent, resolve the exact package\u002Fcontrol from the live `package.json`, confirm it is not runtime-banned, confirm the input\u002Foutput\u002Fstorage constraints, then use the matching route below. If any gate fails, the native functionality is not supported for this app version — do not install packages, edit native config, or create fake wrappers.\n\n| User intent | Add\u002Fuse | Required package or control | Do not use \u002F fallback |\n|---|---|---|---|\n| Form field bound to a Dataverse File column | Host `\u003CFilePicker>` in screen JSX | `@microsoft\u002Fpower-apps-native-host` host control | Do not generate document-picker\u002Ffile-system\u002Fsharing wrappers for this field |\n| Form field bound to a Dataverse Image column | Host `\u003CImagePicker>` in screen JSX | `@microsoft\u002Fpower-apps-native-host` host control | Do not use camera\u002Fimage-picker wrappers for normal form-bound image fields |\n| Dedicated photo\u002Fgallery\u002Fscanner workflow | `\u002Fadd-native camera`, `image-picker`, or `barcode-scanner` | `expo-image-picker` for photo\u002Fgallery; `expo-camera` for scanner; both for combined requests | If packages are absent, stop with missing-package guidance |\n| Pick\u002Fimport\u002Fupload a user-selected PDF\u002Fdocument | `\u002Fadd-native document-picker`, or host `\u003CFilePicker>` for Dataverse File fields | `expo-document-picker` present, or host File control | Do not treat this as `pdf-report` or native PDF viewer |\n| Generate\u002Fexport\u002Fprint an app-owned report PDF | `\u002Fadd-native pdf-report` | `expo-print` present | If `expo-print` is absent, do not add PDF report capability |\n| Share a generated local PDF | `pdfReport.ts` share helper | `expo-sharing` present | If `expo-sharing` is absent, do not render sharing UI |\n| Open\u002Fpreview an HTTPS or local file PDF | `\u002Fadd-native pdf-viewer` | `@microsoft\u002Fpower-apps-native-pdf-viewer` 0.2.9+ present and input is `https:\u002F\u002F` or `file:\u002F\u002F` | Do not pass `content:\u002F\u002F`, `blob:`, or `http:\u002F\u002F` URIs to the viewer |\n| Capture signature, ink, drawing, or sign-off | `\u002Fadd-native pen-input` | `@microsoft\u002Fpower-apps-native-pen-input` present | If persisted, plan Dataverse Image\u002FFile\u002Fchild Evidence target first |\n| Continuous\u002Fbackground GPS tracking with durable Dataverse upload | `\u002Fadd-native geolocation` | `@microsoft\u002Fpower-apps-native-bglocation` present | Do not use one-shot `expo-location` for background tracking; do not use the `GeolocationExtension`\u002FHostingSDK path |\n| Tactile feedback for presses, selections, and operation results | `\u002Fadd-native haptics` | `expo-haptics` present | Use the typed wrapper and pair every haptic with visible UI feedback |\n| Store generated PDF\u002Fsignature artifact | Generated Dataverse services after parent row exists | File\u002FImage column or child Evidence\u002FAttachment table exists | Never put File bytes in create\u002Fupdate JSON |\n| Native capability not listed in this table | Resolve from `package.json`, then add an inline wrapper only when the matching package is present and not runtime-banned | Exact relevant package present in `package.json` | If no relevant package exists, or the package is runtime-banned, add a transparency note and stop |\n\nHandle multi-part requests row-by-row. Example: \"capture signature and attach signed report PDF\" requires `pen-input`, `pdf-report`, Dataverse artifact storage, and possibly `sharing`; do not add only the native capability while leaving storage or screen states undefined. The map is not closed: for new shipped packages, resolve by capability semantics, use the directly matching package when safe, and ask once only if multiple installed packages plausibly match.\n\n## File\u002FImage Picker Ownership\n\nThis skill owns native file capability mechanics. Screen builders own only the final JSX placement.\n\nThere are two different cases:\n\n| Case | Correct implementation | Owner |\n|---|---|---|\n| Dataverse `File` column | Use `\u003CFilePicker>` from `@microsoft\u002Fpower-apps-native-host` | screen-builder JSX rule, documented here |\n| Dataverse `Image` column | Use `\u003CImagePicker>` from `@microsoft\u002Fpower-apps-native-host` | screen-builder JSX rule, documented here |\n| Local device document\u002Ffile workflow not bound to a Dataverse column | Generate\u002Fuse `src\u002Fnative\u002FdocumentPicker.ts`, `src\u002Fnative\u002FfileSystem.ts`, and\u002For `src\u002Fnative\u002Fsharing.ts` wrappers | `\u002Fadd-native` |\n| App-generated PDF report workflow | `\u002Fadd-native` routes internally to `add-pdf-report`; uses `expo-print` and optionally `expo-sharing` only if present | `\u002Fadd-native` |\n| Camera capture, gallery image selection, barcode scanner, or QR scanner workflow | `\u002Fadd-native` routes internally to `add-camera` | `\u002Fadd-native` |\n\n**Dataverse File\u002FImage columns use host controls, not raw Expo modules and not app-specific native wrappers.** When a form field binds to a Dataverse **File** column (`FileAttributeMetadata`), render `\u003CFilePicker>` from `@microsoft\u002Fpower-apps-native-host`. When it binds to a Dataverse **Image** column (`ImageAttributeMetadata`), render `\u003CImagePicker>` from `@microsoft\u002Fpower-apps-native-host`. These controls read accent, surface, and text colors from `PowerAppsProvider` \u002F `ThemeProvider` and produce Dataverse-compatible payloads.\n\n```tsx\nimport { FilePicker, ImagePicker } from '@microsoft\u002Fpower-apps-native-host';\nimport type { PickedFileInfo, PickedImageInfo } from '@microsoft\u002Fpower-apps-native-host';\n\n\u002F\u002F Image column — seed preview from Dataverse bytes\u002Fbase64 and capture upload-ready payload.\n\u003CImagePicker\n  label=\"Site Photo\"\n  initialBase64={imageBase64}\n  onImageChange={(image: PickedImageInfo | null) => { \u002F* store image for upload *\u002F }}\n\u002F>\n\n\u002F\u002F File column — edit-only pick\u002Freplace.\n\u003CFilePicker\n  label=\"Specification Sheet\"\n  fileName={record.cr123_specsheet_name}\n  onChange={(file: PickedFileInfo) => { \u002F* store file for save *\u002F }}\n\u002F>\n\n\u002F\u002F File column — view + replace.\n\u003CFilePicker\n  label=\"Specification Sheet\"\n  fileName={record.cr123_specsheet_name}\n  onDownload={() => Cr123_inspectionService.downloadFile(id, 'cr123_specsheet')}\n  onChange={(file: PickedFileInfo) => { \u002F* store file for save *\u002F }}\n\u002F>\n```\n\nSave\u002Fread pattern for host picker-bound Dataverse columns:\n\n```ts\n\u002F\u002F FILE\u002FIMAGE columns: do not include picker bytes in the PATCH body.\n\u002F\u002F Save normal fields first, then upload through generated service helpers.\nawait Cr123_inspectionService.update(id, { cr123_name: name.trim() });\nawait Cr123_inspectionService.upload(id, 'cr123_specsheet', file.file, file.name);\nawait Cr123_inspectionService.upload(id, 'cr123_sitephoto', image.file, image.name);\n\n\u002F\u002F Download helpers are used for read\u002Fview scenarios.\nconst fileBytes = await Cr123_inspectionService.downloadFile(id, 'cr123_specsheet');\nconst imageBytes = await Cr123_inspectionService.downloadImage(id, 'cr123_sitephoto');\n```\n\nUse host `ImagePicker` \u002F `FilePicker` with generated `upload(...)` for persistence and `downloadFile(...)` \u002F `downloadImage(...)` for read\u002Fview. Do not use `Service.update(...{ \u003CimageColumn>: base64 })` for picker-driven image\u002Ffile persistence.\n\nDo not use raw `expo-document-picker`, `expo-image-picker`, `expo-file-system`, or `expo-sharing` to build custom UI for Dataverse File\u002FImage columns. Host `FilePicker` and `ImagePicker` already handle permissions, reading, sharing\u002Fdownload affordances, and Dataverse-compatible payload shape.\n\nUse `\u002Fadd-native` wrappers only when the workflow is not a Dataverse File\u002FImage column, for example importing a local PDF for offline parsing, exporting a generated report, saving a local draft packet, or opening a share sheet for an app-generated summary.\n\n## Supported capabilities\n\nApply the Native capability gate above. This table is a known capability-to-package map, not a guarantee that every listed package exists in every template version.\n\n| Capability | Module | Wrapper to generate | Notes |\n|---|---|---|---|\n| `camera`, `take-photo`, `photo`, `expo-camera` | `expo-image-picker` | `src\u002Fnative\u002Fcamera.ts` | `\u002Fadd-native` routes internally to `add-camera` for the `photo` artifact; `expo-camera` is a capability alias, not its required package |\n| `image-picker`, `gallery`, `expo-image-picker` | `expo-image-picker` | `src\u002Fnative\u002Fcamera.ts` (`pickImage`) | `\u002Fadd-native` routes internally to `add-camera`; preserve compatible existing re-exports |\n| `barcode-scanner`, `qr-scanner`, `scanner`, `barcode`, `qr` | `expo-camera` | `src\u002Fnative\u002FbarcodeScanner.tsx` | `\u002Fadd-native` routes internally to `add-camera` |\n| `document-picker` | `expo-document-picker` | `src\u002Fnative\u002FdocumentPicker.ts` | Picks\u002Fimports user-selected files (PDF, docs, etc.) from the device |\n| `pdf-viewer`, `native-pdf-viewer`, `pdf-control`, `open-pdf`, `@microsoft\u002Fpower-apps-native-pdf-viewer` | `@microsoft\u002Fpower-apps-native-pdf-viewer` | `src\u002Fnative\u002FpdfViewer.ts` | `\u002Fadd-native` routes internally to `add-pdf-viewer`; 0.2.9+ opens HTTPS URLs and file URIs |\n| `pdf-report`, `pdf-export`, `generate-pdf`, `print-report`, `evidence-packet` | `expo-print` (+ optional `expo-sharing`) | `src\u002Fnative\u002FpdfReport.ts` | `\u002Fadd-native` routes internally to `add-pdf-report`; generated local files are shared only when `expo-sharing` is present, or uploaded to Dataverse |\n| `pen-input`, `signature`, `ink`, `draw`, `@microsoft\u002Fpower-apps-native-pen-input` | `@microsoft\u002Fpower-apps-native-pen-input` | `src\u002Fnative\u002FpenInput.ts` | `\u002Fadd-native` routes internally to `add-pen-input`; captures PNG data URI |\n| `geolocation`, `location-tracking`, `background-location`, `gps-tracking`, `geo-tracking`, `@microsoft\u002Fpower-apps-native-bglocation` | `@microsoft\u002Fpower-apps-native-bglocation` | `src\u002Fnative\u002Fgeolocation.ts` | `\u002Fadd-native` routes internally to `add-geolocation`; native background tracking + durable Dataverse sync. Distinct from one-shot `location` below |\n| `secure-store` | `expo-secure-store` | `src\u002Fnative\u002FsecureStore.ts` | |\n| `file-system` | `expo-file-system` | `src\u002Fnative\u002FfileSystem.ts` | |\n| `sharing` | `expo-sharing` | `src\u002Fnative\u002Fsharing.ts` | |\n| `location` | `expo-location` | `src\u002Fnative\u002Flocation.ts` | One-shot\u002Fforeground fix only. For continuous background tracking with Dataverse sync, use `geolocation` (`@microsoft\u002Fpower-apps-native-bglocation`). Use only when the current template package contains `expo-location` |\n| `biometrics`, `local-authentication` | `expo-local-authentication` | `src\u002Fnative\u002Fbiometrics.ts` | Use only when the current template package contains `expo-local-authentication` |\n| `clipboard` | `expo-clipboard` | `src\u002Fnative\u002Fclipboard.ts` | Use only when the current template package contains `expo-clipboard` |\n| `mail-composer`, `email-draft` | `expo-mail-composer` | `src\u002Fnative\u002FmailComposer.ts` | Opens native mail compose when the package is present; connectors still own server-side email sends |\n| `audio` | `expo-audio` | `src\u002Fnative\u002Faudio.ts` | Use for audio recording\u002Fplayback only when package is present |\n| `video` | `expo-video` | `src\u002Fnative\u002Fvideo.ts` | Use for video playback only when package is present |\n| `sensors` | `expo-sensors` | `src\u002Fnative\u002Fsensors.ts` | Use only for sensor APIs exposed by the installed package |\n| `screen-orientation` | `expo-screen-orientation` | `src\u002Fnative\u002FscreenOrientation.ts` | Use only when package is present; do not edit native config |\n| `haptics`, `vibration-feedback`, `impact-feedback`, `selection-feedback`, `notification-feedback` | `expo-haptics` | `src\u002Fnative\u002Fhaptics.ts` | Impact, selection, and success\u002Fwarning\u002Ferror notification feedback |\n| `device-info` | `expo-device` \u002F `expo-application` \u002F `expo-cellular` | `src\u002Fnative\u002FdeviceInfo.ts` | Read-only device\u002Fapp\u002Fcellular metadata wrappers |\n| `date-time-picker` | `@react-native-community\u002Fdatetimepicker` | screen-level component usage | Use directly in form screens per screen-builder rules; no `\u002Fadd-native` wrapper required |\n\nFor custom workflows outside Dataverse File\u002FImage form fields, use the `image-picker` capability via `\u002Fadd-native image-picker` for user-selected photos and videos, or the `document-picker` capability via `\u002Fadd-native document-picker` for documents and other files. These wrappers use scoped system-picker flows. Dataverse File\u002FImage form fields remain the exception: use host `\u003CFilePicker>` \u002F `\u003CImagePicker>` controls as described above, not `\u002Fadd-native` wrappers or raw Expo module imports.\n\n### PDF \u002F pen routing rules\n\n- Do not treat every PDF request as `document-picker`.\n- Use `document-picker` when the user wants to pick, import, or upload a local PDF\u002Fdocument. This remains supported and should still be used for that use case.\n- Use `pdf-report` when the app generates a PDF from records, evidence, inspection data, certificates, receipts, or reports, but only if `expo-print` is present in `package.json`.\n- Use `native-pdf-viewer` \u002F `pdf-control` when the app opens\u002Fpreviews an HTTPS PDF URL or local `file:\u002F\u002F` URI with `@microsoft\u002Fpower-apps-native-pdf-viewer` 0.2.9+. `content:\u002F\u002F`, `blob:`, and `http:\u002F\u002F` URIs are unsupported.\n- If a request says \"view\u002Fopen PDF\" but the Power Apps viewer package is absent, fall back to `pdf-report` only when the app is generating its own report and `expo-print` is present. Do not claim generic PDF viewing support through `expo-print`; it generates local files, it does not view arbitrary PDFs.\n- Use `pen-input` only for signatures, drawn approvals, ink notes, sketches, and handwritten sign-off with `@microsoft\u002Fpower-apps-native-pen-input`.\n- For other use cases, use the relevant Expo module or other dependency already present in `package.json`; do not force the Power Apps extensions into unrelated flows.\n- For generated local PDFs from `expo-print`, use native PDF viewer 0.2.9+ for open\u002Fpreview, `expo-sharing` for sharing, and Dataverse File storage for retention. Do not require `expo-sharing` merely to preview a local PDF.\n- Host `FilePicker` and `ImagePicker` are still correct for user-selected Dataverse File\u002FImage form fields. Generated PDFs and pen captures use native wrappers first, then Dataverse persistence helpers.\n\n### Dataverse artifact persistence rules\n\n- Pen input returns a PNG data URI like `data:image\u002Fpng;base64,...`.\n- For Dataverse Image columns, normalize the data URI to the generated service payload shape. If raw base64 is required, strip the `data:image\u002Fpng;base64,` prefix.\n- For Dataverse File columns, convert the generated PDF or signature PNG into upload bytes\u002FFile, save or update the parent row first, then upload through the generated service helper.\n- Never put File column bytes in the create\u002Fupdate JSON body. File bytes are uploaded only after the parent row ID exists.\n- Screens must handle unsupported, cancelled, upload failed, and viewer failed states explicitly. Pen cancellation is a non-error result that screens can ignore.\n\n**Missing or gated packages:** `package.json` plus the runtime gate is authoritative. If the relevant package\u002Fcontrol is absent, stop with a transparency note.\n\n## Workflow\n\n1. Verify project → 2. Resolve capability → 3. Auto-route to dedicated skill if one exists → 4. Verify module is template-shipped → 5. Write wrapper → 6. Type-check → 7. Summary\n\n---\n\n### Step 1 — Verify project\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\ntest -f app.config.js && test -f power.config.json && test -f package.json || { echo \"BLOCKED: working_dir is not an initialized app\" >&2; exit 1; }\n```\n\n### Step 2 — Resolve capability\n\n**Telemetry checkpoint: `resolve_native_capability`**\n\nIf `$ARGUMENTS` includes a capability name, package name, or control name, use it. Otherwise look for a `## Native Capabilities` section in `native-app-plan.md` and present the planned capabilities for confirmation. If neither exists, prompt the user with the supported-capabilities list above plus any relevant installed package from `package.json` that directly matches their request.\n\nNormalize the capability name to lowercase, hyphenated form (e.g., `Camera` → `camera`, `ImagePicker` → `image-picker`, `SecureStore` → `secure-store`). Also normalize aliases: `take-photo` \u002F `photo` \u002F `camera-control` \u002F `expo-camera` → `camera`; `gallery` \u002F `pick-image` \u002F `expo-image-picker` → `image-picker`; `scanner` \u002F `barcode` \u002F `qr` → `barcode-scanner`; `open-pdf` \u002F `view-pdf` \u002F `pdf-control` \u002F `pdf-viewer-control` \u002F `@microsoft\u002Fpower-apps-native-pdf-viewer` → `pdf-viewer`; `native-pdf-viewer` → `pdf-viewer`; `generate-pdf` \u002F `pdf-export` → `pdf-report`; `signature` \u002F `sign-off` \u002F `ink` \u002F `draw` \u002F `pen-control` \u002F `@microsoft\u002Fpower-apps-native-pen-input` → `pen-input`; `location-tracking` \u002F `background-location` \u002F `gps-tracking` \u002F `geo-tracking` \u002F `track-location` \u002F `power-apps-native-bglocation` \u002F `@microsoft\u002Fpower-apps-native-bglocation` → `geolocation`; `vibration` \u002F `vibration-feedback` \u002F `impact-feedback` \u002F `selection-feedback` \u002F `notification-feedback` \u002F `expo-haptics` → `haptics`.\n\nWhen the user asks for \"location\" or \"GPS\", disambiguate by intent: continuous\u002Fbackground tracking or durable Dataverse upload → `geolocation` (`@microsoft\u002Fpower-apps-native-bglocation`); a single foreground coordinate read → `location` (`expo-location`). If the intent is unclear, ask once before routing.\n\nIf the user names something not in the supported table, apply the Native capability gate: resolve the relevant package from `package.json`, continue only when present and not runtime-banned, otherwise stop with a transparency note.\n\n### Step 3 — Route to nested helpers or inline wrappers\n\n**Telemetry checkpoint: `dispatch_native_capability`**\n\nFor normalized `camera`, `image-picker`, `barcode-scanner`, `qr-scanner`, `pdf-report`, `pdf-viewer`, `pen-input`, or `geolocation`, do not fall through to the generic wrapper flow and do not tell the user to run another slash command. Read the nested helper and follow its steps inside this `\u002Fadd-native` invocation:\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\ncase \"\u003Ccapability>\" in\n  camera|image-picker|barcode-scanner|qr-scanner) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-camera\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-camera\" ;;\n  pdf-report) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-report\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-pdf-report\" ;;\n  pdf-viewer) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-viewer\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-pdf-viewer\" ;;\n  pen-input) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pen-input\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-pen-input\" ;;\n  geolocation) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-geolocation\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-geolocation\" ;;\n  *) echo \"INLINE\" ;;\nesac\n```\n\n- **INTERNAL_HELPER:** read the printed helper file and execute its workflow with\n  the same absolute `working_dir`, arguments, supplied answers, mode flags, and\n  current owner\u002Fphase\u002F`approved_scope`. The helper must bind every operation to\n  that root using Step 0, even if launched from another directory. Each helper must execute\n  [native-artifact-compatibility.md](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fnative-artifact-compatibility.md)\n  before its writes\u002Freuse and before its success summary; outer Step 5 is not\n  reached on this branch. Propagate `NEEDS_CONTEXT`\u002F`BLOCKED` and the artifact\n  result to the owner, then STOP this leaf, not the owning workflow. Do not\n  fall through to inline generation or report success for a partial helper result.\n  `\u002Fadd-native` remains the only user-facing command for these controls.\n- **INLINE:** continue to Step 4.\n\n### Step 4 — Verify module is template-shipped\n\nConfirm the underlying native-capability package is actually present in the project's `package.json` (catches the case where the user hand-removed it or the template version is older than expected):\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\nnode -e \"const p = require('.\u002Fpackage.json'); const m = '\u003Cexpo-module-name>'; if (!p.dependencies?.[m]) { console.error('MISSING: ' + m + ' is not in package.json. The template should ship it. Re-scaffold via \u002Fcreate-mobile-app, restore it from upstream, or wait for the template release that adds it — this skill will not install it.'); process.exit(1); }\"\n```\n\nIf the check fails, STOP. Do not run `npx expo install`. Print the error verbatim.\n\n### Step 5 — Write wrapper\n\n**Telemetry checkpoint: `generate_native_wrapper`**\n\n**Print before starting:**\n> \"→ Writing src\u002Fnative\u002F\u003Cwrapper>.ts (typed wrapper with discriminated-union result + iOS\u002FAndroid platform guards)…\"\n\nRead and execute [native-artifact-compatibility.md](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fnative-artifact-compatibility.md)\nSteps 1–3 for the inline capability row before writing or reusing output.\nCreate `src\u002Fnative\u002F\u003Cwrapper-filename>.ts` only when missing and in scope. If the\nfile already exists, inspect its exports against the approved capability\ncontract, including behavior and storage. Reuse it unchanged when compatible;\nmake a scoped update without re-asking when already approved. Otherwise return\n`NEEDS_CONTEXT` to the owner (or ask standalone) before updating; never silently\nskip the feature or overwrite custom code.\n\nEach wrapper exports:\n\n- `request\u003CCapability>Permission(): Promise\u003Cboolean>` — only when the underlying API has a permission model (location, biometrics, notifications). Skip for capabilities without one (secure-store, file-system, sharing, clipboard).\n- One or two domain methods returning typed results.\n\n**The contract `screen-builder` agents rely on:**\n- All wrappers return a discriminated-union result (`{ ok: true, ... } | { ok: false, reason, message? }`) — **never throw**\n- Unsupported runtime\u002Fplatform states gracefully degrade or return `{ ok: false, reason: 'unsupported' }` — **never crash**\n- Branch by supported native platform when a capability differs between iOS and Android\n- Screens import these wrappers only for non-Dataverse native workflows. Dataverse File\u002FImage fields use `@microsoft\u002Fpower-apps-native-host` controls from the File\u002FImage Picker Ownership section above.\n\n#### Haptics wrapper\n\nFor normalized capability `haptics`, generate `src\u002Fnative\u002Fhaptics.ts` with this contract:\n\n```ts\nimport * as Haptics from 'expo-haptics';\nimport { Platform } from 'react-native';\n\nexport type HapticImpactStyle = 'light' | 'medium' | 'heavy' | 'soft' | 'rigid';\nexport type HapticNotificationType = 'success' | 'warning' | 'error';\nexport type HapticResult =\n  | { ok: true }\n  | { ok: false; reason: 'unsupported' | 'error'; message: string };\n\nconst impactStyles: Record\u003CHapticImpactStyle, Haptics.ImpactFeedbackStyle> = {\n  light: Haptics.ImpactFeedbackStyle.Light,\n  medium: Haptics.ImpactFeedbackStyle.Medium,\n  heavy: Haptics.ImpactFeedbackStyle.Heavy,\n  soft: Haptics.ImpactFeedbackStyle.Soft,\n  rigid: Haptics.ImpactFeedbackStyle.Rigid,\n};\n\nconst notificationTypes: Record\u003CHapticNotificationType, Haptics.NotificationFeedbackType> = {\n  success: Haptics.NotificationFeedbackType.Success,\n  warning: Haptics.NotificationFeedbackType.Warning,\n  error: Haptics.NotificationFeedbackType.Error,\n};\n\nasync function runHaptic(effect: () => Promise\u003Cvoid>): Promise\u003CHapticResult> {\n  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {\n    return {\n      ok: false,\n      reason: 'unsupported',\n      message: 'Haptic feedback is available only on iOS and Android.',\n    };\n  }\n\n  try {\n    await effect();\n    return { ok: true };\n  } catch (error) {\n    return {\n      ok: false,\n      reason: 'error',\n      message: error instanceof Error ? error.message : 'The native haptic call failed.',\n    };\n  }\n}\n\nexport function triggerImpact(style: HapticImpactStyle = 'medium'): Promise\u003CHapticResult> {\n  return runHaptic(() => Haptics.impactAsync(impactStyles[style]));\n}\n\nexport function triggerNotification(\n  type: HapticNotificationType = 'success'\n): Promise\u003CHapticResult> {\n  return runHaptic(() => Haptics.notificationAsync(notificationTypes[type]));\n}\n\nexport function triggerSelection(): Promise\u003CHapticResult> {\n  return runHaptic(() => Haptics.selectionAsync());\n}\n```\n\nScreens call the wrapper only when haptics appear in the approved native-capabilities and per-screen plan. Choose feedback by semantics:\n\n- `triggerImpact('light' | 'medium' | 'heavy' | 'soft' | 'rigid')` for deliberate presses or physical-feeling actions.\n- `triggerSelection()` when a picker, segmented control, or other selection changes.\n- `triggerNotification('success' | 'warning' | 'error')` after the corresponding outcome is known.\n- Keep visual feedback as the primary signal. Inspect non-OK results and log them, but do not turn an otherwise successful business operation into a failure because tactile feedback is unavailable.\n\nScreen usage after a successful operation:\n\n```ts\nsetSaveState({ kind: 'success', message: 'Saved' });\nconst hapticResult = await triggerNotification('success');\nif (!hapticResult.ok) {\n  console.warn('[haptics] success feedback unavailable', hapticResult);\n}\n```\n\nNo permission request or `app.config.js` change is required for `expo-haptics`.\n\n#### Other inline wrappers\n\nConsult the module's published API docs (linked from its npm page) for method signatures and permission patterns. Use the secure-store skeleton below as the canonical example of the discriminated-union shape, then translate it to the target module's API.\n\nSecure-store canonical skeleton:\n\n```ts\n\u002F\u002F src\u002Fnative\u002FsecureStore.ts\nimport * as SecureStore from 'expo-secure-store';\nimport { Platform } from 'react-native';\n\nexport type SecureResult\u003CT> =\n  | { ok: true; value: T }\n  | { ok: false; reason: 'unsupported' | 'not-found' | 'error'; message?: string };\n\nexport async function getSecret(key: string): Promise\u003CSecureResult\u003Cstring>> {\n  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {\n    return { ok: false, reason: 'unsupported', message: 'SecureStore is not available on this platform.' };\n  }\n  try {\n    const value = await SecureStore.getItemAsync(key);\n    if (value === null) return { ok: false, reason: 'not-found' };\n    return { ok: true, value };\n  } catch (e: any) {\n    return { ok: false, reason: 'error', message: e?.message };\n  }\n}\n\nexport async function setSecret(key: string, value: string): Promise\u003CSecureResult\u003Ctrue>> {\n  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {\n    return { ok: false, reason: 'unsupported', message: 'SecureStore is not available on this platform.' };\n  }\n  try {\n    await SecureStore.setItemAsync(key, value);\n    return { ok: true, value: true };\n  } catch (e: any) {\n    return { ok: false, reason: 'error', message: e?.message };\n  }\n}\n```\n\n### Step 6 — Type-check\n\n**Telemetry checkpoint: `validate_native_wrapper`**\n\n**Print before starting:**\n> \"→ Running tsc to verify wrapper compiles (~10–20 seconds).\"\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\nnpx --no-install tsc --noEmit\n```\n\nFix only in-scope wrapper-side errors. Execute the shared compatibility contract's\nStep 4 after type-checking: recheck all requested artifacts, API\u002Fbehavior, and\nstorage obligations before returning success. Type-check success alone is\ninsufficient. Do not run platform-specific native build commands during normal\n`\u002Fadd-native` use because the module is already part of the current template binary.\nWhen a template\u002Fbase maintainer first adds `expo-haptics` or changes its version,\nthat release must rebuild the Android and iOS base binaries so Expo autolinking\nincludes the module.\n\n### Step 7 — Summary\n\nReturn the shared compatibility result before the summary; use the actual\ncreated\u002Fupdated\u002Freused paths and do not claim completion for unresolved artifacts.\n\n```\n✅ Native wrapper generated: \u003Ccapability>\n─────────────────────────────────────────────\nModule (template-shipped) : \u003Cexpo-module>@\u003Cversion-from-package.json>\nWrapper created           : src\u002Fnative\u002F\u003Ccapability>.ts\npackage.json              : unchanged ✓\napp.config.js             : unchanged ✓\n\nType-check: PASS\n\nSample usage:\n\n  import { takePhoto } from '..\u002F..\u002Fsrc\u002Fnative\u002Fcamera';\n\n  const result = await takePhoto();\n  if (result.ok) {\n    setPhotoUri(result.uri);\n  } else if (result.reason === 'permission-denied') {\n    showToast('Camera permission required');\n  }\n\n⚠️  No per-app native rebuild required when using a binary produced from the current\n    template. The underlying native module is already linked; wrapper edits are picked\n    up by Metro. Template\u002Fbase dependency changes require a new native base build.\n─────────────────────────────────────────────\n```\n\n## Notes\n\n- This skill never modifies `package.json`, `app.config.js`, `src\u002FplayerConfig.ts`, `src\u002Fgenerated\u002F`, or any screen file.\n- For capabilities not in the supported table (`expo-notifications`, Bluetooth, NFC, BLE, AR — until the template adds them), tell the user the template doesn't ship them yet — file a request at the upstream template repo. Do NOT attempt to install or configure anything yourself.\n- Pure-JavaScript libraries are out of scope for this skill. `\u002Fcreate-mobile-app` or `\u002Fedit-app` selects and installs them through [`shared\u002Freferences\u002Fjavascript-dependency-planning.md`](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fjavascript-dependency-planning.md); no native wrapper or Android\u002FiOS rebuild is needed. The prohibition above applies only to packages with native source\u002Fconfig.\n","> **Plugin check**: Run `node \"${PLUGIN_ROOT}\u002Fscripts\u002Fcheck-version.js\"` - if it outputs a message, show it to the user before proceeding.\n\n**? Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}\u002Fshared\u002Fshared-instructions.md)** — read first.\n\n**Working directory:** before any project read or command, execute\n[native-artifact-compatibility.md Step 0](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fnative-artifact-compatibility.md#0-bind-every-operation-to-the-app-root).\nResolve `--working-dir` \u002F owner context there; use the resolved absolute root for\nevery shell call and file tool, not a previous shell's cwd.\n\n# Add Native Capability\n\n**Entry routing:** use the shared [App feature entry points](..\u002F..\u002Fshared\u002Fshared-instructions.md#app-feature-entry-points)\npreflight before the workflow below.\n\nThe leaf generates typed wrappers under `src\u002Fnative\u002F` for native capabilities that\nthe upstream template already ships. Screens import the wrapper instead of touching\nExpo modules directly, so the discriminated-union result contract stays consistent.\n\n## Hard rules — do NOT cross these lines\n\n1. **Never run `npx expo install`, `npm install`, or `yarn add` for a native module.** The set of native modules in `package.json` is fixed by the upstream template. Adding a new one breaks the rewrap pipeline (the customer's binary is built from a pre-built base, not from their `package.json`).\n2. **Never edit `app.config.js`** — plugins, `ios.infoPlist`, `android.permissions`, or anything else. All native config the template ships is intentional and signed off; arbitrary additions cannot be honored at rewrap time.\n3. **Never edit `package.json` `dependencies` for native modules.** Native means the package ships platform source\u002Fprojects, a podspec, codegen, an Expo module\u002Fconfig plugin, or `react-native.config.js`; a package-name prefix alone is not proof. Pure-JavaScript dependencies are out of scope for `\u002Fadd-native` and are installed from an approved `JavaScript Dependencies` plan by `\u002Fcreate-mobile-app` or `\u002Fedit-app`.\n4. **If the requested module isn't actually present in `package.json` — STOP.** That means the upstream template hasn't shipped it yet; do not work around by installing it.\n\n## Routing — `\u002Fadd-native` is the public entry point\n\nSome capabilities have a dedicated implementation helper that does more than a plain wrapper (camera writes scanner\u002Fupload helpers; PDF report\u002Fviewer helpers enforce local-vs-HTTPS boundaries; pen has native-control-specific validation). Users should still call `\u002Fadd-native \u003Ccapability>` for native controls. When a dedicated implementation exists, **run it internally and do not ask the user to run that helper directly**.\n\n**Lookup convention:** after normalizing the capability, first check the internal-helper map below. For `camera`, `image-picker`, `barcode-scanner`, and `qr-scanner`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-camera\u002FSKILL.md` inside this `\u002Fadd-native` invocation. For `pdf-report`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-report\u002FSKILL.md`. For `pdf-viewer`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-viewer\u002FSKILL.md`. For `pen-input`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pen-input\u002FSKILL.md`. For `geolocation`, read and execute `${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-geolocation\u002FSKILL.md`. If no helper exists, fall through to this skill's inline wrapper flow.\n\nCurrent dedicated implementations:\n\n| Capability | Dedicated skill | Why dedicated |\n|---|---|---|\n| `camera`, `take-photo`, `photo`, `expo-camera`, `image-picker`, `gallery`, `expo-image-picker`, `barcode-scanner`, `qr-scanner`, `scanner` | [`add-camera`](add-camera\u002FSKILL.md) internal helper | Owns photo capture, gallery image picking, and barcode\u002FQR scanner controls backed by `expo-camera` \u002F `expo-image-picker` |\n| `pdf-report`, `pdf-export`, `generate-pdf`, `print-report`, `evidence-packet` | [`add-pdf-report`](add-pdf-report\u002FSKILL.md) internal helper | Generates app-owned local PDFs with `expo-print` and shares them only when `expo-sharing` is present |\n| `pdf-viewer`, `native-pdf-viewer`, `pdf-control`, `open-pdf`, `@microsoft\u002Fpower-apps-native-pdf-viewer` | [`add-pdf-viewer`](add-pdf-viewer\u002FSKILL.md) internal helper | Enforces `https:\u002F\u002F` \u002F `file:\u002F\u002F` viewer inputs and native viewer result handling |\n| `pen-input`, `signature`, `ink`, `draw`, `@microsoft\u002Fpower-apps-native-pen-input` | [`add-pen-input`](add-pen-input\u002FSKILL.md) internal helper | Captures PNG data URI and documents Dataverse Image\u002FFile persistence |\n| `geolocation`, `location-tracking`, `background-location`, `gps-tracking`, `geo-tracking`, `@microsoft\u002Fpower-apps-native-bglocation` | [`add-geolocation`](add-geolocation\u002FSKILL.md) internal helper | Native background GPS tracking with durable storage and inline Dataverse sync; distinct from one-shot `expo-location` |\n\nFor every other capability listed below, this skill writes the wrapper directly.\n\n## Native capability gate\n\nBefore adding any native control or wrapper, apply every gate: classify the intent, resolve the exact package\u002Fcontrol from the live `package.json`, confirm it is not runtime-banned, confirm the input\u002Foutput\u002Fstorage constraints, then use the matching route below. If any gate fails, the native functionality is not supported for this app version — do not install packages, edit native config, or create fake wrappers.\n\n| User intent | Add\u002Fuse | Required package or control | Do not use \u002F fallback |\n|---|---|---|---|\n| Form field bound to a Dataverse File column | Host `\u003CFilePicker>` in screen JSX | `@microsoft\u002Fpower-apps-native-host` host control | Do not generate document-picker\u002Ffile-system\u002Fsharing wrappers for this field |\n| Form field bound to a Dataverse Image column | Host `\u003CImagePicker>` in screen JSX | `@microsoft\u002Fpower-apps-native-host` host control | Do not use camera\u002Fimage-picker wrappers for normal form-bound image fields |\n| Dedicated photo\u002Fgallery\u002Fscanner workflow | `\u002Fadd-native camera`, `image-picker`, or `barcode-scanner` | `expo-image-picker` for photo\u002Fgallery; `expo-camera` for scanner; both for combined requests | If packages are absent, stop with missing-package guidance |\n| Pick\u002Fimport\u002Fupload a user-selected PDF\u002Fdocument | `\u002Fadd-native document-picker`, or host `\u003CFilePicker>` for Dataverse File fields | `expo-document-picker` present, or host File control | Do not treat this as `pdf-report` or native PDF viewer |\n| Generate\u002Fexport\u002Fprint an app-owned report PDF | `\u002Fadd-native pdf-report` | `expo-print` present | If `expo-print` is absent, do not add PDF report capability |\n| Share a generated local PDF | `pdfReport.ts` share helper | `expo-sharing` present | If `expo-sharing` is absent, do not render sharing UI |\n| Open\u002Fpreview an HTTPS or local file PDF | `\u002Fadd-native pdf-viewer` | `@microsoft\u002Fpower-apps-native-pdf-viewer` 0.2.9+ present and input is `https:\u002F\u002F` or `file:\u002F\u002F` | Do not pass `content:\u002F\u002F`, `blob:`, or `http:\u002F\u002F` URIs to the viewer |\n| Capture signature, ink, drawing, or sign-off | `\u002Fadd-native pen-input` | `@microsoft\u002Fpower-apps-native-pen-input` present | If persisted, plan Dataverse Image\u002FFile\u002Fchild Evidence target first |\n| Continuous\u002Fbackground GPS tracking with durable Dataverse upload | `\u002Fadd-native geolocation` | `@microsoft\u002Fpower-apps-native-bglocation` present | Do not use one-shot `expo-location` for background tracking; do not use the `GeolocationExtension`\u002FHostingSDK path |\n| Tactile feedback for presses, selections, and operation results | `\u002Fadd-native haptics` | `expo-haptics` present | Use the typed wrapper and pair every haptic with visible UI feedback |\n| Store generated PDF\u002Fsignature artifact | Generated Dataverse services after parent row exists | File\u002FImage column or child Evidence\u002FAttachment table exists | Never put File bytes in create\u002Fupdate JSON |\n| Native capability not listed in this table | Resolve from `package.json`, then add an inline wrapper only when the matching package is present and not runtime-banned | Exact relevant package present in `package.json` | If no relevant package exists, or the package is runtime-banned, add a transparency note and stop |\n\nHandle multi-part requests row-by-row. Example: \"capture signature and attach signed report PDF\" requires `pen-input`, `pdf-report`, Dataverse artifact storage, and possibly `sharing`; do not add only the native capability while leaving storage or screen states undefined. The map is not closed: for new shipped packages, resolve by capability semantics, use the directly matching package when safe, and ask once only if multiple installed packages plausibly match.\n\n## File\u002FImage Picker Ownership\n\nThis skill owns native file capability mechanics. Screen builders own only the final JSX placement.\n\nThere are two different cases:\n\n| Case | Correct implementation | Owner |\n|---|---|---|\n| Dataverse `File` column | Use `\u003CFilePicker>` from `@microsoft\u002Fpower-apps-native-host` | screen-builder JSX rule, documented here |\n| Dataverse `Image` column | Use `\u003CImagePicker>` from `@microsoft\u002Fpower-apps-native-host` | screen-builder JSX rule, documented here |\n| Local device document\u002Ffile workflow not bound to a Dataverse column | Generate\u002Fuse `src\u002Fnative\u002FdocumentPicker.ts`, `src\u002Fnative\u002FfileSystem.ts`, and\u002For `src\u002Fnative\u002Fsharing.ts` wrappers | `\u002Fadd-native` |\n| App-generated PDF report workflow | `\u002Fadd-native` routes internally to `add-pdf-report`; uses `expo-print` and optionally `expo-sharing` only if present | `\u002Fadd-native` |\n| Camera capture, gallery image selection, barcode scanner, or QR scanner workflow | `\u002Fadd-native` routes internally to `add-camera` | `\u002Fadd-native` |\n\n**Dataverse File\u002FImage columns use host controls, not raw Expo modules and not app-specific native wrappers.** When a form field binds to a Dataverse **File** column (`FileAttributeMetadata`), render `\u003CFilePicker>` from `@microsoft\u002Fpower-apps-native-host`. When it binds to a Dataverse **Image** column (`ImageAttributeMetadata`), render `\u003CImagePicker>` from `@microsoft\u002Fpower-apps-native-host`. These controls read accent, surface, and text colors from `PowerAppsProvider` \u002F `ThemeProvider` and produce Dataverse-compatible payloads.\n\n```tsx\nimport { FilePicker, ImagePicker } from '@microsoft\u002Fpower-apps-native-host';\nimport type { PickedFileInfo, PickedImageInfo } from '@microsoft\u002Fpower-apps-native-host';\n\n\u002F\u002F Image column — seed preview from Dataverse bytes\u002Fbase64 and capture upload-ready payload.\n\u003CImagePicker\n  label=\"Site Photo\"\n  initialBase64={imageBase64}\n  onImageChange={(image: PickedImageInfo | null) => { \u002F* store image for upload *\u002F }}\n\u002F>\n\n\u002F\u002F File column — edit-only pick\u002Freplace.\n\u003CFilePicker\n  label=\"Specification Sheet\"\n  fileName={record.cr123_specsheet_name}\n  onChange={(file: PickedFileInfo) => { \u002F* store file for save *\u002F }}\n\u002F>\n\n\u002F\u002F File column — view + replace.\n\u003CFilePicker\n  label=\"Specification Sheet\"\n  fileName={record.cr123_specsheet_name}\n  onDownload={() => Cr123_inspectionService.downloadFile(id, 'cr123_specsheet')}\n  onChange={(file: PickedFileInfo) => { \u002F* store file for save *\u002F }}\n\u002F>\n```\n\nSave\u002Fread pattern for host picker-bound Dataverse columns:\n\n```ts\n\u002F\u002F FILE\u002FIMAGE columns: do not include picker bytes in the PATCH body.\n\u002F\u002F Save normal fields first, then upload through generated service helpers.\nawait Cr123_inspectionService.update(id, { cr123_name: name.trim() });\nawait Cr123_inspectionService.upload(id, 'cr123_specsheet', file.file, file.name);\nawait Cr123_inspectionService.upload(id, 'cr123_sitephoto', image.file, image.name);\n\n\u002F\u002F Download helpers are used for read\u002Fview scenarios.\nconst fileBytes = await Cr123_inspectionService.downloadFile(id, 'cr123_specsheet');\nconst imageBytes = await Cr123_inspectionService.downloadImage(id, 'cr123_sitephoto');\n```\n\nUse host `ImagePicker` \u002F `FilePicker` with generated `upload(...)` for persistence and `downloadFile(...)` \u002F `downloadImage(...)` for read\u002Fview. Do not use `Service.update(...{ \u003CimageColumn>: base64 })` for picker-driven image\u002Ffile persistence.\n\nDo not use raw `expo-document-picker`, `expo-image-picker`, `expo-file-system`, or `expo-sharing` to build custom UI for Dataverse File\u002FImage columns. Host `FilePicker` and `ImagePicker` already handle permissions, reading, sharing\u002Fdownload affordances, and Dataverse-compatible payload shape.\n\nUse `\u002Fadd-native` wrappers only when the workflow is not a Dataverse File\u002FImage column, for example importing a local PDF for offline parsing, exporting a generated report, saving a local draft packet, or opening a share sheet for an app-generated summary.\n\n## Supported capabilities\n\nApply the Native capability gate above. This table is a known capability-to-package map, not a guarantee that every listed package exists in every template version.\n\n| Capability | Module | Wrapper to generate | Notes |\n|---|---|---|---|\n| `camera`, `take-photo`, `photo`, `expo-camera` | `expo-image-picker` | `src\u002Fnative\u002Fcamera.ts` | `\u002Fadd-native` routes internally to `add-camera` for the `photo` artifact; `expo-camera` is a capability alias, not its required package |\n| `image-picker`, `gallery`, `expo-image-picker` | `expo-image-picker` | `src\u002Fnative\u002Fcamera.ts` (`pickImage`) | `\u002Fadd-native` routes internally to `add-camera`; preserve compatible existing re-exports |\n| `barcode-scanner`, `qr-scanner`, `scanner`, `barcode`, `qr` | `expo-camera` | `src\u002Fnative\u002FbarcodeScanner.tsx` | `\u002Fadd-native` routes internally to `add-camera` |\n| `document-picker` | `expo-document-picker` | `src\u002Fnative\u002FdocumentPicker.ts` | Picks\u002Fimports user-selected files (PDF, docs, etc.) from the device |\n| `pdf-viewer`, `native-pdf-viewer`, `pdf-control`, `open-pdf`, `@microsoft\u002Fpower-apps-native-pdf-viewer` | `@microsoft\u002Fpower-apps-native-pdf-viewer` | `src\u002Fnative\u002FpdfViewer.ts` | `\u002Fadd-native` routes internally to `add-pdf-viewer`; 0.2.9+ opens HTTPS URLs and file URIs |\n| `pdf-report`, `pdf-export`, `generate-pdf`, `print-report`, `evidence-packet` | `expo-print` (+ optional `expo-sharing`) | `src\u002Fnative\u002FpdfReport.ts` | `\u002Fadd-native` routes internally to `add-pdf-report`; generated local files are shared only when `expo-sharing` is present, or uploaded to Dataverse |\n| `pen-input`, `signature`, `ink`, `draw`, `@microsoft\u002Fpower-apps-native-pen-input` | `@microsoft\u002Fpower-apps-native-pen-input` | `src\u002Fnative\u002FpenInput.ts` | `\u002Fadd-native` routes internally to `add-pen-input`; captures PNG data URI |\n| `geolocation`, `location-tracking`, `background-location`, `gps-tracking`, `geo-tracking`, `@microsoft\u002Fpower-apps-native-bglocation` | `@microsoft\u002Fpower-apps-native-bglocation` | `src\u002Fnative\u002Fgeolocation.ts` | `\u002Fadd-native` routes internally to `add-geolocation`; native background tracking + durable Dataverse sync. Distinct from one-shot `location` below |\n| `secure-store` | `expo-secure-store` | `src\u002Fnative\u002FsecureStore.ts` | |\n| `file-system` | `expo-file-system` | `src\u002Fnative\u002FfileSystem.ts` | |\n| `sharing` | `expo-sharing` | `src\u002Fnative\u002Fsharing.ts` | |\n| `location` | `expo-location` | `src\u002Fnative\u002Flocation.ts` | One-shot\u002Fforeground fix only. For continuous background tracking with Dataverse sync, use `geolocation` (`@microsoft\u002Fpower-apps-native-bglocation`). Use only when the current template package contains `expo-location` |\n| `biometrics`, `local-authentication` | `expo-local-authentication` | `src\u002Fnative\u002Fbiometrics.ts` | Use only when the current template package contains `expo-local-authentication` |\n| `clipboard` | `expo-clipboard` | `src\u002Fnative\u002Fclipboard.ts` | Use only when the current template package contains `expo-clipboard` |\n| `mail-composer`, `email-draft` | `expo-mail-composer` | `src\u002Fnative\u002FmailComposer.ts` | Opens native mail compose when the package is present; connectors still own server-side email sends |\n| `audio` | `expo-audio` | `src\u002Fnative\u002Faudio.ts` | Use for audio recording\u002Fplayback only when package is present |\n| `video` | `expo-video` | `src\u002Fnative\u002Fvideo.ts` | Use for video playback only when package is present |\n| `sensors` | `expo-sensors` | `src\u002Fnative\u002Fsensors.ts` | Use only for sensor APIs exposed by the installed package |\n| `screen-orientation` | `expo-screen-orientation` | `src\u002Fnative\u002FscreenOrientation.ts` | Use only when package is present; do not edit native config |\n| `haptics`, `vibration-feedback`, `impact-feedback`, `selection-feedback`, `notification-feedback` | `expo-haptics` | `src\u002Fnative\u002Fhaptics.ts` | Impact, selection, and success\u002Fwarning\u002Ferror notification feedback |\n| `device-info` | `expo-device` \u002F `expo-application` \u002F `expo-cellular` | `src\u002Fnative\u002FdeviceInfo.ts` | Read-only device\u002Fapp\u002Fcellular metadata wrappers |\n| `date-time-picker` | `@react-native-community\u002Fdatetimepicker` | screen-level component usage | Use directly in form screens per screen-builder rules; no `\u002Fadd-native` wrapper required |\n\nFor custom workflows outside Dataverse File\u002FImage form fields, use the `image-picker` capability via `\u002Fadd-native image-picker` for user-selected photos and videos, or the `document-picker` capability via `\u002Fadd-native document-picker` for documents and other files. These wrappers use scoped system-picker flows. Dataverse File\u002FImage form fields remain the exception: use host `\u003CFilePicker>` \u002F `\u003CImagePicker>` controls as described above, not `\u002Fadd-native` wrappers or raw Expo module imports.\n\n### PDF \u002F pen routing rules\n\n- Do not treat every PDF request as `document-picker`.\n- Use `document-picker` when the user wants to pick, import, or upload a local PDF\u002Fdocument. This remains supported and should still be used for that use case.\n- Use `pdf-report` when the app generates a PDF from records, evidence, inspection data, certificates, receipts, or reports, but only if `expo-print` is present in `package.json`.\n- Use `native-pdf-viewer` \u002F `pdf-control` when the app opens\u002Fpreviews an HTTPS PDF URL or local `file:\u002F\u002F` URI with `@microsoft\u002Fpower-apps-native-pdf-viewer` 0.2.9+. `content:\u002F\u002F`, `blob:`, and `http:\u002F\u002F` URIs are unsupported.\n- If a request says \"view\u002Fopen PDF\" but the Power Apps viewer package is absent, fall back to `pdf-report` only when the app is generating its own report and `expo-print` is present. Do not claim generic PDF viewing support through `expo-print`; it generates local files, it does not view arbitrary PDFs.\n- Use `pen-input` only for signatures, drawn approvals, ink notes, sketches, and handwritten sign-off with `@microsoft\u002Fpower-apps-native-pen-input`.\n- For other use cases, use the relevant Expo module or other dependency already present in `package.json`; do not force the Power Apps extensions into unrelated flows.\n- For generated local PDFs from `expo-print`, use native PDF viewer 0.2.9+ for open\u002Fpreview, `expo-sharing` for sharing, and Dataverse File storage for retention. Do not require `expo-sharing` merely to preview a local PDF.\n- Host `FilePicker` and `ImagePicker` are still correct for user-selected Dataverse File\u002FImage form fields. Generated PDFs and pen captures use native wrappers first, then Dataverse persistence helpers.\n\n### Dataverse artifact persistence rules\n\n- Pen input returns a PNG data URI like `data:image\u002Fpng;base64,...`.\n- For Dataverse Image columns, normalize the data URI to the generated service payload shape. If raw base64 is required, strip the `data:image\u002Fpng;base64,` prefix.\n- For Dataverse File columns, convert the generated PDF or signature PNG into upload bytes\u002FFile, save or update the parent row first, then upload through the generated service helper.\n- Never put File column bytes in the create\u002Fupdate JSON body. File bytes are uploaded only after the parent row ID exists.\n- Screens must handle unsupported, cancelled, upload failed, and viewer failed states explicitly. Pen cancellation is a non-error result that screens can ignore.\n\n**Missing or gated packages:** `package.json` plus the runtime gate is authoritative. If the relevant package\u002Fcontrol is absent, stop with a transparency note.\n\n## Workflow\n\n1. Verify project → 2. Resolve capability → 3. Auto-route to dedicated skill if one exists → 4. Verify module is template-shipped → 5. Write wrapper → 6. Type-check → 7. Summary\n\n---\n\n### Step 1 — Verify project\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\ntest -f app.config.js && test -f power.config.json && test -f package.json || { echo \"BLOCKED: working_dir is not an initialized app\" >&2; exit 1; }\n```\n\n### Step 2 — Resolve capability\n\n**Telemetry checkpoint: `resolve_native_capability`**\n\nIf `$ARGUMENTS` includes a capability name, package name, or control name, use it. Otherwise look for a `## Native Capabilities` section in `native-app-plan.md` and present the planned capabilities for confirmation. If neither exists, prompt the user with the supported-capabilities list above plus any relevant installed package from `package.json` that directly matches their request.\n\nNormalize the capability name to lowercase, hyphenated form (e.g., `Camera` → `camera`, `ImagePicker` → `image-picker`, `SecureStore` → `secure-store`). Also normalize aliases: `take-photo` \u002F `photo` \u002F `camera-control` \u002F `expo-camera` → `camera`; `gallery` \u002F `pick-image` \u002F `expo-image-picker` → `image-picker`; `scanner` \u002F `barcode` \u002F `qr` → `barcode-scanner`; `open-pdf` \u002F `view-pdf` \u002F `pdf-control` \u002F `pdf-viewer-control` \u002F `@microsoft\u002Fpower-apps-native-pdf-viewer` → `pdf-viewer`; `native-pdf-viewer` → `pdf-viewer`; `generate-pdf` \u002F `pdf-export` → `pdf-report`; `signature` \u002F `sign-off` \u002F `ink` \u002F `draw` \u002F `pen-control` \u002F `@microsoft\u002Fpower-apps-native-pen-input` → `pen-input`; `location-tracking` \u002F `background-location` \u002F `gps-tracking` \u002F `geo-tracking` \u002F `track-location` \u002F `power-apps-native-bglocation` \u002F `@microsoft\u002Fpower-apps-native-bglocation` → `geolocation`; `vibration` \u002F `vibration-feedback` \u002F `impact-feedback` \u002F `selection-feedback` \u002F `notification-feedback` \u002F `expo-haptics` → `haptics`.\n\nWhen the user asks for \"location\" or \"GPS\", disambiguate by intent: continuous\u002Fbackground tracking or durable Dataverse upload → `geolocation` (`@microsoft\u002Fpower-apps-native-bglocation`); a single foreground coordinate read → `location` (`expo-location`). If the intent is unclear, ask once before routing.\n\nIf the user names something not in the supported table, apply the Native capability gate: resolve the relevant package from `package.json`, continue only when present and not runtime-banned, otherwise stop with a transparency note.\n\n### Step 3 — Route to nested helpers or inline wrappers\n\n**Telemetry checkpoint: `dispatch_native_capability`**\n\nFor normalized `camera`, `image-picker`, `barcode-scanner`, `qr-scanner`, `pdf-report`, `pdf-viewer`, `pen-input`, or `geolocation`, do not fall through to the generic wrapper flow and do not tell the user to run another slash command. Read the nested helper and follow its steps inside this `\u002Fadd-native` invocation:\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\ncase \"\u003Ccapability>\" in\n  camera|image-picker|barcode-scanner|qr-scanner) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-camera\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-camera\" ;;\n  pdf-report) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-report\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-pdf-report\" ;;\n  pdf-viewer) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pdf-viewer\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-pdf-viewer\" ;;\n  pen-input) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-pen-input\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-pen-input\" ;;\n  geolocation) test -f \"${PLUGIN_ROOT}\u002Fskills\u002Fadd-native\u002Fadd-geolocation\u002FSKILL.md\" && echo \"INTERNAL_HELPER:add-geolocation\" ;;\n  *) echo \"INLINE\" ;;\nesac\n```\n\n- **INTERNAL_HELPER:** read the printed helper file and execute its workflow with\n  the same absolute `working_dir`, arguments, supplied answers, mode flags, and\n  current owner\u002Fphase\u002F`approved_scope`. The helper must bind every operation to\n  that root using Step 0, even if launched from another directory. Each helper must execute\n  [native-artifact-compatibility.md](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fnative-artifact-compatibility.md)\n  before its writes\u002Freuse and before its success summary; outer Step 5 is not\n  reached on this branch. Propagate `NEEDS_CONTEXT`\u002F`BLOCKED` and the artifact\n  result to the owner, then STOP this leaf, not the owning workflow. Do not\n  fall through to inline generation or report success for a partial helper result.\n  `\u002Fadd-native` remains the only user-facing command for these controls.\n- **INLINE:** continue to Step 4.\n\n### Step 4 — Verify module is template-shipped\n\nConfirm the underlying native-capability package is actually present in the project's `package.json` (catches the case where the user hand-removed it or the template version is older than expected):\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\nnode -e \"const p = require('.\u002Fpackage.json'); const m = '\u003Cexpo-module-name>'; if (!p.dependencies?.[m]) { console.error('MISSING: ' + m + ' is not in package.json. The template should ship it. Re-scaffold via \u002Fcreate-mobile-app, restore it from upstream, or wait for the template release that adds it — this skill will not install it.'); process.exit(1); }\"\n```\n\nIf the check fails, STOP. Do not run `npx expo install`. Print the error verbatim.\n\n### Step 5 — Write wrapper\n\n**Telemetry checkpoint: `generate_native_wrapper`**\n\n**Print before starting:**\n> \"→ Writing src\u002Fnative\u002F\u003Cwrapper>.ts (typed wrapper with discriminated-union result + iOS\u002FAndroid platform guards)…\"\n\nRead and execute [native-artifact-compatibility.md](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fnative-artifact-compatibility.md)\nSteps 1–3 for the inline capability row before writing or reusing output.\nCreate `src\u002Fnative\u002F\u003Cwrapper-filename>.ts` only when missing and in scope. If the\nfile already exists, inspect its exports against the approved capability\ncontract, including behavior and storage. Reuse it unchanged when compatible;\nmake a scoped update without re-asking when already approved. Otherwise return\n`NEEDS_CONTEXT` to the owner (or ask standalone) before updating; never silently\nskip the feature or overwrite custom code.\n\nEach wrapper exports:\n\n- `request\u003CCapability>Permission(): Promise\u003Cboolean>` — only when the underlying API has a permission model (location, biometrics, notifications). Skip for capabilities without one (secure-store, file-system, sharing, clipboard).\n- One or two domain methods returning typed results.\n\n**The contract `screen-builder` agents rely on:**\n- All wrappers return a discriminated-union result (`{ ok: true, ... } | { ok: false, reason, message? }`) — **never throw**\n- Unsupported runtime\u002Fplatform states gracefully degrade or return `{ ok: false, reason: 'unsupported' }` — **never crash**\n- Branch by supported native platform when a capability differs between iOS and Android\n- Screens import these wrappers only for non-Dataverse native workflows. Dataverse File\u002FImage fields use `@microsoft\u002Fpower-apps-native-host` controls from the File\u002FImage Picker Ownership section above.\n\n#### Haptics wrapper\n\nFor normalized capability `haptics`, generate `src\u002Fnative\u002Fhaptics.ts` with this contract:\n\n```ts\nimport * as Haptics from 'expo-haptics';\nimport { Platform } from 'react-native';\n\nexport type HapticImpactStyle = 'light' | 'medium' | 'heavy' | 'soft' | 'rigid';\nexport type HapticNotificationType = 'success' | 'warning' | 'error';\nexport type HapticResult =\n  | { ok: true }\n  | { ok: false; reason: 'unsupported' | 'error'; message: string };\n\nconst impactStyles: Record\u003CHapticImpactStyle, Haptics.ImpactFeedbackStyle> = {\n  light: Haptics.ImpactFeedbackStyle.Light,\n  medium: Haptics.ImpactFeedbackStyle.Medium,\n  heavy: Haptics.ImpactFeedbackStyle.Heavy,\n  soft: Haptics.ImpactFeedbackStyle.Soft,\n  rigid: Haptics.ImpactFeedbackStyle.Rigid,\n};\n\nconst notificationTypes: Record\u003CHapticNotificationType, Haptics.NotificationFeedbackType> = {\n  success: Haptics.NotificationFeedbackType.Success,\n  warning: Haptics.NotificationFeedbackType.Warning,\n  error: Haptics.NotificationFeedbackType.Error,\n};\n\nasync function runHaptic(effect: () => Promise\u003Cvoid>): Promise\u003CHapticResult> {\n  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {\n    return {\n      ok: false,\n      reason: 'unsupported',\n      message: 'Haptic feedback is available only on iOS and Android.',\n    };\n  }\n\n  try {\n    await effect();\n    return { ok: true };\n  } catch (error) {\n    return {\n      ok: false,\n      reason: 'error',\n      message: error instanceof Error ? error.message : 'The native haptic call failed.',\n    };\n  }\n}\n\nexport function triggerImpact(style: HapticImpactStyle = 'medium'): Promise\u003CHapticResult> {\n  return runHaptic(() => Haptics.impactAsync(impactStyles[style]));\n}\n\nexport function triggerNotification(\n  type: HapticNotificationType = 'success'\n): Promise\u003CHapticResult> {\n  return runHaptic(() => Haptics.notificationAsync(notificationTypes[type]));\n}\n\nexport function triggerSelection(): Promise\u003CHapticResult> {\n  return runHaptic(() => Haptics.selectionAsync());\n}\n```\n\nScreens call the wrapper only when haptics appear in the approved native-capabilities and per-screen plan. Choose feedback by semantics:\n\n- `triggerImpact('light' | 'medium' | 'heavy' | 'soft' | 'rigid')` for deliberate presses or physical-feeling actions.\n- `triggerSelection()` when a picker, segmented control, or other selection changes.\n- `triggerNotification('success' | 'warning' | 'error')` after the corresponding outcome is known.\n- Keep visual feedback as the primary signal. Inspect non-OK results and log them, but do not turn an otherwise successful business operation into a failure because tactile feedback is unavailable.\n\nScreen usage after a successful operation:\n\n```ts\nsetSaveState({ kind: 'success', message: 'Saved' });\nconst hapticResult = await triggerNotification('success');\nif (!hapticResult.ok) {\n  console.warn('[haptics] success feedback unavailable', hapticResult);\n}\n```\n\nNo permission request or `app.config.js` change is required for `expo-haptics`.\n\n#### Other inline wrappers\n\nConsult the module's published API docs (linked from its npm page) for method signatures and permission patterns. Use the secure-store skeleton below as the canonical example of the discriminated-union shape, then translate it to the target module's API.\n\nSecure-store canonical skeleton:\n\n```ts\n\u002F\u002F src\u002Fnative\u002FsecureStore.ts\nimport * as SecureStore from 'expo-secure-store';\nimport { Platform } from 'react-native';\n\nexport type SecureResult\u003CT> =\n  | { ok: true; value: T }\n  | { ok: false; reason: 'unsupported' | 'not-found' | 'error'; message?: string };\n\nexport async function getSecret(key: string): Promise\u003CSecureResult\u003Cstring>> {\n  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {\n    return { ok: false, reason: 'unsupported', message: 'SecureStore is not available on this platform.' };\n  }\n  try {\n    const value = await SecureStore.getItemAsync(key);\n    if (value === null) return { ok: false, reason: 'not-found' };\n    return { ok: true, value };\n  } catch (e: any) {\n    return { ok: false, reason: 'error', message: e?.message };\n  }\n}\n\nexport async function setSecret(key: string, value: string): Promise\u003CSecureResult\u003Ctrue>> {\n  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {\n    return { ok: false, reason: 'unsupported', message: 'SecureStore is not available on this platform.' };\n  }\n  try {\n    await SecureStore.setItemAsync(key, value);\n    return { ok: true, value: true };\n  } catch (e: any) {\n    return { ok: false, reason: 'error', message: e?.message };\n  }\n}\n```\n\n### Step 6 — Type-check\n\n**Telemetry checkpoint: `validate_native_wrapper`**\n\n**Print before starting:**\n> \"→ Running tsc to verify wrapper compiles (~10–20 seconds).\"\n\n```bash\ncd -- '\u003Cworking_dir>' || { echo \"BLOCKED: cannot enter working_dir\" >&2; exit 1; }\nnpx --no-install tsc --noEmit\n```\n\nFix only in-scope wrapper-side errors. Execute the shared compatibility contract's\nStep 4 after type-checking: recheck all requested artifacts, API\u002Fbehavior, and\nstorage obligations before returning success. Type-check success alone is\ninsufficient. Do not run platform-specific native build commands during normal\n`\u002Fadd-native` use because the module is already part of the current template binary.\nWhen a template\u002Fbase maintainer first adds `expo-haptics` or changes its version,\nthat release must rebuild the Android and iOS base binaries so Expo autolinking\nincludes the module.\n\n### Step 7 — Summary\n\nReturn the shared compatibility result before the summary; use the actual\ncreated\u002Fupdated\u002Freused paths and do not claim completion for unresolved artifacts.\n\n```\n✅ Native wrapper generated: \u003Ccapability>\n─────────────────────────────────────────────\nModule (template-shipped) : \u003Cexpo-module>@\u003Cversion-from-package.json>\nWrapper created           : src\u002Fnative\u002F\u003Ccapability>.ts\npackage.json              : unchanged ✓\napp.config.js             : unchanged ✓\n\nType-check: PASS\n\nSample usage:\n\n  import { takePhoto } from '..\u002F..\u002Fsrc\u002Fnative\u002Fcamera';\n\n  const result = await takePhoto();\n  if (result.ok) {\n    setPhotoUri(result.uri);\n  } else if (result.reason === 'permission-denied') {\n    showToast('Camera permission required');\n  }\n\n⚠️  No per-app native rebuild required when using a binary produced from the current\n    template. The underlying native module is already linked; wrapper edits are picked\n    up by Metro. Template\u002Fbase dependency changes require a new native base build.\n─────────────────────────────────────────────\n```\n\n## Notes\n\n- This skill never modifies `package.json`, `app.config.js`, `src\u002FplayerConfig.ts`, `src\u002Fgenerated\u002F`, or any screen file.\n- For capabilities not in the supported table (`expo-notifications`, Bluetooth, NFC, BLE, AR — until the template adds them), tell the user the template doesn't ship them yet — file a request at the upstream template repo. Do NOT attempt to install or configure anything yourself.\n- Pure-JavaScript libraries are out of scope for this skill. `\u002Fcreate-mobile-app` or `\u002Fedit-app` selects and installs them through [`shared\u002Freferences\u002Fjavascript-dependency-planning.md`](${PLUGIN_ROOT}\u002Fshared\u002Freferences\u002Fjavascript-dependency-planning.md); no native wrapper or Android\u002FiOS rebuild is needed. The prohibition above applies only to packages with native source\u002Fconfig.\n",{"name":5,"model":53,"description":7,"allowed-tools":54,"user-invocable":55},"sonnet","Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, Skill",true,"main","0a55a88fa2855404029d1e5450faf92878baf763","plugins\u002Fmobile-apps\u002Fskills\u002Fadd-native",230,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills","https:\u002F\u002Fmicrosoft.github.io\u002Fpower-platform-skills\u002F","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-14 02:26:14",{"source":8,"skillName":5,"commands":67,"prompt":77,"zipAvailable":55},[68,71,74],{"tool":69,"command":70},"npx","npx skills add microsoft\u002Fpower-platform-skills --skill add-native",{"tool":72,"command":73},"bunx","bunx skills add microsoft\u002Fpower-platform-skills --skill add-native",{"tool":75,"command":76},"pnpm","pnpm dlx skills add microsoft\u002Fpower-platform-skills --skill add-native","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fmobile-apps\u002Fskills\u002Fadd-native\u002FSKILL.md",[],[80,100,123,141,155,165],{"skillNo":81,"name":82,"nameZh":6,"description":83,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":86,"publishedAt":48},"01M699WVHX0XN59EPBTTPEKYPG","setup","Use when Process Intelligence needs building, explicit cloud\u002Faccount\u002Fenvironment configuration, sign-in, connection diagnostics, or recovery from an authentication error.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fprocess-intelligence\u002Fskills\u002Fsetup\u002FSKILL.md",[],[82,30,87,88,89,90,91,92,93,94,95,96,97,98,99,40],"when","connection","error","sign","intelligence","configuration","use","process","diagnostics","environment","authentication","cloud","recovery",{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":111,"publishedAt":48},"01M699WSR9EX55YY1WD9J22FEG","report-issue","Use when the user wants a Process Intelligence bug-report draft or support summary for a failed operation.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fprocess-intelligence\u002Fskills\u002Freport-issue\u002FSKILL.md",[106],{"categoryNo":107,"categoryName":108,"taxonomyType":109,"taxonomyTypeDesc":110},"01JBSEEDCAT000000000000034","行政与办公人员","PROFESSION","适用职业",[112,113,114,115,116,94,93,117,118,91,119,120,121,87,122],"report issue","report","issue","support","user","bug","summary","draft","operation","for","bug-report",{"skillNo":124,"name":125,"nameZh":6,"description":126,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":127,"categories":128,"keywords":129,"publishedAt":48},"01M699WR26YQ9Q21KE6MJF5B2G","investigate-process","Use when a broad Process Mining business question, observed pattern or competing explanation needs investigation across analytical perspectives.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fprocess-intelligence\u002Fskills\u002Finvestigate-process\u002FSKILL.md",[],[94,130,131,132,87,133,134,135,136,30,137,93,138,139,140],"investigate","investigate process","mining","process mining","investigation","perspectives","question","business","pattern","across","analytical",{"skillNo":142,"name":143,"nameZh":6,"description":144,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":145,"categories":146,"keywords":147,"publishedAt":48},"01M699WP9SDG5ZR12P8VYNXKTN","derive-metric","Use when a requested measure is missing, a saved custom metric needs reuse, or an ephemeral formula or derived scalar is needed.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fprocess-intelligence\u002Fskills\u002Fderive-metric\u002FSKILL.md",[],[148,149,30,93,150,151,152,153,154,87],"metric","derive metric","formula","missing","reuse","saved","custom",{"skillNo":156,"name":157,"nameZh":6,"description":158,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":159,"categories":160,"keywords":161,"publishedAt":48},"01M699WMG5R8P6SMTDAS1TKF9E","compare-cohorts","Use when comparing process cohorts, departments, periods or before-and-after results under matched definitions.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fprocess-intelligence\u002Fskills\u002Fcompare-cohorts\u002FSKILL.md",[],[162,163,87,93,94,164,30],"compare","compare cohorts","results",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":48},"01M699WJWNVTMP9XK3NCJHV14D","analyze-variants","Use when investigating common or slow variants, repeated work, unusual paths, or a specific Process Mining case.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fpower-platform-skills\u002Fblob\u002Fmain\u002Fplugins\u002Fprocess-intelligence\u002Fskills\u002Fanalyze-variants\u002FSKILL.md",[],[172,173,174,133,87,132,175,176,93,94,30,177,178],"analyze","variants","analyze variants","case","paths","work","common"]