You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: plugins/mobile-apps/agents/data-model-architect.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -126,6 +126,8 @@ Standard table mappings to bias toward:
126
126
| An activity event |`appointment`, `task`, `phonecall`, `email`|
127
127
| A user / system identity |`systemuser` (read-only — never propose extending) |
128
128
129
+
For every `Reuse` decision, add `Service required: yes|no`. Use `yes` whenever any screen, hook, role check, lookup picker, related-field fetch, or authenticated-identity flow reads the table. `systemuser` identity resolution is always `Service required: yes`; read-only means no schema mutation, not no generated data source.
-**Requiredpattern** — usethelookup's **schema name** (PascalCase navigation property), suffix with `@odata.bind`, value is `/<entitySetName>(<guid>)`:
-**Schemaname** (leftof`@odata.bind`): thelookupcolumn's PascalCase logical name, usually exposed in the generated model file (`src/generated/models/<Entity>Model.ts`). Often differs from the `_value` read property by case + dropped underscore (read `_cr3e9_project_value`, write `cr3e9_Project@odata.bind`).
-**FormpickerUI** — whentheform's spec calls for a parent picker (e.g., "select Project"), the picker stores the selected record's`id` (GUIDstring), andthesubmithandlerconvertsittothebindstringatwritetime. Neverstorethebindstringincomponentstate — onlyintheAPIpayload.
197
205
-**Paginationrule:**Ifyourspecsays`pagination: cursor`, doNOTuse`useListData`or`useSearchFilter`. Usetheskeleton's `useCursorListData` call, React Query's`useInfiniteQuery`, oranapp-specific`use<Entity>CursorList`hookwithFlatList`onEndReached`perthepatternin [`data-performance.md`](${PLUGIN_ROOT}/shared/references/data-performance.md). Neverfetchallrecordsatonce, andnevertreat`top: 50`aspagination. RealgeneratedDataverseservicesuseSDK`maxPageSize`forpagesizeandreturn`IOperationResult.skipToken`forthenextpage; passthatvaluebackas`skipToken`. Alwaysincludedeterministic`orderBy`withauniquekeyand`select`intheservicecall. Pushsearch/filterintoDataversewith`filter`. Ifthegeneratedserviceintheappdoesnotexpose`maxPageSize`/`skipToken`foranunboundedtable, return`BLOCKED [<screen_name>]: generated service does not expose cursor paging for <Service>; do not downgrade to useListData`.
Copy file name to clipboardExpand all lines: plugins/mobile-apps/agents/screen-planner.md
+11-6Lines changed: 11 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -210,12 +210,14 @@ Always include these baseline screens (already in template — keep them):
210
210
Then design the user's screens. For a typical CRUD app:
211
211
212
212
-**List screen** per primary entity (e.g., `accounts/index.tsx`)
213
-
-**Detail screen** per primary entity (e.g., `accounts/[id].tsx`)
213
+
-**Detail screen** per primary entity (e.g., `accounts/[id].tsx` when it has no children, or `accounts/[id]/index.tsx` when it owns child workflows)
214
214
-**Create/edit form screen** per primary entity (e.g., `accounts/new.tsx`, `accounts/[id]/edit.tsx`)
215
215
- Plus any workflow-specific screens (e.g., `capture-receipt.tsx`)
216
216
217
217
**Folder rule (HARD — prevents phantom tabs):** any entity that has children (`[id]`, `new`, `edit`, sub-screens) becomes a **folder** with `<entity>/index.tsx` for the list/root view and the children inside. Never use a flat `accounts.tsx` AND a sibling `accounts/[id].tsx` — expo-router auto-registers every top-level `.tsx` under `app/(app)/` as a tab/drawer entry, so a flat `accounts.tsx` next to an `accounts/` folder produces both a phantom "accounts" tab AND the real "accounts" tab. Folders collapse the whole stack into one navigable entry.
218
218
219
+
**Dynamic-route collision rule (HARD):** never emit both `<parent>/[id].tsx` and `<parent>/[id]/<child>.tsx`. Expo Router maps the file and folder to the same `[id]` navigator entry and crashes with `duplicate screen named '[id]'`. When a detail route has child workflows, its detail file is `<parent>/[id]/index.tsx`; child files and `_layout.tsx` live in that same `[id]/` folder.
220
+
219
221
Decision rule per top-level destination:
220
222
221
223
| Destination has any sub-routes? | Layout |
@@ -226,7 +228,10 @@ Decision rule per top-level destination:
226
228
Examples:
227
229
-`home.tsx` (no children) → flat file `app/(app)/home.tsx`
228
230
-`profile.tsx` (no children) → flat file `app/(app)/profile.tsx`
-`inspections` (list + detail + form, no detail children) → folder `app/(app)/inspections/` with `index.tsx`, `[id].tsx`, `new.tsx`
232
+
-`inspections` (detail owns photo/edit children) → `app/(app)/inspections/[id]/index.tsx`, `app/(app)/inspections/[id]/photo.tsx`, and `app/(app)/inspections/[id]/edit.tsx`; never also create `inspections/[id].tsx`
233
+
234
+
**Authenticated Dataverse identity rule:** when app identity links through `systemuser`, require `SystemusersService` in the data-source/service plan. Resolve the access-token `oid` with `SystemusersService.getAll({ filter: "azureactivedirectoryobjectid eq <oid> and isdisabled eq false", top: 2 })`, then query the profile table with `_lookup_value eq <systemuserid>`. Do not use relationship traversal (`lookupNavigation/azureactivedirectoryobjectid`) in generated mobile-service filters.
230
235
231
236
Keep total screen count tight — under 8 for v0 unless the requirements explicitly demand more. The user can iterate later.
232
237
@@ -428,7 +433,7 @@ For each screen the user adds, provide this compact shape:
428
433
429
434
**Hard rule:** if the screen displays a related-entity field but you do NOT emit a `related_entity_fields` block for it, the data-model-architect cannot propose the calc column, the screen-builder will hit `BLOCKED` at scaffold time, and the user will see a `—` cell in the built app. The block is the ONLY signal — there is no fallback inference.
430
435
- **Audit** (omit for read-only / non-write screens) — one line per audit-bearing action: `<trigger>: event <code> (<event label>); payload: <field, field, field>`. Example: `On submit: event 100000006 (Inspection Submitted); payload: inspectionId, submittedAt, defectCount, openCriticalCount.` The screen-builder wraps the payload field list in `JSON.stringify({...})` and writes the full `cr3e9_audit_log_entriesService.create(...)` call from the Generated Services table — do NOT spell out the wrapper or service name.
431
-
- **Lookup writes** — for form/edit screens that set a parent reference (Task → Project, Comment → Task, etc.), explicitly list each lookup field with its `@odata.bind` name + entity set, e.g. `'cr3e9_Project@odata.bind': '/cr3e9_projects(<guid>)'`. Without this the screen-builder will guess and silently lose the relationship. Skip for read-only and no-lookup screens.
436
+
- **Lookup writes** — for form/edit screens that set a parent reference (Task → Project, Comment → Task, etc.), explicitly copy the exact quoted `@odata.bind` property from the generated target model and pair it with the entity set, e.g. `'cr3e9_projectid@odata.bind': '/cr3e9_projects(<guid>)'` when that exact key exists in `src/generated/models/<Entity>Model.ts`. Never derive casing from Dataverse schema-name conventions. Without the generated-model key, mark the spec `BLOCKED: lookup write key not verified`. Skip for read-only and no-lookup screens.
432
437
- **Pagination** — `cursor` if the table has no natural record ceiling (visits, inspections, work orders, tickets, any user-created records over time); `none` if the table is a bounded lookup (status types, categories, job types). When `cursor`, include SDK `maxPageSize: 50`, deterministic `orderBy` with a unique key, `select`, `skipToken` continuation support, and server-side `filter` for search in the data spec. Do not imply that `top: 50` alone is pagination.
433
438
- **Native capabilities** — which native modules/wrappers it uses, and which iOS/Android platforms or permission states need fallback handling. For PDF/pen screens, be precise: `document-picker` (`expo-document-picker`) for user-picked files; `pdf-report` (`expo-print`, plus `expo-sharing` only when present and sharing is required) for generated local PDFs; `native-pdf-viewer` (`@microsoft/power-apps-native-pdf-viewer` 0.2.9+) for HTTPS PDF URLs and local `file://` URIs; `pen-input` (`@microsoft/power-apps-native-pen-input`) for signature/ink capture. For location screens, distinguish `geolocation` (`@microsoft/power-apps-native-bglocation`) — continuous/background tracking with native Dataverse sync, needs start/stop/tracking-status UI plus a permission-denied state — from one-shot `location` (`expo-location`) for a single foreground coordinate read.
434
439
- **Calendar library** — REQUIRED for screens with `Calendar pattern` unless the pattern is `timeline-day-list`. Write `react-native-calendars` and name the exact components expected, for example `CalendarProvider`, `ExpandableCalendar`, `AgendaList`, `Calendar`, `CalendarList`, or `Agenda`. The package must also appear in `### JavaScript Dependencies`; the screen-builder imports it directly after the orchestrator installs it. No `/add-native` wrapper or native rebuild is involved.
@@ -475,7 +480,7 @@ This is the target shape for every spec. ~120 words, ~450 tokens. No inlined cat
475
480
- **UX contract:** header title = current zone name; primary action = `Save & Continue` bottom CTA; disabled reason = "Capture required photo first" when evidence missing; FAB = `extended FAB` on defects, label "Add defect"; badge count = `defects.filter(d => d.zone === currentZone).length`.
- **Audit:** On zone Save: event 100000001 (Zone Step Completed); payload: zoneIndex, zoneName, completedAt, evidenceCount, defectCount.
478
-
- **Lookup writes:** `'cr3e9_Inspection@odata.bind': '/cr3e9_inspections(<id>)'` on every zone-progress upsert.
483
+
- **Lookup writes:** exact generated-model key, for example `'cr3e9_inspectionid@odata.bind': '/cr3e9_inspections(<id>)'`, on every zone-progress upsert.
0 commit comments