Skip to main content

setup-offline-profile

Use when the user wants to enable offline mode for a Power Apps mobile app and create a Mobile Offline Profile in Dataverse — designs per-table row scope, relationships, columns, and sync frequency through a 3-gate approval flow.

Quellinformationen

Repository
microsoft/power-platform-skills
Letzte Quellaktivität
7. Oktober 2026 um 11:02
Erkannte Sprache von SKILL.md
Englisch
Sterne
969
Forks
196

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
setup-offline-profile
description
Use when the user wants to enable offline mode for a Power Apps mobile app and create a Mobile Offline Profile in Dataverse — designs per-table row scope, relationships, columns, and sync frequency through a 3-gate approval flow.
user-invocable
true
allowed-tools
Read, Edit, Write, Grep, Glob, Bash, AskUserQuestion, EnterPlanMode, ExitPlanMode, Task
model
opus
> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` - if it outputs a message, show it to the user before proceeding. **Shared instructions: [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)** — read first. **References:** - [offline-profile-schema.md](${PLUGIN_ROOT}/shared/references/offline-profile-schema.md) — Dataverse entity field map - [dataverse-offline-api.md](${PLUGIN_ROOT}/shared/references/dataverse-offline-api.md) — Web API recipes for profile / item / association POSTs - [offline-profile-reconciliation.md](${PLUGIN_ROOT}/shared/references/offline-profile-reconciliation.md) — the `schemaColumns` baseline this skill writes + the lifecycle delta check that consumes it # Setup Offline Profile End-to-end wizard for creating a Dataverse Mobile Offline Profile that the app (and any other compatible Power Apps client) can use to download data for offline access. **Scope of v0**: configuration only. This skill creates records in the existing Dataverse Mobile Offline Profile tables (`mobileofflineprofile`, `mobileofflineprofileitem`, `mobileofflineprofileitemassociation`) and writes the full app-level offline config — profile metadata, per-table scope, and the temporary SDK-workaround fields — to `offline-profile.json`. **This skill does NOT modify `power.config.json`** (that file is owned by `pa app init` and its schema is controlled upstream). The template already bundles `@microsoft/power-apps-native-offline`; `@microsoft/power-apps-native-host` consumes the profile and owns local SQLite access, queued synchronization, reconnect behavior, and status UX. This skill does not scaffold duplicate app-owned offline runtime code. **Out of scope for v0**: - Custom filter mode (`recorddistributioncriteria=3`, savedquery picker) — defer to v0.5 - User/team membership assignment — split into `/assign-offline-profile` - Row-count download estimation — split into `/preview-offline-scope` ## Workflow 1. Verify project & auth → 2. Resolve mode (create vs extend) → 3. Spawn architect agent → **Gate 1** (table prerequisites) → 4. Run the internal `enable-tables-offline` workflow if needed → 5. POST profile shell → **Gate 2** (per-table row scope) → 6. POST profile items → **Gate 3** (relationships + columns + sync) → 7. POST associations → 8. Validate + publish → 9. Persist artifacts → 10. Summary --- ### Step 1 — Verify project & auth ```bash test -f power.config.json && test -f app.config.js # Manifest lives at either root (legacy) or docs/plan-artifacts/ (newer scaffolds) MANIFEST=$(test -f .datamodel-manifest.json && echo ".datamodel-manifest.json" || \ (test -f docs/plan-artifacts/.datamodel-manifest.json && echo "docs/plan-artifacts/.datamodel-manifest.json")) test -n "$MANIFEST" && echo "✓ manifest at $MANIFEST" node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$(node -e \"console.log(require('./power.config.json').environmentId)\")" ``` Capture **Environment URL** for `<envUrl>` and **manifest path** for the architect spawn (Step 3) and the artifacts write (Step 9). **Web-only target detection** — Mobile Offline Profiles only apply to native targets (iOS/Android). If the project is web-only, the profile will be created in Dataverse but **the generated app will never use it**: ```bash # Inspect platforms declared in app.config.js node -e " const c = require('$(pwd)/app.config.js'); const platforms = c?.expo?.platforms ?? []; const hasNative = platforms.includes('ios') || platforms.includes('android'); console.log(JSON.stringify({ platforms, hasNative })); " 2>/dev/null ``` | `hasNative` | Action | |---|---| | `true` (has `ios` and/or `android`) | Continue normally | | `false` (web-only or no platforms) | Print: `⚠ This project only targets web — Mobile Offline Profiles don't apply (they require iOS/Android). Continuing will create the profile in Dataverse but no app will use it.` Ask via `AskUserQuestion`: "Continue anyway?" Default No. | | Parse error / app.config.js missing key | Warn, but assume native (don't block on a parser quirk) | STOP conditions: - No `power.config.json` → "Run `/create-mobile-app` first." - Neither `.datamodel-manifest.json` nor `docs/plan-artifacts/.datamodel-manifest.json` → "Run `/add-dataverse` first — offline profiles require a data model." - Environment resolution failure → standard auth recovery (`az login --tenant <env-tenant>` or provide environment URL directly; see [shared-instructions.md](${PLUGIN_ROOT}/shared/shared-instructions.md)). - Web-only + user declines override → STOP. Print: `Offline profile creation skipped — no native target.` #### Step 1a — Environment consistency check Same as `/add-dataverse` Step 3a — verify `power.config.json` resolves and `az` can token for the target tenant. STOP if it cannot; user must re-auth with `az login --tenant <env-tenant>`. #### Step 1b — Resume check Read `memory-bank.md` `## Offline profile` block. Decide based on `status`: | `status` value | Action | |---|---| | (section absent) OR `status: none` | First-time run. Continue to Step 2. | | `status: not-applicable` | User previously opted out during `/create-mobile-app` offline-profile setup ("doesn't need offline support"). Re-confirm: "Memory-bank says this app doesn't need offline. Override and proceed? (y/N)". Default N stops here. | | `status: done` AND a profile matching `profileId` still exists in env | Already complete. Print summary from the memory-bank block; ask user if they want to `/edit-offline-profile` (v0.2) or just exit. | | `status: done` BUT `GET /mobileofflineprofiles(<profileId>)` returns 404 | Profile was deleted externally (maker portal or another env). Treat as `none`; clear the section; continue to Step 2. | | `status: in-progress` AND profile exists in env | **Resume flow** — see below. | | `status: in-progress` BUT profile doesn't exist in env | Memory-bank stale. Auto-clean: clear the section, log `recovered from stale in-progress state`, continue to Step 2 as a fresh run. | **Resume flow** — when memory-bank has `status: in-progress` AND the profile still exists: 1. `GET /mobileofflineprofiles(<profileId>)?$expand=MobileOfflineProfile_MobileOfflineProfileItem` to compute what's actually been committed: - 0 items → profile shell exists, items not yet POSTed. Resume from Step 6. - 1+ items, missing some from manifest → resume from Step 6, skipping items already present. - All items present, `selectedcolumns` empty on ≥1 → resume from Step 7 (PATCH). - All items present, all have `selectedcolumns` → resume from Step 8 (Publish). - `componentstate=0` (Published) → memory-bank lies; treat as `done`. 2. Ask the user one consolidated AskUserQuestion (NOT a per-step approval): ``` Resume from <computed step> on profile "<name>" (<profileId>)? Items already committed: <N> of <M> PATCHes already applied: <K> Published: <yes|no> Options: - Resume from where it left off (recommended) - Start fresh — delete the half-built profile and re-run from Step 5 - Cancel ``` 3. On `Resume` → jump to the computed step. Skip already-committed items by matching `selectedentitytypecode`. 4. On `Start fresh` → `DELETE /mobileofflineprofiles(<profileId>)` (cascade-deletes items + associations), clear memory-bank section, continue to Step 2 as new. 5. On `Cancel` → STOP, leave memory-bank untouched. **Memory-bank checkpoint contract:** the skill writes `status: in-progress` + `profileId` immediately after Step 5 (profile shell POST). Each subsequent step updates a `lastSuccessfulStep:` field so resume knows where to pick up. On `BLOCKED:` from any step, the skill leaves `status: in-progress`; on `DONE`, it writes `status: done` in Step 9c. **Idempotency on POST retries:** if Step 6 re-POSTs an item with the same `selectedentitytypecode` against the same parent profile, Dataverse may return `409 Conflict` ("duplicate"). The wrapper `dataverse-request.js` treats this as silent success via its `looksLikeDuplicate` logic — safe to re-attempt. ### Step 2 — Resolve mode (create vs extend vs reconcile) **Telemetry checkpoint: `resolve_offline_profile_mode`** **Print before starting:** > "→ Checking for existing offline profiles in the environment…" ```bash node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET \ "mobileofflineprofiles?\$select=mobileofflineprofileid,name,description,publishedon" ``` Decision tree — evaluate in order: | # | Condition | Action | |---|---|---| | 1 | Step 1b already detected `status: in-progress` in memory-bank | Resume flow (handled in Step 1b). Do not re-evaluate here. | | 2 | `offline-profile.json` has top-level `profileId: <X>` AND profile `<X>` still exists in env | **Collision case — ASK USER.** AskUserQuestion: (a) Extend the pinned profile (re-architect against current data model + add/PATCH items as needed), (b) Delete the pinned profile and create fresh (irreversible — cascade-deletes items + associations + memberships), (c) Cancel. Default = (a) extend. NEVER silently delete. | | 3 | `offline-profile.json` has top-level `profileId: <X>` AND profile `<X>` does NOT exist (404 from GET) | `offline-profile.json` is stale (env reset, profile manually deleted). Delete the local file, continue to row #4. | | 4 | Any existing profile in env has `name` matching this app's name (case-insensitive substring match on `power.config.appDisplayName` or directory name) | **ASK USER.** AskUserQuestion: (a) Extend the name-matching profile, (b) Create a new profile (the name-matching one may belong to another app — your call), (c) Cancel. No default — both are legitimate. | | 5 | Zero profiles in env | Mode = `create-new`. Continue to Step 3. | | 6 | 1+ profiles in env but no name match + no pin | Mode = `create-new` (the env has unrelated profiles). Continue to Step 3. Print one-line note: `↷ <N> unrelated profiles exist in env; not extending — see /edit-offline-profile to manage them.` | > **Why row #2 is critical (empirical 2026-05-25)**: the chanel-rm and FCB Tracker test runs hit this case. Without this check, the skill cascade-deleted the pinned profile silently. After this patch, the user gets a clear three-way choice and `delete` is always an explicit action. > **Extend mode (rows #2a, #4a) implementation**: re-spawn the architect with `Mode: extend, existingProfileId: <X>`. The architect compares its current proposal to the on-server items + associations and outputs three lists: (i) items to ADD, (ii) items to PATCH (scope/columns/sync diffs), (iii) items to DELETE (in-server but not in current data model). Surface to the user at Gate 2. After approval, the skill issues incremental writes — no profile-shell POST. ### Step 3 — Spawn architect agent **Telemetry checkpoint: `design_offline_profile_scope`** **Print before starting:** > "→ Spawning mobile-app:offline-profile-architect agent (read-only) to design the profile…" Spawn via `Task`: ```text agent: mobile-app:offline-profile-architect prompt: Working directory: <workdir> Plugin root: ${PLUGIN_ROOT} Environment URL: <envUrl> Publisher prefix: <prefix> Mode: default ``` The agent returns `_offline_section.md` in the working directory. Read it. Parse its first-line status code: - `DONE` → continue - `DONE_WITH_CONCERNS: <list>` → surface concerns at Gate 2 / Gate 3 where relevant; continue - `NEEDS_CONTEXT: <missing>` → resolve the missing context (most often: data model file absent), re-spawn once (cap: 2 retries). If still failing, STOP with the agent's reason. - `BLOCKED: <reason>` → STOP, surface to user, do not silently retry. ### Step 3.5 — Configuration review (interactive AskUserQuestion flow) **Telemetry checkpoint: `review_offline_profile_configuration`** **Print before starting:** > "→ Presenting the proposed offline profile configuration. You'll tap an option to accept, adjust, or cancel — no need to type." The skill renders the proposal as a one-screen summary (read-only context) and then drives the decision through structured `AskUserQuestion` prompts — the same click-style pattern `/create-mobile-app` uses for its 4 plan gates. This replaces the older "type `accept` / plain-English edits / `cancel`" reply flow, which was hit-or-miss when users typed something the parser didn't recognize. #### Step 3.5a — Print the summary (informational only) Substitute real values from `_offline_section.md` and the architect output: ```text ══════════════════════════════════════════════════════════════════════ Mobile Offline Profile — Proposed Configuration ══════════════════════════════════════════════════════════════════════ PROFILE METADATA Name : <proposed name> Description : <proposed description, or "(none)"> Mode : create-new | extend existing (<profileId>) TABLE PREREQUISITES (IsAvailableOffline + ChangeTrackingEnabled) cr123_note ❌/❌ → will be enabled cr123_visit ✅/❌ → ChangeTracking will be enabled contact ✅/✅ → no change PER-TABLE CONFIG ▸ cr123_note (Organization rows, User's rows) Sync : every 10 min Columns : 12 (cr123_title, cr123_body, cr123_visitid, …, modifiedon, createdon) ▸ cr123_visit (Related rows only) Sync : every 10 min Columns : 8 (cr123_name, cr123_date, …, modifiedon) ▸ contact (All records) Sync : every 60 min Columns : 5 (fullname, emailaddress1, …, modifiedon) RELATIONSHIPS (will be POSTed as mobileofflineprofileitemassociation rows) cr123_note → cr123_note_visit (links to cr123_visit) cr123_note → cr123_note_image (includes image bytes) ══════════════════════════════════════════════════════════════════════ ``` No prompt is shown alongside this text — it's informational context, immediately followed by the interactive prompt below. #### Step 3.5b — Top-level decision (single `AskUserQuestion`) ```text header : Offline profile question : "Accept the proposed offline profile configuration and publish to Dataverse?" options : - Accept and publish (Recommended) - Adjust before publishing - Cancel — abort, nothing is mutated ``` Outcomes: | User picks | Action | |---|---| | **Accept and publish** | Write memory bank checkpoint (see below), continue to Step 4 (enable prereqs if needed) → Step 5 (POST shell) → Step 6 (POST items) → Step 7 (PATCH selectedcolumns) → Step 8 (publish) → Step 9 (persist) → Step 9.5 (verify) → Step 10 (summary). **No further prompts** unless something fails. | | **Cancel** | STOP. No Dataverse mutations. No artifacts written. Update `memory-bank.md` with `status: cancelled-at-config-review`. | | **Adjust before publishing** | Continue to Step 3.5c. | #### Step 3.5c — Pick the areas to adjust (single `AskUserQuestion`, multiSelect) ```text header : Adjust which question : "Which sections of the proposal do you want to change?" multiSelect: true options : - Profile name or description - Per-table row scope (who's rows get synced) - Per-table sync interval - Per-table column selection ``` Whatever the user picks drives Step 3.5d. If they pick nothing (closing the dialog), treat that as a no-op and loop back to Step 3.5b. #### Step 3.5d — Drill-down per area For each area the user picked in 3.5c, run the corresponding sub-flow. Apply edits to an in-memory config copy as you go; nothing hits Dataverse until Step 3.5e. **Profile name or description** — text prompt (free-form input, no enumeration possible): > "Type the new profile name, or `skip` to keep `<current name>`." > > Then: "Type the new description, or `skip` to keep `<current description>`." Validate name ≤ 100 chars; trim whitespace; reject empty. **Per-table row scope** — one `AskUserQuestion` per table (batch up to 4 tables per call; 5+ tables → multiple calls): ```text header : Scope <table> question : "Row scope for `<table-logical-name>`?" options : - Related rows only (child of a parent table; default for pure children)
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen