<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Riccardo Gregori</title>
    <description>The latest articles on Forem by Riccardo Gregori (@_neronotte).</description>
    <link>https://forem.com/_neronotte</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1533094%2Fe0d7f2bb-ed60-4921-9b81-254de405c0c4.png</url>
      <title>Forem: Riccardo Gregori</title>
      <link>https://forem.com/_neronotte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/_neronotte"/>
    <language>en</language>
    <item>
      <title>PCF's default Fluent UI version is broken... and how to fix it</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 25 May 2026 17:56:15 +0000</pubDate>
      <link>https://forem.com/_neronotte/pcfs-default-fluent-ui-version-is-broken-and-how-to-fix-it-4a5o</link>
      <guid>https://forem.com/_neronotte/pcfs-default-fluent-ui-version-is-broken-and-how-to-fix-it-4a5o</guid>
      <description>&lt;p&gt;If you build PCF (Power Apps Component Framework) controls for Dynamics 365 / Power Platform, there's a very good chance you've already hit this wall — or you will, the next time you spin up a new control. It's one of those small, infuriating papercuts that has nothing to do with your code and everything to do with the tooling assuming things about your target environment that simply aren't true.&lt;/p&gt;

&lt;p&gt;Let me walk you through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ The Setup
&lt;/h2&gt;

&lt;p&gt;You do what the docs tell you to do. You open a terminal, you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pac pcf init &lt;span class="nt"&gt;--namespace&lt;/span&gt; MyNamespace &lt;span class="nt"&gt;--name&lt;/span&gt; MyControl &lt;span class="nt"&gt;--template&lt;/span&gt; field
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(or &lt;code&gt;--template dataset&lt;/code&gt;, it doesn't matter — the bug is template-agnostic).&lt;/p&gt;

&lt;p&gt;The CLI happily scaffolds the project for you: &lt;code&gt;index.ts&lt;/code&gt;, &lt;code&gt;ControlManifest.Input.xml&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, the &lt;code&gt;tsconfig&lt;/code&gt;, the lot. You write your control, you test it locally with &lt;code&gt;npm start&lt;/code&gt;, everything looks great. You're feeling productive.&lt;/p&gt;

&lt;p&gt;Now look at what the scaffolding generated for the Fluent UI dependency.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;ControlManifest.Input.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;platform-library&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Fluent"&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"9.68.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"@fluentui/react-components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9.68.0"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both files agree. The CLI is internally consistent. Surely Microsoft's own tool wouldn't hand you a default that doesn't work… right?&lt;/p&gt;

&lt;h2&gt;
  
  
  ❌ The Crash
&lt;/h2&gt;

&lt;p&gt;You package the control into a solution (managed or unmanaged, doesn't matter), you push it to your target environment, and the import blows up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Import Solution Failed: CustomControl with name  failed to import with error:
CustomControls with Name &amp;lt;YourControlName&amp;gt; Import Solution Failed with following error:
platform library fluent_9_68_0 with version 9.68.0 is not supported by the platform.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;😠🤬 The version the CLI just generated for you, two minutes ago, isn't the version actually supported by the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔨 The Fix
&lt;/h2&gt;

&lt;p&gt;You need to manually downgrade Fluent in &lt;strong&gt;both&lt;/strong&gt; places to a version the platform actually accepts. At the time of writing, the version that works for me is &lt;strong&gt;9.46.2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;ControlManifest.Input.xml&lt;/code&gt; becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;platform-library&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Fluent"&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"9.46.2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;package.json&lt;/code&gt; becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"@fluentui/react-components"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9.46.2"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;npm install&lt;/code&gt;, rebuild, repackage, reimport.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧐 Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust the scaffolded Fluent version.&lt;/strong&gt; As soon as &lt;code&gt;pac pcf init&lt;/code&gt; finishes, check &lt;code&gt;ControlManifest.Input.xml&lt;/code&gt; and &lt;code&gt;package.json&lt;/code&gt;, and swap the version to one your environment actually supports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep both files in sync.&lt;/strong&gt; They have to match, or you'll get a different — equally cryptic — error at build or runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9.46.2 is a known-good value&lt;/strong&gt; for me right now. Yours may differ depending on what your target environment has rolled out; the safest move is to check what other working controls in your tenant declare and copy that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you maintain a starter template / yeoman generator / project scaffold&lt;/strong&gt; at your company, hard-code the Fluent version there and stop relying on the CLI's default. You'll save every junior dev on your team a half-day of debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a small thing. But it's a small thing that has tripped me up on &lt;strong&gt;every single PCF I have ever created&lt;/strong&gt;, and judging by the volume of forum posts on the subject, I'm very much not alone.&lt;/p&gt;

&lt;p&gt;Fingers crossed that Microsoft will fix this soon, so that the next dev running &lt;code&gt;pac pcf init&lt;/code&gt; gets a project that just works.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>powerfuldevs</category>
      <category>pcf</category>
    </item>
    <item>
      <title>Power Pages SPA, local authentication, and the "Private" flag that blocks local development</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Sun, 24 May 2026 07:31:58 +0000</pubDate>
      <link>https://forem.com/_neronotte/power-pages-spa-local-authentication-and-the-private-flag-that-blocks-local-development-514b</link>
      <guid>https://forem.com/_neronotte/power-pages-spa-local-authentication-and-the-private-flag-that-blocks-local-development-514b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;TL;DR: if you're developing a Power Pages SPA locally with local authentication (not Entra ID) and the login redirects to Microsoft with a &lt;code&gt;redirect_uri&lt;/code&gt; pointing to the production URL, the issue isn't in your application configuration. It's the portal's "Private" flag in the admin center. Switching it to "Public" unblocks the flow.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📖 Background: Power Pages SPA
&lt;/h2&gt;

&lt;p&gt;For those who haven't come across it yet, &lt;strong&gt;Power Pages Code Sites&lt;/strong&gt; is the "code-first" mode of Power Pages portals: it lets you build the portal as a Single Page Application (React, Vue, or other frameworks), instead of managing its content through Liquid and the Dataverse admin interface.&lt;/p&gt;

&lt;p&gt;One of the main advantages of this approach is the &lt;strong&gt;local developer experience&lt;/strong&gt;: you spin up the dev server (typically Vite, on &lt;code&gt;localhost:5173&lt;/code&gt;), and the portal behaves as it would in production, talking to the remote backend of your development environment. This way, you don't need to push every change to verify it works.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At least in theory&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 The constraint: local authentication
&lt;/h2&gt;

&lt;p&gt;In its official documentation, Microsoft &lt;strong&gt;advises against using local authentication&lt;/strong&gt; on Power Pages portals, recommending external providers such as Entra ID or other configurable identity providers.&lt;/p&gt;

&lt;p&gt;In my case, however, client constraints required local authentication: no Entra ID for end users, no social login, just locally managed usernames and passwords.&lt;/p&gt;

&lt;p&gt;This is where the first hurdle shows up: &lt;strong&gt;all the documentation and examples available online for Power Pages SPA authentication assume Entra ID&lt;/strong&gt;. Searching for "Power Pages Code Sites authentication" returns guides on configuring Entra app registrations, handling JWT tokens, and silent token renewal. For local auth, you get a few lines confirming that it's supported, with no further detail.&lt;/p&gt;

&lt;p&gt;The missing documentation can be reconstructed by reading the output generated by the published portal and analyzing its behavior. It's extra work, but it's doable.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧱 The authentication stack
&lt;/h2&gt;

&lt;p&gt;To give you a concrete reference, here's — in simplified form — the React context that handles authentication on the client side. The logic is fairly straightforward: a reducer for the auth state, a fetch helper that first tries the user object injected by Power Pages into &lt;code&gt;window&lt;/code&gt; and then falls back to a REST endpoint, and &lt;code&gt;login&lt;/code&gt;/&lt;code&gt;logout&lt;/code&gt; methods that redirect to the portal's native endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AuthContext.tsx (simplified)&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PowerPagesUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;contactId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="nb"&gt;global&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Dynamic365&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Portal&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;PowerPagesUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AuthUser&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Window object (production, page-load check)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ppUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Dynamic365&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Portal&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ppUser&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ppUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contactId&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ppUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;ppUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ppUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ppUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;contactId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ppUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contactId&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Fetch fallback (post-login session check / local dev)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/_services/auth/user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// ... parse the JSON response or the HTML with embedded data ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the login/logout methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/Account/SignIn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/Account/Login/LogOff?returnUrl=%2F&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is delegated to the endpoints Power Pages exposes natively: &lt;code&gt;/Account/SignIn&lt;/code&gt; is the portal's login page, &lt;code&gt;/Account/Login/LogOff&lt;/code&gt; is the logout endpoint. On the SPA side, after login the user is returned to the site, the page reloads, and the fetch helper retrieves the user from the &lt;code&gt;window&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In production, all of this works correctly.&lt;/strong&gt; On the portal's public URL, the login button opens the native page, credentials are validated, and the session is established as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  🐛 The problem: the wrong &lt;code&gt;redirect_uri&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Locally, things were different.&lt;/p&gt;

&lt;p&gt;The scenario was as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I was working on a development portal configured as &lt;strong&gt;"Private"&lt;/strong&gt; in the Power Pages admin center. Private means the portal is only visible to Entra ID users explicitly assigned as "portal users" in the admin center: a sensible choice during development, to prevent unintended access to a work-in-progress site.&lt;/li&gt;
&lt;li&gt;On the same portal, &lt;strong&gt;Entra ID authentication for end users was disabled&lt;/strong&gt;: portal users authenticate via local auth.&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;npm run dev&lt;/code&gt;, the portal starts on &lt;code&gt;localhost:5173&lt;/code&gt;. When you click the login button, however, the local portal login page doesn't open — instead, the browser is redirected to &lt;code&gt;login.microsoftonline.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The behavior, in itself, makes sense: the portal is "Private", so before any page can be reached — including the local login page — Power Pages requires the developer to authenticate via Entra, to verify that they are authorized to view the portal.&lt;/p&gt;

&lt;p&gt;The problem lies in the &lt;strong&gt;&lt;code&gt;redirect_uri&lt;/code&gt;&lt;/strong&gt; of the request to Microsoft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://login.microsoftonline.com/.../oauth2/v2.0/authorize
  ?client_id=...
  &amp;amp;redirect_uri=https%3A%2F%2Fmy-dev-portal.powerappsportals.com%2F...
  &amp;amp;response_type=code
  &amp;amp;scope=...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;redirect_uri&lt;/code&gt; points to the &lt;strong&gt;portal's public URL&lt;/strong&gt;, not to &lt;code&gt;http://localhost:5173&lt;/code&gt;. As a result, once the Entra authentication flow completes, Microsoft redirects the browser to the portal's public URL — the one configured in the underlying app registration — and the local flow breaks: the dev server is on &lt;code&gt;localhost&lt;/code&gt;, the local code is no longer running, and the session built locally is lost.&lt;/p&gt;

&lt;p&gt;The practical outcome is that &lt;strong&gt;the login cannot be completed locally&lt;/strong&gt;, and therefore &lt;strong&gt;no part of the application that requires authentication can be tested locally without first deploying to the published portal&lt;/strong&gt; 😵😟.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔍 The search for a solution
&lt;/h2&gt;

&lt;p&gt;Looking for a solution online, I didn't find much useful material.&lt;/p&gt;

&lt;p&gt;Googling for combinations like "Power Pages Code Sites local development authentication", "Power Pages SPA private site redirect_uri localhost", or "Power Pages Vite dev server login loop" returns always the official documentation on configuring Entra authentication &lt;em&gt;for portal end users&lt;/em&gt;, which is a different scenario from the gate for private portals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asking an AI assistant didn't help either, which is worth mentioning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These days, when the documentation falls short, the natural next step is to turn to a coding assistant — and for most problems it works. Not here: Power Pages Code Sites is recent enough, and this particular combination of settings is niche enough, that there's barely any signal in the training data. The answers I got back were either generic guidance on Entra app registrations (the same material I'd already found in the docs), or plausible-sounding but ultimately wrong suggestions — the kind of confident answer that wastes more time than it saves, because you end up verifying it before discarding it. &lt;strong&gt;When a technology is both new and poorly documented, AI assistants reflect that gap rather than filling it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I tried several approaches manually: checking whether &lt;code&gt;localhost:5173&lt;/code&gt; could be added as a valid redirect URI in the app registration used by Power Pages for the private-portal gate (it cannot, because that app registration is managed internally and isn't exposed to the tenant administrator); tracing where that &lt;code&gt;redirect_uri&lt;/code&gt; was generated by inspecting the client-side code; setting session cookies manually; calling the auth endpoints with custom headers.&lt;/p&gt;

&lt;p&gt;None of these led anywhere. The &lt;code&gt;redirect_uri&lt;/code&gt; of the private-portal gate is effectively bound to the portal's production URL, and cannot be overridden from the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ The solution
&lt;/h2&gt;

&lt;p&gt;After spending a fair amount of time exploring alternatives, the solution turned out to be a &lt;strong&gt;single change in the Power Pages admin center&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Power Pages admin center → Site Details → switch the flag from &lt;strong&gt;"Private"&lt;/strong&gt; to &lt;strong&gt;"Public"&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's all. By disabling the Entra gate at the portal level:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The local dev server can reach the portal's endpoints without first having to clear the Entra login.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/Account/SignIn&lt;/code&gt; opens the portal's local auth page directly.&lt;/li&gt;
&lt;li&gt;The login completes, the session is established, the fetch helper retrieves the user, and local development is operational again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason this works is that the "Private/Public" flag has no role in end-user authentication, which remains handled by the configured providers (in my case, local auth). "Private/Public" controls the &lt;strong&gt;outer gate&lt;/strong&gt; that Power Pages places in front of the portal and that requires Entra authentication &lt;em&gt;only to access the portal itself&lt;/em&gt;. Disabling it removes the second authentication layer that, locally, was running into the wrong &lt;code&gt;redirect_uri&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  📝 A note on the documentation
&lt;/h2&gt;

&lt;p&gt;It's worth making one observation about the documentation. The "Private" flag is described as a feature intended for the development phase, useful for protecting a work-in-progress portal from unintended access. In practice, however, &lt;strong&gt;it is incompatible with one of the main development patterns of Code Sites mode&lt;/strong&gt;, namely working locally with the dev server.&lt;/p&gt;

&lt;p&gt;This incompatibility isn't documented: there is no warning, footnote, or FAQ flagging it. The problem is also specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;strong&gt;Entra ID also configured for end users&lt;/strong&gt;, the "Private" flag causes no issues locally, because the gate flow and the application authentication flow converge and the redirect is handled consistently.&lt;/li&gt;
&lt;li&gt;It is only in the combination &lt;strong&gt;"Private" + "local auth for end users"&lt;/strong&gt; that the flow breaks. And this is precisely the combination least covered by the official documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚙️ Operational notes
&lt;/h2&gt;

&lt;p&gt;A few practical notes for anyone in the same situation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. First and above a all, avoid local auth whenever possible&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Document the constraint in the repository.&lt;/strong&gt; It's worth adding a &lt;code&gt;LOCAL_DEV.md&lt;/code&gt; file or a README section that clearly states: &lt;em&gt;"To develop locally, the Power Pages portal must be set to 'Public' in the admin center. Switching it back to 'Private' prevents login from completing locally."&lt;/em&gt; It's information that will save time for future developers on the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧐 Conclusions
&lt;/h2&gt;

&lt;p&gt;The "local auth + local development" scenario on Power Pages Code Sites is one of the least documented, but it's realistic in many enterprise contexts — particularly when existing identity management systems are already in place and can't be replaced by Entra ID for end users. I hope this article is useful to anyone walking the same path, especially to those searching for an answer online after a few unproductive hours of troubleshooting.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>powerpages</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Vibe coded Power Pages SPA: unfiltered</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 11 May 2026 08:02:08 +0000</pubDate>
      <link>https://forem.com/_neronotte/vibe-coded-power-pages-spa-unfiltered-11a6</link>
      <guid>https://forem.com/_neronotte/vibe-coded-power-pages-spa-unfiltered-11a6</guid>
      <description>&lt;p&gt;Today I tried something that feels deeply unnatural to me.&lt;/p&gt;

&lt;p&gt;Not just uncomfortable—almost wrong.  &lt;/p&gt;

&lt;p&gt;Partly to challenge my own mental limits and break through some of my built-in biases. Partly to answer a question I’d been avoiding for a while: &lt;em&gt;how far can I really go if I stop controlling everything and just… trust the technology?&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;I don't like blog posts describing vibe coding as "type something, do something else, and everything magically works". So I wanted to give it a try by myself, reporting everything I face on my journey. The goods, and the bads.&lt;/p&gt;

&lt;p&gt;So I set myself a rule: no shortcuts, no safety nets.  &lt;/p&gt;

&lt;p&gt;I tried to build a Power Pages SPA portal from scratch—without using PACX, and without any of the templates or tools I’ve spent years building to make my life easier.  &lt;/p&gt;

&lt;p&gt;Just an empty folder in VS Code.&lt;br&gt;&lt;br&gt;
And two simple plugins provided by Microsoft.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwvyiodovai1eg4gs71u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwvyiodovai1eg4gs71u.png" alt="Plugin abilitati" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  💡 Let's start with the idea
&lt;/h2&gt;

&lt;p&gt;I’ve never been particularly fond of our corporate welfare portal, so I decided to ask my trusted friend, M365 Copilot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Help me draft the requirements for a corporate welfare website.&lt;br&gt;
Be synthetic, it's for a demo. Don't design approval workflow or too fancy features. Draft everything in markdown.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# 📌 Requirements – Corporate Welfare Portal (Demo)&lt;/span&gt;

&lt;span class="gu"&gt;## 1. Objective&lt;/span&gt;

The portal aims to provide a simple solution that allows employees to:
&lt;span class="p"&gt;
-&lt;/span&gt; View their available welfare budget  
&lt;span class="p"&gt;-&lt;/span&gt; Access a catalog of available services  
&lt;span class="p"&gt;-&lt;/span&gt; Request benefits (vouchers or reimbursements)  
&lt;span class="p"&gt;-&lt;/span&gt; Monitor the status of their requests  

&lt;span class="gu"&gt;## 2. Actors&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Employee**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**HR / Administration**&lt;/span&gt;

&lt;span class="gu"&gt;## 3. Functional Requirements&lt;/span&gt;

&lt;span class="gu"&gt;### 3.1 User Access and Profile&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Access via corporate login (SSO)  
&lt;span class="p"&gt;-&lt;/span&gt; User profile view including:
&lt;span class="p"&gt;  -&lt;/span&gt; name and role  
&lt;span class="p"&gt;  -&lt;/span&gt; available welfare budget  

&lt;span class="gu"&gt;### 3.2 Budget Management&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Display of:
&lt;span class="p"&gt;  -&lt;/span&gt; available budget  
&lt;span class="p"&gt;  -&lt;/span&gt; used budget  
&lt;span class="p"&gt;-&lt;/span&gt; HR capability to assign and update budget  

&lt;span class="gu"&gt;### 3.3 Welfare Catalog&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Display available services:
&lt;span class="p"&gt;  -&lt;/span&gt; organized by categories (e.g., health, education, leisure)  
&lt;span class="p"&gt;-&lt;/span&gt; Search and filtering capabilities  
&lt;span class="p"&gt;-&lt;/span&gt; Service detail view including:
&lt;span class="p"&gt;  -&lt;/span&gt; description  
&lt;span class="p"&gt;  -&lt;/span&gt; eligible amount  
&lt;span class="p"&gt;  -&lt;/span&gt; usage conditions  

&lt;span class="gu"&gt;### 3.4 Benefit Request&lt;/span&gt;

&lt;span class="gu"&gt;#### Voucher&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Service selection  
&lt;span class="p"&gt;-&lt;/span&gt; Voucher generation  
&lt;span class="p"&gt;-&lt;/span&gt; Automatic budget deduction  

&lt;span class="gu"&gt;#### Reimbursement&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Submission of reimbursement request  
&lt;span class="p"&gt;-&lt;/span&gt; Upload of supporting documents (e.g., receipts)  
&lt;span class="p"&gt;-&lt;/span&gt; Request status tracking  

&lt;span class="gu"&gt;### 3.5 Operations History&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; List of user requests including:
&lt;span class="p"&gt;  -&lt;/span&gt; date  
&lt;span class="p"&gt;  -&lt;/span&gt; type  
&lt;span class="p"&gt;  -&lt;/span&gt; amount  
&lt;span class="p"&gt;  -&lt;/span&gt; status  
&lt;span class="p"&gt;-&lt;/span&gt; Detailed view of individual requests  

&lt;span class="gu"&gt;### 3.6 HR Back Office&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; User management:
&lt;span class="p"&gt;  -&lt;/span&gt; budget assignment  
&lt;span class="p"&gt;-&lt;/span&gt; Catalog management:
&lt;span class="p"&gt;  -&lt;/span&gt; create and update services  
&lt;span class="p"&gt;-&lt;/span&gt; View user requests  

&lt;span class="gu"&gt;## 4. Non-Functional Requirements&lt;/span&gt;

&lt;span class="gu"&gt;### 4.1 Security&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Authenticated access (SSO)  
&lt;span class="p"&gt;-&lt;/span&gt; Personal data protection (GDPR compliance)  

&lt;span class="gu"&gt;### 4.2 Usability&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Simple and intuitive user interface  
&lt;span class="p"&gt;-&lt;/span&gt; Accessible from desktop and mobile devices  

&lt;span class="gu"&gt;### 4.3 Performance&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Fast response time for standard operations (within a few seconds)  

&lt;span class="gu"&gt;### 4.4 Traceability&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Logging of user operations  
&lt;span class="p"&gt;-&lt;/span&gt; History of submitted requests  

&lt;span class="gu"&gt;## 5. Open Points (for Demo / Future Evolution)&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Integration with HR systems  
&lt;span class="p"&gt;-&lt;/span&gt; Automatic handling of fiscal constraints  
&lt;span class="p"&gt;-&lt;/span&gt; Catalog extension (new services)  
&lt;span class="p"&gt;-&lt;/span&gt; Personalization based on employee segments  

&lt;span class="gu"&gt;## ✅ Demo Implementation Suggestion&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Frontend:**&lt;/span&gt; Power Pages  
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Backend:**&lt;/span&gt; Dataverse  
&lt;span class="p"&gt;  -&lt;/span&gt; Tables: Users, Budgets, Services, Requests  
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Automation (optional):**&lt;/span&gt; Power Automate for notifications  

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More than enough for a simple demo.&lt;/p&gt;

&lt;p&gt;To start, I've:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;created a local folder called &lt;code&gt;neronotte01-site&lt;/code&gt;, that will be the root of our workspace.&lt;/li&gt;
&lt;li&gt;opened the folder in VSCode&lt;/li&gt;
&lt;li&gt;copied the above text and pasted it in a &lt;code&gt;requirements.md&lt;/code&gt; file under the &lt;code&gt;docs&lt;/code&gt; folder of my repo. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now my repo looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- 📂 neronotte01-site
  - 📂 docs
    - 📄 requirements.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's start by asking Copilot&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting from the attached requirement document, design
and implement a power page portal with a modern and
effective UI. Put the source code in the /portal subfolder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmy0id0rtn9l0xpe1nv3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmy0id0rtn9l0xpe1nv3i.png" alt="Start from a requirement document" width="400" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I prefer using Claude Sonnet 4.6 for this kind of tasks, so I started with that model.&lt;/p&gt;

&lt;p&gt;Copilot starts by building a plan:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtjh1vxsw9xfd6ptlir2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtjh1vxsw9xfd6ptlir2.png" alt="Copilot builds a plan" width="404" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then goes... coding everything following the plan. In this first draft it designs the site UI and wires up mock data, but that's good as a start.&lt;/p&gt;

&lt;p&gt;After a couple of minutes the build is completed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ulwh0gk04qdfhe71wq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ulwh0gk04qdfhe71wq6.png" alt="Site finalized" width="406" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is the working result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ty2izixifw756jv71fn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ty2izixifw756jv71fn.gif" alt="Site 1" width="560" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fkik0a2lx8zzyovxt3c.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fkik0a2lx8zzyovxt3c.gif" alt="Site 2" width="600" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty good result by now, considering we started from a simple spec document.&lt;/p&gt;

&lt;p&gt;Now let's try to build the backend and manage the authenticated flow.&lt;/p&gt;




&lt;h2&gt;
  
  
  💻 Let's build the backend
&lt;/h2&gt;

&lt;p&gt;Let's move forward by asking copilot to create the tables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanxv6934m0gw81st4i9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fanxv6934m0gw81st4i9q.png" alt="I asked to design and build the backend" width="395" height="71"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a bit of reasoning, Copilot starts asking a couple of questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What will be the publisher prefix: &lt;code&gt;nn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How shoud he call the solution that will contain the tables: &lt;code&gt;portal&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo83wb8txzop0983fclnd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo83wb8txzop0983fclnd.png" alt="Copilot asks for publisher" width="414" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2g932d7jy8uk5w1tmhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2g932d7jy8uk5w1tmhk.png" alt="Copilot asks for solution name" width="417" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we fought a bit around Python structure. I'm not (and I wont be) a Python developer. I have PIP installed in my machine, but just for the sake to run some script... so Copilot tried to initialize a virtual environment to launch the commands into.&lt;/p&gt;

&lt;p&gt;In this step there was a lot of "try and error". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8wkf0o5q96yzr59dipw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8wkf0o5q96yzr59dipw.png" alt="Copilot tries and fails..." width="409" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But let's trust the process... for now.&lt;br&gt;
After a few more tries, we got stuck on an authentication issue towards Dataverse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1hkq0tixltp3drt7dz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1hkq0tixltp3drt7dz0.png" alt="Auth failed 1" width="420" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca528qmffmylplhyeh2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca528qmffmylplhyeh2i.png" alt="Auth failed 2" width="723" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7gg734popy6fvgrav2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs7gg734popy6fvgrav2b.png" alt="Auth failed 3" width="459" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuga8m0uh8ysmxtwrfwv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuga8m0uh8ysmxtwrfwv1.png" alt="Auth failed 4" width="426" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the analysis, Copilot came up with another solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The device code flow is blocked by Conditional Access policies. 
Since `az` CLI is already authenticated, let me use it 
to get a token directly for the Dataverse API instead.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And... it worked!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`az` CLI can get a Dataverse token. 
Now I'll write a comprehensive Python script that uses it 
to create the publisher, solution, and all 3 tables via 
the Dataverse Web API.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot did 3 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;added a .env file at the root of my workspace
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env
DATAVERSE_URL=https://neronotte01.crm4.dynamics.com/
TENANT_ID=31c42b90-d584-4ff8-b5b9-1c65de456516
MCP_CLIENT_ID=***REDACTED***
SOLUTION_NAME=Portal
PUBLISHER_PREFIX=nn
PAC_AUTH_PROFILE=neronotte01
CLIENT_ID=***REDACTED***
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;created a folder called &lt;code&gt;scripts&lt;/code&gt; in my repo&lt;/li&gt;
&lt;li&gt;added 2 files in that folder: &lt;code&gt;auth.py&lt;/code&gt; and &lt;code&gt;create_tables.py&lt;/code&gt;. The second is the one containing the actual datamodel setup:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx54tadkaezkgozbf14uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx54tadkaezkgozbf14uq.png" alt="The generated script" width="669" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then it asked to run the script:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpf5agi3r1q7e86d06eg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpf5agi3r1q7e86d06eg.png" alt="Run powershell script?" width="419" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately &lt;strong&gt;the run failed&lt;/strong&gt; ❌. Authentication issue, again. &lt;/p&gt;

&lt;p&gt;Long story short, I had to provide a client secret to make it work. It updated the &lt;code&gt;.env&lt;/code&gt; file adding &lt;code&gt;CLIENT_SECRET=&lt;/code&gt; at the bottom&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env
DATAVERSE_URL=https://neronotte01.crm4.dynamics.com/
TENANT_ID=31c42b90-d584-4ff8-b5b9-1c65de456516
MCP_CLIENT_ID=***REDACTED***
SOLUTION_NAME=Portal
PUBLISHER_PREFIX=nn
PAC_AUTH_PROFILE=neronotte01
CLIENT_ID=***REDACTED***
CLIENT_SECRET=***REDACTED***
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it updated also the script with a few changes to leverage that client secret.&lt;/p&gt;

&lt;p&gt;A few try-and-error again, and we got the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2s7yg6i6edgxzgc4op6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2s7yg6i6edgxzgc4op6.png" alt="Tables created" width="626" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 My personal opinion on this step
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;With PACX it would have been WAAAAAAAY more straightforward&lt;/strong&gt;. I do the same step almost daily (asking GH Copilot: "start from a requirement document and draft the datamodel, then build the PACX script to create it"), and it goes as a charme. PACX wins drastically on this.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔗 Let's wire things up
&lt;/h2&gt;

&lt;p&gt;Third prompt to Copilot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now replace mock data with actual calls to 
the Dataverse tables you just created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It started by drafting a 7 step plan:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd2ozzj1apowhl3mb5n8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd2ozzj1apowhl3mb5n8.png" alt="Wiring plan" width="245" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install @azure/msal-browser&lt;/li&gt;
&lt;li&gt;Create MSAL config + auth context&lt;/li&gt;
&lt;li&gt;Create Dataverse service layer&lt;/li&gt;
&lt;li&gt;Wrap main.tsx with auth&lt;/li&gt;
&lt;li&gt;Update Navbar to use real user&lt;/li&gt;
&lt;li&gt;Update all 5 pages to use Dataverse&lt;/li&gt;
&lt;li&gt;Build and verify&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It runs for 5-6 minutes, and then...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfh8ihpfpz9409svfim7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfh8ihpfpz9409svfim7.png" alt="Build completed" width="642" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It asks me to configure the app registration properly on Azure. Once done, I launch the portal and it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z6bjidjn7sa28qipv8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z6bjidjn7sa28qipv8o.png" alt="My Welfare Login" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But... unfortunately the login is not working. If I click on the "Sign in with Microsoft" button, it opens a popup where I can peek the user (&lt;a href="mailto:riccardo@neronotte1.onmicrosoft.com"&gt;riccardo@neronotte1.onmicrosoft.com&lt;/a&gt;). Then it moves to the next screen, reopening the login page into the popup. And there I'm stuck.&lt;/p&gt;

&lt;p&gt;I say the same stuff to the copilot and try, collaboratively, to fix it.&lt;/p&gt;

&lt;p&gt;After a while, it cames with a solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The problem is that after authentication, Microsoft redirects 
the popup back to window.location.origin — which loads the 
full React app inside the popup, showing the login screen again.

The fix is a dedicated blank redirect page for the popup, 
which is the MSAL-recommended pattern for SPA popup flows.

...

Now you need to add http://localhost:5173/blank.html as a 
redirect URI in the Azure App Registration. Go to:

Azure Portal → App registrations → 6beaf75f-4793-4b3f-b364-14b9647556f2 
→ Authentication → Single-page application 
→ Add URI → http://localhost:5173/blank.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK. I do it, try again and...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c50g52rsnxugewkm6vh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3c50g52rsnxugewkm6vh.png" alt="Still stuck" width="603" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm still stuck. Let's go to copilot again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now it goes to blank.html, but remains opened (see attached screenshot).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this time i decided to change model, from &lt;code&gt;Claude Sonnet 4.6&lt;/code&gt; to &lt;code&gt;GPT 5.4&lt;/code&gt; (sometimes it is more effective).&lt;/p&gt;

&lt;p&gt;It started working again for a while and... &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frkrfke2fdu12pyqk3jqu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frkrfke2fdu12pyqk3jqu.png" alt="Fixed" width="469" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;boom&lt;/strong&gt; solution found. It was a problem on how the App Registration was configured. Now the portal opens properly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mvduczpftgf1wznqm5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mvduczpftgf1wznqm5w.png" alt="Portal opened" width="800" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, of course without data.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 My personal opinion on this step
&lt;/h3&gt;

&lt;p&gt;On Power Pages SPA you have to rebuild the auth method from scratch every time. To avoid an useless token burning, do it once until it works properly, then extract the &lt;code&gt;AuthContext.tsx&lt;/code&gt; that works, and reuse it across all your sites.&lt;/p&gt;

&lt;p&gt;I did it, creating my own Power Page template that I reuse every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✍🏻 Create sample data
&lt;/h2&gt;

&lt;p&gt;I don't want to do it manually, so let's leverage Dataverse MCP server for this purpose.&lt;/p&gt;

&lt;p&gt;First of all we need to enable it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Access &lt;a href="https://admin.powerplatform.microsoft.com/" rel="noopener noreferrer"&gt;https://admin.powerplatform.microsoft.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Manage&lt;/strong&gt; &amp;gt; &lt;strong&gt;Environments&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the environment that contains the portal&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Product&lt;/strong&gt; &amp;gt; &lt;strong&gt;Features&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Scroll until you find &lt;strong&gt;Dataverse Model Context Protocol&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglyjz7ppcpjj78ymfwpb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglyjz7ppcpjj78ymfwpb.png" alt="Dataverse Model Context Protocol" width="735" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;in the &lt;strong&gt;Step 2: Add MCP clients to allow list&lt;/strong&gt; select &lt;strong&gt;Advanced Settings&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;in the &lt;strong&gt;Allowed MCP Clients&lt;/strong&gt; list, ensure &lt;strong&gt;Microsoft GitHub Copilot App&lt;/strong&gt; is enabled:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qot4uk9e2ykvkr3bqzn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qot4uk9e2ykvkr3bqzn.png" alt="Microsoft GitHub Copilot App" width="800" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's setup the MCP Server in VSCode&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On VSCode, click &lt;code&gt;CTRL+SHIFT+P&lt;/code&gt; to open the command palette&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;MCP: Add Server...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;HTTP&lt;/code&gt; as streaming&lt;/li&gt;
&lt;li&gt;Add your MCP url, &lt;code&gt;https://{environment}.crm4.dynamics.com/api/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;Workspace&lt;/code&gt; as the place where to save the MCP config (it will create a local &lt;code&gt;.vscode/mcp.json&lt;/code&gt; file with the MCP configuration)&lt;/li&gt;
&lt;li&gt;Insert &lt;code&gt;Dataverse&lt;/code&gt; as the MCP name&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;mcp.json&lt;/code&gt; file will be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dataverse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://{environment}.crm4.dynamics.com/api/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready. Let's ask copilot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now create sample data into the dataverse tables 
you've created, to show something into the app.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It iterates for a couple of minutes and then...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftplo98259yoa4kexmd0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftplo98259yoa4kexmd0k.png" alt="Data created" width="481" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I refreshed the browser, and the data appears.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ysz4705fpsze2a3z4uw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ysz4705fpsze2a3z4uw.png" alt="Now we see the data in the app" width="800" height="769"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if I try to create a new request... it works! Request is created and saved in dataverse!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggt0f6335lfnv7fvveo6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggt0f6335lfnv7fvveo6.png" alt="New request saved" width="800" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsw2cbm3wz64f6uxdblp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsw2cbm3wz64f6uxdblp.png" alt="Record in Dataverse" width="576" height="354"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 One more to go: deploy
&lt;/h2&gt;

&lt;p&gt;As of now we've worked locally. Let's deploy the site into the dev environment. Let's type in the Copilot chat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Now publish the site to dataverse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it started thinking...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3336i4w2e9fw3u6vu7q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3336i4w2e9fw3u6vu7q.png" alt="Copilot Publishing the site" width="482" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It tried to upload the site, but got stuck on the fact that the JS attachments are blocked, &lt;a href="https://learn.microsoft.com/en-us/power-pages/configure/create-code-sites#allow-javascript-file-uploads" rel="noopener noreferrer"&gt;as described here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprpu34h08nbe8dlb51ne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprpu34h08nbe8dlb51ne.png" alt="Blocked attachments" width="467" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately it stopped here, because in the &lt;code&gt;/dist&lt;/code&gt; subfolder I can clearly see there's the &lt;a href="https://dev.to/_neronotte/power-pages-spa-learning-by-doing-keep-it-clean-mg4"&gt;issue I described in my previous article&lt;/a&gt;, so I decided now to manually update the &lt;code&gt;vite.config.ts&lt;/code&gt; before moving forward.&lt;/p&gt;

&lt;p&gt;Once I've updated my &lt;code&gt;vite.config.ts&lt;/code&gt; in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;defineConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vite"&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;react&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@vitejs/plugin-react"&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;defineConfig(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;plugins:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;react()&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;build:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;outDir:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;rollupOptions:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;output:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;entryFileNames:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`assets/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;chunkFileNames:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`assets/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;assetFileNames:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`assets/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;manualChunks:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;router:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"react-router-dom"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="err"&gt;msal:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@azure/msal-browser"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: it's using &lt;code&gt;@azure/msal-browser&lt;/code&gt;, even if &lt;a href="https://learn.microsoft.com/en-us/power-pages/configure/create-code-sites#configuration-steps" rel="noopener noreferrer"&gt;as stated here&lt;/a&gt; you should use &lt;code&gt;ADAL.js&lt;/code&gt;... but it's working, for now, so let's leave it as it is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I typed &lt;code&gt;Yes, remove js from blockedattachments and retry&lt;/code&gt; in the GitHub Copilot chat, and it run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$blocked&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ade;adp;app;asa;ashx;asmx;asp;bas;bat;cdx;cer;chm;class;cmd;com;config;cpl;crt;csh;dll;exe;fxp;hlp;hta;htr;htw;ida;idc;idq;inf;ins;isp;its;jar;jse;ksh;lnk;mad;maf;mag;mam;maq;mar;mas;mat;mau;mav;maw;mda;mdb;mde;mdt;mdw;mdz;msc;msh;msh1;msh1xml;msh2;msh2xml;mshxml;msi;msp;mst;ops;pcd;pif;prf;prg;printer;pst;reg;rem;scf;scr;sct;shb;shs;shtm;shtml;soap;stm;tmp;url;vb;vbe;vbs;vsmacros;vss;vst;vsw;ws;wsc;wsf;wsh'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;update-settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;blockedattachments&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$blocked&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course it worked. Once done, it run again &lt;code&gt;pac pages upload-code-site&lt;/code&gt; to upload the site.&lt;/p&gt;

&lt;p&gt;Next step is (as per GH Copilot suggestion):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Activate/provision the site if it is not already live&lt;/li&gt;
&lt;li&gt;Test the deployed site end-to-end in the browser&lt;/li&gt;
&lt;li&gt;Clear/restart the site cache so the latest changes appear immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In fact, if I go to &lt;a href="https://make.powerpages.microsoft.com/" rel="noopener noreferrer"&gt;make.powerpages.microsoft.com&lt;/a&gt;, I can clearly see the site uploaded and disabled, ready to be reactivated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7tfustrx6a5u31d4cys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7tfustrx6a5u31d4cys.png" alt="MyWelfare Portal" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: since this was the first site created on this environment, once I got into &lt;a href="https://make.powerpages.microsoft.com/" rel="noopener noreferrer"&gt;https://make.powerpages.microsoft.com/&lt;/a&gt; I had to create a new, empty, website to move forward, otherwise the portal didn't get me to the page where I could barely reactivate the old one. I think it's a sort of bug... hope Microsoft fixes this soon.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reactivation took a while (almost 30 minutes), but... once done... &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0uknaq12yxfdqhzas3wo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0uknaq12yxfdqhzas3wo.png" alt="Wrong portal?!" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong portal?!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me ask Copilot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The published portal is showing something completely different, 
see attached screenshot.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, Copilot hypothesis was a site mismatch between the one I had to create manually, and the one generated by it... but of course that wasn't the problem. First miss.&lt;/p&gt;

&lt;p&gt;Then it tried with other stuff... let me skip the details. Unconclusive.&lt;/p&gt;

&lt;p&gt;Then I spotted something in the admin center:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw0b1twy6hgox0kgt3d2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgw0b1twy6hgox0kgt3d2.png" alt="Standard data model" width="120" height="53"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It has created the site with &lt;strong&gt;Standard&lt;/strong&gt; data model, while Power Pages SPA requires the &lt;strong&gt;Enhanced&lt;/strong&gt; data model.&lt;/p&gt;

&lt;p&gt;I told this to it explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The problem is the site you deployed is with "Standard" data model, 
while power page SPA works with the Enhanced data model. 
I think you have to re-create the site completely.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It started iterating for several minutes, but no meaningful solution came out. I even tried to change model back to Sonnet 4.6... useless.&lt;/p&gt;

&lt;p&gt;After more than 1h of try-and-error, I tried the old way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new empty website. It gets created with Enhanced datamodel by default&lt;/li&gt;
&lt;li&gt;changed the value of the &lt;code&gt;sitename&lt;/code&gt; node of my local &lt;code&gt;powerpages.config.json&lt;/code&gt; to the name of the newly created site&lt;/li&gt;
&lt;li&gt;opened the local &lt;code&gt;/portal/.powerpages-site/website.yml&lt;/code&gt; and changed the id node to the GUID of the newly created website&lt;/li&gt;
&lt;li&gt;Manually push the code to this new site via &lt;code&gt;pac pages upload-code-site -rp .&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Purged the cache via admin center.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Et voilà... site up and running!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5nkw29n0di8tsil1tfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5nkw29n0di8tsil1tfv.png" alt="Site Up and running" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...not so fast... at login:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81spihwm3helbzwpw1zo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81spihwm3helbzwpw1zo.png" alt="Other app reg to fix" width="606" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to fix this other app registration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;https://portal.azure.com/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;App Registrations&lt;/strong&gt; &amp;gt; &lt;strong&gt;All applications&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the App Registration mentioned in the screenshot by ID&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Authentication&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Edit&lt;/strong&gt; button near the &lt;strong&gt;Redirect URI configuration&lt;/strong&gt; for &lt;strong&gt;Single-page application&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add the Url of your website and click &lt;strong&gt;configure&lt;/strong&gt; (don't forget to add also &lt;code&gt;/blank.html&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rh92yr31ijvnuzq8jk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rh92yr31ijvnuzq8jk6.png" alt="Redirect URIs" width="557" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's try again.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the admin center, refresh the portal cache&lt;/li&gt;
&lt;li&gt;Press &lt;strong&gt;F5&lt;/strong&gt; in the browser and re-execute the login process&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's it, we're in!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bq2zdayarcaj7h071mv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bq2zdayarcaj7h071mv.png" alt="Final" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ☺️ Conclusions
&lt;/h2&gt;

&lt;p&gt;In the end, this experiment turned out to be far more than just a technical exercise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power Pages SPA&lt;/strong&gt; prove to be &lt;strong&gt;incredibly powerful&lt;/strong&gt;—much more than they might initially appear. When you strip everything down to the bare essentials, the core platform already provides everything you need to build a fully functional solution.&lt;/p&gt;

&lt;p&gt;And that’s where the real surprise lies: &lt;strong&gt;the tools provided by Microsoft are a true game changer&lt;/strong&gt;. They cover the entire lifecycle, from development to deployment, in a way that feels cohesive and increasingly mature. You can genuinely go from zero to a working application without relying on any external scaffolding.&lt;/p&gt;

&lt;p&gt;That said, &lt;strong&gt;it’s not a smooth ride&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There are moments where things don’t click&lt;/strong&gt;. Where the tooling feels a bit rough around the edges. Where you need to &lt;em&gt;push&lt;/em&gt;, tweak, retry—and sometimes just &lt;strong&gt;hammer it until it works&lt;/strong&gt;. And that’s exactly where &lt;strong&gt;experience makes the difference&lt;/strong&gt;. Knowing how the platform behaves, understanding its quirks, and having the intuition to work around them becomes your real advantage.&lt;/p&gt;

&lt;p&gt;And this is also &lt;strong&gt;where external tools (like PACX) still shine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even if the out-of-the-box Microsoft tooling gets you surprisingly far, there are still phases that feel “tricky” or not fully streamlined. &lt;strong&gt;External tools don’t replace the platform—they augment it&lt;/strong&gt;. They smooth out the friction, reduce the cognitive load, and accelerate the parts that would otherwise slow you down. And, most important, &lt;strong&gt;the knowledge of who's between the chair and the keyboard still makes the difference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But the biggest takeaway is probably this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This way of building changes how you think.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It shifts the balance between control and trust. Between handcrafted tooling and platform-native capabilities. Between “how I’ve always done it” and “what’s now possible.”&lt;/p&gt;

&lt;p&gt;And honestly—I didn’t expect to enjoy it this much.&lt;/p&gt;

&lt;p&gt;Because once you let go of the need to control every detail, and start embracing what the platform already offers, you realize something subtle but powerful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You’re not just building differently. &lt;br&gt;
You’re working differently. Focusing on what really matters, and leaving the details behind.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>powerplatform</category>
      <category>powerapps</category>
    </item>
    <item>
      <title>ALM on Power Platform: ADO + GitHub, the best of both worlds</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 04 May 2026 08:50:02 +0000</pubDate>
      <link>https://forem.com/_neronotte/alm-on-power-platform-ado-github-the-best-of-both-worlds-5dbh</link>
      <guid>https://forem.com/_neronotte/alm-on-power-platform-ado-github-the-best-of-both-worlds-5dbh</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;You can integrate Azure DevOps with GitHub to get the best of both worlds in Power Platform development.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ADO stays as the backbone&lt;/strong&gt;: work items, sprint planning, test plans, and deploy pipelines all remain on Azure DevOps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code moves to GitHub&lt;/strong&gt;: &lt;em&gt;Power App Code Apps&lt;/em&gt; or &lt;em&gt;Power Pages SPA&lt;/em&gt; live in GitHub repos, unlocking native GitHub Copilot integration and the Copilot Cloud Agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The two platforms are linked&lt;/strong&gt;: commits reference ADO work items via &lt;code&gt;AB#{id}&lt;/code&gt;, creating a bidirectional traceability layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Copilot Cloud Agent works autonomously&lt;/strong&gt;: assign it a task, it opens a PR; assign it to a PR, it reviews and fixes issues — all guided by your repo's own instructions.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Managing ALM on Power Platform&lt;/strong&gt; has never been straightforward. Anyone who works seriously with CRM and business applications knows it: an application's lifecycle doesn't end when the code is written. &lt;em&gt;Requirements, sprints, pipelines, tests, reviews&lt;/em&gt; — everything needs to fit together coherently. And for years, my answer to that problem has been Azure DevOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ The starting point: Azure DevOps as the backbone
&lt;/h2&gt;

&lt;p&gt;Azure DevOps has always given me what I needed: Work Items for tracking requirements, Git repositories, CI/CD pipelines, integrated test plans. A complete ecosystem built for teams that want full control over the application lifecycle — from user stories all the way to production deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Power Platform applications ADO remains a rock-solid choice&lt;/strong&gt;, whether you're building &lt;em&gt;canvas apps, model-driven apps, or Power Pages portals&lt;/em&gt;. Solution pipelines, ALM tooling like Power Platform Build Tools, and native environment integration make the workflow reproducible and governable.&lt;/p&gt;

&lt;p&gt;All good. &lt;/p&gt;

&lt;p&gt;Then came the &lt;strong&gt;Power App Code Apps&lt;/strong&gt; and &lt;strong&gt;Power Pages SPA&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎮 New App types are changing the rules of the game
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Power App Code Apps&lt;/strong&gt; and &lt;strong&gt;Power Pages SPAs&lt;/strong&gt; represent a paradigm shift: &lt;strong&gt;you're no longer working with a low-code designer&lt;/strong&gt;, you're writing real code — TypeScript, React, custom components. And when you write real code, &lt;strong&gt;AI in the development cycle stops being a luxury and becomes a genuine accelerator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They benefit enormously from AI in their development cycle&lt;/strong&gt;: code completion, automated review, test generation. This changes the weight of your platform choice significantly.&lt;/p&gt;

&lt;p&gt;By company policy, our trusted assistant is &lt;strong&gt;GitHub Copilot&lt;/strong&gt;. And here's where the first point of friction emerges: &lt;strong&gt;GitHub Copilot lives on GitHub&lt;/strong&gt;, not on ADO.&lt;/p&gt;

&lt;h2&gt;
  
  
  💎 GitHub is more than just a repository
&lt;/h2&gt;

&lt;p&gt;In recent months, with the introduction of the GitHub Copilot Cloud Agent, the platform has taken a significant leap forward. GitHub is &lt;strong&gt;no longer a mere Git repo storage: it's an autonomous agent platform&lt;/strong&gt;, that is able to run agents in the cloud, in the background, like real collaborators.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;GitHub doesn't match ADO's completeness for the rest of the lifecycle&lt;/strong&gt;: requirements management, sprint planning, structured test plans — what an enterprise team needs to govern a complex project remains ADO's strong suit.&lt;/p&gt;

&lt;p&gt;The question I asked myself was: &lt;em&gt;can I get the best of both?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 The hybrid setup: ADO + GitHub integrated
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8osbrqado1r7rvrrbmw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8osbrqado1r7rvrrbmw.png" alt="ALM Architecture" width="681" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The answer is yes — and the setup is less complicated than it might sound. &lt;a href="https://learn.microsoft.com/en-us/azure/devops/cross-service/github-integration?view=azure-devops" rel="noopener noreferrer"&gt;Azure DevOps and GitHub integrate natively&lt;/a&gt;. Once the connection between the two environments is configured, the two systems start talking to each other bidirectionally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code lives on GitHub&lt;/strong&gt;. &lt;strong&gt;Work items, requirements, sprint planning, test plans — everything stays on ADO&lt;/strong&gt;. &lt;strong&gt;Deployment pipelines to Power Platform keep running on ADO Pipelines&lt;/strong&gt;, with the connectors you already know.&lt;/p&gt;

&lt;h2&gt;
  
  
  ↔️ The bidirectional link in commits
&lt;/h2&gt;

&lt;p&gt;One of the things I cared most about was &lt;strong&gt;not losing the connection between code and work items&lt;/strong&gt;. In pure ADO, this works naturally: a commit can reference a work item and build a traceable link. I wanted the same result in the hybrid setup.&lt;/p&gt;

&lt;p&gt;And I have it. The syntax to use in commit messages (or PR comments) is straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In GitHub commits, use the &lt;code&gt;AB#{WORKITEMID}&lt;/code&gt; syntax to automatically link the commit to the corresponding work item on Azure Boards. The link appears on both sides, bidirectionally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Writing &lt;code&gt;fix: error handling on component init AB#1234&lt;/code&gt; in the commit message is enough to create a traceable link between the code change and the ADO task. The integration &lt;strong&gt;automatically populates the "Development" section with a reference to the commit&lt;/strong&gt; or PR.&lt;/p&gt;

&lt;p&gt;Anyone who works with ADO knows how important this kind of traceability is during audits, release planning, or simply when trying to understand "why was this code written this way."&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 The Copilot Cloud Agent: a collaborator that works while you sleep
&lt;/h2&gt;

&lt;p&gt;This is the part that &lt;strong&gt;genuinely changed how I work&lt;/strong&gt;. The &lt;strong&gt;GitHub Copilot Cloud Agent&lt;/strong&gt; can be &lt;strong&gt;assigned directly to a bug or a task&lt;/strong&gt; — and from that point, &lt;strong&gt;it starts working on it autonomously&lt;/strong&gt;, opening a Pull Request and building the solution in the background.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jzonp2a9ww91i8u6fbs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jzonp2a9ww91i8u6fbs.png" alt="Assign a work item to GitHub Copilot Cloud Agent" width="613" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The quality of the output depends heavily on &lt;strong&gt;how you write the task&lt;/strong&gt;. A well-structured issue — with clear context, acceptance criteria, and references to the existing codebase — produces a significantly better PR than a vague, one-line title. The investment you make when writing the task pays off when the agent delivers the work done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ful6lr9qp9doht0dr92vk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ful6lr9qp9doht0dr92vk.png" alt="Activities performed by GH Copilot Cloud Agent" width="681" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the most compelling part is the &lt;strong&gt;automated Pull Request review&lt;/strong&gt;. The Copilot Cloud Agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;analyzes every PR opened by developers, &lt;/li&gt;
&lt;li&gt;anticipates problems, &lt;/li&gt;
&lt;li&gt;flags questionable patterns, &lt;/li&gt;
&lt;li&gt;and can be actively engaged to fix them — all inside the PR, in comments, in a collaborative way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr89zvck7g1n6nqiyftex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr89zvck7g1n6nqiyftex.png" alt="Pull Request reviewed by GitHub Copilot" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeoxrykbvhpdbe5sfh12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feeoxrykbvhpdbe5sfh12.png" alt="Pull request fixed by GitHub Copilot" width="800" height="1100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Contextual review, not generic feedback
&lt;/h2&gt;

&lt;p&gt;There's one aspect that sets this configuration apart from a generic "AI that does code review": &lt;strong&gt;the Copilot Cloud Agent works with the repository's context&lt;/strong&gt;. This means its &lt;strong&gt;reviews aren't generic&lt;/strong&gt; — they're &lt;strong&gt;grounded in the guidelines, architectural patterns, and conventions the team has defined in the repository itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Through instruction files, skills, and rules configurable at the repository level (&lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; and custom instructions), you can teach the agent how your Code App is structured, which patterns to follow, which anti-patterns to avoid, and which naming conventions to enforce.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; file is your control point: here you define architectural guidelines, team conventions, and patterns to follow. The agent uses them as a reference for every review and every task you assign to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result is a review that speaks your project's language — not a generic checklist that anyone could copy from a blog post (including this one). If the team has decided that components follow a certain error-handling pattern, the agent knows it — and verifies it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The ADO + GitHub setup isn't a compromise: it's a deliberate choice that leverages the strengths of each platform in the domain where it excels. ADO for governance, planning, and the full lifecycle. GitHub for development, AI, and the Copilot Agent's capabilities.&lt;/p&gt;

&lt;p&gt;For teams working on Power App Code Apps — and increasingly on Power Pages SPAs too — this configuration reduces the cost of review, improves the quality of the code produced, and maintains the traceability that an enterprise project demands.&lt;/p&gt;

&lt;p&gt;The Copilot Cloud Agent doesn't replace the developer: it anticipates problems, handles the more mechanical tasks, and frees up developers' time for the decisions that matter. And that's exactly what you'd ask of a good collaborator.&lt;/p&gt;

&lt;p&gt;One frontier I'm actively looking forward to exploring: &lt;strong&gt;bringing the same degree of automation to model-driven apps&lt;/strong&gt;. Today, the code-first nature of Code Apps is what makes the Copilot Agent so effective — but &lt;strong&gt;with tools like PACX, App Maker MCP Server, Dataverse MCP Server, Power Platform Skills for Generative Pages, and so on&lt;/strong&gt;, the prospect of applying a similar AI-driven workflow to model-driven development is becoming increasingly concrete. Watch this space.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>Power Pages SPA Learning By Doing - Keep it clean</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Sun, 22 Mar 2026 15:09:23 +0000</pubDate>
      <link>https://forem.com/_neronotte/power-pages-spa-learning-by-doing-keep-it-clean-mg4</link>
      <guid>https://forem.com/_neronotte/power-pages-spa-learning-by-doing-keep-it-clean-mg4</guid>
      <description>&lt;p&gt;Lately, I’ve been working a lot with &lt;a href="https://learn.microsoft.com/en-us/power-pages/configure/create-code-sites" rel="noopener noreferrer"&gt;&lt;strong&gt;Power Pages Single Page Applications (SPA)&lt;/strong&gt;&lt;/a&gt;.&lt;br&gt;
I find this approach &lt;strong&gt;far more effective&lt;/strong&gt; than the previous one, and its &lt;strong&gt;potential&lt;/strong&gt;, combined with &lt;strong&gt;GitHub Copilot’s ability to generate React code&lt;/strong&gt;, makes portal development &lt;strong&gt;faster&lt;/strong&gt;, &lt;strong&gt;more efficient&lt;/strong&gt;, and honestly even &lt;strong&gt;fun&lt;/strong&gt; 😎.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power Pages SPA&lt;/strong&gt; reached General Availability at the beginning of &lt;strong&gt;February&lt;/strong&gt;, almost &lt;strong&gt;two months ago&lt;/strong&gt;, but even today the &lt;strong&gt;official documentation&lt;/strong&gt; is still not fully exhaustive. Many things can only be discovered by &lt;strong&gt;hands-on experimentation&lt;/strong&gt; with the platform.&lt;/p&gt;

&lt;p&gt;This article — or rather, this &lt;strong&gt;series of articles&lt;/strong&gt; — is born with the goal of sharing the &lt;strong&gt;pain points&lt;/strong&gt; and &lt;strong&gt;hidden gems&lt;/strong&gt; I’ve encountered during my personal &lt;strong&gt;journey&lt;/strong&gt; exploring this new technology.&lt;/p&gt;

&lt;p&gt;So… what are we waiting for?  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s get started! 🚀&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 The issue - Stale files generated by chunking strategy
&lt;/h2&gt;

&lt;p&gt;If you build a Power Pages SPA using &lt;strong&gt;React+Vite&lt;/strong&gt;, as in &lt;a href="https://github.com/microsoft/power-pages-samples/tree/main/samples/bring-your-own-code/react/car-sales-website" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;, every time you build your portal via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build process automatically splits your site source code in a set of chunked files&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vp1hrjh6azcn7814fin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vp1hrjh6azcn7814fin.png" alt="Chunked files" width="555" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;random-looking hashes&lt;/strong&gt; in chunk names change because Vite uses content hashes by default. This means that when you change the content of your site, each chunk content may change, thus the chunk name changes too.&lt;/p&gt;

&lt;p&gt;Those chunks become &lt;strong&gt;Web Files&lt;/strong&gt; records in dataverse, that are &lt;strong&gt;created&lt;/strong&gt; or &lt;strong&gt;updated&lt;/strong&gt; when you type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;upload-code-site&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rootPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exactly... that command, by itself, &lt;strong&gt;adds&lt;/strong&gt; or &lt;strong&gt;updates&lt;/strong&gt; Web Files, but &lt;strong&gt;does not remove old, now unused, files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On the medium-long run, this is a &lt;strong&gt;real issue, because your site definition will be crowded by tens or thousands of useless Web Files&lt;/strong&gt;, that you will deploy between environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 How to fix it?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📌 Step A. Apply deterministic names on chunks
&lt;/h3&gt;

&lt;p&gt;To avoid generate random-looking hashed names, you can customize how Vite produces the output files, with different possible strategies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important notice&lt;/strong&gt;: these strategies are tested with &lt;strong&gt;Vite 7.3.1&lt;/strong&gt;. &lt;strong&gt;Vite 8&lt;/strong&gt; (released March 2026) may require a different approach.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  1. Disable hashing entirely (simplest)
&lt;/h4&gt;

&lt;p&gt;In &lt;code&gt;vite.config.ts&lt;/code&gt;, override the &lt;code&gt;rollupOptions.output&lt;/code&gt; naming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Static names — no hashes at all&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you fully predictable names like &lt;code&gt;index.js&lt;/code&gt;, &lt;code&gt;vendor.js&lt;/code&gt;, etc. The downside is no &lt;strong&gt;cache-busting&lt;/strong&gt;, but for Power Pages/ALM this is often exactly what you want.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Use a fixed/stable hash (content-based, but stable)
&lt;/h4&gt;

&lt;p&gt;If you want hashes but want them to only change when the &lt;strong&gt;content&lt;/strong&gt; actually changes (which is actually Vite's intent, but chunk splitting can cause cascading renames):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[hash].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[hash].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[hash].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue here is usually not the hash algorithm but &lt;strong&gt;chunk splitting&lt;/strong&gt; — if Rollup splits chunks differently between builds, names drift. Fix that with option 3.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Pin your manual chunks (most robust for ALM)
&lt;/h4&gt;

&lt;p&gt;The real culprit is often Rollup's &lt;strong&gt;automatic code splitting&lt;/strong&gt; producing differently-named dynamic chunks. Lock it down with &lt;code&gt;manualChunks&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;manualChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Pin vendor libs into a stable named chunk&lt;/span&gt;
          &lt;span class="na"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="c1"&gt;// Add other large deps as needed&lt;/span&gt;
          &lt;span class="c1"&gt;// router: ['react-router-dom'],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way Rollup won't invent new chunk names — you've explicitly defined the boundaries.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Disable code splitting entirely (nuclear option)
&lt;/h4&gt;

&lt;p&gt;If the &lt;strong&gt;SPA is small enough&lt;/strong&gt;, just bundle everything into one file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;inlineDynamicImports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// single bundle&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/index.js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Recommended approach for Power Pages
&lt;/h4&gt;

&lt;p&gt;In the end I've decided to combine options 1+3, that work for my needs. &lt;br&gt;
Considering that I'm using the following libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fluentui&lt;/code&gt;: for ui components of my website&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-router-dom&lt;/code&gt;: to handle url routing in my SPA&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;powerbi-client-react&lt;/code&gt;: to embed powerbi reports on the site&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-google-charts&lt;/code&gt;: for home page charts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adal-angular&lt;/code&gt;: to manage authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is (part of) my &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// absolute path - Power Pages handles routing&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;manualChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;fluent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fluentui/react-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fluentui/react-icons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;powerbi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;powerbi-client-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;charts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-google-charts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;adal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adal-angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives me &lt;strong&gt;a fixed, known set of filenames&lt;/strong&gt; that map 1:1 to my Power Pages Web Files, and they only change if you explicitly add a new &lt;code&gt;manualChunks&lt;/code&gt; group.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hfu4nrl3fo9urfn6xvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hfu4nrl3fo9urfn6xvb.png" alt="Generated chunks" width="623" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧹 B. Good, but now how I get rid of the old files?
&lt;/h3&gt;

&lt;p&gt;A small hint on how to fix it is given by &lt;code&gt;pac pages upload-code-site&lt;/code&gt; command itself. When you run it, it says:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo1172oawfylx3ldqpvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo1172oawfylx3ldqpvl.png" alt="Power pages bundles" width="800" height="293"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found 10 bundle pattern(s) to match for cleanup:
1. main .*. js
3. vendor .*. js
4. vendor .*. css
5. chunk -*. js
6. chunk -*. css
7. bundle .*. js
8. bundle .*. css
9. index -*. js
10. index -*. css

Note: You can customize these patterns by adding 'bundleFilePatterns' array in 'powerpages.config.json' file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, even if isn't documented anywhere, it seems it tries to clean up the mess before pushing, and it seems we can tweak the &lt;code&gt;powerpages.config.json&lt;/code&gt; file to customize it's behavior. After a few tests, I've found that if you add a &lt;code&gt;bundleFilePatterns&lt;/code&gt; node with an &lt;strong&gt;array of file patterns&lt;/strong&gt;, those are "pruned" by &lt;code&gt;pac pages upload-code-site&lt;/code&gt; before being replaced by the new bundles generated from your build.&lt;/p&gt;

&lt;p&gt;In my case, the configuration that works is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"siteName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-site-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"defaultLandingPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compiledPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bundleFilePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"main.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"vendor.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"vendor.*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"chunk-*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"chunk-*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bundle.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bundle.*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index-*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index-*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index.*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Filled.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Filled.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Filled.*.ttf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Light.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Light.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Light.*.ttf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Regular.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Regular.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Regular.*.ttf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Resizable.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Resizable.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Resizable.*.ttf"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it works... it effectively removes all stale files from your local &lt;code&gt;.powerpages-site&lt;/code&gt; folder, and when pushed to Dataverse, those file are removed from there as well... or at least, 90% of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  🖖🏼 C. Let's finish by hand
&lt;/h3&gt;

&lt;p&gt;Even after tweaking the &lt;code&gt;vite.config.ts&lt;/code&gt; and the &lt;code&gt;powerpages.config.json&lt;/code&gt;, I've found a few files that still remain on my Dataverse environment. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oxcfhv0bqpz6mmvj5li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oxcfhv0bqpz6mmvj5li.png" alt="Stale files on Dataverse" width="353" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Frankly I think it's a bug of PAC CLI. The only solution I've found is to remove them manually, hoping they won't be re-generated again thanks to &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt;: when you do it, you may occur in the following &lt;strong&gt;non-blocking-error&lt;/strong&gt; while running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XRM Network error: An error occurred in the PowerPageComponentDeletePlugin.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only solution I've found to solve this issue is to &lt;strong&gt;edit manually the file&lt;/strong&gt; called &lt;code&gt;.portalconfig/my-site-url-manifest.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use &lt;strong&gt;Plugin Trace Viewer&lt;/strong&gt; XrmToolBox Plugin to get all exceptions coming from plugin &lt;code&gt;Microsoft.PowerPages.Core.Plugins.PowerPageComponentDeletePlugin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;from the trace message, extract the GUID of the missing component.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rap14jamy8hwnlwuwg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rap14jamy8hwnlwuwg6.png" alt="Exception" width="800" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;search for that GUID into file &lt;code&gt;.portalconfig/my-site-url-manifest.yml&lt;/code&gt; and remove the yaml section referring to it. It should be similar to the following.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sample block to remove&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;RecordId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;f9d754fb-4b71-4491-bad7-7c06eb21bce5&lt;/span&gt;
  &lt;span class="na"&gt;DisplayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.COcDBgFa.css&lt;/span&gt;
  &lt;span class="na"&gt;CheckSum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fa7ab4d0d9716e5ae809c29050b94cdaf004c4c51e1f35241716333b1352fb4a&lt;/span&gt;
  &lt;span class="na"&gt;IsDeleted&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also want to find all lines with &lt;code&gt;IsDeleted: true&lt;/code&gt; and &lt;strong&gt;remove them&lt;/strong&gt;, and also &lt;strong&gt;clear the contents&lt;/strong&gt; of the &lt;code&gt;.portalconfig/manifest.yml&lt;/code&gt; file (that contains only references to deleted files).&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Final considerations
&lt;/h2&gt;

&lt;p&gt;Power Pages SPA is a &lt;strong&gt;huge step forward&lt;/strong&gt; compared to the classic portal model, especially if you come from a modern frontend background. Being able to bring your own React stack, use Vite, and rely on tools like GitHub Copilot dramatically changes the &lt;strong&gt;developer experience&lt;/strong&gt; and the &lt;strong&gt;delivery speed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That said, today’s reality is that &lt;strong&gt;Power Pages SPA is still a young product&lt;/strong&gt;. Some important behaviors — like how bundles are handled, how cleanup works, and how Dataverse Web Files are managed over time — are either &lt;strong&gt;underdocumented&lt;/strong&gt; or &lt;strong&gt;not documented at all&lt;/strong&gt;. As a result, production-ready solutions still require a fair amount of &lt;strong&gt;experimentation, reverse‑engineering&lt;/strong&gt;, and sometimes even &lt;strong&gt;manual intervention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The chunking issue described in this article is a perfect example:&lt;br&gt;
nothing is &lt;em&gt;technically broken&lt;/em&gt;, but without deterministic filenames and explicit cleanup rules, you end up with a &lt;strong&gt;slowly degrading site definition&lt;/strong&gt;, polluted by stale Web Files that get deployed across environments and make ALM harder than it should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My personal takeaway&lt;/strong&gt; so far is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treat the build output as a contract&lt;/strong&gt;: Stable filenames and pinned chunks are not optional in enterprise scenarios — they’re a prerequisite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume PAC CLI cleanup is best‑effort, not authoritative&lt;/strong&gt;: &lt;code&gt;bundleFilePatterns&lt;/code&gt; helps a lot, but today it doesn’t guarantee a 100% clean state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect to occasionally "drop below the abstraction"&lt;/strong&gt;: Editing manifests and cleaning Dataverse artifacts by hand shouldn’t be necessary — but right now, it sometimes is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite all of this, &lt;strong&gt;I still strongly believe Power Pages SPA is the right direction&lt;/strong&gt;. The flexibility it gives you largely outweighs the current rough edges, especially if you’re already comfortable with React, modern bundlers, and CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;Hopefully, future iterations of the platform — and better documentation — will make many of the workarounds shown here obsolete. Until then, &lt;strong&gt;knowing where the sharp edges are&lt;/strong&gt; is the key to using this technology effectively.&lt;/p&gt;

&lt;p&gt;In the next articles of this series, I’ll dive into other &lt;strong&gt;real-world issues and patterns&lt;/strong&gt; I’ve encountered while building Power Pages SPAs — from authentication quirks, to CSP constraints, to deployment strategies that actually scale.&lt;/p&gt;

&lt;p&gt;Stay tuned 🚀&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>powerfuldevs</category>
      <category>powerpages</category>
    </item>
    <item>
      <title>Simple, but yet rock hard, truth.</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 02 Mar 2026 22:20:43 +0000</pubDate>
      <link>https://forem.com/_neronotte/simple-but-yet-rock-hard-truth-25la</link>
      <guid>https://forem.com/_neronotte/simple-but-yet-rock-hard-truth-25la</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/the_nortern_dev" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3630167%2F2e206d7e-04d3-484b-8a73-1f98d17a0e1a.png" alt="the_nortern_dev"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/the_nortern_dev/the-hardest-part-of-being-a-developer-isnt-coding-its-disappearing-quietly-52l" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;The Hardest Part of Being a Developer Isn’t Coding. It’s Disappearing Quietly.&lt;/h2&gt;
      &lt;h3&gt;NorthernDev ・ Feb 28&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#discuss&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#mentalhealth&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>discuss</category>
      <category>mentalhealth</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Greg.KeySub – The Ultimate Revenge Against the Italian Keyboard</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 09 Feb 2026 21:42:04 +0000</pubDate>
      <link>https://forem.com/_neronotte/gregkeysub-the-ultimate-revenge-against-the-italian-keyboard-2ena</link>
      <guid>https://forem.com/_neronotte/gregkeysub-the-ultimate-revenge-against-the-italian-keyboard-2ena</guid>
      <description>&lt;p&gt;There’s a group of people who have been silently suffering for years.&lt;br&gt;
A forgotten minority, ignored by institutions, abandoned by the tech industry, left behind even by Microsoft, Apple, and the United Nations.&lt;/p&gt;

&lt;p&gt;I’m talking about us: &lt;strong&gt;Italian developers forced to use the Italian keyboard layout&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Yes, &lt;strong&gt;that one&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The one that gives you the incredibly useful “§” key, which you have never pressed voluntarily in your entire life, but denies you the most fundamental character of the modern software era:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The backtick: `&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The backbone of Markdown.&lt;br&gt;
The unsung hero of code snippets.&lt;br&gt;
The vital organ of shell commands.&lt;br&gt;
A character so essential that GitHub should send you a condolence letter every time you try to write documentation with an Italian keyboard.&lt;/p&gt;

&lt;p&gt;The Italian layout? It simply forgot it. Oh, but it has plenty of "useful" keys such as "§" or "ç"... &lt;/p&gt;

&lt;p&gt;Every Italian developer has faced at least one of the following situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typing ALT+96 with the elegance of a musician playing the piano with their elbows.&lt;/li&gt;
&lt;li&gt;Juggling with WIN+. &amp;gt; move to chars &amp;gt; select the `&lt;/li&gt;
&lt;li&gt;Googling “backtick” just to copy and paste the damn symbol.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So… I decided to stop suffering.&lt;/p&gt;

&lt;p&gt;I did what any developer does when reality refuses to cooperate:&lt;br&gt;
&lt;strong&gt;I wrote a tool to bypass reality entirely&lt;/strong&gt;. Or better, I &lt;strong&gt;vibe coded&lt;/strong&gt; a tool to save me hours per week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing: Greg.KeySub
&lt;/h2&gt;

&lt;p&gt;Available here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/neronotte/Greg.KeySub" rel="noopener noreferrer"&gt;https://github.com/neronotte/Greg.KeySub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Greg.KeySub&lt;/strong&gt; is a tiny Windows tool that quietly lives in your system tray — right next to a few utilities, five unnecessary Electron apps, and three icons whose meaning you’ve forgotten years ago.&lt;/p&gt;

&lt;p&gt;Its one and only job?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen to the keys you press&lt;/strong&gt;, and whenever you hit that useless §, it magically transforms it into a glorious, essential, life-improving `.&lt;/p&gt;

&lt;p&gt;Yes, you read that right: &lt;strong&gt;§ becomes `&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because that key had no purpose. None.&lt;br&gt;
Now, finally, it serves a higher mission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Should Install It Right Now
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🎯 Because it saves time.&lt;/strong&gt;&lt;br&gt;
Write Markdown like a normal person, not like someone performing finger acrobatics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔥 Because it gives purpose to a key that had none.&lt;/strong&gt;&lt;br&gt;
Digital recycling at its finest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;😤 Because the Italian keyboard layout will never fix itself.&lt;/strong&gt;&lt;br&gt;
Let’s be honest. It’s been decades. It’s not happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⭐ Because it’s open source.&lt;/strong&gt;&lt;br&gt;
You can contribute, improve it, fork it, or just stare at the code thinking “I would’ve done it differently.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it, try it, love it ❤️
&lt;/h2&gt;

&lt;p&gt;Find it here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/neronotte/Greg.KeySub" rel="noopener noreferrer"&gt;https://github.com/neronotte/Greg.KeySub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re an Italian programmer who writes Markdown, this tool is like that 4 PM coffee: &lt;strong&gt;You didn’t know you needed it until you started using it&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Modern Power App development takes a lot of... SPACE.</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Tue, 20 Jan 2026 22:03:37 +0000</pubDate>
      <link>https://forem.com/_neronotte/modern-power-app-development-takes-a-lot-of-space-3f3j</link>
      <guid>https://forem.com/_neronotte/modern-power-app-development-takes-a-lot-of-space-3f3j</guid>
      <description>&lt;h2&gt;
  
  
  🧠 The Paradigm Shift: Why We Don’t Argue UI Anymore
&lt;/h2&gt;

&lt;p&gt;There was a time (you remember it) when any client request that drifted outside the "blessed" Power Apps patterns triggered a &lt;strong&gt;long conversation about platform constraints&lt;/strong&gt;. We tried to reshape requirements, push standard controls, and hope stakeholders would accept a slightly clunky interaction with the UI in the name of long-term maintainability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That era is over&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;AI-assisted development&lt;/strong&gt; (hello my dear friend, GitHub Copilot), it’s now faster and safer to build &lt;em&gt;small&lt;/em&gt;, &lt;em&gt;targeted&lt;/em&gt; customizations—&lt;em&gt;PCFs or React WebResources&lt;/em&gt;—than to litigate UX. &lt;/p&gt;

&lt;p&gt;You can ship exactly what the client envisions, document it properly, and make it maintainable long-term. The payoff is huge: &lt;strong&gt;better UX → higher adoption → stronger platform stickiness&lt;/strong&gt;. In other words, great UI is not a "nice-to-have"—it’s a retention strategy.&lt;/p&gt;

&lt;p&gt;When you replace “discuss why we can’t” with “deliver what delights,” you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Earn trust faster&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accelerate sign-off&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anchor the customer to the platform&lt;/strong&gt; with experiences they love&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there’s a catch…&lt;/p&gt;

&lt;h2&gt;
  
  
  🧱 The Problem: A Dozen PCFs/WebResources = Disk Space Meltdown
&lt;/h2&gt;

&lt;p&gt;React-based PCFs and WebResources lean on Node’s ecosystem. And Node’s ecosystem leans on… &lt;code&gt;node_modules&lt;/code&gt;—the folder that eats hard drives for breakfast.&lt;/p&gt;

&lt;p&gt;Every project carries its own dependency tree. Multiply that across 8, 12, 20 PCFs/WebResources… and your laptop screams. It’s common to see hundreds of thousands of files and tens (or hundreds) of GB consumed across projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good news&lt;/strong&gt;: there’s a clean, robust solution that many Power Platform devs underuse:&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 The Hidden Gem: NPM Workspaces
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NPM Workspaces&lt;/strong&gt; let you manage multiple packages/projects (PCFs/WebResources) under one repository, sharing a single top-level node_modules whenever possible. This dramatically reduces duplication and makes dependency management more predictable and CI-friendly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.npmjs.com/cli/v7/using-npm/workspaces" rel="noopener noreferrer"&gt;Official docs: NPM Workspaces&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;One central &lt;code&gt;node_modules&lt;/code&gt;&lt;/strong&gt; (plus small per-package shims when needed)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Shared tooling&lt;/strong&gt; (TypeScript config, lint rules, build scripts)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Shared local packages&lt;/strong&gt; (@types/xrm, @fluentui/react-components) used across PCFs/WRs via workspace:&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Unified scripts&lt;/strong&gt; (build, test, lint) across all packages&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Faster CI&lt;/strong&gt; with a single dependency tree and cache&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤔 How Does It Works?
&lt;/h2&gt;

&lt;p&gt;It's quite straightforward. All you need is to define a &lt;code&gt;package.json&lt;/code&gt; and a &lt;code&gt;package-lock.json&lt;/code&gt; files at the root of your project folder, just above the folders containing PCFs and WebResources. This will make your project folder a &lt;strong&gt;monorepo&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📂 my-monorepo/
├─ 📄 package.json         &amp;lt;-- workspace root
├─ 📄 package-lock.json
│
├─ 📂 Pcf/
│  ├─ 📂 pcf1/
│  │  ├─ 📄 package.json
│  │  └─ ...
│  └─ 📂 pcf2/
│     ├─ 📄 package.json
│     └─ ...
│
└─ 📂 WebResources/
   ├─ 📂 webresource1/
   │  ├─ 📄 package.json
   │  └─ ...
   └─ 📂 webresource2/
      ├─ 📄 package.json
      └─ ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is important that the root &lt;code&gt;package.json&lt;/code&gt; file contains the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-monorepo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Pcf/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"WebResources/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can do it manually, or via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-monorepo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-y&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workspaces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Pcf/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workspaces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"WebResources/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can create your PCFs into the PCF and WebResources folder as usual... &lt;strong&gt;Just a tip&lt;/strong&gt;: remember that the value of the &lt;code&gt;name&lt;/code&gt; node of the &lt;code&gt;package.json&lt;/code&gt; files under each of your PCFs/WRs &lt;strong&gt;must&lt;/strong&gt; be unique, otherwise you'll get an error while running &lt;code&gt;npm &amp;lt;whatever&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you'll run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All dependencies will be installed in a single, above all, &lt;code&gt;node_modules&lt;/code&gt; folder created just under the &lt;code&gt;my-monorepo&lt;/code&gt; folder.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: If two packages need &lt;strong&gt;different versions of the same dep&lt;/strong&gt;, npm may place some deps locally. That’s fine; you’ll still save tons of space overall.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🗂️ A Practical Folder Structure for Power Platform Projects
&lt;/h2&gt;

&lt;p&gt;Here’s a realistic example of the output folder you'll get, on a standard Power App project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📂 project-folder
├─ 📄 .gitignore
├─ 📂 src
│  ├─ 📄 package.json
│  ├─ 📄 package-lock.json
│  ├─ 📂 node_modules
│  ├─ 📂 Pcf
│  │  ├─ 📂 NumericInputControl
│  │  │  ├─ 📄 package.json
│  │  │  ├─ 📄 package-lock.json
│  │  │  └─ ...
│  │  ├─ 📂 PurchaseOrderGrid
│  │  │  ├─ 📄 package.json
│  │  │  ├─ 📄 package-lock.json
│  │  │  └─ ...
│  │  └─ 📂 CustomTreeView
│  │     ├─ 📄 package.json
│  │     ├─ 📄 package-lock.json
│  │     └─ ...
│  └─ 📂 WebResources
│     ├─ 📂 HomePage
│     │  ├─ 📄 package.json
│     │  ├─ 📄 package-lock.json
│     │  └─ ...
│     └─ 📂 ControlDashboard
│        ├─ 📄 package.json
│        ├─ 📄 package-lock.json
│        └─ ...
└─ 📂 test
   └─ ... # other stuff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📉 How Much Space Do You Actually Save?
&lt;/h2&gt;

&lt;p&gt;In Power Platform solutions with 8–20 React packages, we’ve seen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;60–80% reduction&lt;/strong&gt; in duplicated dependency files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build times drop&lt;/strong&gt; (less overall I/O and fewer cold installs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your mileage varies with library choices, but the direction is always the same: less bloat, more flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  🪄 Conclusions
&lt;/h2&gt;

&lt;p&gt;AI copilot tools make building &lt;strong&gt;custom&lt;/strong&gt;, &lt;strong&gt;crisp UX&lt;/strong&gt; easy. &lt;strong&gt;NPM Workspaces&lt;/strong&gt; make maintaining many customizations sane. Put them together and you’ve got a Power Platform development lifecycle that’s fast, clean, and—dare I say—fun.&lt;/p&gt;

&lt;p&gt;Give it a try, and let me know in the comments what you think about it!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
      <category>powerapps</category>
      <category>npm</category>
    </item>
    <item>
      <title>Automatically Refresh Forms After Status Changes in Model Driven Apps</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Fri, 26 Dec 2025 14:16:52 +0000</pubDate>
      <link>https://forem.com/_neronotte/automatically-refresh-forms-after-status-changes-in-model-driven-apps-ig</link>
      <guid>https://forem.com/_neronotte/automatically-refresh-forms-after-status-changes-in-model-driven-apps-ig</guid>
      <description>&lt;p&gt;When designing complex state models in Model Driven Apps, sometimes it can happen you need to "&lt;strong&gt;freeze&lt;/strong&gt;" the possibility for the user to update a record data &lt;strong&gt;while some operation runs in background&lt;/strong&gt;, and then make it editable again when the operation completes.&lt;/p&gt;

&lt;p&gt;A few practical examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document Generation and E-signature&lt;/strong&gt;: A quote record triggers automatic PDF generation and sends it to DocuSign or Adobe Sign. While the document is being generated, sent, and awaiting signature, the contract terms should be locked. Once the signature is complete (or rejected), the form becomes editable again for next steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit Check Integration&lt;/strong&gt;: When a customer service rep creates a new customer account, an external credit check service is called in the background. The form needs to be frozen while waiting for the credit score, payment terms recommendations, and risk assessment to return. You don't want users changing customer details while the credit bureau is still processing the original information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory Reservation in Order Processing&lt;/strong&gt;: When an order is placed, a background process checks inventory across multiple warehouses and reserves stock. During this reservation process (which might involve calling external warehouse management systems), the order quantities should be frozen to prevent overselling or conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance and Regulatory Checks&lt;/strong&gt;: In healthcare or financial services, when a case is submitted, it might trigger automated compliance screenings (sanctions lists, fraud detection, regulatory reporting). The case details must remain locked during these checks to maintain audit trail integrity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Enrichment from Third-party APIs&lt;/strong&gt;: A lead record triggers enrichment processes pulling company data from LinkedIn, Clearbit, or Dun &amp;amp; Bradstreet. While these APIs are called and data is being validated and merged, you want to prevent users from manually entering conflicting information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Typically, you can leverage the out of the box &lt;code&gt;statecode&lt;/code&gt; and &lt;code&gt;statuscode&lt;/code&gt; fields of the interested table to model the &lt;strong&gt;frozen&lt;/strong&gt; statuses. If we take for instance the first example above, a possible state model for the quote table can be the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy30uv7435a7f9cdmo3l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy30uv7435a7f9cdmo3l.png" alt="State model representation" width="573" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Logical Status&lt;/th&gt;
&lt;th&gt;&lt;code&gt;statecode&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;statuscode&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;The quote is being created and filled with data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Submitted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Submitted&lt;/td&gt;
&lt;td&gt;The quote has been submitted for the digital signature, the background operation is running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Signed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;Signed&lt;/td&gt;
&lt;td&gt;The quote is open for post-signature steps (approvals, project generation, etc)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cancelled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Cancelled&lt;/td&gt;
&lt;td&gt;The quote has been logically removed from the system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Sent&lt;/td&gt;
&lt;td&gt;Quote sent to the client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Won&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Won&lt;/td&gt;
&lt;td&gt;The client accepted the quote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Lost&lt;/td&gt;
&lt;td&gt;The client has rejected the quote&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Where the meaning of the &lt;code&gt;statecode&lt;/code&gt;=&lt;code&gt;Inactive&lt;/code&gt; is "not updatable by the user". I often prefer to change the label of that state to "Read only", as I find this wording less misleading and more intuitive.&lt;/p&gt;

&lt;p&gt;If the trigger that changes the status to the inactive one comes from the form, we have a small UX problem: the form will be disabled (as required), the operation will run in the background (as required), but when the operation ends (after a couple of seconds, potentially), the user doesn't have any clue about the completeness. He needs to manually trigger the form refresh to see the changes to the current record.&lt;/p&gt;

&lt;p&gt;Of course you can leverage &lt;a href="https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/send-in-app-notifications?tabs=clientapi" rel="noopener noreferrer"&gt;MDA Push Notification system&lt;/a&gt; in the async workflow to notify the user but... he still needs to reopen the record to see the changes.&lt;/p&gt;

&lt;p&gt;Fixing this UX issue is quite easy, you can just add a few lines of &lt;strong&gt;JavaScript&lt;/strong&gt; in the form that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs only when the status changes to the inactive one you want to monitor&lt;/li&gt;
&lt;li&gt;checks iteratively via WebApi for the status to change&lt;/li&gt;
&lt;li&gt;when the status changes, refreshes the form data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;just like the following one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;formType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ReadOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;BulkEdit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;statuscode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;New&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Submitted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Polling interval for status monitoring&lt;/span&gt;
    &lt;span class="nx"&gt;pollingInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;onLoad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFormContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFormType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Start auto-refresh monitoring if status is Submitted&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statuscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Submitted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOnLoad&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Starts monitoring the record status if it's in Initializing state
     * @param {object} formContext - The form context
     */&lt;/span&gt;
    &lt;span class="nf"&gt;startStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Only start monitoring if status is Submitted&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentStatus&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Starting status monitoring for Initializing record&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Clear any existing interval&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Start polling every 2 seconds&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkStatusChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error checking status change:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="c1"&gt;// Continue polling even if there's an error&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Checks if the record status has changed and refreshes the page if it has
     * @param {object} formContext - The form context
     */&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;checkStatusChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recordId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entityName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntityName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Retrieve the current statuscode from the server&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Xrm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WebApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieveRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entityName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?$select=statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statuscode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentFormStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Current form status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentFormStatus&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Server status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;serverStatus&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// If status has changed from Submitted, refresh the page&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentFormStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;serverStatus&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status changed, refreshing page...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="c1"&gt;// Clear the polling interval&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

                &lt;span class="c1"&gt;// Refresh the current window&lt;/span&gt;
                &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to retrieve record status:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Don't stop polling on API errors, as they might be temporary&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Stops the status monitoring (useful for cleanup)
     */&lt;/span&gt;
    &lt;span class="nf"&gt;stopStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status monitoring stopped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Greg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Greg&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;Greg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greg_quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Greg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greg_quote&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;Greg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greg_quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerapps</category>
      <category>powerplatform</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to: Fix WebResources Layout in Dataverse Dashboards</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Tue, 18 Nov 2025 22:59:07 +0000</pubDate>
      <link>https://forem.com/_neronotte/how-to-fix-webresources-layout-in-dataverse-dashboards-3646</link>
      <guid>https://forem.com/_neronotte/how-to-fix-webresources-layout-in-dataverse-dashboards-3646</guid>
      <description>&lt;h2&gt;
  
  
  🤔 The problem
&lt;/h2&gt;

&lt;p&gt;If you ever managed to create a &lt;strong&gt;Dataverse Dashboard containing an HTML WebResource&lt;/strong&gt; (e.g. to show some calculated KPI), by default the resulting &lt;strong&gt;UI is quite ugly&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6yaua2mvhs11f0ulmxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6yaua2mvhs11f0ulmxt.png" alt="UI" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I don't like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each WebResource component has a &lt;strong&gt;surrounding black border&lt;/strong&gt; I would like to get rid of&lt;/li&gt;
&lt;li&gt;The minimum &lt;strong&gt;vertical space&lt;/strong&gt; that can be occupied by the WebResources row is 6 lines. Too much for my needs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For both points, the Dashboard editor doesn't provides any option to fix the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No option to remove the border&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdukn97bsk8pxst02kbck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdukn97bsk8pxst02kbck.png" alt="No option to remove the border" width="800" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;And I cannot reduce the vertical space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8y1js5wkpjffena59fn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8y1js5wkpjffena59fn.png" alt="Cannot reduce the vertical space" width="484" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 The solution
&lt;/h2&gt;

&lt;p&gt;As usual, the solution is quite simple via small XML tweaking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Create a new solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my case I already had a "ribbon" solution ready to use, thus in the screenshots below you'll see that one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Add the dashboard as the only component&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Access the solution, then click on &lt;strong&gt;Add existing&lt;/strong&gt; &amp;gt; &lt;strong&gt;Dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz208ygxt9agp9slwkkxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz208ygxt9agp9slwkkxt.png" alt="Add existing dashboards" width="194" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And peek the dashboard you want to update (in my case, &lt;code&gt;Tickets&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyis5rxg5ndle2z4ydy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyis5rxg5ndle2z4ydy9.png" alt="Peek the right dashboard" width="334" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Move to VS Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In VSCode terminal, run the following PAC CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your solution unique name&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PAC CLI will automatically download the solution and unpack its components in the current folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4. Update the Dashboard XML definition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Access the dashboard xml definition you'll find at &lt;code&gt;&amp;lt;solution name&amp;gt;\src\Dashboards\&amp;lt;dashboard guid&amp;gt;.xml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69acma0r1jpgmjqxbfn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69acma0r1jpgmjqxbfn2.png" alt="Path to dashboard XML" width="323" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the XML definition, find the WebResources controls. They can be easily found searching by the following string &lt;code&gt;&amp;lt;control id="WebResource_&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cell&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;rowspan=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;showlabel=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"{77846b48-5139-4c61-a5d1-fda39d80f21b}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"KPI1"&lt;/span&gt; &lt;span class="na"&gt;languagecode=&lt;/span&gt;&lt;span class="s"&gt;"1033"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;control&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"WebResource_kpi1"&lt;/span&gt; &lt;span class="na"&gt;classid=&lt;/span&gt;&lt;span class="s"&gt;"{9FDF5F91-88B1-47f4-AD53-C11EFC01A01D}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;parameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Url&amp;gt;&lt;/span&gt;prefix_/pages/dashboardkpi/index.html&lt;span class="nt"&gt;&amp;lt;/Url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Data&amp;gt;&lt;/span&gt;ticket_total_count&lt;span class="nt"&gt;&amp;lt;/Data&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;PassParameters&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/PassParameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ShowOnMobileClient&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/ShowOnMobileClient&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Security&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Security&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Scrolling&amp;gt;&lt;/span&gt;auto&lt;span class="nt"&gt;&amp;lt;/Scrolling&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Border&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/Border&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;WebResourceId&amp;gt;&lt;/span&gt;{F30B702C-C9C4-F011-BBD3-7CED8D454E57}&lt;span class="nt"&gt;&amp;lt;/WebResourceId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/parameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/control&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/cell&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To remove the border, update the &lt;code&gt;&amp;lt;Border&amp;gt;&lt;/code&gt; node to &lt;code&gt;false&lt;/code&gt;.&lt;br&gt;
To decrease the height, set the &lt;code&gt;rowspan&lt;/code&gt; attribute to &lt;code&gt;3&lt;/code&gt; (or adjust it as you wish).&lt;/p&gt;

&lt;p&gt;Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cell&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;rowspan=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;showlabel=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"{77846b48-5139-4c61-a5d1-fda39d80f21b}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"KPI1"&lt;/span&gt; &lt;span class="na"&gt;languagecode=&lt;/span&gt;&lt;span class="s"&gt;"1033"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;control&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"WebResource_kpi1"&lt;/span&gt; &lt;span class="na"&gt;classid=&lt;/span&gt;&lt;span class="s"&gt;"{9FDF5F91-88B1-47f4-AD53-C11EFC01A01D}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;parameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Url&amp;gt;&lt;/span&gt;prefix_/pages/dashboardkpi/index.html&lt;span class="nt"&gt;&amp;lt;/Url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Data&amp;gt;&lt;/span&gt;ticket_total_count&lt;span class="nt"&gt;&amp;lt;/Data&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;PassParameters&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/PassParameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ShowOnMobileClient&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/ShowOnMobileClient&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Security&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Security&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Scrolling&amp;gt;&lt;/span&gt;auto&lt;span class="nt"&gt;&amp;lt;/Scrolling&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Border&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Border&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;WebResourceId&amp;gt;&lt;/span&gt;{F30B702C-C9C4-F011-BBD3-7CED8D454E57}&lt;span class="nt"&gt;&amp;lt;/WebResourceId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/parameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/control&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/cell&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5. Rebuild the solution in unmanaged mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the terminal in the folder containing the &lt;code&gt;&amp;lt;solution&amp;gt;.cdsproj&lt;/code&gt; file and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will generate the unmanaged version of the solution in the &lt;code&gt;\bin\Debug&lt;/code&gt; subfolder of the current folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6. Import the generated solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Via PAC CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ribbon is the name of my solution, change it with your own&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\bin\debug\ribbon.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7. Publish all&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This time, let's use PACX&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pacx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can always do it the old way, via maker portal&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  😎 The result
&lt;/h2&gt;

&lt;p&gt;Et voilà, the same dashboard, with both issues fixed ☺️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhf8kv1bi1rx0hnk2dfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhf8kv1bi1rx0hnk2dfu.png" alt="Final result" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
    </item>
    <item>
      <title>Model Driven App: disable "Open in Excel Online" button</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Thu, 13 Nov 2025 16:17:20 +0000</pubDate>
      <link>https://forem.com/_neronotte/model-driven-app-disable-open-in-excel-online-button-15j9</link>
      <guid>https://forem.com/_neronotte/model-driven-app-disable-open-in-excel-online-button-15j9</guid>
      <description>&lt;h2&gt;
  
  
  🤔 The issue
&lt;/h2&gt;

&lt;p&gt;The out-of-the-box Excel integration in Model Driven apps is a powerful, but &lt;em&gt;very dangerous&lt;/em&gt;, feature.&lt;/p&gt;

&lt;p&gt;Import from Excel, especially, can be very harmful because an user could leverage it to bypass any client side logic put on forms, generating inconsistent data within the system. Moreover, it cannot be customized: you cannot say "you can import opportunities records, but you cannot import accounts", neither you could say "import works only on columns A, B, C".&lt;/p&gt;

&lt;p&gt;That's the reason why, in most of the CRM (and MDAs) implementations I've seen so far, the &lt;strong&gt;Import from Excel button is disabled for standard users&lt;/strong&gt;, and &lt;strong&gt;replaced with custom data-import&lt;/strong&gt; capabilities.&lt;/p&gt;

&lt;p&gt;Disabling the &lt;strong&gt;Import from Excel&lt;/strong&gt; button is quite easy, It just requires from you to remove the &lt;strong&gt;Data Import&lt;/strong&gt; and &lt;strong&gt;Data Map&lt;/strong&gt; privileges from the role assigned to users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpevdsqw31issmqcbjr8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpevdsqw31issmqcbjr8s.png" alt="Data Import and Data Map privileges removed from roles" width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works... more or less...&lt;/p&gt;

&lt;p&gt;...even if the Data Import privilege is removed, you can have the possibility to open the "Export In Excel" &amp;gt; "Open in Excel Online" capability&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrsfbhc6olvqygum8m9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrsfbhc6olvqygum8m9j.png" alt="Open in Excel Online" width="347" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the edit capability enabled, and the &lt;strong&gt;Save&lt;/strong&gt; button &lt;strong&gt;visible and clickable&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepcwqsb1mnm4oaels7ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepcwqsb1mnm4oaels7ck.png" alt="Save button" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you change something and click on Save, the following (ugly) error message appears (depending on the UI language of the user, mine is Italian)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63m2kpl3g9tl6tr67dpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63m2kpl3g9tl6tr67dpg.png" alt="Error message" width="400" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's a behavior that &lt;strong&gt;looks more like a bug than a feature&lt;/strong&gt;, IMHO.&lt;/p&gt;

&lt;p&gt;The best would be to &lt;strong&gt;disable that "Open in Excel Online" button too... tipically for specific users/roles&lt;/strong&gt;. But there is no such option in the role privileges...&lt;/p&gt;

&lt;h2&gt;
  
  
  😖 Failed fix tentatives
&lt;/h2&gt;

&lt;p&gt;If you try to hide the button using Ribbon Workbench or the Modern Ribbon editor, you will quickly see that's &lt;strong&gt;any customization on that button is disabled&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ao26kamobjw7r2zunmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ao26kamobjw7r2zunmc.png" alt="Disabled" width="427" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  👍🏻 How to do it
&lt;/h2&gt;

&lt;p&gt;For people like me, mold by CRM implementations before Ribbon Workbench, the fix is quite straightforward: &lt;strong&gt;let's tweak the RibbonDiffXml "the old way"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First of all, let's create a solution containing only the definition of the entity we want to disable that button for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl2xfz5sm65o8ce54tqk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl2xfz5sm65o8ce54tqk.png" alt="Temp solution" width="559" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6g4ekshkef9n36a8mdhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6g4ekshkef9n36a8mdhk.png" alt="Temp solution" width="229" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then let's use &lt;code&gt;Power Platform CLI&lt;/code&gt; commands to export and unpack the solution in a local folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It generates locally a structure similar to the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkihqswcfrqnfiqlhbpl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkihqswcfrqnfiqlhbpl9.png" alt="Exported solution" width="266" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's open the &lt;code&gt;RibbonDiff.xml&lt;/code&gt; file, it will be like the following one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;RibbonDiffXml&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CustomActions&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Templates&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RibbonTemplates&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Templates"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/RibbonTemplates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Templates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CommandDefinitions&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RuleDefinitions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TabDisplayRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DisplayRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EnableRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/RuleDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;LocLabels&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/RibbonDiffXml&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;&amp;lt;DisplayRules&amp;gt;&lt;/code&gt; node, let's add a rule that is evaluated to "true" only in the case an user is system administrator (here you can use your fantasy)...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EntityPrivilegeRule&lt;/span&gt; &lt;span class="na"&gt;PrivilegeType=&lt;/span&gt;&lt;span class="s"&gt;"Delete"&lt;/span&gt; &lt;span class="na"&gt;PrivilegeDepth=&lt;/span&gt;&lt;span class="s"&gt;"Basic"&lt;/span&gt; &lt;span class="na"&gt;EntityName=&lt;/span&gt;&lt;span class="s"&gt;"solution"&lt;/span&gt; &lt;span class="na"&gt;Default=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;InvertResult=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/DisplayRule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the &lt;code&gt;&amp;lt;CommandDefinitions&amp;gt;&lt;/code&gt; node, let's add the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;CommandDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CommandDefinition&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.Online"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EnableRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.ValidForXlsxExport"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableExportToExcelOnlineForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableOnlyInBrowsersForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/EnableRules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Live"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.IsExportToExcelFCBEnabled"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnOutlookClient"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.NotAdvancedFind"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnPhoneForNonModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideForTabletExperience"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Actions&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;JavaScriptFunction&lt;/span&gt; &lt;span class="na"&gt;FunctionName=&lt;/span&gt;&lt;span class="s"&gt;"XrmCore.Commands.Export.exportToExcel"&lt;/span&gt; &lt;span class="na"&gt;Library=&lt;/span&gt;&lt;span class="s"&gt;"$webresource:Main_system_library.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedControl"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedEntityTypeName"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;IntParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/JavaScriptFunction&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Actions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/CommandDefinition&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/CommandDefinitions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final RibbonDiffXML should look something similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;RibbonDiffXml&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CustomActions&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Templates&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RibbonTemplates&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Templates"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/RibbonTemplates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Templates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CommandDefinitions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;CommandDefinition&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.Online"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRules&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.ValidForXlsxExport"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableExportToExcelOnlineForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableOnlyInBrowsersForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/EnableRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Live"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.IsExportToExcelFCBEnabled"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnOutlookClient"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.NotAdvancedFind"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnPhoneForNonModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideForTabletExperience"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Actions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;JavaScriptFunction&lt;/span&gt; &lt;span class="na"&gt;FunctionName=&lt;/span&gt;&lt;span class="s"&gt;"XrmCore.Commands.Export.exportToExcel"&lt;/span&gt; &lt;span class="na"&gt;Library=&lt;/span&gt;&lt;span class="s"&gt;"$webresource:Main_system_library.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedControl"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedEntityTypeName"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;IntParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/JavaScriptFunction&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Actions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/CommandDefinition&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/CommandDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RuleDefinitions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TabDisplayRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EntityPrivilegeRule&lt;/span&gt; &lt;span class="na"&gt;PrivilegeType=&lt;/span&gt;&lt;span class="s"&gt;"Delete"&lt;/span&gt; &lt;span class="na"&gt;PrivilegeDepth=&lt;/span&gt;&lt;span class="s"&gt;"Basic"&lt;/span&gt; &lt;span class="na"&gt;EntityName=&lt;/span&gt;&lt;span class="s"&gt;"solution"&lt;/span&gt; &lt;span class="na"&gt;Default=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;InvertResult=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/DisplayRule&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EnableRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/RuleDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;LocLabels&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/RibbonDiffXml&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now build the solution ad upload the generated solution zip file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\bin\debug\temp.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the import to complete, then go to your environment, select the solution you just uploaded, and click &lt;strong&gt;Publish All Customizations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89pfvtpl49kxjqdqzsop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89pfvtpl49kxjqdqzsop.png" alt="Publish all" width="207" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or, if you're using PACX in your project, simply type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pacx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it... Now that button will be visible only by System Administrator.&lt;/p&gt;

&lt;p&gt;Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
      <category>powerapps</category>
    </item>
    <item>
      <title>Dataverse MCP Server configuration failed...</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Thu, 06 Nov 2025 20:35:04 +0000</pubDate>
      <link>https://forem.com/_neronotte/dataverse-mcp-server-configuration-failed-3ip5</link>
      <guid>https://forem.com/_neronotte/dataverse-mcp-server-configuration-failed-3ip5</guid>
      <description>&lt;p&gt;If you want to configure the Dataverse MCP Server as described in the &lt;a href="https://learn.microsoft.com/en-us/power-apps/maker/data-platform/data-platform-mcp" rel="noopener noreferrer"&gt;official Microsoft Learn article&lt;/a&gt;, you'll need to write in your &lt;code&gt;mcp.json&lt;/code&gt; file an url with the following &lt;em&gt;exact format&lt;/em&gt; (without the line breaks, of course):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://make.powerautomate.com/environments/
59fd5b0e-f1e3-4f86-920d-509f44cb1846/connections
?apiName=shared_commondataserviceforapps
&amp;amp;connectionName=263a3d1e-1279-45f6-a593-070f9efaaad3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tutorial instructs you to &lt;strong&gt;create a connection&lt;/strong&gt; via &lt;a href="https://make.powerautomate.com/" rel="noopener noreferrer"&gt;make.powerautomate.com&lt;/a&gt;, and then &lt;strong&gt;take the URL from the connection page&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If you &lt;strong&gt;do it exactly as stated in the official docs&lt;/strong&gt;, the URL you'll get has this form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://make.powerautomate.com/environments/
59fd5b0e-f1e3-4f86-920d-509f44cb1846/connections
/shared_commondataserviceforapps
/263a3d1e-1279-45f6-a593-070f9efaaad3/details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fact is that URL, put in the &lt;code&gt;mcp.json&lt;/code&gt; file, &lt;strong&gt;won't work&lt;/strong&gt; 😡🤬. &lt;/p&gt;

&lt;p&gt;You need to tweak the url manually in the following way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;take the first part of the url, from &lt;code&gt;https://&lt;/code&gt; to &lt;code&gt;connections&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;replace the &lt;code&gt;/&lt;/code&gt; after &lt;code&gt;connections&lt;/code&gt; with &lt;code&gt;?apiName=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;replace the &lt;code&gt;/&lt;/code&gt; after &lt;code&gt;shared_commondataserviceforapps&lt;/code&gt; with &lt;code&gt;&amp;amp;connectionName=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;remove the leading &lt;code&gt;/details&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you're ready to go. &lt;/p&gt;

&lt;p&gt;🙏🏻 Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
