All notable changes to the model-apps plugin.
Adds plugin update notices, proves what persona roles actually grant, makes jobs-to-be-done surfaces checkable, fixes four crash paths, and corrects a smoke-eval assertion that could never pass live.
verifynow proves what a persona security role GRANTS, not just that it exists. Therolecheck only asserted a role row carrying the SDK ownership marker, so a role built with the wrong access — or one whose privilege write failed after the row landed — verified clean. The newrole-privilegescheck resolves every declared(entity, access)to its DataversePrivilegeIdfrom the same metadata source the SDK writes against, and asserts the role holds it at at least the declared depth. A subset check by design: extra privileges are never a finding, becauseappAccessinjectsappmoduleread, unioned jobs escalate a shared entity+access to the max declared scope, and distinct entities can share one Dataverse privilege. Fails closed on an unreadable role or table. Reader-gated, so existence-only callers are unchanged.personas[].jobs[].surfaces[]is checked instead of documentary. Each entry is now resolved against the spec's own views, forms, pages, dashboards, tables and sitemap titles.spec-lintwarns when a surface matches nothing — a warning, not an error, because a surface may legitimately name an out-of-the-box artifact this spec never authors.verifyadds ajob-surfacerollup that reports a deployed failure as the job it broke ("persona P can no longer do job J"), rather than only "view X is missing".- Automatic plugin update notice. Every user-invocable skill now runs the
non-blocking
scripts/check-version.jspreflight, which compares the installed Model Apps version withorigin/mainand shows update commands for the active GitHub Copilot CLI or Claude Code host when a newer version is available.
- Malformed specs now produce validation errors instead of raw
TypeErrors.validateAppSpec()andlintAppSpec()crashed on anullspec, an object- or string-shaped collection (entities: {}), andnullentries inside a collection;preview-appcrashed when a persona privilege'saccesswas a scalar rather than an array. These are work-in-progress shapes an author hits constantly, and a crash killed the authoring flow instead of reporting the problem. Coverage is now a single recursive descriptor shared by both gates (lib/spec-shape.js), reaching nested collections too —entities[].columns,views[].filters,forms[].tabs[].sections,pages[].dataSources,commands[].buttons[].children, and theappShell.areas → groups → subAreaschain — and errors name the exact path (appShell.areas[0].groups must be an array). - A malformed collection can no longer pass validation and then crash mid-build. Validation
inspects a normalized copy while the caller keeps the original, so a silently-repaired
appShell.areas: [null]reported PASS and then threw inside the build — after the solution and data model had been written to Dataverse. A null entry is now a blocking error, so the failure happens at the gate with nothing deployed. migrateAppSpecno longer crashes ahead of the gate. Every CLI entry point migrates the spec it just read before validating it, so a malformed collection threw a rawTypeErrorbefore the validator that exists to report it ever ran. Migration is now defensive but does not repair — repairing would hand the gate a clean spec and the real problem would vanish.verify-model-appno longer surfaces a raw Dataverse HTTP 400 for a missing table. A declared table that does not exist makes the saved-view query 400 (returnedtypecodenames an unknown entity); the read error is now captured and reported as a structured missing-artifact finding. Both the verify CLI and the build's verify step now print the failuredetail, so a read that failed (throttling, auth expiry, a 5xx) is distinguishable from an artifact that is genuinely absent instead of both rendering as a bare✗ view: <name>.- The live smoke eval asserted an outcome the builder never produces. Its spec put a bare Fluent
vectorIcon("Grid") on an entity subarea — the one shape the builder deliberately drops, because it breaks the modern app-designer property pane — while asserting the deployed sitemap containedVectorIcon="Grid". The offline test hid it by hand-writing the sitemap XML it wanted to see. The spec now uses an emittable/WebResources/<name>.svgreference and keeps the bare token as a negative control; assertions are scoped to the<SubArea>that declared the icon (the spec reuses one icon on the parent<Area>, which a document-wide scan let satisfy every subarea check) and their expected values stay independent of the builder, so a builder that stops emitting the icon makes the eval FAIL rather than silently invert into an absence check.
download-model-app.js --appnow accepts a display name, not just an id oruniquename. It resolves in identity order (id →uniquename→ display name) and fails closed when a display name matches more than one app, listing the candidate unique names instead of guessing. A display name previously hit a dead-end "app 'x' not found", even though that is the only name the maker portal shows.
- A contract test drives the real vendored SDK and asserts the serialized sitemap bytes: a
platform-ref
VectorIconreaches the XML on an Entity subarea, and the bundle does not filter a bare Fluent token — pinning thatappDefis the only guard, so the drop cannot be delegated to the SDK.
Fixes a malformed app module: generated apps did not actually contain their tables.
- Generated apps contained invalid
entitytable components instead of their real tables (ADO 6612527). An exported app read<AppModuleComponent type="1" schemaName="entity" />where it should have listedaccount,contact,activitypointer— and the malformed app then broke unrelated app-processing and metadata-discovery paths. Tables are now pinned by OData reference ({ '@odata.id': '<EntitySetName>(<MetadataId>)' }) instead of as an@odata.typeinstance:Microsoft.Dynamics.CRM.entitynames a real Dataverse table (metadata-as-data), so the old payload pinned theentitytable exactly as asked. The reference form is also the only one that can express an abstract EDM table such asactivitypointer. - A table that cannot be resolved now halts the build, naming it. One bad component fails the
whole
AddAppComponentscall, so a silently-skipped table previously emptied the app's component list rather than degrading it. - App components are read back and verified after the write.
AddAppComponentsreturned 204 for every corrupt app — a 2xx means the request was accepted, not which rows it wrote — andValidateAppreported success too. The build now asserts every declared table has acomponenttype: 1row carrying that table's MetadataId, and fails closed if it cannot check.
- Re-vendored
cds-maker-sdkwith the above.
app-entity-components-real-bundle.test.jsdrives the shipped bundle: tables sent as references, an unresolvable table refused, the read-back catching both a missing component and the exact 6612527 corruption (rows present but pointing atentity). 6 of its 7 tests fail against the previous bundle.
- ADO 6603388 (download drops entity components not in the sitemap) is still open. A live attempt to construct the hidden component it describes did not succeed — pinning a table with no sitemap entry returned 204 but wrote no row, before or after publish — so the download-side change cannot be verified end to end yet.
- A value-less or malformed runner flag is now rejected instead of silently changing scope.
argv[++i]isundefinedfor a trailing flag andundefinedis falsy, so--tieralone became "no tier filter" and--fixturesalone fell back to the built-in fixtures — the run then reported PASS for a scope the caller never asked for.--eval 1.5was truncated to fixture1and graded the wrong one; an unknown--tierproduced "no fixtures matched the filter", blaming the fixtures rather than the argument. All three runners (app-builder + genpage layers 1/2) shipped a byte-identical copy of this parser, so it is now shared atevals/model-apps/lib/eval-args.js. - A malformed fixture names the fixture. A bare
JSON.parsereported only a character offset, which tells an operator running a corpus nothing about which fixture to fix. A UTF-8 BOM (the Windows editor default) no longer fails an otherwise-valid file, and a spec that isnull, an array, a string or a number is rejected up front instead of surfacing later as an opaque stage-facts error.
Bug fixes for apps built on out-of-the-box tables, and the matching cds-maker-sdk uptake.
No change to any skill's public surface.
- AI app features had no effect on a newly built app — an app-scope setting write is a no-op until the app is published, so the build wrote nothing while reporting success. The write is now re-issued after publish.
--verifyPASSed when AI features were skipped or never applied — it now proves an app-scope override row inappsettings. Reading the setting back is unsound:RetrieveSettingfalls back to the environment value when an app has no override.ai.appFeaturescould not express non-boolean values such as2("on for everyone") — a value may now be a boolean or an integer0..1000000.- Download invented primary-name columns (
account_name,contact_name— neither exists) — now read from Dataverse metadata, never synthesized. Because a spec requiresprimaryAttribute, a table whose metadata does not supply one can no longer be emitted: a table reached from the app's navigation now fails the download naming it (--allow-lossy-downloaddrops it instead), while a table found only as a hidden component is dropped with a warning. - Download replaced the solution's publisher prefix with
new— now read from the solution's owning publisher. - Download dropped tables with no sitemap entry — the entity set is now the sitemap set unioned with tables owned by the app's view/chart/form components.
- Teardown could permanently burn an app's unique name — an app is two rows (
appmodule+sitemaps) with no server-side cascade, so deleting only the app module stranded the sitemap and reserved its name forever. Both rows are now deleted atomically in one OData$batch, and any delete that cannot be proven refuses rather than guessing. - An unreadable app produced a raw SDK throw instead of the download's documented
{ ok: false, error }.
- Re-vendored
cds-maker-sdk. An injectedHttpClientmust now implementpostRaw(verbatim multipart body out, raw response string back) for the atomic$batch; without it app deletion fails withAPP_DELETE_NOT_ATOMIC. The plugin's client implements it and does not retry a$batch— it carries record deletes, and a racing retry wedges the row.
- 1266 → 1340 tests; coverage 92.7 → 93.9% line, 82.7 → 83.6% branch.
- model-apps now runs in CI (
model-apps-script-tests, ubuntu × windows × macos, Node 20 × 22, plus the offline evals) — previously every test workflow was scoped toplugins/power-pages/**, so this suite never ran on a PR. - Real-bundle suites (
ai-app-features-real-bundle,app-delete-real-bundle) drive the shipped vendored bundle, including one test that wires the real HTTP transport to it — the SDK's own Jest suite cannot run here (Node-20-ABIcanvas).
- Table (
entity) app components cannot be pinned viaAddAppComponents— the documented shape returns 204 but records a component pointing at the metadata table namedentity. Platform defect AB#39140211; until it is fixed a table with no sitemap entry cannot be added to an app.
A new /app-builder skill (Preview) that builds and edits whole model-driven apps,
plus local-dev ergonomics, sample coverage, and an automated eval suite. Builds on v2.3;
no breaking changes.
- Table icons are described before they are drawn — each custom table proposes what its glyph
will depict in plain language (
entities[].iconDescription, e.g. "an outlined clipboard with a checkmark"), shown inmodel-app-plan.mdfor approval before any SVG is authored. A Fluent token name is rejected: the SVG is drawn fresh, so a token the user has never seen describes nothing. Also valid on sitemap areas, groups and non-entity subareas. - Jobs-to-be-done drive the design — authoring now starts at Level (a0) by asking who uses the
app and what each of them needs to get done, before the data model, and carries those jobs
through to the surfaces that satisfy them (
personas[].jobs[].surfaces[]). Previously jobs were only asked for at the end, to size security roles — and the playbook contradicted the skill by declaring roles out of scope, so they were never enumerated at all. scripts/write-app-spec-doc.js— rendersmodel-app-plan.md, a readable design document (jobs → surfaces traceability, data model, every surface, navigation, access model, sample data) from the spec. It replaces a hand-written counts summary, so it is complete, always agrees with what will build, and is regenerable after any edit.- Design-gap warnings at the lint gate — jobs with no covering surface, an app with no generative pages, or no personas at all are now surfaced as warnings instead of passing silently.
/app-builderskill (Preview) — natural-language intent → deployed model-driven app: tables, columns, relationships, adaptive forms with sub-grids, views, Choice-column charts, dashboards, generative pages, app + sitemap, and sample data, via the headless vendoredcds-maker-sdk.- Security roles per persona (
personas[]) — one role per persona, sized to the privileges its jobs declare (unioned, max scope wins). Injectsappmoduleread and associates the app so it opens for non-admins (appAccess: falseopts out). Idempotent and converging; fail-closed on a same-name role the builder did not author. --changed-onlypartial apply (Preview, off by default) — after a fresh baseline, a page-only.tsxedit re-runs just the pages phase; any other change falls back to a full build.- AI-first features (
aiblock →ai-featuresphase) — form fill, NL search, NL charts, M365 Copilot and row summaries, admin-gated viaai-preflight.js. - Table icons (
entities[].vectorIcon/icon), plus a semantic default icon per table from the authoring flow instead of the stock cube. - Opt-in auto sub-grids (
forms[].autoSubgrids: true) — a sub-grid on the parent form per 1:N child. - Form edits can REMOVE a field, not just add one; forms and views now update in place on edit.
- The app shows your form, not the blank stock form — a built main form becomes the entity default.
- Parent lookups surface in views — 1:N lookups appear in auto-layout, authored, and built-in default views.
- Fail-closed page deployment + verification —
PAGEREF_<key>navigation is resolved to real page ids before upload and an unresolved token halts the build; a manifest-aware download round-trips pages. scripts/preview-app.js— renders the whole app design (data model, sitemap, forms, page intents) for review before building.- Page generation reuses the
/genpageworker via a plan adapter —write-page-plan.jsprojects the App Spec into thegenpage-plan.mdthe page-builder actually reads, so an intent page can no longer silently fail to become.tsx. Untrusted spec text (including download-derived) is neutralised so it cannot forge plan sections. promote-intent-pages.js— validates every generated page (written, structurally a module,PAGEREF_tokens in exact parity withnavigatesTo) and flips them allintent → tsxin one atomic write, or exits 3 leaving the spec untouched.- Eval suites — an offline
/app-builderstructural harness (evals/model-apps/app-builder/) and the genpage Layer 1/2 TAP runners with shipping fixtures. check-auth.js --envsupport — SDK builds accept--env <url>or a positional URL, treat a missing PAC login as a warning, and reserve--require-pacfor genpage./app-builderis covered by the v2.3 hooks + telemetry — tracked-skill discovery is derived fromskills/*/SKILL.md, and the write-safety and icon-import guards now recognise app-builder working dirs.- Docs + marketplace metadata cover both skills —
/app-builderis documented as Preview in the plugin and repository READMEs.
- The connectors feature flag is re-probed before code generation — the result is passed as
Connectors: enabled|disabledin every page-builder dispatch and overrides the plan, so a plan authored while the flag was ON can no longer emit connector calls the run never binds. - Surface classification is explicit — the authoring flow now enumerates every surface each job needs and classifies it (record CRUD → form + view; overview/dashboard/analytics/wizard/composite → generative page), and states each call out loud. Pages were previously one item in a long list behind a "never force-add it" hedge, so they were routinely skipped.
- Forms resolve by
(entity, name, type)— a table's same-named Main/Quick View/Card forms no longer block an edit, and teardown no longer over-deletes them. - Views are identified by
entity|name— same-named views on different tables no longer cross-wire a dashboard tile, sub-grid, or snapshot entry to the wrong view. - App identity round-trips by its real uniquename — no duplicate app on rebuild.
- Nav icons round-trip — custom icon web resources are re-declared on download (portable across environments) and entity-subarea sitemap icons survive a download→build cycle.
- Pre-existing duplicate page names no longer block an unrelated build — the check only errors when a new page collides.
- Staged-flow authoring — design-only authoring, an explicit generate-pages step, a
--stageselector, and fewer approval gates with checks run earlier and automatically. - Sample-data idempotency is explicit (
seedRecordGraph.matchOn), and every artifact push is verified (requireSuccessfulPush). - Re-vendored
cds-maker-sdk— backlog capability fills (pagination, quick create, idempotent global choice, authored column width), shared input-safety boundaries, and thehardening-2bundle.
- Teardown leaves nothing behind — the table icon and generated app-icon web resources are removed (web resources delete after tables), cascade cleanup failures are reported instead of silently orphaning rows, and reused/system tables are skipped with a reason rather than erroring.
- Command-bar teardown is fail-closed — only the bar for a table this spec created is deleted, so a command on an existing table can never remove another app's buttons.
- Exported solutions are self-contained — the app icon and sitemap are added to the solution, so import no longer fails on missing components.
- Relationships to a standard/system table no longer halt the build — the schema name auto-prepends the publisher prefix.
- System tables keep their default sitemap icon — the transparent-spacer placeholder is stripped.
- Editing an existing app updates the sitemap for page-less apps too.
- Sub-grids pass
targetEntityto the SDK — prevents empty<TargetEntityType/>output and edit-time validation failures. - Classic dashboards round-trip through download/edit with id-passthrough tiles, and dashboard tiles render in a 2-column grid.
- AI row summaries errored on every record — the prompt filtered on the primary name column instead of the primary key.
- CLI flags fail loudly instead of silently — a value-less flag no longer passes the usage guard;
notably
--apply --only(with no phase list) used to run a full apply. - The Dataverse token is never sent to another origin — the HTTP client requires an absolute
httpsorg URL and refuses any request outside it. - A lossy download fails instead of reporting success — unmapped sitemap subareas are named and the
spec is validated before it is written (
--allow-lossy-downloadopts in). - Assorted robustness — temp workspaces cannot leak on a failed SDK init,
%in prompts survives cmd.exe, an omitted columntypedefaults toText,vectorIconis verified in the sitemap, and entity-subareavectorIconno longer breaks the app designer.
- Plugin unit tests (+ optional vendored SDK suite):
node scripts/run-tests.js --with-sdk <ppux>fromplugins/model-apps/. - Eval fixtures run separately, from the repo root:
node evals/model-apps/genpage/run-layer-1.js,node evals/model-apps/genpage/run-layer-2.js,node evals/model-apps/app-builder/run-app-builder.js. - Vendored-SDK contract tests lock OData filter encoding, name-based identifiers, and sitemap free-text XML escaping.
- Standalone entity/solution scripts, consolidated into the SDK —
create-table.js,add-column.js,create-relationship.js,create-record.js,create-solution.jsandadd-to-solution.jsare replaced byprovision-entities.jsandprovision-solution.js.
- App EDIT does not re-pin a new chart as an explicit app component — a chart added to an existing app needs a manual pin or a rebuild.
Plugin observability + authoring guardrails: default-on (but ship-disabled) anonymous telemetry with a local diagnostic log, PostToolUse validators, and a hardened Playwright launcher. No breaking changes.
- Anonymous 1DS telemetry (default-on, ships
disableduntil provisioned). Copied the shared telemetry library intoscripts/lib/telemetry/libwith a plugin-ownedikey.json(Tier-1 static key; shipsdisabled: trueuntil go-live). Emitsskill_startedvia PreToolUse(Skill) + UserPromptSubmit hooks and writes a local diagnostic mirror at~/.power-platform-skills/telemetry/model-apps/sessions/<id>/events.jsonl. New/model-apps:telemetry on|off|statuscontrol skill; CI/automation opt-out viaPOWER_PLATFORM_SKILLS_TELEMETRY_MODEL_APPS_OPTOUT=1. Fail-closed throughout — never changes a skill's exit code. Carries no user-level identifier (no Entra object id) — only org/tenant GUIDs when signed in. - PostToolUse validators (
hooks/hooks.json). A per-skill validator runner plus an@fluentui/react-iconsallowlist check that validates every genpage.tsxwrite againstreferences/verified-icons.txt, automating the page-builder's manual icon-grep step (hallucinated or sized icon names are blocked at write time). - PreToolUse write-safety guard. Flags (non-blocking, exit 1)
Write/Edit/MultiEdit outside the cwd, and only during an active genpage session
(a
genpage-plan.mdat/under cwd) — so a globally-installed plugin never blocks or interferes with unrelated work. Silence withMODEL_APPS_SKIP_WRITE_GUARD=1.
- Generated-page double-fetch / render flash on open. Generated data pages
could fetch twice and re-flash the spinner because (1) the webplayer host
double-mounts the page on open (a cache-bypassing app relaunch ~300ms after
the first mount re-runs the data effect — confirmed via network capture: two
POST .../powerapps/apps/<app>/launch, the secondbypass-cache=true), and (2)dataApiis a new reference each render, so listing it in auseEffectdep array re-fires the effect every render. Reworked the data-fetch guidance (references/data-caching.md,rules.mdRule 15) and every exemplar (samples 3/9/10/11,localization.md) to use an in-flight-promise de-dupe +windowcache (concurrent mounts share one round-trip; later mounts paint from cache with no spinner) and a readiness boolean dependency —dataApiis now forbidden in any dependency array. The de-dupe applies to any page that fetches on mount, including single-visit overviews/dashboards (previously excluded from caching);Needs caching:in the plan schema now means "fetches on mount." The host relaunch itself is a platform-side issue tracked separately. - Playwright MCP launcher.
scripts/launch-playwright-mcp.jsnow exportslaunch()— satisfying the.mcp.jsoncontract instead of relying on a require-time side-effect — adds-y(avoids the npx first-run prompt hang), opens the browser fullscreen viaplaywright-mcp-fullscreen.config.json, and quotes the config path so Windows paths with spaces work. Browser detection extracted to the reusablescripts/lib/detect-browser.js. - Eval fixture
18-sharepoint-connectors-on: relabel the connector-only pageData mode: mock + connectorsto match the page-builder contract (connectors are orthogonal to the Dataverse axis).
- New
node:testcoverage for the launcher,modelapps-hook-utils, the icon-import and write-safety validators, and the telemetry pretool hook.
Local-dev ergonomics, sample coverage, and an automated eval suite with real and synthetic fixtures. Builds on v2.1; no breaking changes.
- Phase 0.5 — local-dev manifest. Working dirs now get
package.jsonandgenpage.d.tssonpm install+ editor IntelliSense work after generation. Versions inreferences/supported-dependencies.md. - Eval suite runners.
run-layer-1.js(workflow assertions) andrun-layer-2.js(code assertions) emit TAP v13.EVAL_GUIDE.mdcovers types, tiers, capture flow. - 10 fixtures under
evals/.../fixtures/(6 synthetic + 4 real captures; all green under the tightened v2.2 spec). scripts/capture-fixture.js— copies/genpageworking dirs into fixtures and runs both layers.samples/11-kanban-with-dnd.tsx— native HTML5 drag-and-drop sample.samples/12-dialog-form-overlay.tsx+ Dialogs and Overlays guidance (rules.md rules 16–18 and Special Patterns section, plus a troubleshooting entry): confine portalled Fluent surfaces (Dialog,Popover,Menu,Tooltip,Combobox/Dropdown) to the page viamountNode+contain: layout, default dialogs tomodalType="non-modal", and never nest dialogs — so a modal can't escape the preview and cover the designer / coding-agent panel.- Feature-flag gate for connectors (default OFF).
feature-flags.jsonat the plugin root plusscripts/lib/feature-flags.jsgate connector support so the skill can ship ahead of its cross-repo dependencies (pac connector verbs, the GenUX authoring control, and the maker/admin setting) reaching PROD. When OFF, the planner skips connector discovery and recordsNo connector bindings., and the connector scripts (list-connections.js,create-connection-reference.js) fail closed with exit 3. Precedence: envGENPAGE_ENABLE_CONNECTORSoverrides the committed file; default is OFF (fail-closed). Flip the file totrueonce the dependencies are GA. genpage-connector-builderagent — single owner of connector work. Connector discovery, connection-reference creation, the feature gate, and the## Connector Bindingscontract now live in one agent invoked from both the create flow (planner) and the edit flow (edit-planner) — so edits can add/replace connectors (previously only preserve/clear worked) and the gate can't drift across markdown. Hardened per review: deploy (SKILL Phase 4.5) re-probes the flag and treats an absent/malformed## Connector Bindingsas no bindings; the page-builder emits connector code only for an actual binding table; the--connection-refsbranch ofadd-page-to-solution.jsis gated; scripts shareexitIfConnectorsDisabled(); andfeature-flags.jsgains--list,describe(), and config validation.
- Spec tightening so workflow-logs are command-verbatim and
pageInputdestructure is required even on mock pages (planner, page-builder, SKILL.md Phase 6 + Phase 8). - 8 runner regex relaxations to accept functionally-equivalent agent
patterns (typed
(window as any).Xrmaliases,pac solution list, local enum mapping, etc.) — no rule loosening.
- Synthetic fixtures + sample 11 now follow Rule 11 (queryTable returns
DataTable, not an array). 7 files were iterating
resultdirectly (setTasks(result),result.map(...)) instead ofresult.rows, producingX.map is not a functionat runtime. Fixed insamples/11-kanban-with-dnd.tsxand 6 fixture.tsxfiles. New Layer 2 assertion catches this pattern going forward: any Dataverse file callingdataApi.queryTablemust access.rowssomewhere.
- 215 passing across
scripts/tests/+evals/.../tests/.
Replaces the Dataverse MCP server + Python SDK fallback with Node.js Web API scripts. Adds solution selection, prefix discipline, and a consolidated auth pre-flight. Trim of ~27K tokens on hot-path page-builder runs.
- Azure CLI (
az) is now required for entity creation. Theazidentity must have access to the target Dataverse env (same as the activepacprofile). - Dataverse Skills plugin is no longer required. Soft dep removed.
.env,scripts/auth.py, and device-code prompts from the Dataverse Skills plugin no longer used.
- Node.js Web API scripts under
plugins/model-apps/scripts/:check-auth.js,dataverse-request.js,create-table.js,add-column.js,create-relationship.js,create-record.js(with$batchbulk),create-solution.js,add-to-solution.js,lib/dataverse-auth.js. - Solution selection in planner with prefix-conflict warnings.
- Transactional log at
<working-dir>/entity-creation-log.md. node --testcoverage underscripts/tests/(47 tests).
- Prefix drift made structurally impossible. Plan stores logical-name
suffixes only; entity-builder constructs
${prefix}_${suffix}from the singlePublisher Prefix:source of truth. pac model createalways passes--solution. Default value isDefault. The CLI's "active solution" fallback errors in practice.--promptis now scoped per upload role: full description on create, delta only on every subsequent upload (PAGEREF, fix re-deploy, edit flow).- Bulk-insert partial failure emits structured JSON to stdout (not
[object Object]). - entity-builder bash snippets no longer mix JS template literals.
- planner no longer shells
grep/awk/sed(Windows-incompatible).
- Page-builder no longer loads
verified-icons.txtupfront (~26K tokens saved per run). Validation switched to post-writeGreponly. rules.mdtrimmed −98 lines: dropped duplicated DataAPI type definitions (canonical source isRuntimeTypes.ts); tightened usage examples.rules.mdPage Input section trimmed −25 lines: pure prose tighten.- Phase 7 (browser verification) extracted to
skills/genpage/verify-flow.md, loaded only when the user opts in. SKILL.md trimmed an additional −95 lines. - Reference docs renamed for consistency:
genpage-rules-reference.md→rules.md,genpage-plan-schema.md→plan-schema.md,genpage-localization-reference.md→localization.md,data-caching-pattern.md→data-caching.md. - Removed stale
samples/3-poa-revocation-wizard.tsx(327 lines, redundant with2-wizard-multi-step.tsxfor the wizard pattern; the DataGrid / file-upload / multiselect patterns it composed are covered by other samples). Renumbered 4–8 → 3–7 to close the gap.
samples/8-dashboard-with-charts.tsx— KPI cards + two D3 charts (area + donut) with the animation guard from rules.md. Covers the dashboard page type and the D3 chart pattern that evals 2 and 6 expect.samples/9-list-with-caching.tsx— list page using Rule 15's window cache- inline async IIFE pattern. Cross-page navigation to the detail sample via
PAGEREF_placeholder.
- inline async IIFE pattern. Cross-page navigation to the detail sample via
samples/10-detail-with-pageinput.tsx— detail page paired with the list. ReceivespageInput.recordIdsynchronously, initialloading: trueon frame 0,Map<recordId, row>cache onwindow. Demonstrates the formatted-value lookup for_parentcustomerid_value.- Added scope headers to
rules.md"Common Errors" (generation-time anti-patterns) andtroubleshooting.md(deployment/runtime/env) so readers can pick the right one without scanning.
az login(use the same identity aspac auth who).- Uninstall the Dataverse Skills plugin if it was only for
/genpage. - No code/page changes needed; existing pages keep working.
Major refactor of /genpage into an agent-orchestrated architecture.
- PAC CLI ≥ 2.7.0 required (for
pac model create,pac model list-tables --search). - Skill output now lives in a per-invocation working directory
(
genpage-plan.md,RuntimeTypes.ts, one.tsxper page,workflow-log.md). - Plan-mode approval is mandatory; no skip/auto-accept.
- Four specialist agents:
genpage-planner,genpage-entity-builder,genpage-page-builder,genpage-edit-planner. - Multi-page parallel generation; cross-page navigation via
PAGEREF_<filename>placeholders resolved in Phase 6.5. pac model createinline app provisioning.- Plan schema contract at
references/plan-schema.md. - Verified Fluent icon list at
references/verified-icons.txt(~5000 names). - Eval suite: 16 evals across smoke/full/stress tiers + runbook.
- Entity detection uses native
pac model list-tables --searchwith exact logical-name match. - Component template destructures
pageInputin addition todataApi. - Rules reference adds Rule 14 (batched async state) and Rule 15 (data-fetching IIFE + cache guard).
dotnet tool update --global Microsoft.PowerApps.CLI.Tool(to ≥ 2.7.0).- Existing deployed pages keep working — only local workflow/layout changed.
PageInput support, FluentProvider flicker fix, lookup $select rule, data
caching pattern. See git history for details.