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
+19-30Lines changed: 19 additions & 30 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -207,56 +207,45 @@ and document their role instead.
207
207
## Step 6a — Cross-entity Read Audit
208
208
209
209
**Print before starting:**
210
-
> "→ Auditing planned screens for cross-entity reads (calc-column candidates)…"
210
+
> "→ Auditing planned screens for supported cross-entity read paths…"
211
211
212
212
**Run condition:** execute this step when EITHER (a) `<working_dir>/_screens_section.md` exists at this point in the workflow OR (b) you were invoked with `mode: cross-entity-audit`. **Skip silently otherwise** (default-mode first-pass run, before screen-planner has produced its section) — the orchestrator will re-spawn you in `mode: cross-entity-audit` after Gate 4a/4b lands.
213
213
214
214
When `mode: cross-entity-audit`, the orchestrator's prompt also includes the path to the existing `_dm_section.md` so you can append (do NOT regenerate it from scratch — Steps 1–6 are skipped in this mode).
215
215
216
-
This step exists because of the runtime constraint documented at [`shared/references/data-performance.md` § Cross-entity Reads](${PLUGIN_ROOT}/shared/references/data-performance.md#cross-entity-reads) — the SDK has no `$expand`, so cross-entity fields on hot paths (lists, dashboards) MUST be denormalized via calculated columns at the data-model layer. This step proposes those calc columns based on the screen plan; `/setup-datamodel` (or `/add-dataverse`) Phase 6.1b creates them.
216
+
This step exists because the generated SDK has no `$expand`. It classifies each
217
+
cross-entity field into a supported formatted lookup or bounded chained fetch.
218
+
Dataverse does not support defining calculated/formula expressions through
219
+
code, so this audit never proposes generated formula metadata.
217
220
218
221
**Algorithm:**
219
222
220
223
1.**Read the screen plan.** Look for `<working_dir>/_screens_section.md` first (graph-only mode after Gate 4a). If absent, parse `<working_dir>/native-app-plan.md` and extract the `## Screens` section. Walk every per-screen spec and collect every `related_entity_fields` block.
221
224
222
225
2.**Per entry, branch on `recommends`:**
223
226
224
-
-**`recommends: calc-column`** — first classify the projection instead of assuming every dotted path is a supported formula:
225
-
-**Supported calculated projection** — a scalar value reachable only through N:1 lookups and supported by the target Dataverse formula capability. Emit a calc column.
226
-
-**Lookup annotation** — the screen only needs the related record's display name already exposed by the lookup. Reuse the lookup annotation; create no column.
227
-
-**Materialized projection** — the value must be snapshotted for history/offline use or the formula capability is unsupported. Emit an ordinary column and state which create/update flow owns synchronization.
228
-
-**Chained fetch** — 1:many, M:N, aggregate, conditional, or otherwise unsupported navigation. Create no column; the screen-builder fetches it.
227
+
-**`recommends: formatted-lookup`** — verify the source is the primary
228
+
display name of a direct lookup. Create no column.
229
+
-**`recommends: chained-fetch`** — create no column. The screen-builder
230
+
performs one bounded related request outside row rendering.
231
+
-**`recommends: external-projection-required`** — record a blocker for a hot
232
+
list/dashboard field that cannot use the direct lookup annotation. Omit
233
+
the field until the user supplies a maker-created formula column or other
234
+
server-owned projection.
229
235
230
-
Confirm the `cardinality` is `1:1` (calc columns CANNOT traverse 1:many or M:N). If cardinality or formula capability is unsupported, choose `materialized projection` only when snapshot semantics are required; otherwise downgrade to `chained-fetch` and add a `DONE_WITH_CONCERNS` note. For a supported calculation, propose a calculated column on the **primary entity of that screen** (the entity its primary `Data` service queries):
231
-
-**Logical name:**`<prefix>_<resolved_field>_calc` (lowercased, e.g. `cr3e9_gatename_calc`)
-**Display name:** human label from the planner's `field` value (e.g. "Gate name")
234
-
-**Type:** matches the resolved field's TypeScript type → Dataverse type (`string` → `Edm.String`, `datetime` → `Edm.DateTimeOffset`, `decimal` / `money` → `Edm.Decimal`, `integer` → `Edm.Int32`, `boolean` → `Edm.Boolean`)
235
-
-**Formula source:** the dotted path from the planner's `source` field, normalized — e.g. `cr3e9_flightid → cr3e9_gateid → cr3e9_gatename` becomes `cr3e9_flightid.cr3e9_gateid.cr3e9_gatename`. The formula is N:1 lookup chain only; no aggregations, no conditionals, no string concat in v0.
236
-
237
-
-**`recommends: chained-fetch`** — do NOT add any column. The screen-builder handles this at scaffold time per the decision table in `data-performance.md`. Just include the entry in the addendum's `Chained-fetch fields (informational)` row so the user sees what the screen-builder will scaffold.
238
-
239
-
3.**De-duplicate.** A field driven by N screens (e.g. "Gate name" used on home, list, AND detail) collapses to ONE calc-column row in the addendum. Track all driving screens in the `Driven by` column.
240
-
241
-
4.**Cap at 20 calc columns per parent entity.** If you exceed, truncate and add a `DONE_WITH_CONCERNS` note — large calc-column counts indicate a denormalization problem that should be solved at the data-model level (probably an extracted entity), not by piling on calc columns.
236
+
3.**De-duplicate.** Collapse identical source/resolution pairs and track all
237
+
consuming screens.
242
238
243
239
5.**Emit the addendum.** Write the `### Cross-entity Reads (auto-derived from screen plan)` subsection of `_dm_section.md`. Schema:
244
240
245
241
```markdown
246
242
### Cross-entity Reads (auto-derived from screen plan)
247
243
248
-
| Calc column | On table | Type | Resolves | Driven by |
In `mode: default` (Step 6a runs because `_screens_section.md` was found), append this subsection to the Step 7 output. In `mode: cross-entity-audit`, append it directly to the existing `_dm_section.md` (read it, append the subsection AFTER `### Notes` if present, otherwise at the end, then write back) and skip Step 7 entirely — return immediately.
> "→ Auditing the locked screen plan for cross-entity reads (calc-column candidates from related_entity_fields blocks)…"
501
+
> "→ Auditing the locked screen plan for supported cross-entity read paths…"
502
502
503
-
**Run condition:** execute this step ONLY after Gate 4b has been approved AND the screen-planner's per-screen specs include at least one `related_entity_fields` block. Skip silently otherwise (no cross-entity reads = no calc-column proposals needed).
503
+
**Run condition:** execute this step ONLY after Gate 4b has been approved AND the screen-planner's per-screen specs include at least one `related_entity_fields` block. Skip silently otherwise.
504
504
505
505
**Detection (cheap):** before spawning, `Grep` the locked plan for `related_entity_fields:` in `<working_dir>/native-app-plan.md`. Zero matches → skip Step 5c entirely, mark `[x]` and proceed to Step 6. One or more matches → spawn the audit pass below.
506
506
507
-
This step exists because of the runtime constraint documented at [`shared/references/data-performance.md` § Cross-entity Reads](${PLUGIN_ROOT}/shared/references/data-performance.md#cross-entity-reads) — the SDK has no `$expand`, so cross-entity fields on hot paths (lists, dashboards) MUST be denormalized via calculated columns at the data-model layer. The screen-planner emits `related_entity_fields` per screen; this step turns those into calc-column proposals.
507
+
This step exists because the SDK has no `$expand`. It verifies that every
508
+
cross-entity field uses a formatted lookup or bounded chained fetch, and flags
509
+
hot-path fields that require an externally supplied projection. It never
510
+
synthesizes calculated/formula metadata.
508
511
509
512
#### 5c.1 — Spawn `data-model-architect` in `cross-entity-audit` mode
510
513
@@ -530,22 +533,23 @@ Wait for return; apply the Step 3.0 status switch:
530
533
-`DONE_WITH_CONCERNS: <list>` → embed addendum, propagate concerns into your own final `DONE_WITH_CONCERNS:`.
531
534
-`NEEDS_CONTEXT:` / `BLOCKED:` — propagate up per the standard switch.
If 5c.1 wrote a `### Cross-entity Reads` addendum, present it to the user as a Gate 1 addendum (NOT a fresh Gate 1 — the original schema is already approved and unchanged):
536
539
537
540
```
538
541
## Gate 1 — Addendum: Cross-entity Reads
539
542
540
-
The screen plan you approved at Gate 4b reads N fields from related entities (gate names on inspections, customer phones on orders, etc.). Because the Power Apps SDK has no $expand, those fields need calculated columns on the parent tables to display efficiently — otherwise list screens would either render "—" or trigger N+1 fetches per row.
543
+
The screen plan reads N fields from related entities. The generated SDK has no
544
+
$expand, so each field must use a formatted lookup, a bounded chained fetch, or
545
+
an external server-owned projection.
541
546
542
-
Proposed calculated columns (auto-derived from your screen plan, no schema reshape):
547
+
Proposed read paths:
543
548
544
549
[paste the ### Cross-entity Reads table from _dm_section.md]
545
550
546
-
[paste the Chained-fetch fields (informational) table if present — these need NO schema change, the screen-builder handles them at scaffold time]
547
-
548
-
Approve to add these calc columns to the data model? (Reject → revise the audit. Approve → /setup-datamodel will create them in Phase 6.1b.)
551
+
Approve these read paths? Any `external-projection-required` row remains a
552
+
blocker until the user supplies that projection outside this workflow.
549
553
```
550
554
551
555
Reject loop = re-spawn data-model-architect in `mode: cross-entity-audit` with the user's feedback (e.g. "drop cr3e9_tailnumber_calc, the list doesn't actually show it"). Approve = mark `[x]` Gate 1 addendum approved, proceed to Step 6.
Copy file name to clipboardExpand all lines: plugins/mobile-apps/agents/screen-builder.md
+12-12Lines changed: 12 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -116,26 +116,26 @@ You will be invoked by `/create-mobile-app` Step 11 or `/edit-app` screen-rebuil
116
116
```
117
117
118
118
For lookup labels, select the real `_<lookup>_value` field in your `$select` and read with `lookupName(record, '<lookupLogicalName>')`. For choice / status / boolean / datetime / money labels, read with `formattedValue(record, '<columnLogicalName>')` or fall back to the generated option const. On bounded lists that use `useSearchFilter(...)`, fields MUST be real string properties from generated types; never add an inferred display-name field just to make search prettier. Cursor lists do not use `useSearchFilter`; they push search into the service `filter` option.
119
-
-**HARD RULE — Cross-entity Field Resolution.** Before writing the screen's `select: [...]` or load step, walk every UI field your spec displays. For each field that sources data from an entity OTHER than the screen's primary fetch target, follow this algorithm exactly. The full reference (with cost-profile rationale, calc-column naming, and pattern examples) is at [`shared/references/data-performance.md` § Cross-entity Reads](${PLUGIN_ROOT}/shared/references/data-performance.md#cross-entity-reads). The screen-builder MUST apply the rule mechanically — do NOT invent your own resolution.
119
+
-**HARD RULE — Cross-entity Field Resolution.** Before writing the screen's `select: [...]` or load step, walk every UI field your spec displays. For each field that sources data from an entity OTHER than the screen's primary fetch target, follow this algorithm exactly. The full supported-path reference is at [`shared/references/data-performance.md` § Cross-entity Reads](${PLUGIN_ROOT}/shared/references/data-performance.md#cross-entity-reads). The screen-builder MUST apply the rule mechanically — do NOT invent your own resolution.
120
120
121
-
1.**Calc column check first.** Open `src/generated/models/<PrimaryEntity>Model.ts` and search for a column matching `<prefix>_<field>_calc` (or any `_calc`-suffixed column resolving the field you need). If present, add it to your `select: [...]` and render directly. Done — no chained fetch needed.
122
-
2.**No calc column? Branch on screen archetype × cardinality** (archetype is in your spec under `**Archetype:**`):
121
+
1.**Follow the planned recommendation** from `related_entity_fields`.
123
122
124
-
|Archetype | Cardinality| Action |
123
+
|Recommendation| Action |
125
124
|---|---|---|
126
-
|List (`top ≥ 5`), Tab-root, Dashboard | 1:1 (N:1 lookup chain) |**STOP — do NOT chain a fetch in the list `map()` / `renderItem`.** Emit a `// TODO(cross-entity-read):` header comment naming the field, the related entity, and the recommended `<prefix>_<field>_calc` column to add. Render the cell as `'—'` for now. Then return `BLOCKED [<screen_name>]: list field <field> requires cross-entity read; needs calc column <prefix>_<field>_calc on <primary_entity> — re-run /setup-datamodel (or /add-dataverse for existing apps) to add the calc column.` Do NOT scaffold an N+1 fetch storm. |
127
-
|Detail (single record) | 1:1 (N:1 lookup chain) | Scaffold a chained `<RelatedService>.get(record._<lookup>_value, { select: [...] })` in the screen's load step. One record on screen = one extra round trip is fine. Display via `lookupName(...)` or direct field read. |
128
-
|Any|1:many or M:N| Scaffold a chained `<ChildService>.getAll({ filter: \`_<parentid>_value eq '${id}'\`, select: [...] })`. Calc columns CANNOT traverse 1:many or M:N — chained fetch is the ONLY pattern. |
125
+
|`formatted-lookup`| Select the real `_<lookup>_value` field and render `lookupName(record, '<lookupLogicalName>')`. |
126
+
|`chained-fetch`| Perform one bounded related `get` / `getAll` in the screen load step, never inside `map()` or `renderItem`. |
127
+
|`external-projection-required`|Render no fake fallback data. Return `BLOCKED` and name the field/source. The user must supply a supported server-owned projection outside this workflow. |
129
128
130
-
3.**Verify before exit.** Every UI field in your spec must have either (a) a `select` entry on the primary fetch (covered by direct column or calc column) OR (b) a chained fetch path. If a field has neither, return `BLOCKED [<screen_name>]: field <field> on <screen> has no fetch path — add to spec or add calc column`.
129
+
2.**Verify before exit.** Every UI field in your spec must have either a
130
+
primary select, formatted lookup annotation, or bounded chained fetch.
131
+
Otherwise return `BLOCKED [<screen_name>]: field <field> requires an external projection`.
131
132
132
-
4.**TODO comment shape**(when emitting at step 2 / list branch):
133
+
3.**TODO comment shape**for an external projection:
133
134
134
135
```ts
135
136
// TODO(cross-entity-read): screen displays <field> from related <entity>.
136
-
// Re-run /setup-datamodel (or /add-dataverse for existing apps) and add calc
137
-
// column <prefix>_<field>_calc to <primary_entity> for one-round-trip reads.
138
-
// List screens MUST NOT chain fetches in renderItem — N+1 storm.
137
+
// Supply a maker-created formula column or another server-owned projection,
138
+
// then rerun Dataverse reconciliation. Never chain reads in renderItem.
139
139
```
140
140
141
141
-**HARDRULE — server-managedcolumnsareNEVERinacreateorupdatepayload.**TheDataverseserverownsthesefields; includingthemina`*Service.create({...})`or`*Service.update({...})`returnsHTTP400oneverysave. Generated`create()`typesmayincludeserver-managedfields (`ownerid`, `statecode`, primaryIDs, etc.) becausetheymirrorthefullmodel; do**not**satisfythosetypesbyemittingjunkvalues. Foranyscreenwithcreate/updatebehavior, useanarrowwritehelper/typewhose input contains only editable fields. If the skeleton imports an app-level helper, call it; otherwisedefinethehelperinsideyourassignedscreenfile. Do**not**createormodifyshared`src/utils/`, `src/hooks/`, orservicefilesfromascreen-builder. Forbiddenkeysinanycreate/updatepayload:
0 commit comments