Skip to content

Commit 9810f4a

Browse files
authored
Merge origin/main into branch — resolve setup-auth:2.1 conflict
Keep gate marker + 🚦 prose block from PR branch; incorporate multi-provider re-run handling content added in main (b4c46e9).
2 parents 8ddcc36 + ff0d191 commit 9810f4a

93 files changed

Lines changed: 22851 additions & 450 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

evals/model-apps/genpage/EVAL_GUIDE.md

Lines changed: 541 additions & 0 deletions
Large diffs are not rendered by default.

evals/model-apps/genpage/eval-runbook.md

Lines changed: 0 additions & 140 deletions
This file was deleted.

evals/model-apps/genpage/evals.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"skill_name": "genpage",
3-
"eval_instructions": "See eval-runbook.md in this folder for how to run these evals, how the 3-layer check works (workflow → code quality → UX rubric), and what pass/fail means. Every eval run MUST save a workflow-log.md file documenting all phases attempted — agents invoked, commands run, questions asked, files read, decisions made.",
3+
"eval_instructions": "See EVAL_GUIDE.md in this folder for how to run these evals, how the 3-layer check works (workflow → code quality → UX rubric), and what pass/fail means. Every eval run MUST save a workflow-log.md file documenting all phases attempted — agents invoked, commands run, questions asked, files read, decisions made.",
44
"common_workflow_assertions": [
55
"A workflow-log.md file is saved to the working directory documenting all phases attempted",
66
"Phase 0: A working directory is created with a kebab-case name derived from the user's description",
@@ -23,6 +23,7 @@
2323
"Generated .tsx destructures props including `pageInput` (e.g., `const { dataApi, pageInput } = props;`)",
2424
"Generated .tsx imports types from `./RuntimeTypes` (for Dataverse pages) or uses realistic inline mock data (for mock pages)",
2525
"Generated .tsx uses `makeStyles` with tokens for styling — no inline styles for static values",
26+
"Generated .tsx nests `@media` queries inside `makeStyles` slots — no media query used as a top-level slot key",
2627
"Generated .tsx does NOT use `100vh` or `100vw`",
2728
"Generated .tsx does NOT import `createTheme`, `mergeThemes`, or `useTheme` (these do not exist in Fluent UI V9)",
2829
"Generated .tsx does NOT wrap content in a top-level `<FluentProvider>` (already provided at root)",
@@ -36,7 +37,8 @@
3637
"For DataGrids, generated .tsx imports `createTableColumn` from `@fluentui/react-components` and configures column sizing (columnSizingOptions or resizableColumns)",
3738
"For Dataverse pages with fetching, separate setState calls inside async callbacks are batched into a single setData(...) call (Rule 14)",
3839
"For Dataverse list/detail pages, the inline IIFE + window cache + cache-guard pattern from references/data-caching.md is used (Rule 15); no useCallback for data-fetching functions",
39-
"For multi-page builds, cross-page navigation uses quoted `\"PAGEREF_<filename>\"` placeholders that the orchestrator's Phase 6.5 resolves to real GUIDs"
40+
"For multi-page builds, cross-page navigation uses quoted `\"PAGEREF_<filename>\"` placeholders that the orchestrator's Phase 6.5 resolves to real GUIDs",
41+
"For Dataverse pages that call dataApi.queryTable, the result is accessed via .rows (DataTable<T> = { rows: T[], hasMoreRows, loadMoreRows() }) — never used directly as an array (Rule 11)"
4042
],
4143
"evals": [
4244
{
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Genpage Plan
2+
3+
## User Requirements
4+
5+
Build a page showing Account records as a gallery of cards. Include name,
6+
website, email, phone number. Make the gallery scrollable and each card
7+
clickable to open the Account record.
8+
9+
## Working Directory
10+
11+
account-card-gallery/
12+
13+
## Plugin Root
14+
15+
D:\Projects\power-platform-skills\plugins\model-apps
16+
17+
## Environment
18+
19+
- Active Profile: aurora365-user1@auroratstgeo.onmicrosoft.com
20+
- Environment URL: https://aurorabapenv4ab3f.crm10.dynamics.com/
21+
- App: Sales Hub (12345678-1234-1234-1234-123456789abc)
22+
- Solution: Default
23+
- Publisher Prefix: new
24+
25+
## Pages
26+
27+
| Page | File | Purpose | Entities |
28+
|------|------|---------|----------|
29+
| Accounts Gallery | page.tsx | Card-based gallery of Account records, click-to-open detail | account |
30+
31+
## Entity Creation Required
32+
33+
No entity creation required — all entities already exist.
34+
35+
## Existing Entities
36+
37+
account
38+
39+
## Design Preferences
40+
41+
- Card grid layout, responsive auto-fill columns
42+
- Each card shows name, website, email, phone
43+
- Card click navigates to the Account record via Xrm.Navigation.navigateTo
44+
- Scrollable container, not 100vh / 100vw
45+
46+
## Relevant Samples
47+
48+
- plugins/model-apps/samples/7-responsive-cards.tsx (card layout reference)
49+
- plugins/model-apps/samples/9-list-with-caching.tsx (Dataverse list pattern + window cache)
50+
51+
## Per-Page Specifications
52+
53+
### Accounts Gallery
54+
55+
- File: page.tsx
56+
- Entity: account
57+
- Data fetching: dataApi.queryTable on mount, window cache (`__genpage_accounts_v1`), no useCallback wrap
58+
- Columns shown: name, websiteurl, emailaddress1, telephone1
59+
- Navigation: Xrm.Navigation.navigateTo({ pageType: 'entityrecord', entityName: 'account', entityId })
60+
- Icons: BuildingRegular, MailRegular, PhoneRegular, GlobeRegular (unsized form)
61+
- Styling: makeStyles with tokens; no inline styles for static values
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import * as React from 'react';
2+
import {
3+
makeStyles,
4+
tokens,
5+
Card,
6+
CardHeader,
7+
Text,
8+
Body1,
9+
Caption1,
10+
Spinner,
11+
MessageBar,
12+
MessageBarBody,
13+
} from '@fluentui/react-components';
14+
import {
15+
BuildingRegular,
16+
MailRegular,
17+
PhoneRegular,
18+
GlobeRegular,
19+
} from '@fluentui/react-icons';
20+
import { GeneratedComponentProps, Account } from './RuntimeTypes';
21+
22+
const useStyles = makeStyles({
23+
root: {
24+
display: 'flex',
25+
flexDirection: 'column',
26+
height: '100%',
27+
padding: tokens.spacingHorizontalL,
28+
gap: tokens.spacingVerticalM,
29+
boxSizing: 'border-box',
30+
},
31+
header: { display: 'flex', alignItems: 'center', gap: tokens.spacingHorizontalS },
32+
gallery: {
33+
display: 'grid',
34+
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
35+
gap: tokens.spacingVerticalM,
36+
overflowY: 'auto',
37+
flex: 1,
38+
},
39+
card: {
40+
cursor: 'pointer',
41+
padding: tokens.spacingHorizontalM,
42+
},
43+
detail: {
44+
display: 'flex',
45+
alignItems: 'center',
46+
gap: tokens.spacingHorizontalXS,
47+
color: tokens.colorNeutralForeground2,
48+
},
49+
});
50+
51+
declare const Xrm: {
52+
Navigation: {
53+
navigateTo: (
54+
pageInput: { pageType: string; entityName: string; entityId: string },
55+
) => Promise<unknown>;
56+
};
57+
};
58+
59+
const GeneratedComponent = (props: GeneratedComponentProps) => {
60+
const { dataApi, pageInput } = props;
61+
void pageInput; // list page does not consume pageInput
62+
const styles = useStyles();
63+
const [accounts, setAccounts] = React.useState<Account[]>([]);
64+
const [loading, setLoading] = React.useState(true);
65+
const [error, setError] = React.useState<string | null>(null);
66+
67+
React.useEffect(() => {
68+
(async () => {
69+
const cacheKey = '__genpage_accounts_v1';
70+
const w = window as unknown as Record<string, Account[] | undefined>;
71+
if (w[cacheKey]) {
72+
setAccounts(w[cacheKey] as Account[]);
73+
setLoading(false);
74+
return;
75+
}
76+
try {
77+
const result = await dataApi.queryTable('account', {
78+
select: ['accountid', 'name', 'websiteurl', 'emailaddress1', 'telephone1'],
79+
top: 100,
80+
});
81+
// queryTable returns DataTable<T> = { rows: T[], hasMoreRows, loadMoreRows() } — access records via .rows
82+
w[cacheKey] = result.rows;
83+
setAccounts(result.rows);
84+
} catch (e) {
85+
setError(e instanceof Error ? e.message : String(e));
86+
} finally {
87+
setLoading(false);
88+
}
89+
})();
90+
}, [dataApi]);
91+
92+
const openAccount = async (id: string) => {
93+
await Xrm.Navigation.navigateTo({
94+
pageType: 'entityrecord',
95+
entityName: 'account',
96+
entityId: id,
97+
});
98+
};
99+
100+
if (loading) return <Spinner label="Loading accounts..." />;
101+
if (error) return (
102+
<MessageBar intent="error"><MessageBarBody>{error}</MessageBarBody></MessageBar>
103+
);
104+
105+
return (
106+
<div className={styles.root}>
107+
<div className={styles.header}>
108+
<BuildingRegular />
109+
<Text size={500} weight="semibold">Accounts ({accounts.length})</Text>
110+
</div>
111+
<div className={styles.gallery}>
112+
{accounts.map((a) => (
113+
<Card
114+
key={a.accountid}
115+
className={styles.card}
116+
onClick={() => openAccount(a.accountid)}
117+
>
118+
<CardHeader header={<Body1 weight="semibold">{a.name}</Body1>} />
119+
{a.websiteurl && (
120+
<div className={styles.detail}>
121+
<GlobeRegular />
122+
<Caption1>{a.websiteurl}</Caption1>
123+
</div>
124+
)}
125+
{a.emailaddress1 && (
126+
<div className={styles.detail}>
127+
<MailRegular />
128+
<Caption1>{a.emailaddress1}</Caption1>
129+
</div>
130+
)}
131+
{a.telephone1 && (
132+
<div className={styles.detail}>
133+
<PhoneRegular />
134+
<Caption1>{a.telephone1}</Caption1>
135+
</div>
136+
)}
137+
</Card>
138+
))}
139+
</div>
140+
</div>
141+
);
142+
};
143+
144+
export default GeneratedComponent;

0 commit comments

Comments
 (0)