create-ec-app 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-ec-app",
3
- "version": "1.4.0",
4
- "description": "Unified CLI tool to create different types of EC applications: Webresource, Portal, Power Pages",
3
+ "version": "1.6.0",
4
+ "description": "Unified CLI tool to create different types of EC applications: Webresource, Portal, Power Pages, Power Apps Code Apps",
5
5
  "bin": {
6
6
  "create-ec-app": "./dist/index.js"
7
7
  },
@@ -20,6 +20,8 @@
20
20
  "tailwind",
21
21
  "cli",
22
22
  "powerpages",
23
+ "code-apps",
24
+ "power-apps-code-apps",
23
25
  "portal",
24
26
  "webresource",
25
27
  "dynamics",
@@ -38,7 +40,8 @@
38
40
  "type": "module",
39
41
  "dependencies": {
40
42
  "@clack/prompts": "^0.11.0",
41
- "fs-extra": "^11.3.2"
43
+ "fs-extra": "^11.3.2",
44
+ "postcss": "^8.5.15"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@semantic-release/commit-analyzer": "^13.0.1",
@@ -2,60 +2,100 @@
2
2
 
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
+ import postcss from "postcss";
5
6
 
6
- const appPath = process.argv[2];
7
+ const pcfPath = process.argv[2];
7
8
 
8
- if (!appPath) {
9
- console.error("Usage: node scripts/check-generated-css-scope.mjs <generated-app>");
9
+ if (!pcfPath) {
10
+ console.error("Usage: node scripts/check-generated-css-scope.mjs <generated-pcf-control>");
10
11
  process.exit(1);
11
12
  }
12
13
 
13
- const cssPath = path.join(path.resolve(appPath), "dist", "main.css");
14
+ const cssPath = path.join(path.resolve(pcfPath), "pcf-scoped.css");
14
15
 
15
16
  if (!fs.existsSync(cssPath)) {
16
- console.error(`Could not find ${cssPath}. Build the generated app first.`);
17
+ console.error(`Could not find ${cssPath}. Generate the PCF control first.`);
17
18
  process.exit(1);
18
19
  }
19
20
 
20
21
  const css = fs.readFileSync(cssPath, "utf8");
22
+ const root = postcss.parse(css, { from: cssPath });
23
+ const failures = [];
21
24
 
22
- const forbidden = [
23
- { name: "global body rule", pattern: /(^|})\s*body\s*\{/ },
24
- {
25
- name: "global shadcn :root token rule",
26
- pattern: /(^|})\s*:root\s*\{[^}]*--background\s*:/,
27
- },
28
- {
29
- name: "global shadcn dark token rule",
30
- pattern: /(^|})\s*\.dark\s*\{[^}]*--background\s*:/,
31
- },
32
- { name: "unprefixed flex utility", pattern: /(^|})\s*\.flex\s*\{/ },
33
- { name: "unprefixed grid utility", pattern: /(^|})\s*\.grid\s*\{/ },
34
- { name: "unprefixed hidden utility", pattern: /(^|})\s*\.hidden\s*\{/ },
35
- {
36
- name: "unprefixed bg-background utility",
37
- pattern: /(^|})\s*\.bg-background\s*\{/,
38
- },
39
- ];
40
-
41
- const required = [
42
- { name: "ec app scope", pattern: /\.ec-app\b/ },
43
- { name: "prefixed flex utility", pattern: /\.ec\\:flex\b/ },
44
- ];
45
-
46
- const failures = [
47
- ...forbidden.filter((check) => check.pattern.test(css)).map((check) => check.name),
48
- ...required
49
- .filter((check) => !check.pattern.test(css))
50
- .map((check) => `missing ${check.name}`),
51
- ];
25
+ root.walkRules((rule) => {
26
+ if (!rule.nodes?.some((node) => node.type === "decl" && node.prop.startsWith("--"))) {
27
+ return;
28
+ }
29
+
30
+ for (const selector of splitSelectorList(rule.selector)) {
31
+ if (!selector.includes(".ec-pcf-shell-control")) {
32
+ failures.push(`unscoped custom-property rule: ${selector}`);
33
+ }
34
+ }
35
+ });
36
+
37
+ if (!css.includes("[data-ec-pcf-control=")) {
38
+ failures.push("missing PCF control data attribute scope");
39
+ }
52
40
 
53
41
  if (failures.length > 0) {
54
- console.error("CSS scope check failed:");
42
+ console.error("PCF CSS variable scope check failed:");
55
43
  for (const failure of failures) {
56
44
  console.error(`- ${failure}`);
57
45
  }
58
46
  process.exit(1);
59
47
  }
60
48
 
61
- console.log("CSS scope check passed.");
49
+ console.log("PCF CSS variable scope check passed.");
50
+
51
+ function splitSelectorList(selectorList) {
52
+ const selectors = [];
53
+ let start = 0;
54
+ let nesting = 0;
55
+ let quote = null;
56
+ let escaped = false;
57
+
58
+ for (let index = 0; index < selectorList.length; index += 1) {
59
+ const char = selectorList[index];
60
+
61
+ if (escaped) {
62
+ escaped = false;
63
+ continue;
64
+ }
65
+
66
+ if (char === "\\") {
67
+ escaped = true;
68
+ continue;
69
+ }
70
+
71
+ if (quote) {
72
+ if (char === quote) {
73
+ quote = null;
74
+ }
75
+ continue;
76
+ }
77
+
78
+ if (char === '"' || char === "'") {
79
+ quote = char;
80
+ continue;
81
+ }
82
+
83
+ if (char === "(" || char === "[") {
84
+ nesting += 1;
85
+ continue;
86
+ }
87
+
88
+ if (char === ")" || char === "]") {
89
+ nesting = Math.max(0, nesting - 1);
90
+ continue;
91
+ }
92
+
93
+ if (char === "," && nesting === 0) {
94
+ selectors.push(selectorList.slice(start, index).trim());
95
+ start = index + 1;
96
+ }
97
+ }
98
+
99
+ selectors.push(selectorList.slice(start).trim());
100
+ return selectors.filter(Boolean);
101
+ }
@@ -2,7 +2,7 @@ import "./App.css";
2
2
 
3
3
  function App() {
4
4
  return (
5
- <div className="ec:flex ec:flex-col ec:h-screen ec:items-center ec:justify-center">
5
+ <div className="flex h-screen flex-col items-center justify-center">
6
6
  <p>Hello, world!</p>
7
7
  </div>
8
8
  );
@@ -1,6 +1,3 @@
1
- @layer theme, base, components, utilities;
2
-
3
- @import "tailwindcss/theme.css" layer(theme) prefix(ec);
4
- @import "tailwindcss/utilities.css" layer(utilities) prefix(ec);
1
+ @import "tailwindcss";
5
2
 
6
3
  @custom-variant hover (&:hover);
@@ -1,9 +1,5 @@
1
1
  import * as React from "react";
2
2
 
3
- export const EC_APP_SCOPE_CLASS = "ec-app";
4
- export const EC_APP_ID = "{{APP_NAME}}";
5
- export const EC_PCF_SCOPE_CLASS = "ec-pcf-shell-control";
6
-
7
3
  const EcPortalContainerContext = React.createContext<HTMLElement | null>(null);
8
4
 
9
5
  export function useEcPortalContainer() {
@@ -15,11 +11,7 @@ export function EcAppShell({ children }: { children: React.ReactNode }) {
15
11
  React.useState<HTMLDivElement | null>(null);
16
12
 
17
13
  return (
18
- <div
19
- className={EC_APP_SCOPE_CLASS}
20
- data-ec-app-id={EC_APP_ID}
21
- data-ec-app-root=""
22
- >
14
+ <div data-ec-app-root="">
23
15
  <EcPortalContainerContext.Provider value={portalContainer}>
24
16
  {children}
25
17
  <div data-ec-portal-root="" ref={setPortalContainer} />
@@ -18,19 +18,14 @@ npm run build
18
18
 
19
19
  ## Notes
20
20
 
21
- - The wrapper imports `src/App` directly and reuses the built `dist/main.css`.
21
+ - The wrapper imports `src/App` directly and uses generated `pcf-scoped.css` derived from the webresource build.
22
22
  - Regenerate this folder after rebuilding the webresource whenever the app changes.
23
23
  - The project includes both `pcf-scripts` build support and a `.pcfproj` for Dataverse solution packaging flows.
24
24
 
25
25
  ## CSS scoping
26
26
 
27
- This PCF control renders the app inside `.ec-app` and keeps the PCF host container class `.ec-pcf-shell-control`.
27
+ This PCF control renders the app inside the PCF host container class `.ec-pcf-shell-control`.
28
28
 
29
- Generated Tailwind/shadcn styles are scoped for embeddability:
29
+ During PCF generation, `create-ec-app` reads the webresource's built `dist/main.css`, scopes CSS custom-property rules to this control's `.ec-pcf-shell-control[data-ec-pcf-control="{{PCF_CONSTRUCTOR}}"]` host selector, and writes the result to `pcf-scoped.css`.
30
30
 
31
- - Tailwind Preflight is not imported globally.
32
- - Tailwind utilities use the `ec:` prefix.
33
- - shadcn theme variables are defined under `.ec-app`.
34
- - Radix/shadcn portals render into the app-local portal root where supported.
35
-
36
- If this PCF was generated from an older app whose `dist/main.css` contains global Tailwind/shadcn styles, those styles may still leak into the model-driven app form. Regenerate or migrate the source app to the scoped CSS template.
31
+ Tailwind utilities remain unprefixed, while shadcn/Tailwind theme variables are local to the generated PCF control. Radix/shadcn portals render into the app-local portal root where supported.
@@ -0,0 +1,246 @@
1
+ # EC Power Apps Code App
2
+
3
+ React + TypeScript template for Power Apps code apps. Generated by `create-ec-app`, it keeps the EC base app structure, Tailwind setup, TanStack Query provider, and selected UI layer, then adds Microsoft Power Apps code app support through `@microsoft/power-apps` and `@microsoft/power-apps-vite`.
4
+
5
+ ## Features
6
+
7
+ - React + TypeScript with Vite
8
+ - Tailwind CSS
9
+ - UI library choice: Kendo UI or shadcn/ui
10
+ - TanStack Query provider pre-wired in `src/main.tsx`
11
+ - Power Apps SDK dependency for code app APIs and generated connector services
12
+ - Power Apps Vite plugin for local play URLs, Power Apps CORS support, and `base: "./"` build output
13
+
14
+ ## Prerequisites
15
+
16
+ - Node.js LTS and npm
17
+ - Git
18
+ - A Power Platform environment with Power Apps code apps enabled
19
+ - A Power Apps Premium license for users who run the code app
20
+
21
+ Admins enable code apps in Power Platform admin center by opening the target environment, then Settings > Product > Features, and turning on **Power Apps code apps**.
22
+
23
+ ## Getting Started
24
+
25
+ Install dependencies if they were not installed during scaffolding:
26
+
27
+ ```bash
28
+ npm install
29
+ ```
30
+
31
+ Initialize the code app metadata and authenticate against your Power Platform environment:
32
+
33
+ ```bash
34
+ npx power-apps init --display-name "{{APP_NAME}}" --environment-id <environment-id> --app-url http://localhost:5173
35
+ ```
36
+
37
+ You can omit the options and run `npx power-apps init` to answer the interactive prompts instead. The command creates `power.config.json`, which the Power Apps SDK, Vite plugin, and deployment commands use.
38
+
39
+ This template includes `power.config.example.json` only as a reference. Do not rename it before running `npx power-apps init`; the Microsoft CLI refuses to initialize when a real `power.config.json` already exists. The generated `power.config.json` includes your environment ID, app metadata, build settings, and connection/data references. It does not contain authentication tokens, but it is environment-specific, so decide whether to commit it based on your ALM flow.
40
+
41
+ ## Local Development
42
+
43
+ Start the app locally:
44
+
45
+ ```bash
46
+ npm run dev
47
+ ```
48
+
49
+ The Power Apps Vite plugin prints a **Local Play** URL. Open that URL in the same browser profile you use for your Power Platform tenant so authentication and connector calls work as expected.
50
+
51
+ If browser local network access restrictions appear, allow the browser prompt for the local endpoint. For iframe embedding scenarios, Microsoft recommends adding `allow="local-network-access"` to the iframe.
52
+
53
+ ## Data Access
54
+
55
+ Code apps should not use the Dynamics webresource `AuthService`/`token.json` pattern. In a code app, the Power Apps host manages end-user authentication, and `@microsoft/power-apps` plus generated services perform data requests through Power Platform connectors and Dataverse references.
56
+
57
+ The normal flow is:
58
+
59
+ 1. Create or identify the connection in [Power Apps](https://make.powerapps.com).
60
+ 2. Add the data source to this project with the Power Apps CLI.
61
+ 3. Import the generated model and service files from `src/generated`.
62
+ 4. Build and push the app with `npx power-apps push`.
63
+
64
+ ### Connect to Dataverse
65
+
66
+ After `npx power-apps init`, add a Dataverse table by logical name:
67
+
68
+ ```bash
69
+ npx power-apps add-data-source --api-id dataverse --resource-name <table-logical-name>
70
+ ```
71
+
72
+ Example:
73
+
74
+ ```bash
75
+ npx power-apps add-data-source --api-id dataverse --resource-name account
76
+ ```
77
+
78
+ The CLI updates `power.config.json` and generates typed files under `src/generated/models` and `src/generated/services`. For example, after adding Accounts you can use the generated service:
79
+
80
+ ```ts
81
+ import { AccountsService } from "./generated/services/AccountsService";
82
+ import type { Accounts } from "./generated/models/AccountsModel";
83
+
84
+ export async function listAccounts() {
85
+ const result = await AccountsService.getAll({
86
+ select: ["name", "accountnumber"],
87
+ top: 50,
88
+ });
89
+
90
+ return result.data ?? [];
91
+ }
92
+
93
+ export async function createAccount(name: string) {
94
+ const account = { name } satisfies Partial<Accounts>;
95
+ const result = await AccountsService.create(
96
+ account as Omit<Accounts, "accountid">,
97
+ );
98
+
99
+ return result.data;
100
+ }
101
+ ```
102
+
103
+ For Dataverse CRUD, use the generated service methods such as `create`, `get`, `getAll`, `update`, and `delete`. Prefer `select` when reading data so the app retrieves only the columns it needs.
104
+
105
+ ### Connect to Other Power Platform Connectors
106
+
107
+ For non-Dataverse connectors, first create or locate the connection in Power Apps, then get the API name and connection ID. You can use the Power Apps maker portal or:
108
+
109
+ ```bash
110
+ pac connection list
111
+ ```
112
+
113
+ Add a non-tabular connector data source:
114
+
115
+ ```bash
116
+ npx power-apps add-data-source --api-id <api-name> --connection-id <connection-id>
117
+ ```
118
+
119
+ Add a tabular connector data source such as SQL or SharePoint:
120
+
121
+ ```bash
122
+ npx power-apps add-data-source \
123
+ --api-id <api-name> \
124
+ --connection-id <connection-id> \
125
+ --resource-name <table-or-list-name> \
126
+ --dataset <dataset-name>
127
+ ```
128
+
129
+ You can discover datasets and tables through the npm CLI:
130
+
131
+ ```bash
132
+ npx power-apps list-datasets --api-id <api-name> --connection-id <connection-id>
133
+ npx power-apps list-tables --api-id <api-name> --connection-id <connection-id> --dataset <dataset-name>
134
+ ```
135
+
136
+ For ALM-friendly apps, prefer connection references instead of binding directly to a maker's connection:
137
+
138
+ ```bash
139
+ npx power-apps list-connection-references --solution-id <solution-id>
140
+ npx power-apps add-data-source \
141
+ --api-id <api-name> \
142
+ --connection-ref <connection-reference-logical-name> \
143
+ --solution-id <solution-id>
144
+ ```
145
+
146
+ For tabular connectors, environment variable references can be used for dataset and table values:
147
+
148
+ ```bash
149
+ npx power-apps add-data-source \
150
+ --api-id shared_sharepointonline \
151
+ --connection-id <connection-id> \
152
+ --dataset "@envvar:<site-environment-variable-schema-name>" \
153
+ --resource-name "@envvar:<list-environment-variable-schema-name>"
154
+ ```
155
+
156
+ ### Dataverse Actions and Functions
157
+
158
+ Dataverse actions and functions use the newer npm-only commands:
159
+
160
+ ```bash
161
+ npx power-apps find-dataverse-api --search "WhoAmI"
162
+ npx power-apps add-dataverse-api --api-name WhoAmI
163
+ ```
164
+
165
+ The command updates `power.config.json`, writes schema files, and generates a service such as:
166
+
167
+ ```ts
168
+ import { WhoAmIService } from "./generated/services/WhoAmIService";
169
+
170
+ const result = await WhoAmIService.WhoAmI();
171
+ ```
172
+
173
+ ### Runtime Context
174
+
175
+ Use `getContext` from the Power Apps SDK when you need app, environment, user, query parameter, or session details:
176
+
177
+ ```ts
178
+ import { getContext } from "@microsoft/power-apps/app";
179
+
180
+ const context = await getContext();
181
+
182
+ console.log(context.app.environmentId);
183
+ console.log(context.user.userPrincipalName);
184
+ console.log(context.host.sessionId);
185
+ ```
186
+
187
+ This replaces most webresource-style assumptions about `window.Xrm`, parent frame context, or manually loaded local tokens.
188
+
189
+ ## Code Apps vs Webresources
190
+
191
+ This target is intentionally different from the `webresource` target:
192
+
193
+ - Webresources run inside Dynamics 365/model-driven app pages and can use `window.Xrm`; Code Apps run in the Power Apps host.
194
+ - Webresources in this repo use `src/services/AuthService.ts` and `token.json` for local Dataverse Web API calls; Code Apps should use `@microsoft/power-apps`, `power.config.json`, and generated services.
195
+ - The Code Apps scaffold removes webresource/Power Pages auth artifacts if they are present during generation.
196
+ - Webresource builds are tuned for web resource upload with deterministic `main.css`/JS files; Code Apps builds are published with `npx power-apps push`.
197
+ - Code Apps can use Power Platform connectors, connection references, environment variables, generated Dataverse CRUD services, and generated Dataverse action/function services as part of the platform hosting model.
198
+
199
+ ## Build and Deploy
200
+
201
+ Build the production assets:
202
+
203
+ ```bash
204
+ npm run build
205
+ ```
206
+
207
+ Publish a new version to the environment configured in `power.config.json`:
208
+
209
+ ```bash
210
+ npx power-apps push
211
+ ```
212
+
213
+ When the push succeeds, the command returns a Power Apps URL where you can run the app. You can also open [Power Apps](https://make.powerapps.com) to play, share, or review the app details.
214
+
215
+ ## Solution and ALM Notes
216
+
217
+ For healthy ALM, use a non-default solution. If your environment has a preferred solution, new code apps are saved there by default when deployed. To target a specific solution with the PAC CLI path, use:
218
+
219
+ ```bash
220
+ pac code push --solutionName <solution-name>
221
+ ```
222
+
223
+ If you already deployed the app and need to add it to a solution manually, open Power Apps, go to Solutions, choose the solution, then select Add existing > App > Code app.
224
+
225
+ Microsoft's current npm CLI is the preferred path for new code app work. Older docs still show the PAC CLI flow:
226
+
227
+ ```bash
228
+ pac auth create
229
+ pac env select --environment <environment-id>
230
+ pac code init --displayname "{{APP_NAME}}"
231
+ npm run build | pac code push
232
+ ```
233
+
234
+ Use the npm CLI unless you specifically need a PAC CLI option that has not moved yet.
235
+
236
+ ## Related Microsoft Documentation
237
+
238
+ - [Power Apps code apps overview](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/overview)
239
+ - [Quickstart with npm CLI](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/npm-quickstart)
240
+ - [Code apps architecture](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/architecture)
241
+ - [Connect your code app to data](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/connect-to-data)
242
+ - [Connect your code app to Dataverse](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/connect-to-dataverse)
243
+ - [Add a Dataverse action or function](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/add-dataverse-action-function)
244
+ - [Get context data](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/retrieve-context)
245
+ - [Use environment variables in data sources](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/use-environment-variables)
246
+ - [Application lifecycle management for code apps](https://learn.microsoft.com/en-us/power-apps/developer/code-apps/how-to/alm)
@@ -0,0 +1,8 @@
1
+ {
2
+ "dependencies": {
3
+ "@microsoft/power-apps": "^1.1.9"
4
+ },
5
+ "devDependencies": {
6
+ "@microsoft/power-apps-vite": "^1.0.2"
7
+ }
8
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": "1.0",
3
+ "appId": null,
4
+ "appDisplayName": "{{APP_NAME}}",
5
+ "region": "prod",
6
+ "environmentId": "<environment-id>",
7
+ "description": " ",
8
+ "buildPath": "./dist",
9
+ "buildEntryPoint": "index.html",
10
+ "localAppUrl": "http://localhost:5173",
11
+ "logoPath": "Default",
12
+ "connectionReferences": {},
13
+ "databaseReferences": {}
14
+ }
@@ -0,0 +1,15 @@
1
+ import path from "node:path";
2
+ import tailwindcss from "@tailwindcss/vite";
3
+ import react from "@vitejs/plugin-react";
4
+ import { powerApps } from "@microsoft/power-apps-vite/plugin";
5
+ import { defineConfig } from "vite";
6
+
7
+ // https://vite.dev/config/
8
+ export default defineConfig({
9
+ plugins: [react(), tailwindcss(), powerApps()],
10
+ resolve: {
11
+ alias: {
12
+ "@": path.resolve(__dirname, "./src"),
13
+ },
14
+ },
15
+ });
@@ -4,7 +4,7 @@ function App() {
4
4
  const { isAuthenticated, user } = useAuth(); //INFO: User is where the token information is stored (user?.idToken)
5
5
 
6
6
  return (
7
- <div className="ec:flex ec:flex-col ec:items-center ec:justify-center ec:h-screen ec:gap-4">
7
+ <div className="flex h-screen flex-col items-center justify-center gap-4">
8
8
  {isAuthenticated ? (
9
9
  <div>You are logged in</div>
10
10
  ) : (
@@ -3,6 +3,8 @@
3
3
  This is a Dynamics 365 / Dataverse web resource template using React + TypeScript + Vite.
4
4
  Treat it as a **Dynamics-hosted frontend** — not a generic SPA. Do not modernise it into something else unless explicitly asked.
5
5
 
6
+ The default philosophy is: keep the app small, readable, and easy to ship into Dynamics. Prefer obvious code, existing components, and the template's established runtime patterns over broad abstractions or clever defensive layers.
7
+
6
8
  ---
7
9
 
8
10
  ## Hard Constraints
@@ -43,10 +45,13 @@ Never mix the two modes or duplicate their logic — reuse `authService.ts`.
43
45
  - Always reuse `getApiUrl()` and `getAuthHeaders()` — never duplicate them
44
46
  - Use narrow `$select` queries; throw on non-OK responses
45
47
  - No raw fetch calls scattered across UI components
48
+ - Use Zod for validation where data crosses a boundary: form inputs, URL/search params, config, and Dataverse/API responses that the UI depends on
49
+ - Keep service functions boring and explicit: one fetch function, one hook, one mutation where needed
50
+ - Do not add repository/client layers unless the app has enough repeated API logic to justify them
46
51
 
47
52
  **Preferred pattern:**
48
53
  ```ts
49
- // service.ts — fetch function + TanStack Query hook
54
+ // {entity}Service.ts — fetch function + TanStack Query hook
50
55
  // invalidate relevant queryKey on mutation success
51
56
  ```
52
57
 
@@ -65,21 +70,24 @@ See the example at the bottom of this file.
65
70
 
66
71
  ## UI & Styling
67
72
 
68
- - Kendo UI or Shadcn/ui — stay consistent with whichever the project uses; don't mix
69
- - Tailwind is the default styling approach; preserve `main.css` output
70
- - Reuse existing components and utilities before building new ones
73
+ - Kendo UI or Shadcn/ui — stay consistent with whichever the project uses; don't mix UI systems unless explicitly asked.
74
+ - If the project uses Shadcn/ui, use Shadcn components from `@/components/ui` and style them with Tailwind utility classes.
75
+ - If the project uses Kendo UI, use Kendo React components for controls, grids, menus, dialogs, inputs, and other rich UI. Use Tailwind for layout, spacing, and local composition around those components.
76
+ - Tailwind is the default styling approach for both Shadcn/ui and Kendo projects; preserve `main.css` output.
77
+ - Do not hand-roll component styling or custom CSS unless Tailwind/component props cannot reasonably express the requirement.
78
+ - Prefer existing component APIs, theme tokens, variants, and utility helpers before creating new wrappers.
79
+ - Keep screen layouts practical for embedded Dynamics use: compact, scannable, responsive, and not marketing-page styled.
71
80
 
72
81
  ---
73
82
 
74
- ## Planning Rule
75
-
76
- For changes touching multiple files, auth, build config, or new service patterns — write a short plan first:
77
- - current state
78
- - intended change
79
- - files to touch
80
- - risks / compatibility concerns
83
+ ## Code Shape
81
84
 
82
- For large or risky changes, write an `ExecPlan` in `PLANS.md` before implementing.
85
+ - Keep code simple and readable. A future maintainer should understand the main path quickly.
86
+ - Avoid over-engineering: no generic frameworks, broad factories, speculative abstractions, or excessive configuration for small features.
87
+ - Avoid being overly defensive. Validate real external inputs and API responses where useful, but don't wrap every local value in ceremony.
88
+ - Prefer direct, typed functions over classes unless the existing code already uses classes for that concern.
89
+ - Keep React components focused: UI in components, reusable data access in services, shared client state in Zustand only when local state is no longer enough.
90
+ - Make the smallest change that solves the request cleanly.
83
91
 
84
92
  ---
85
93
 
@@ -120,4 +128,4 @@ export const useUpdateAccount = () => {
120
128
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ["accounts"] }),
121
129
  });
122
130
  };
123
- ```
131
+ ```
@@ -286,7 +286,7 @@ npm install
286
286
  This writes a standalone PCF project to the `--pcf-dir` folder. The generated control:
287
287
 
288
288
  - imports `src/App.tsx` directly instead of wrapping built HTML in an iframe
289
- - reuses the built stylesheet from `dist/main.css`
289
+ - creates and imports `pcf-scoped.css` from the built `dist/main.css`
290
290
  - creates `src/runtime/types.ts` only if that file does not already exist
291
291
  - provides a runtime object with record context and `context.webAPI` access inside the generated PCF shell, following the `PcfBase` pattern
292
292
  - mounts your React app directly into the PCF container
@@ -302,32 +302,12 @@ npm install
302
302
  npm run build
303
303
  ```
304
304
 
305
- Useful options:
306
-
307
- - `--dist dist` to point at a different build folder
308
- - `--output pcf/MyControl` to choose the target folder
309
- - `--template /path/to/create-ec-app/templates/pcf/base` to swap the base shell
310
- - `--layer path/to/layer` to apply one or more patch layers after the base copy
311
-
312
- Build the generated PCF project from inside the generated folder:
313
-
314
- ```bash
315
- cd pcf/FusionNotebookHost
316
- npm install
317
- npm run build
318
- ```
319
-
320
305
  What gets generated:
321
306
 
322
307
  - a minimal PCF wrapper project under `pcf/<ConstructorName>`
323
308
  - a checked-in PCF shell stamped out from `create-ec-app/templates/pcf/base`
324
- - direct imports back to your webresource source and built CSS
325
-
326
- What does not happen:
327
-
328
- - your existing webresource project is not converted in place
329
- - your React source is not moved into the PCF project
330
- - the generated PCF project does not automatically get added to a Dataverse solution
309
+ - direct imports back to your webresource source
310
+ - a generated `pcf-scoped.css` file with CSS custom properties scoped to the PCF control
331
311
 
332
312
  ## Notes
333
313
 
@@ -8,7 +8,7 @@
8
8
  "css": "src/index.css",
9
9
  "baseColor": "neutral",
10
10
  "cssVariables": true,
11
- "prefix": "ec"
11
+ "prefix": ""
12
12
  },
13
13
  "iconLibrary": "lucide",
14
14
  "rtl": false,