From power-platform
Diagnose and fix Power Fx canvas app performance — delegation as a P1 design constraint, lazy-load patterns, Concurrent() vs sequential, ClearCollect vs collection growth, OnStart vs OnVisible, screen-transition cost, control-count budgets per screen, and the connector-call audit via Monitor. Used by `power-fx-engineer` (primary).
How this skill is triggered — by the user, by Claude, or both
Slash command
/power-platform:canvas-app-performanceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Purpose:** Senior-maker playbook for diagnosing and fixing canvas app performance — and, more importantly, for designing apps that don't get slow in the first place. Performance work in canvas is not "optimisation after the fact"; the design choices that lock in performance are made on day one. Most slow apps got slow at design time, not because of "extra features."
Purpose: Senior-maker playbook for diagnosing and fixing canvas app performance — and, more importantly, for designing apps that don't get slow in the first place. Performance work in canvas is not "optimisation after the fact"; the design choices that lock in performance are made on day one. Most slow apps got slow at design time, not because of "extra features."
Text, Visible, Items property is not "set once" — it's a function the runtime calls when dependencies change. A LookUp() in a Gallery's ThisItem context runs once per visible row, on every refresh.OnVisible per screen, or App.StartScreen for the initial routing decision. OnStart blocks the splash screen.A canvas app is a tree of controls. Every control has properties that are expressions. The runtime evaluates an expression whenever a dependency it references changes. So:
Label1.Text = LookUp(Accounts, ID = ThisItem.AccountID).Name inside a Gallery — the LookUp runs once per row, on every refresh of the gallery. With 200 rows and a Dataverse source, that's 200 connector calls per gallery refresh.Button1.Visible = User().Email = "[email protected]" — evaluated once on screen show, then cached until User() invalidates.Gallery1.Items = Filter(Accounts, Status = "Active") — if Filter is delegable, the connector receives the filter and returns at most the delegation limit; if not, the runtime pulls up to 500 rows and filters client-side.The mental model: every property is a function; every function runs whenever its inputs change. Build with that in mind.
| Data source | Common delegable | Common NON-delegable | Default row cap |
|---|---|---|---|
| Dataverse | Filter, Search, LookUp, Sort, SortByColumns, most operators (=, >, <, In, StartsWith) on most columns | Distinct on some types, complex If in predicates, Sum/Count on filtered subsets in older runtimes | 500 (raise to 2000 in Advanced Settings) |
| SharePoint | Filter, LookUp, Sort on indexed columns; StartsWith, = on text | Search, complex predicates, lookups across lists, In on choice columns (partial) | 500 (raise to 2000) |
| SQL Server | Filter, Sort, LookUp, most operators | Search, IsBlank() in predicate, some If patterns | 500 (raise to 2000) |
| Excel / OneDrive Excel | Almost nothing delegates | Almost everything | 500 hard cap |
Rule: if Power Fx underlines your expression with a blue warning, it doesn't delegate. The warning is not a suggestion. The 500-row cap will silently truncate your results in production where the table has grown.
Patterns to work past the ceiling:
See resources/delegation-cheatsheet.md for the per-source table with current Dataverse Web API operator coverage.
Pattern: App.OnStart sets only user-context (Set(varUser, User())) and config (Set(varAppConfig, LookUp(Config, Key="App"))). Screens load their own data OnVisible. The splash is short; data appears as the user navigates.
;Sequential operations (Op1; Op2; Op3) run one after the other. With three independent Dataverse calls, that's three round-trip latencies stacked.
Concurrent(Op1, Op2, Op3) runs them in parallel and waits for all to complete. Same total work, ~1/3 the wall time on a network-bound workload.
Rule: any time you have 2+ independent operations in OnVisible or OnStart, wrap them in Concurrent(). The only reason not to is if Op2 depends on Op1's result — and even then, the dependent parts can often be re-grouped.
ClearCollect(colA, source) — clears colA, then re-collects. Use for refresh.Collect(colA, item) — appends. Useful for streaming pages.Collect(colA, source) in a loop with no Clear() — collection grows unbounded across user navigations.OnSelect of a button the user mashes — fires a network round-trip each time. Throttle or cache.Hard guidance: >500 controls on one screen is a smell. The runtime re-evaluates all visible expressions on relevant changes; control count drives that workload directly.
Common offenders:
Visible toggling — all four are still in the tree.Fixes:
Anti-pattern: Label.Text = LookUp(Accounts, ID = ThisItem.AccountID).Name inside a 200-row Dataverse-backed gallery. That's 200 LookUps per refresh.
Fix: in the source query, project the label column onto the row. Either:
When an app is slow:
Patch() on a complex form → split into smaller patches or push to a flow.Always Monitor first, then fix. Don't optimize what isn't slow.
Set(...) and Collect(...) calls without var*/col* prefix (§3 #6)power-platform-admin for capacity / API limit review.flow-engineer for connector-call alternatives, or dataverse-architect if the source is Dataverse and the slowness is query-shape related.dataverse-architect and architect in.pcf-developer.Always pair this skill's findings with the power-platform-tester agent for repeatable load/responsiveness assertions before declaring "fixed."
npx claudepluginhub mcorbett51090/ravenclaude --plugin power-platformGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.