Activates and provisions a Power Pages website in a Power Platform environment via the Power Platform REST API. Use when the user wants to activate, provision, turn on, or enable a Power Pages website or portal.
日本語の概要は準備中です。原文の説明を表示しています。
Creates a new Power Pages code site (SPA) from a curated template or from scratch using React, Angular, Vue, or Astro. Guides through the full process from initial concept to deployed site: requirements discovery, template selection or scaffolding, component planning, design, implementation, validation, and deployment. Use when the user wants to create, build, use a template for, or scaffold a new Power Pages website or portal.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Plugin check: Run
node "${PLUGIN_ROOT}/scripts/check-version.js"— if it outputs a message, show it to the user before proceeding.
Guide the user through creating a complete, production-quality Power Pages code site from initial concept to deployed site. Follow a systematic approach: discover requirements, scaffold and launch immediately, plan components and design, implement with design applied, validate, review, and deploy.
design-aesthetics.md, page-blueprints.md, design-critique.md under ${PLUGIN_ROOT}/references/) define the bar, and your own taste fills the rest.browser_navigate + browser_snapshot) to verify the structure of every significant change. Visual design review captures every page with one script call and opens the screenshots in one turn (see 5.7); use Playwright MCP screenshots only for brand extraction in Phase 3.public/scaffold-status.json. Update this file before every AskUserQuestion (to raise the "waiting for your input" banner so the user doesn't miss a terminal prompt) and before each implementation step in Phase 5 (so the progress-bar label matches what you're actually doing while the decorative spinner continues its default cycle). See Live Preview Status Protocol.<img> tags pointing to nonexistent files.en-US, LCID 1033) in this workflow.${PLUGIN_ROOT}/references/bidirectional-design.md. Every site must use direction-neutral layout, mixed-content boundaries, and script-capable typography even when its initial language has only one direction. A later LTR/RTL locale must not require a general redesign.${PLUGIN_ROOT}/references/site-modification-integrity.md so later pages and components can preserve localization, direction, and content-expansion behavior through the shared post-modification validator.Constraint: Only static SPA frameworks are supported (React, Vue, Angular, Astro). NOT supported: Next.js, Nuxt.js, Remix, SvelteKit, Liquid.
Initial request: $ARGUMENTS
While the scaffold loading screen is visible (from Phase 2.6 until the Home page itself is replaced in Phase 5), the loader polls GET /scaffold-status.json every 1.5 seconds. The message you write into <PROJECT_ROOT>/public/scaffold-status.json appears as the label under the progress bar, and awaitingInput controls the "waiting for your input" banner. The decorative spinner above the progress bar continues its built-in phrase cycle; keep the progress-bar label current so the loader still reflects what is actually happening.
Why this matters: When the browser with the loader takes over the user's screen, a prompt in the terminal can sit unanswered for a long time because the user doesn't realize anything is waiting. The banner makes it obvious.
File shape (all fields optional — omit any field you don't want to change):
{
"message": "Creating Contact page",
"awaitingInput": false,
"inputPrompt": "Please check your terminal to respond."
}
message — one short present-participle phrase shown as the status line under the progress bar in the loader (replacing the default "Getting started…" / "Setting up infrastructure…" cycle). Include the grouping context inline when it helps (e.g., "Creating Footer component (shared components)").awaitingInput — when true, a prominent pulsing banner appears at the top of the loader and stays visible until this field is cleared. Set this before every AskUserQuestion call and clear it (false) immediately after the user answers.inputPrompt — short context for the banner (e.g., "Choose a framework"). Optional.When to update the file:
{ "message": "Planning your site", "awaitingInput": false }.AskUserQuestion that runs while the scaffold is visible (Phases 3, 4, and any in-scaffold prompt in Phase 5): set awaitingInput: true with a short inputPrompt. After the user answers, write again with awaitingInput: false.message to the specific action. Examples: "Applying design tokens", "Creating Navbar component", "Creating Contact page".public/scaffold-status.json so it isn't deployed with the site.Write the file with the Write tool (atomic overwrite). You do not need to read it first.
Goal: Understand what site needs to be built and what problem it solves
Actions:
<!-- gate: create-site:1.purpose | category=plan | cancel-leaves=nothing -->🚦 Gate (plan · create-site:1.purpose): Path-agnostic discovery prompt collecting site name, purpose, and audience. Determines what kind of site the user needs before the skill decides between a template-backed path and the from-scratch scaffold path. Fires only on the "site purpose unclear" branch (step 3 below).
Trigger: Phase 1 when site name, purpose, or audience was not provided in
$ARGUMENTS. Why we ask: Wrong purpose/audience context → wrong branch decision and wrong generated site plan; cleanup is annoying. Cancel leaves: Nothing — no scaffolding has started yet.
Create the minimal upfront todo list (see Progress Tracking):
If site name, purpose, and audience are clear from arguments:
If site name, purpose, or audience is unclear, use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
| What should the site be called? (e.g., "Contoso Portal", "HR Dashboard") | Site Name | (free text — use a single generic option so the user types a custom name via "Other") |
| What is the site's purpose? | Purpose | Company Portal, Blog/Content, Dashboard, Landing Page |
| Who is the target audience? | Audience | Internal (employees, partners), External (public-facing customers) |
From the user's answers, derive:
__SITE_NAME__ (Title Case, e.g., Contoso Portal)__SITE_SLUG__ (lowercase ASCII kebab-case derived from
the site name when possible)Summarize the path-agnostic understanding and confirm with user before proceeding:
Do not ask for framework or project location in Phase 1. Each creation path asks for its location after Phase 1.5 selects that path.
Audience influences site generation:
Output: Clear statement of site purpose, audience, site name, and preliminary technical slug.
Goal: Route the user into the appropriate creation path after path-agnostic Discovery.
Current implementation state: Template discovery, selection, supporting-solution import, packaged SPA cloning/upload, optional seed data, activation, live-site preview, and terminal telemetry are implemented for
kind: "spa". Traditional catalog entries are accepted but not shown until their solution-only provisioning flow is implemented. The user can always choose Start from scratch to continue into the existing scaffold flow.
Actions:
Mark Select template or choose from-scratch as in_progress.
Fetch the template catalog:
node "${PLUGIN_ROOT}/scripts/fetch-template-catalog.js"
Use the returned immutable commit SHA for every template artifact request in this run. Pass --ref <tag-or-branch> only for a deliberate test or rollback.
Evaluate the JSON result:
ok: false: tell the user templates are temporarily unavailable and continue with the from-scratch path. This is additive; a catalog failure must never block create-site.ok: true but selectableCatalog.templates is empty: tell the user no supported SPA templates are currently available and continue with the from-scratch path. Do not offer entries from catalog.templates whose kind is traditional.selectableCatalog.templates for every matching, preview, browse, and selection step below. Keep catalog.templates only as the complete downloaded manifest.Semantically match the template families against the Phase 1 context ($ARGUMENTS, site name, purpose, audience, and any framework mentioned by the user):
displayName, description, keywords, audience, available variant frameworks, and any variant-specific previews.react, vue, angular, astro).Ask the user how to proceed after semantic matching:
| Match situation | Question | Header | Options |
|---|---|---|---|
| One or more clear matches | I found matching template(s) for your site. What would you like to do? | Creation Path | Show matching templates (Recommended), Browse all templates, Create from scratch |
| No clear match | I couldn't find a matching template for your site. What would you like to do? | Creation Path | Browse all templates, Create from scratch (Recommended) |
Branch on the answer:
TEMPLATE_PREVIEW_FAMILIES to the matched family or families and continue to browser preview.TEMPLATE_PREVIEW_FAMILIES to all entries in selectableCatalog.templates and continue to browser preview.CREATION_PATH = "from-scratch" and continue to the deferred framework/location questions.Render TEMPLATE_PREVIEW_FAMILIES for browser preview:
previewImages artifact into the SHA-keyed cache before rendering:
node "${PLUGIN_ROOT}/scripts/fetch-template-artifact.js" --sha "<catalog-sha>" --artifactPath "<preview-image-path>"
Replace each preview image path with the returned localUrl. If a preview download returns ok: false, omit that one image from the gallery and continue; a missing preview should not block using an otherwise-valid template.TEMPLATES_JSON array with the template families the user should preview and the cached preview image URLs. Include each family's available variants so the browser can show framework badges/tabs, but keep the browser read-only.node "${PLUGIN_ROOT}/scripts/render-template-browser.js" --templatesJsonPath "<temp-json>" --outputPath "<temp-html>" --open
Evaluate the JSON result before telling the user to use the browser:
status: "ok": continue; the browser preview was generated and opened.status: "invalid": do not continue with template selection. Surface the validation errors, rebuild the TEMPLATES_JSON file from the matched families, and rerun the render. If it is still invalid, fall back to from-scratch rather than showing an empty or incomplete template browser.status: "ok" with opened: false: the HTML validated but the browser could not be opened automatically. Show the file path and continue with terminal selection only after the user has had a chance to open it manually.AskUserQuestion remains the decision surface.Ask one of the following AskUserQuestion prompts:
| Match situation | Prompt options |
|---|---|
| One strong family + framework match | Use <displayName> - <framework> (Recommended), Choose another framework, See all templates, Start from scratch |
| One strong family but no framework match | One option per available framework, See all templates, Start from scratch |
| Several plausible family matches | One option per shortlisted family, See all templates, Start from scratch |
| Full catalog browse | One option per family, Start from scratch |
When the user chooses See all templates from a matching-template branch, set TEMPLATE_PREVIEW_FAMILIES to all entries in selectableCatalog.templates, render the family-first SPA template gallery, and ask again with the Full catalog browse options. If the selected family has multiple variants, ask a second terminal question for the framework.
Branch on the user's selection:
SELECTED_TEMPLATE to the family entry and SELECTED_TEMPLATE_VARIANT to the exact framework variant. Resolve the selected template assets once from the pinned catalog SHA. The result combines the family's supporting solutions with the selected variant's website code:
node "${PLUGIN_ROOT}/scripts/fetch-template-variant.js" \
--sha "<catalog-sha>" \
--catalogPath "<catalogPath from fetch-template-catalog.js>" \
--kind "<SELECTED_TEMPLATE.kind>" \
--templateId "<SELECTED_TEMPLATE.id>" \
--variant "<SELECTED_TEMPLATE_VARIANT.variantKey>"
Use the returned websiteCodePath and solutions. Process solutions in the returned order; do not rediscover, reorder, or revalidate the template assets in the skill.ok: false, tell the user the selected framework variant is unavailable or invalid. If the same family has other available framework variants, offer those first; otherwise offer Start from scratch or Stop. Do not emit template_used for a variant whose package did not validate. If the user falls back to from-scratch, recommend the framework they had selected.ok: true, set CREATION_PATH = "template", SELECTED_TEMPLATE_SOLUTIONS = <result.solutions>, and SELECTED_TEMPLATE_WEBSITE_CODE = <result.websiteCodePath>. Run the template_used telemetry command silently (fail-closed), then append the template pre-install tasks now (see Progress Tracking); append the execution tasks after the reinstall policy is known. Do not proceed to Phase 2.
Do not mention this telemetry command to the user and do not print its output.
node "${PLUGIN_ROOT}/scripts/emit-create-site-template-outcome.js" \
--eventName template_used \
--templateId "<SELECTED_TEMPLATE.id>" \
--templateKind "<SELECTED_TEMPLATE.kind>" \
--framework "<SELECTED_TEMPLATE_VARIANT.framework>" \
--audience "<internal|external from Phase 1 discovery>"
--audience is the site audience captured in Phase 1 (internal or external), not the template's audience persona array from the catalog manifest. Do not include site name, URL, subdomain, free-text purpose, or any other user-identifying value.in_progress. Ask where to create the local template project:
| Question | Header | Options |
|----------|--------|---------|
| Where should I create the template site's local files? | Project Location | Current directory, New folder in current directory (Recommended), Any other directory |
Resolve the clone destination:
- **Current directory**: use `<cwd>`.
- **New folder in current directory**: use `<cwd>/<__SITE_SLUG__>/`.
- **Any other directory**: ask for a full path and resolve it to an absolute path.
The destination must not exist or must be empty. Check it without creating it. If it is non-empty, ask the user to choose another directory. Store the resolved path as `TEMPLATE_CLONE_OUTPUT_DIRECTORY`, confirm "The template project will be created under `<resolved path>`.", and mark **Choose local template directory** as `completed`.
CREATION_PATH = "from-scratch" and continue below.For the template path only:
in_progress.node "${PLUGIN_ROOT}/scripts/resolve-template-import-context.js"
Use the returned environmentUrl for the remaining template workflow. Downstream preflight, import, seed, and polling helpers acquire Azure tokens internally so credentials are never returned in JSON or carried between tasks. If ok: false, surface the error and stop before import. Do not emit template_import_failure because no import was attempted; template_used was already emitted when the template path was selected.🚦 Gate (consent · create-site:1.5.confirm-environment): Confirm the resolved target environment before running template install preflights.
Trigger: Phase 1.5 after
resolve-template-import-context.jsreturnsenvironmentUrland before any CLI-tenant,.jsunblock, language, solution import, seed, or activation step. Why we ask: PAC auth can point at a different Dataverse environment than the user intended. Even preflights can inspect or modify environment-level settings, so the skill must not continue silently. Cancel leaves:template-cache— template catalog assets, the discovered solution sources, and website code may already be cached locally; no org mutation has happened if cancelled here.
Mark Resolve target environment as completed and Confirm target environment as in_progress, then ask the user to confirm the resolved target environment before any environment preflight:
| Question | Header | Options |
|---|---|---|
Use <environmentUrl> as the target environment for this template install? | Confirm Environment | Yes, use this environment (Recommended), No, start from scratch, Cancel |
CREATION_PATH = "from-scratch" and continue to the deferred framework/location questions. Do not run environment preflights or import the template.template_import_failure because no import was attempted; template_used was already emitted when the template path was selected.Mark Confirm target environment as completed and Validate CLI tenant alignment as in_progress, then confirm PAC CLI and Azure CLI are authenticated to the same tenant before any import preflight that depends on both CLIs:
node "${PLUGIN_ROOT}/scripts/validate-cli-tenant-alignment.js" --envUrl "<environmentUrl>"
ok: true: mark Validate CLI tenant alignment as completed.ok: false: surface the error and the pacTenantId, azTenantId, and tokenTenantId fields when present. Stop before import and tell the user to switch either PAC auth or Azure CLI to the same tenant, then rerun the skill. Do not emit template_import_failure because no import was attempted.Mark both Validate JavaScript unblock requirement and Validate Dataverse language requirements as in_progress. Run the .js blocked-attachment dry run and the Dataverse language check in parallel because both are read-only preflights against the confirmed target environment:
node "${PLUGIN_ROOT}/scripts/lib/fix-blocked-attachments.js" --envUrl "<environmentUrl>" --extensions js --dry-run --quiet
node "${PLUGIN_ROOT}/scripts/check-available-languages.js" --envUrl "<environmentUrl>" --requiredLocaleIds "<SELECTED_TEMPLATE_VARIANT.requiredDataverseLanguages or SELECTED_TEMPLATE.requiredDataverseLanguages comma-separated>"
Capture both JSON results before deciding what to do next. Do not mutate blockedattachments until the language check has also completed; if the language check blocks import, route to the language-requirement question without changing attachment settings.
Evaluate the .js result:
wasBlocked does not include js: mark Validate JavaScript unblock requirement as completed; JavaScript attachments are allowed.wasBlocked includes js: keep Validate JavaScript unblock requirement as in_progress; this may need the .js unblock consent gate below, but handle language failures first because they block import without any environment mutation.
Evaluate the language result:ok: true and hasRequiredLanguages: true: mark Validate Dataverse language requirements as completed; the target environment has every LCID required by the selected variant, falling back to the family requirements when the variant does not override them.ok: false: tell the user the skill could not verify available Dataverse languages, surface the script error, then ask whether to switch to from-scratch or stop. Do not import the template and do not mutate blockedattachments.ok: true and hasRequiredLanguages: false: explain that the selected template requires the missing Dataverse language LCIDs from missingLocaleIds. Block before any solution import mutation. Do not provide a "proceed anyway" branch and do not mutate blockedattachments.🚦 Gate (consent · create-site:1.5.unblock-js): Preflight unblock of
.jsfrom the target environment'sblockedattachmentssetting before uploading website code.Trigger: Phase 1.5 when
fix-blocked-attachments.js --dry-run --extensions jsreports.jsis blocked in the target environment. Why we ask: The packaged SPA is uploaded withpac pages upload-code-siteafter its supporting solutions are ready. That upload includes JavaScript files and fails when.jsis blocked. Checking before solution import avoids leaving supporting artifacts behind when the site cannot be created. Cancel leaves:attachment-block-modifiedis possible only if the user approved and the update partially completed. Pure Cancel here leaves the originalblockedattachmentsvalue untouched and no template installation has started.
Use `AskUserQuestion`:
| Question | Header | Options |
|----------|--------|---------|
| This environment currently blocks `.js` attachments. The template site's code upload will fail unless `.js` is unblocked. Remove only `js` from `blockedattachments` now? | Unblock JavaScript | Yes, unblock `.js` and continue (Recommended), No, start from scratch, Cancel |
- **Yes**: run the helper without `--dry-run`, preserving every other blocked extension:
```bash
node "${PLUGIN_ROOT}/scripts/lib/fix-blocked-attachments.js" --envUrl "<environmentUrl>" --extensions js --quiet
```
Confirm the JSON result has `removed` containing `js` or `changed: true`, then rerun the dry-run check and continue only if `.js` is no longer blocked.
- **No, start from scratch**: set `CREATION_PATH = "from-scratch"` and continue to the deferred framework/location questions. Do not import the template.
- **Cancel**: stop before template installation. Do not emit `template_import_failure` because no import was attempted; `template_used` was already emitted when the template path was selected.
<!-- not-a-gate: route selection after a blocking preflight; no org mutation has happened and there is no option to override missing template language requirements -->
6. Use `AskUserQuestion` only when the language preflight cannot continue:
| Question | Header | Options |
|----------|--------|---------|
| This template requires Dataverse language LCID(s) `<requiredLocaleIds>`, but the target environment is missing `<missingLocaleIds>` or the language check could not be completed. What would you like to do? | Template Language Requirement | Start from scratch (Recommended), Cancel |
- **Start from scratch**: set `CREATION_PATH = "from-scratch"` and continue to the deferred framework/location questions. Do not import the template.
- **Cancel**: stop before template installation. Do not emit `template_import_failure` because no import was attempted; `template_used` was already emitted when the template path was selected.
<!-- gate: create-site:1.5.template-import | category=consent | cancel-leaves=template-cache -->
🚦 Gate (consent · create-site:1.5.template-import): Confirm installing the selected template in the current Power Platform environment.
Trigger: Phase 1.5 after the template variant and its unpacked solutions are downloaded and the target environment is resolved. Why we ask: The install can import unmanaged supporting solutions and create a new code site. Choosing the wrong environment or template is disruptive and cannot be cleanly undone. Cancel leaves:
template-cache— the discovered solution sources and preview images may remain in the private SHA-keyed template cache; no org mutation has occurred.
If the language preflight passed but .js was blocked and the user approved/verification passed, mark Validate JavaScript unblock requirement as completed. Then mark Confirm template install as in_progress, present the template and environment, and ask:
| Question | Header | Options |
|---|---|---|
Install <SELECTED_TEMPLATE.displayName> into <environmentUrl>? This imports any required unmanaged supporting solutions, clones and builds the website code, and uploads the new code site. Seed data is applied before activation when available. | Install Template | Yes, install this template (Recommended), No, start from scratch, Cancel |
CREATION_PATH = "from-scratch" and continue to the deferred framework/location questions.template_import_failure because no import was attempted; template_used was already emitted when the template path was selected.Mark Confirm template install as completed. Initialize TEMPLATE_SOLUTIONS_TO_IMPORT = [] and TEMPLATE_SOLUTIONS_TO_SKIP = [], then process every entry in SELECTED_TEMPLATE_SOLUTIONS in the returned order:
node "${PLUGIN_ROOT}/scripts/inspect-template-solution.js" --solutionPath "<solution.solutionPath>"
node "${PLUGIN_ROOT}/scripts/check-solution-installed.js" --solutionName "<solution.uniqueName>" --envUrl "<environmentUrl>"
node "${PLUGIN_ROOT}/scripts/inspect-template-solution.js" --solutionPath "<solution.solutionPath>" --installed "<true|false>" --installedVersion "<version-or-empty>"
Do not derive a website name from a solution or expect supporting-solution import to add a pac pages list -v row. If metadata inspection returns ok: false after variant validation, or check-solution-installed.js exits 1, treat that solution as decision: "ask" rather than assuming it is absent.
decision: "import": append the solution to TEMPLATE_SOLUTIONS_TO_IMPORT.
decision: "confirm-update": tell the user that this specific solution has a newer version available and confirm before adding it to TEMPLATE_SOLUTIONS_TO_IMPORT.
🚦 Gate (consent · create-site:1.5.update-installed): Confirm updating an already-installed unmanaged template solution. Repeat for each solution that needs an update.
Trigger: Phase 1.5 when one of the selected template solutions is already installed and the downloaded source has a newer version. Loop behavior: Fires once per matching entry in
SELECTED_TEMPLATE_SOLUTIONS; three solutions needing updates require three confirmations. Why we ask: Updating an unmanaged solution merges changes into the environment and cannot be cleanly rolled back. Cancel leaves:template-cache— downloaded template artifacts remain in the private SHA-keyed template cache; no org mutation happens if cancelled.
Use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
Solution <solution.uniqueName> is installed at version <installedVersion>, and the template contains <availableVersion>. Update it in this environment? | Update Solution | Yes, update this solution (Recommended), No, cancel |
If the user declines or cancels, stop before import; no org mutation has happened. Do not emit template_import_failure because no import was attempted; template_used was already emitted when the template path was selected.
If the user confirms, append this solution to TEMPLATE_SOLUTIONS_TO_IMPORT.
decision: "offer-clone": append the same-or-newer solution to TEMPLATE_SOLUTIONS_TO_SKIP.
decision: "ask" or detection failure: ask whether to import that solution anyway, start from scratch, or stop.
🚦 Gate (consent · create-site:1.5.reinstall-unknown): Confirm whether to import when installed-solution detection failed.
Trigger: Phase 1.5 when
check-solution-installed.jscannot determine whether one selected template solution already exists. Loop behavior: Fires once per unknown entry inSELECTED_TEMPLATE_SOLUTIONS; an answer applies only to the named solution. Why we ask: Importing an unmanaged solution that may already exist can merge components. Cancel leaves:template-cache— downloaded template artifacts remain in the private SHA-keyed template cache; no org mutation happens if cancelled.
Use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
I couldn't determine whether solution <solution.uniqueName> is installed. Importing it may merge unmanaged components. How would you like to proceed? | Solution Install Unknown | Import anyway (advanced), Start from scratch (Recommended), Stop |
Branch on the answer:
TEMPLATE_SOLUTIONS_TO_IMPORT.CREATION_PATH = "from-scratch" and continue to the deferred framework/location questions.template_import_failure because no import was attempted; template_used was already emitted when the template path was selected.After all solutions are classified, append the full site-install tasks. Include Import template supporting solutions only when TEMPLATE_SOLUTIONS_TO_IMPORT is non-empty. If every solution was skipped, ask once before creating the site:
🚦 Gate (consent · create-site:1.5.clone-existing): Confirm creating a new site while reusing already-installed supporting solutions.
Trigger: Phase 1.5 when every selected template solution is installed at the same or a newer version. Why we ask: The skill will skip all solution imports but still clone and upload a new code site. Cancel leaves:
template-cache— downloaded template artifacts remain in the private SHA-keyed template cache; no site clone/upload happens if cancelled.
| Question | Header | Options |
|---|---|---|
All supporting solutions are already installed at the same or newer versions. Create a new <SELECTED_TEMPLATE_VARIANT.framework> site from the website code? | Create Template Site | Yes, create the site (Recommended), No, cancel |
If the user confirms, set SKIP_TEMPLATE_SOLUTION_IMPORT = true. If the user declines, stop. Do not emit an import result event because no solution import was attempted.
Render and open a read-only status page:
# Create <temp-import-status-dir>/ under the operating-system temporary directory,
# then write this initial status JSON to <temp-import-status-dir>/status.json:
# Import path:
# Use "Installing solution" when TEMPLATE_SOLUTIONS_TO_IMPORT has one entry,
# otherwise use "Installing solutions":
# { "state": "running", "phase": "solution", "message": "<solution install label>" }
# SKIP_TEMPLATE_SOLUTION_IMPORT path:
# { "state": "running", "phase": "site", "message": "Preparing template site" }
node "${PLUGIN_ROOT}/scripts/render-template-import-status.js" \
--templateName "<SELECTED_TEMPLATE.displayName>" \
--statusPath "<temp-import-status-dir>/status.json" \
--previewImagesJson '<JSON array of SELECTED_TEMPLATE.previewImages localUrl values from the template browser step>' \
--solutionCount "<TEMPLATE_SOLUTIONS_TO_IMPORT.length, or SELECTED_TEMPLATE_SOLUTIONS.length when imports are skipped>" \
--outputPath "<temp-import-status-dir>/index.html"
node "${PLUGIN_ROOT}/scripts/serve-static-dir.js" --root "<temp-import-status-dir>" --urlFile "<temp-import-status-dir>/url.txt" --cleanupRoot
node "${PLUGIN_ROOT}/scripts/open-url.js" --url "<url from <temp-import-status-dir>/url.txt>"
Reuse the already-downloaded local preview image URLs from the browser step; do not fetch preview images again for this page. The server command returns only after the listener is ready. It removes the temporary status directory after a successful redirect, after the browser stops polling, or when the maximum lifetime expires.
Unless SKIP_TEMPLATE_SOLUTION_IMPORT = true, mark Import template supporting solutions as in_progress. Process TEMPLATE_SOLUTIONS_TO_IMPORT sequentially in its existing case-insensitive lexical unique-name order. For each entry, set CURRENT_TEMPLATE_SOLUTION and prepare its unmanaged solution for import:
# Update the status JSON:
# { "state": "running", "phase": "solution", "message": "Preparing solution <CURRENT_TEMPLATE_SOLUTION.uniqueName>" }
node "${PLUGIN_ROOT}/scripts/pack-template-solution.js" \
--solutionPath "<CURRENT_TEMPLATE_SOLUTION.solutionPath>"
Set PACKED_TEMPLATE_SOLUTION_ZIP = <result.zipPath>, PACKED_TEMPLATE_SOLUTION_WORK_DIRECTORY = <result.workDirectory>, PACKED_TEMPLATE_SOLUTION_CLEANUP_MARKER = <result.cleanupMarker>, and PACKED_TEMPLATE_SOLUTION_CLEANUP_TOKEN = <result.cleanupToken> for the current solution. The packer creates the ZIP only in a token-owned OS temporary directory. Never write a packed ZIP into the downloaded template cache or another repository path.
If packing fails, do not call Dataverse and do not emit template_import_failure because no import was attempted. The packer removes partial output automatically.
🚦 Gate (progress · create-site:1.5.pack-failed): Choose how to proceed after preparing the local solution package fails.
Trigger: Phase 1.5 when local validation or
pac solution packfails for one discovered solution. Loop behavior: Fires per failed iteration ofTEMPLATE_SOLUTIONS_TO_IMPORT; an answer applies only toCURRENT_TEMPLATE_SOLUTION. Why we ask: No environment mutation has happened, but template installation cannot continue without a valid temporary solution ZIP. Cancel leaves:template-cache— downloaded template source remains in the private SHA-keyed template cache; partial pack output has been removed.
Use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
Solution <CURRENT_TEMPLATE_SOLUTION.uniqueName> could not be prepared for import. How would you like to proceed? | Template Pack Failed | Retry packing, Fall back to from-scratch (Recommended), Stop |
CREATION_PATH = "from-scratch" and continue to the deferred framework/location questions.After packing succeeds, import the temporary ZIP inline. Do not invoke /import-solution or write ALM artifacts. After ImportSolutionAsync returns the async operation id, immediately clean the packer's work directory, then launch a Task subagent to run poll-async-operation.js and write <temp-import-status-dir>/status.json:
node "${PLUGIN_ROOT}/scripts/encode-solution-file.js" --zipPath "<PACKED_TEMPLATE_SOLUTION_ZIP>"
# Write a temp JSON body file containing:
# {
# "CustomizationFile": "<encoded>",
# "OverwriteUnmanagedCustomizations": true,
# "PublishWorkflows": true,
# "ConvertToManaged": false
# }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<environmentUrl>" POST "ImportSolutionAsync" \
--bodyFile "<temp-import-body.json>" \
--include-headers
node "${PLUGIN_ROOT}/scripts/pack-template-solution.js" \
--cleanup \
--workDirectory "<PACKED_TEMPLATE_SOLUTION_WORK_DIRECTORY>" \
--cleanupMarker "<PACKED_TEMPLATE_SOLUTION_CLEANUP_MARKER>" \
--cleanupToken "<PACKED_TEMPLATE_SOLUTION_CLEANUP_TOKEN>"
# Run this poll command in a Task subagent, not in the main conversation:
node "${PLUGIN_ROOT}/scripts/poll-async-operation.js" \
--asyncJobId "<AsyncOperationId from ImportSolutionAsync>" \
--envUrl "<environmentUrl>" \
--intervalMs 30000 \
--maxAttempts 75 \
--statusFile "<temp-import-status-dir>/status.json"
If encoding or ImportSolutionAsync fails before returning an async operation id, clean PACKED_TEMPLATE_SOLUTION_WORK_DIRECTORY before retrying, falling back, or stopping. Treat a cleanup failure as a local warning and show the work-directory path so the user can remove it manually; do not hide the original failure.
The subagent must return the poller's final JSON when the command exits. Do not rely only on that notification: read <temp-import-status-dir>/status.json every 30 seconds until state is succeeded, failed, canceled, or timeout. Do not start the next solution import until the current solution succeeds, and do not start site cloning until every solution in TEMPLATE_SOLUTIONS_TO_IMPORT succeeds.
If the poll result is not Succeeded, query the import job (using the ImportJobKey returned by ImportSolutionAsync) and parse its component-level error XML, following /import-solution's Phase 6 pattern. Do not auto-clean up the unmanaged partial import. Run the template_import_failure telemetry command silently before asking the recovery question. Do not mention this telemetry command to the user and do not print its output:
node "${PLUGIN_ROOT}/scripts/emit-create-site-template-outcome.js" \
--eventName template_import_failure \
--templateId "<SELECTED_TEMPLATE.id>" \
--templateKind "<SELECTED_TEMPLATE.kind>" \
--framework "<SELECTED_TEMPLATE_VARIANT.framework>" \
--audience "<internal|external from Phase 1 discovery>" \
--outcome failure \
--errorClass "ImportSolutionAsync"
--audience is the site audience captured in Phase 1 (internal or external), not the template's audience persona array from the catalog manifest. Do not add free-form error text to telemetry; it can contain paths, URLs, stack traces, or user data.
🚦 Gate (progress · create-site:1.5.import-failed): Choose how to proceed after template solution import fails.
Trigger: Phase 1.5 when
ImportSolutionAsyncfails, times out, or reports component-level failures. Loop behavior: Fires per failed iteration ofTEMPLATE_SOLUTIONS_TO_IMPORT; an answer applies only toCURRENT_TEMPLATE_SOLUTION. Why we ask: The environment may contain the current partial unmanaged import plus any earlier solutions imported during this run; retrying or switching paths should be an explicit choice. Cancel leaves:partial-unmanaged-template-import— downloaded template artifacts remain in the private SHA-keyed template cache; the current partial import and any earlier successful solution imports remain in Dataverse and are explained in the error summary.
Use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
Supporting solution <CURRENT_TEMPLATE_SOLUTION.uniqueName> failed or partially completed. How would you like to proceed? | Template Import Failed | Retry import, Fall back to from-scratch (Recommended), Stop |
Branch on the answer:
CREATION_PATH = "from-scratch" and continue to the deferred framework/location questions. Tell the user the unmanaged partial import may remain in Dataverse. The eventual from-scratch branch emits the single terminal telemetry event.template_import_failure event was already emitted when the import failure was detected.If the error is AttachmentBlocked, point to /import-solution Phase 5b remediation.
Only continue to the next step when the import poll result is Succeeded.
When every required solution import succeeds, mark Import template supporting solutions as completed and set EMIT_TEMPLATE_IMPORT_SUCCESS = true. Defer that telemetry event until the seed-data workstream joins so seedApplied reflects this run. If every solution import was skipped, mark the task as skipped and set EMIT_TEMPLATE_IMPORT_SUCCESS = false.
Start site provisioning and seed-data application concurrently after all required solution imports finish:
in_progress.in_progress and launch the seed-data workstream with Task using run_in_background: true. The background task must only fetch, apply, and verify seed data. It must not clone, build, upload, activate, update the shared status file, ask the user questions, or retry failed writes.When seed data is present, update the status page:
{ "state": "running", "phase": "siteAndSeed", "message": "Creating template site and seeding data" }
When seed data is absent, update it with:
{ "state": "running", "phase": "site", "message": "Cloning, building, and uploading template site" }
Clone the packaged SPA source into the directory selected earlier, then upload the clone:
node "${PLUGIN_ROOT}/scripts/provision-template-site.js" \
--sourcePath "<SELECTED_TEMPLATE_WEBSITE_CODE>" \
--outputDirectory "<TEMPLATE_CLONE_OUTPUT_DIRECTORY>" \
--siteName "<__SITE_NAME__>"
Treat the wrapper as the sole template-site provisioning entry point. Do not rerun its underlying pac, npm, or build commands directly for diagnosis. Never pipe a mutating command such as pac pages clone or pac pages upload-code-site through head, tail, or another consumer that can close the output stream before the command finishes. The wrapper safely captures a bounded diagnostic tail without interrupting the command. On success, save the returned clonedPath as both CLONED_TEMPLATE_SITE_PATH and PROJECT_ROOT, siteName as IMPORTED_SITE_NAME, and websiteRecordId as IMPORTED_WEBSITE_RECORD_ID.
In the seed-data background task, run:
node "${PLUGIN_ROOT}/scripts/fetch-template-seed-data.js" --sha "<catalog-sha>" --seedDataPath "<SELECTED_TEMPLATE_VARIANT.seedDataPath or SELECTED_TEMPLATE.seedDataPath>"
If the result is ok: true, use localDir as the attachment base and seedFile as the only seed JSON:
node "${PLUGIN_ROOT}/scripts/apply-seed-data.js" \
--seedDir "<localDir>" \
--seedFile "<seedFile>" \
--envUrl "<environmentUrl>"
Return the JSON summary (inserted, failed, skipped, errors) to the main conversation. Seed records must use the exact table entity-set names, column logical names, and <NavigationProperty>@odata.bind lookup names from the template solution metadata. Never derive lookup navigation properties from a primary key, entity set, display name, or app-style alias such as categoryId; the seeder rejects ambiguous aliases before its first Dataverse write. For a lightweight read-only verification path, query each seeded entitySetName with dataverse-request.js using GET "<entitySetName>?$top=1" and report whether the seeded table is reachable. Prefer the selected variant's seedDataPath when present; otherwise use the family seedDataPath.
Wait for both workstreams to finish before showing the inactive-site summary or starting activation. Record the seed summary, then mark Apply template seed data as completed; if seed data is absent, mark it skipped. Seed fetch and insertion remain best-effort: surface their result, but do not fail site creation or block activation.
If EMIT_TEMPLATE_IMPORT_SUCCESS = true, emit the import result now:
node "${PLUGIN_ROOT}/scripts/emit-create-site-template-outcome.js" \
--eventName template_import_success \
--templateId "<SELECTED_TEMPLATE.id>" \
--templateKind "<SELECTED_TEMPLATE.kind>" \
--framework "<SELECTED_TEMPLATE_VARIANT.framework>" \
--audience "<internal|external from Phase 1 discovery>" \
--seedApplied "<true only when the seed summary has inserted > 0 and failed = 0; otherwise false>"
--audience is the site audience captured in Phase 1 (internal or external), not the template's audience persona array from the catalog manifest. Do not include site name, URL, subdomain, free-text purpose, or any other user-identifying value.
If clone, cloned-identity inspection, dependency installation, build, build-output validation, or upload fails, run template_clone_failure telemetry silently. Map the returned step to errorClass: clone/clone-output → PacPagesClone, install → NpmInstall, build → NpmBuild, build-output → CompiledOutput, and upload → PacPagesUploadCodeSite:
node "${PLUGIN_ROOT}/scripts/emit-create-site-template-outcome.js" \
--eventName template_clone_failure \
--templateId "<SELECTED_TEMPLATE.id>" \
--templateKind "<SELECTED_TEMPLATE.kind>" \
--framework "<SELECTED_TEMPLATE_VARIANT.framework>" \
--audience "<internal|external from Phase 1 discovery>" \
--outcome failure \
--errorClass "<PacPagesClone|NpmInstall|NpmBuild|CompiledOutput|PacPagesUploadCodeSite>"
Update the status page before firing the recovery gate:
{ "state": "error", "phase": "site", "message": "Template site creation failed" }
Diagnose the failure only from the wrapper's returned step, error, and preserved local path. Do not rerun the wrapper or any underlying command until the user chooses a recovery option.
Then fire this gate:
<!-- gate: create-site:1.5.clone-failed | category=progress | cancel-leaves=partial-template-clone -->
> 🚦 **Gate (progress · create-site:1.5.clone-failed):** Choose how to proceed after cloning, building, or uploading the packaged template site fails.
>
> **Trigger:** Phase 1.5 when cloning, cloned identity inspection, dependency installation, the project build, compiled-output validation, or `pac pages upload-code-site` fails.
> **Why we ask:** The selected project directory can contain partial clone or build output, the environment can contain a partial code-site upload, and supporting solutions may already be installed.
> **Cancel leaves:** `partial-template-clone` — cached template artifacts and local files remain in the selected project directory; supporting solutions or a partial site upload may also remain in Dataverse.
| Question | Header | Options |
|----------|--------|---------|
| The template site could not be cloned, built, or uploaded. How would you like to proceed? | Site Creation Failed | Retry site creation (Recommended), Fall back to from-scratch, Stop |
Do not retry automatically. If the wrapper returned `outputDirectoryRemoved: true`, **Retry site creation** may reuse the same selected location. Otherwise, ask for a new empty directory using the same **Project Location** prompt, update `TEMPLATE_CLONE_OUTPUT_DIRECTORY`, and rerun the wrapper. If the user falls back to from-scratch, explain that local files, supporting solutions, or a partial site upload may remain and recommend `<SELECTED_TEMPLATE_VARIANT.framework>`.
13. When clone, build, and upload succeed, mark Clone, build, and upload template site as completed and run template_clone_success telemetry silently:
bash node "${PLUGIN_ROOT}/scripts/emit-create-site-template-outcome.js" \ --eventName template_clone_success \ --templateId "<SELECTED_TEMPLATE.id>" \ --templateKind "<SELECTED_TEMPLATE.kind>" \ --framework "<SELECTED_TEMPLATE_VARIANT.framework>" \ --audience "<internal|external from Phase 1 discovery>"
Do not include the site name, clone path, URL, or Website Record ID in telemetry.
14. Mark Show inactive template site as in_progress. Tell the user: "Template <displayName> was created as <IMPORTED_SITE_NAME> (<IMPORTED_WEBSITE_RECORD_ID>). It is not activated yet. Seed-data processing is complete, and activation is next." If the template has no seed data, say that instead of implying records were inserted. Mark Show inactive template site as completed.
15. Mark Activate template site as in_progress. Before invoking /activate-site, update the status page:
json { "state": "running", "phase": "activation", "message": "Activating template site", "awaitingInput": false }
Then invoke /activate-site, passing the resolved identity and status path in the request so it skips local-project discovery and can show a toast while waiting for activation input:
text Activate cloned template site: - siteName: <IMPORTED_SITE_NAME> - websiteRecordId: <IMPORTED_WEBSITE_RECORD_ID> - environmentUrl: <environmentUrl> - statusPath: <temp-import-status-dir>/status.json - source: create-site template path
The activate-site skill owns subdomain selection, the waiting-for-input toast, final activation confirmation, provisioning-status polling, and recovery. Its foreground activation script is the only poll required by this flow.
If activation fails, tell the user the cloned site exists but is not live and can be activated later by rerunning /activate-site with this identity. Do not treat activation failure as a failed supporting-solution import or site upload.
16. When /activate-site returns a siteUrl, mark Activate template site as completed and Show live template site as in_progress.
17. Redirect the already-open status page to the live site:
json { "state": "succeeded", "message": "Template site is live. Opening it now...", "redirectUrl": "<siteUrl>", "shutdownServer": true }
Do not open a second browser page for the template path. The status page polls this file and redirects the same tab to redirectUrl when the URL is http or https. shutdownServer: true tells the local server to remove the temporary status directory after a short redirect grace period. If the user closed the status page, show the siteUrl for manual opening.
Always surface the activate-site DNS propagation caveat: the site may take a few minutes to load even after activation succeeds. Do not start a separate background command or Task to wait for the live URL to return HTTP 200. The status-page redirect and DNS note handle that delay without leaving work that can resume the conversation after the completion summary. If a reachability poll was started accidentally, stop it before marking **Show live template site** as `completed`.
18. Mark Show live template site as completed, then present the template-path summary:
- Template name and framework
- Cloned site name and Website Record ID
- Live site URL
- DNS propagation note: the site may take a few minutes to load everywhere
- Local project path (PROJECT_ROOT)
- "Your site is live. Want to keep customizing the local project?"
Use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
| Your template site is live. Do you want to customize the local project now? | Customize Template Site | Yes, customize now (Recommended), No, finish here |
completed, then stop.Mark Plan template customizations as in_progress, then inspect the
cloned project before asking what the user wants changed:
node "${PLUGIN_ROOT}/scripts/lib/localization-config.js" inspect --projectRoot "<PROJECT_ROOT>"
Establish the language context required by the Phase 4 plan in this order:
localization.detected=true and localization.valid=true, use
localization.defaultLocale as the authoritative locale. Resolve it
with resolve-locale, set SITE_LOCALE, SITE_DIRECTION, and
SITE_LANGUAGE from the returned locale, direction, and
languageName, and retain localeName when regional or script detail
needs to be displayed. Do not scan locale-specific document roots as
though they described one single-language site.localization.detected=true and localization.valid=false, show
the localization conflicts and stop before planning customization. The
localization configuration must be repaired; do not fall back to
static document attributes or report localized roots as a
single-language conflict.siteLanguage.detected=true with
siteLanguage.valid=true, resolve siteLanguage.locale with
resolve-locale. Use the returned locale, direction, and
languageName to establish SITE_LOCALE, SITE_DIRECTION, and
SITE_LANGUAGE.siteLanguage.reason is document-not-found,
html-root-not-found, document-ambiguous, or
document-configuration-invalid, show its conflicts and
expectedSources, then stop before planning customization. Locate or
repair the actual application root document before asking for a
content language.resolve-locale, and include repairing
siteLanguage.source in the approved customization plan.After SITE_LANGUAGE, SITE_LOCALE, and SITE_DIRECTION are all
established, ask what the user wants changed in PROJECT_ROOT. Use the
existing Phase 3/4/5/6/7 implementation, verification, and review flow
against the cloned project; do not run Phase 2 scaffold/copy-template.
In Phase 3, skip the Brand sub-prompt and set BRAND_SOURCE to
template - the template's existing theme tokens are the source of truth
unless the user asks for a redesign. On this path Phase 5 edits the cloned
project in place: keep the existing theme file, layout, and pages, change
only what the approved plan names, and skip every scaffold-status.json
step because no loader is running. Run the Phase 5.7 design critique on
the pages you changed.
After the customization plan is approved, mark Plan template customizations as completed, Implement pages and components as in_progress, and make the requested changes.
Run the existing validation/review flow. Do not automatically deploy unless the user explicitly asks to run /deploy-site.
For the from-scratch path only, tell the user: "I'll scaffold this site from scratch."
Ask the from-scratch-only questions that were deferred from Phase 1:
| Question | Header | Options |
|---|---|---|
| Which frontend framework? | Framework | React (Recommended), Vue, Angular, Astro |
| Where should the project be created? | Location | Current directory, New folder in current directory (Recommended), Any other directory |
Determine the site's single content language independently of whether the
purpose was supplied in $ARGUMENTS.
When the content language was not explicit, get a best-effort suggestion:
node "${PLUGIN_ROOT}/scripts/suggest-content-locale.js"
The script returns the selected Dataverse environment's base language as a
canonical BCP-47 locale when PAC and Azure CLI authentication are available.
It silently returns en-US with "source": "fallback" when the environment,
authentication, API response, or LCID mapping is unavailable. Do not retry,
display an error, or block site creation when the lookup falls back.
Use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
| Which language should the site content use?<br><br>Pages, navigation, buttons, forms, and accessibility text will use this language. Dataverse and Power Pages system messages will remain in English. | Site content language | <language> (<locale>) (Suggested), English (en-US) |
Replace <language> and <locale> with the script result. If that result is
en-US, show only English (United States) (en-US) (Suggested) rather than
adding a duplicate English option. Label the environment-derived choice
Suggested, not Recommended: an environment's base language is useful
context but may not match the intended language of a public site.
The question UI's free-text option lets the maker enter another language or
locale, such as Spanish (es-ES), Japanese (ja-JP), or Arabic (ar-SA).
Convert a language name to an appropriate BCP-47 tag, preserving an explicit
region or script when supplied. Keep the raw answer as data only: never place
it in a shell command. In agent memory, convert it to
SAFE_LOCALE_CANDIDATE, which must match
^[A-Za-z0-9]{1,8}(?:-[A-Za-z0-9]{1,8})*$ and be at most 255 characters.
Reject and re-prompt before running a command if no safe candidate can be
derived. Validate and canonicalize only that constrained candidate:
node "${PLUGIN_ROOT}/scripts/lib/localization-config.js" resolve-locale --locale "<SAFE_LOCALE_CANDIDATE>"
Do not quote, escape, sanitize, or otherwise interpolate the raw maker answer into this command. Reject invalid resolver output and re-prompt with the reason. Record the canonical resolver output rather than inventing a language label:
SITE_LANGUAGE — resolver languageName, such as Spanish; if display
names are unavailable, show the canonical locale and ask the maker for a
readable label rather than guessingSITE_LOCALE — canonical BCP-47 tag, such as es-ESSITE_DIRECTION — ltr or rtl from the resolverSITE_LOCALE_NAME — resolver localeName, such as European Spanish,
retained for plan text that needs regional or script specificity<cwd>.__SITE_NAME__ inside the cwd. Project root =
<cwd>/__SITE_NAME__/. First verify that __SITE_NAME__ is a valid
directory name on the current operating system. Non-Latin characters alone
are not a reason to reject it. If the site name contains invalid path
characters, is reserved by the operating system, is empty after
sanitization, or otherwise cannot be used safely, fall back to
__SITE_SLUG__. Before creating a fallback folder, tell the maker which
name will be used and why the original site name was not filesystem-safe.After resolving, confirm: "The site will be created at <resolved path>."
Store this as PROJECT_ROOT.
__SITE_NAME__ — preserve the maker's site/brand name as entered, e.g.
Contoso Portal; it does not need to match the content language.__SITE_SLUG__ — a lowercase ASCII kebab-case technical identifier used
by npm, Angular project configuration, build paths, and the default folder.
Derive it from the site name when possible, e.g. Contoso Portal →
contoso-portal. Transliterate when reliable. If a non-Latin name cannot
produce a safe, meaningful slug, derive one from the site's purpose or ask
for a technical project name rather than emitting an empty or invalid
identifier.__SITE_DESCRIPTION__ — one-line description written in SITE_LANGUAGE
based on the name and purpose.SITE_LANGUAGE (SITE_LOCALE, SITE_DIRECTION), and the explicit statement
that Dataverse and Power Pages system messages remain English.company-portal, blog-content, dashboard,
landing-page, or other. Never pass the maker's free-text purpose.choiceSource to arguments only when both framework and content
locale came from the initial request; otherwise use prompt.node "${PLUGIN_ROOT}/scripts/emit-skill-configured-telemetry.js" --skillName "create-site" --projectRoot "<PROJECT_ROOT>" --framework "<react|vue|angular|astro>" --siteContentLocale "<SITE_LOCALE>" --purpose "<PURPOSE_TAXONOMY>" --audience "<internal|external>" --choiceSource "<arguments|prompt>"
The telemetry helper is fail-closed and removes locale extensions/private-use subtags. Do not add the site name, description, pages, routes, components, feature labels, or any other free-text requirement to this command.
completed.Output: cloned template site identity (IMPORTED_SITE_NAME,
IMPORTED_WEBSITE_RECORD_ID) and a local project path ready for optional
customization; or CREATION_PATH = "from-scratch" with selected framework,
content language, derived naming values, and resolved project location.
Goal: Get a running site immediately so the user has something to preview while features and design are planned
The scaffold is a temporary branded loading screen — it shows a Power Pages animated "Building your site" experience with orbiting elements, status messages, and feature cards. Its only purpose is to get the dev server running quickly so the user has something to look at while you plan and build. During Phase 5 (Implementation), the entire scaffold — including theme.css, Layout, Home page, and all placeholder components — is completely replaced with the user's actual site: their chosen typography, color palette, pages, components, and navigation. Do NOT try to build on top of the loading screen; replace it entirely.
See
${PLUGIN_ROOT}/references/framework-conventions.mdfor the full framework → build tool → router → output path mapping.
Actions:
${PLUGIN_ROOT}is already resolved to the plugin's absolute path at runtime. Use it directly in Glob/Read paths — do NOT search for the plugin directory.
Read and copy all files from the matching asset template to the project directory:
| Framework | Asset Directory |
|---|---|
| React | ${PLUGIN_ROOT}/skills/create-site/assets/react/ |
| Vue | ${PLUGIN_ROOT}/skills/create-site/assets/vue/ |
| Angular | ${PLUGIN_ROOT}/skills/create-site/assets/angular/ |
| Astro | ${PLUGIN_ROOT}/skills/create-site/assets/astro/ |
Use Glob to discover all files in the asset directory, Read each file, then Write to the project directory preserving the relative path structure.
Also copy the shared loader icon that the scaffold references from its CSS (url('/power-pages-icon.png')):
Read the binary file ${PLUGIN_ROOT}/skills/create-site/assets/shared/power-pages-icon.png and Write it to <PROJECT_ROOT>/public/power-pages-icon.png. (All four supported frameworks serve public/ at the web root, so the same /power-pages-icon.png URL works for every framework.)
Seed the live status file so the loader shows a real message the moment it mounts. Write <PROJECT_ROOT>/public/scaffold-status.json:
{ "message": "Planning your site", "awaitingInput": false }
See Live Preview Status Protocol for the full contract — from here on, update this file before every AskUserQuestion and before each Phase 5 implementation step.
After copying, replace all __PLACEHOLDER__ tokens in every file. Use Edit with replace_all: true on each file.
__SITE_NAME__, __SITE_SLUG__, __SITE_DESCRIPTION__).__SITE_LOCALE__ with
SITE_LOCALE and __SITE_DIRECTION__ with SITE_DIRECTION.Note: The scaffold loading screen uses hardcoded Power Pages branding colors — there are no color placeholders (
__PRIMARY_COLOR__, etc.) to replace. The user's chosen color palette is applied fresh during Phase 5 when the scaffold is completely replaced.
Rename gitignore → .gitignore in the project root (stored without dot prefix to avoid git interference in the plugin repo).
Run npm install before initializing git so that package-lock.json is included in the initial commit:
cd "<PROJECT_ROOT>"
npm install
Astro requires Node 22.12 or newer. Astro 7 exits with
Node.js vX is not supported by Astro!on anything older, so when the chosen framework is Astro, runnode --versionfirst and ask the user to upgrade before continuing.
Initialize a git repo and make the first commit. This captures all template files AND package-lock.json in one clean baseline:
cd "<PROJECT_ROOT>"
git init
git add -A
git commit -m "Initial scaffold: __SITE_NAME__ (__FRAMEWORK__)"
From this point, commit after every significant milestone so any breaking change can be reverted.
This MUST happen now — before any planning or customization begins. The dev server gives the user a live preview while features and design are being planned:
cd "<PROJECT_ROOT>"
npm run dev
Run npm run dev in the background using Bash with run_in_background: true. Note the local URL (typically http://localhost:5173 for Vite or http://localhost:4200 for Angular or http://localhost:4321 for Astro).
Immediately after the dev server starts, verify the scaffold is working:
mcp__plugin_power-pages_playwright__browser_navigate to open the dev server URLmcp__plugin_power-pages_playwright__browser_snapshot to verify the page loaded correctly (accessibility snapshots only - screenshots are reserved for the Phase 5 design checks)http://localhost:5173 — open it in your browser to follow along as I build.")GATE: Do NOT proceed to Phase 3 until ALL of the following are true:
- Template files copied and placeholders replaced
- Git repo initialized with initial scaffold commit
npm installcompleted successfully- Dev server is running in the background (
npm run dev)- Playwright has opened the site and verified it loads via
browser_snapshot- The dev server URL has been shared with the user
If any of these are not done, complete them now before moving on.
Output: Running dev server with verified scaffold, URL shared with user
Goal: Determine what pages, components, and design elements the site needs — while the user previews the running scaffold
<!-- gate: create-site:3.requirements | category=plan | cancel-leaves=nothing -->🚦 Gate (plan · create-site:3.requirements): Four sub-prompts (features multi-select, aesthetic, mood, brand) plus a brand follow-up when the user has brand inputs - shape the Phase 4 plan and the Phase 5 implementation. Fires at step 2 of the action list below.
Trigger: Phase 3 entry; scaffold loader is up. Why we ask: Wrong feature set / aesthetic / brand gets baked into the rendered plan - the Phase 4.7 gate would still catch most errors, but it's wasteful to defer the catch. Cancel leaves: Nothing — scaffold loader files are throwaway artifacts replaced wholesale in Phase 5.
Actions:
Raise the "awaiting input" banner so the user notices the terminal prompt even while the browser loader is full-screen. Write <PROJECT_ROOT>/public/scaffold-status.json:
{ "message": "Planning your site", "awaitingInput": true, "inputPrompt": "Features, aesthetic, mood, and brand - please answer in the terminal." }
Immediately after the user answers (including any brand follow-up), Write the same file again with "awaitingInput": false so the banner disappears.
Use AskUserQuestion to collect feature and design requirements:
| Question | Header | Options |
|---|---|---|
| Which features? (multi-select) | Features | (generate 3-4 context-aware options based on the site name, purpose, and audience from Phase 1) |
| What aesthetic direction do you want? | Aesthetic | Minimal & Clean (Recommended), Bold & Vibrant, Dark & Moody, Warm & Organic |
| What's the overall mood? | Mood | Professional & Trustworthy (Recommended), Creative & Playful, Technical & Precise, Elegant & Premium |
| Is there an existing brand the site should match? | Brand | No, create a fresh identity (Recommended), Match my existing website, Use my brand colors or logo |
Brand follow-up. Record the answer as BRAND_SOURCE (fresh, website, or assets). For Match my existing website, use AskUserQuestion to ask for the URL (free text - use a single generic option so the user types it via "Other"); accept only an http:// or https:// URL and ask again otherwise. For Use my brand colors or logo, ask the same way for the hex colors and/or the absolute path of a logo file. On the template customization path, skip this sub-prompt (see Phase 1.5 step 20).
Feature options are NOT hardcoded. Infer relevant features from Phase 1 answers. For example:
- "HR Dashboard" + Internal → Employee Directory, Leave Requests, Announcements, Org Chart
- "Contoso Portal" + External → Contact Form, Service Catalog, Knowledge Base, FAQ
- "Partner Hub" + Internal → Document Library, Partner Directory, Deal Tracker, Notifications
Always generate options that make sense for the specific site — never reuse a fixed list.
If you include an Authentication feature option, describe it generically as "Login/signup for tracking application status" or similar. Do NOT mention a specific identity provider (e.g., "Entra ID", "SAML", "Google") in the feature description — the
/power-pages:setup-authskill will ask the user which provider they want.
AI Component Planning — Based on Phase 1 answers (site name, purpose, audience) and the feature selection above, propose which of the Power Pages generative-AI summarization APIs the site might use. The site itself does not depend on them — the page ships with reserved slots and runs without AI; /add-ai-webapi populates the slots later when the user is ready. Use AskUserQuestion with multi-select to let the user opt in:
| Question | Header | Options |
|---|---|---|
Which AI summarization features should the site have? (multi-select — each can be added later with /add-ai-webapi) | AI Summaries | (generate 2-4 context-aware options plus "None for now") |
Options are NOT hardcoded. Infer relevant AI summary features from Phase 1 and the features picked above. Examples:
- "HR Dashboard" + Leave Requests feature → "Data summarization for leave requests", "Search summary on the knowledge base"
- "Contoso Portal" + Knowledge Base → "Search summary on site-wide search", "Data summarization for articles"
- "Customer Self-Service" + Support Cases / Incidents → "Data summarization for support cases (Microsoft-shipped recipe)", "Data summarization for attached knowledge articles"
Treat the standard
incidenttable like any other Dataverse table — propose Data Summarization for it when the site handles support cases, but don't force the Microsoft-shipped recipe ($select=description,title+ the portal-comments expand) unless that genuinely fits the user's UX. A custom case-like table or a different facet of the standard incident is a regular Data Summarization pick. Always include None for now so the user can defer. Do NOT integrate the APIs in this skill — only record the user's picks so Phase 4's plan can mention them and Phase 8 can suggest/add-ai-webapias a recommended next step.Capture the selection in memory as
AI_SUMMARY_PICKS— a list of one or more of:search-summary,data-summarization.
Map picks to target pages. For each entry in AI_SUMMARY_PICKS, decide which page will carry the AI surface and store the mapping as AI_SUMMARY_PLACEMENTS. This is what Phase 4 shows the user and what Phase 5 reserves slots for. Use the feature selection from step 2 — and treat this mapping as an input to the page list Claude proposes in step 7: if a pick has no natural target page, add one to the plan so the summary has a home:
| Pick | Default target page | If no matching page is planned |
|---|---|---|
search-summary | A search / search-results page (e.g., SearchResults, Search) | Add a search page to the plan so the summary has a home |
data-summarization | The detail page of the table the user called out (e.g., ProductDetail for products, CaseDetail for support cases) — ask the user if ambiguous | Propose adding a detail page; if rejected, fall back to a list/dashboard page |
AI_SUMMARY_PLACEMENTS shape: one record per placement, e.g.
[{ pick: "data-summarization", targetPage: "CaseDetail", marker: "POWERPAGES:AI-SLOT kind=data-summarization" }].
The marker string is the comment tag Phase 5 emits into the page source as a reserved anchor that /add-ai-webapi later finds. Keep the shape uniform — one marker per placement, always the same tag, so the follow-up skill's explore step can grep for them deterministically.
Read the design references: ${PLUGIN_ROOT}/references/design-aesthetics.md, ${PLUGIN_ROOT}/references/page-blueprints.md, and ${PLUGIN_ROOT}/references/bidirectional-design.md.
Write the experience brief (design-aesthetics.md section 1) - audience and job, primary and secondary action, principal doubt, proof strategy, design thesis, hero concept, and signature moment. Resolve the brand source first (section 2): for website, extract the brand from the URL with the Playwright snippet there; for assets, build the palette around the supplied colors; for fresh, start from the matching cell of the aesthetic x mood map (section 11). Record the display and body fonts, color direction, geometry, and motion direction. Font choices must support the complete writing system used by SITE_LOCALE; verify glyph coverage and shaping rather than selecting a Latin-only font solely because it matches the aesthetic, and use a compatible script-aware fallback stack.
Analyze requirements and determine needed components. Plan each page's content as narrative beats from page-blueprints.md, in order, one line per section with its purpose. If AI_SUMMARY_PLACEMENTS from step 4 implies a page that wasn't already in the plan (e.g., a CaseDetail page for a data-summarization pick on the support-case table), add it to the page list now. Present the component plan to the user as a table:
| Component Type | Count | Details |
|---------------------|-------|---------|
| Pages | 4 | Home, About, Services, Contact |
| Shared Components | 3 | Navbar, Footer, ContactForm |
| Design Elements | 5 | Schibsted Grotesk + Public Sans, 15 color tokens, product-moment hero, status-timeline signature moment, paper-grain backgrounds |
| Routes | 4 | /, /about, /services, /contact |
Build BIDIRECTIONAL_REVIEW_DATA for every planned visible or interactive
component, including page-local controls that are not shared source
components. Classify each entry as direction-neutral, direction-aware,
direction-fixed, or unknown-third-party using the shared bidirectional
standard. Record the localized reason, applicable states, applicable
desktop/narrow viewports, and planned checks. Treat anything involving
inline placement, text direction, horizontal movement, sequence,
directional meaning, mixed-script content, or rendering outside the normal
component subtree as a potential bidirectional surface.
This is the expected review scope, not a project manifest. Phase 5 must reconcile it against the actual source and rendered application, adding components or states discovered during implementation. Do not write a separate component-inventory JSON file.
Choose the final color tokens - every color role in design-aesthetics.md section 3 - from the brief and brand source. Check each text and background pair against WCAG AA now, before the values reach the plan. These are written fresh into a new theme.css during Implementation (Phase 5) when the scaffold loading screen is completely replaced.
Output: Confirmed list of pages (with narrative beats), components, design elements, and routes to create, plus the experience brief and color tokens
Goal: Render the implementation plan as an HTML document, open it in the user's default browser, and get approval before starting implementation.
Why HTML instead of a chat message: A structured HTML plan (like the ones produced by
/integrate-backend,/add-server-logic, and/add-cloud-flow) lets the user skim sections, compare swatches, and preview typography — all impossible in a terminal. The scaffold loader in their browser may also be full-screen, so surfacing the plan in a new tab puts it where they can actually read it.
Read ${PLUGIN_ROOT}/references/design-aesthetics.md and ${PLUGIN_ROOT}/references/page-blueprints.md if they are not already in context. Every field you populate below must trace back to the experience brief from Phase 3.
AI Readiness in the plan. If
AI_SUMMARY_PLACEMENTSfrom Phase 3 is non-empty, reflect each placement in the matchingPAGES_DATAentry'sdescriptionorcontent— e.g., "Reserved slot for an AI summary card; populated later by/add-ai-webapi. The page ships without AI." This keeps the user's expectation honest: the site does not depend on generative-AI features being enabled on the tenant, and there is no "Run /add-ai-webapi" placeholder visible to end-users. IfAI_SUMMARY_PLACEMENTSis empty, omit any AI references from the plan.
Assemble a single JSON object with the following keys. The plan template rejects any data that's missing a required key, so include all of them.
| Key | Type | Content |
|---|---|---|
SITE_NAME | string | Site/brand name from Phase 1, preserved as entered |
PLAN_TITLE | string | Localized equivalent of "Implementation Plan" in SITE_LOCALE |
FRAMEWORK | string | React / Vue / Angular / Astro |
SITE_LANGUAGE | string | Readable content-language name from Phase 1, written in that language when practical |
SITE_LOCALE | string | Canonical BCP-47 content locale |
SITE_DIRECTION | string | ltr or rtl |
AESTHETIC | string | Chosen aesthetic (e.g., Minimal & Clean) |
MOOD | string | Chosen mood (e.g., Professional & Trustworthy) |
SUMMARY | string | One localized paragraph describing what the site is and who it serves |
PLAN_LABELS | object | Complete localized template-label dictionary defined below |
DESIGN_DIRECTION_DATA | object | The experience brief: { thesis, brandSource, audience, primaryAction, secondaryAction, principalDoubt, proofStrategy, heroConcept, signatureMoment } - all strings. brandSource says where tokens came from (e.g., "Fresh identity", "Matched to https://contoso.com") |
TYPOGRAPHY_DATA | object | { primary: { name, sample, reason }, secondary: { name, sample, reason } } - primary is the body face, secondary the display face; name must be a family from the verified faces table in design-aesthetics.md (or the brand's own Google Font) |
PALETTE_DATA | array | [{ var, hex, description }] - one entry per color token from design-aesthetics.md section 3 |
MOTION_DATA | array | [{ label, description }] - first-screen entrance, signature moment, scroll reveals, interaction states, route transitions |
BACKGROUNDS_DATA | array | [{ label, description }] - hero atmosphere, section bands, textures, patterns |
PAGES_DATA | array | [{ name, route, description, content: [...], components: [...] }] - content is the page's narrative beats in order, one line per section with its purpose; components is shared component names used |
COMPONENTS_DATA | array | [{ name, purpose, usedBy: [...] }] — shared components with the page names that consume them |
ROUTES_DATA | array | [{ path, page }] — every route the router will register |
BIDIRECTIONAL_REVIEW_DATA | array | Every planned visible or interactive component as { component, classification, reason, states: [...], viewports: ["desktop" or "narrow"], checks: [...] }; all maker-facing strings are localized |
REVIEW_DATA | object | { agentChecks: [...], makerReview: [...] } using the responsibility contract below |
DEPLOYMENT_DATA | array | [{ title, description, recommended?: boolean }] — mark exactly one as recommended: true |
PLAN_LABELS must contain every key below. The English values describe each
label's meaning; translate every value into SITE_LOCALE. Preserve {siteName}
and {count} exactly because the renderer substitutes those tokens at runtime.
Do not omit keys or fall back to English for individual labels.
{
"navigation": {
"group": "Plan",
"overview": "Overview",
"design": "Design",
"pages": "Pages & Components",
"deployment": "Deployment & Review"
},
"overview": {
"title": "Overview",
"description": "Implementation plan for {siteName}",
"stats": {
"pages": "Pages",
"components": "Shared Components",
"routes": "Routes"
},
"nextSteps": {
"title": "What happens next",
"design": {
"title": "Apply design tokens",
"description": "Fonts, palette, motion, and backgrounds written into your theme"
},
"components": {
"title": "Build shared components",
"descriptionOne": "{count} reusable component used across pages",
"descriptionOther": "{count} reusable components used across pages"
},
"pages": {
"title": "Create pages",
"descriptionOne": "{count} page with routing and navigation",
"descriptionOther": "{count} pages with routing and navigation"
},
"critique": {
"title": "Critique the design",
"description": "Desktop and mobile screenshots scored and refined against the design rubric"
},
"verify": {
"title": "Verify & accessibility",
"description": "axe-core audit, Playwright checks, and user review"
}
}
},
"design": {
"title": "Design",
"description": "Design direction, typography, palette, motion, and background treatments",
"direction": {
"title": "Design direction",
"heroConcept": "Hero concept",
"signatureMoment": "Signature moment",
"primaryAction": "Primary action",
"secondaryAction": "Secondary action",
"principalDoubt": "Principal doubt",
"proofStrategy": "Proof strategy",
"brandSource": "Brand source",
"audience": "Audience and job"
},
"typography": "Typography",
"palette": "Color palette",
"motion": "Motion & animation",
"backgrounds": "Background treatment",
"primaryRole": "Primary — body & UI",
"secondaryRole": "Secondary — headings"
},
"pages": {
"title": "Pages & Components",
"description": "Pages to build, their content outline, and the shared components they rely on",
"pages": "Pages",
"components": "Shared components",
"routing": "Routing",
"path": "Path",
"page": "Page",
"content": "Content",
"componentsUsed": "Components used",
"usedBy": "Used by",
"noComponents": "No shared components planned yet."
},
"bidirectional": {
"title": "Bidirectional review scope",
"description": "Planned component, state, and viewport coverage; reconciled with the actual implementation before handoff",
"classification": "Classification",
"reason": "Reason",
"states": "Applicable states",
"viewports": "Applicable viewports",
"checks": "Planned checks",
"classifications": {
"directionNeutral": "Direction-neutral",
"directionAware": "Direction-aware",
"directionFixed": "Direction-fixed",
"unknownThirdParty": "Unknown or third-party"
},
"viewport": {
"desktop": "Desktop",
"narrow": "Narrow or mobile"
}
},
"deployment": {
"title": "Deployment & Review",
"description": "Verification checklist and deployment options",
"verify": "Before handoff — verify",
"agentChecks": "Agent verification",
"agentChecksDescription": "The agent will run and report these checks before handoff.",
"makerReview": "Maker review",
"makerReviewDescription": "Review these judgment-based items in the live preview.",
"options": "Deployment options",
"recommended": "Recommended"
},
"common": {
"noneSpecified": "None specified."
},
"footer": {
"aiWarning": "AI-generated content may be incorrect"
}
}
Write all maker-facing plan data in the selected content language. This
includes PLAN_TITLE, SUMMARY, PLAN_LABELS, descriptive AESTHETIC and
MOOD names, typography reasons/samples, palette descriptions, motion and
background labels/descriptions, page display names/descriptions/content
outlines, component display names/purposes, route page labels, review items,
and deployment titles/descriptions.
Keep technical values unchanged: framework and package names, file paths,
routes, commands, locale tags, CSS variables, hex colors, identifiers, API
names, and brand/product names. When a page or component name is also the
planned code identifier, keep it unchanged; localize only human-readable
display names. Use complete translated phrases rather than joining translated
fragments in English word order.
Build REVIEW_DATA as two non-empty localized string arrays:
{
"agentChecks": [
"The root document uses the canonical site locale and resolved writing direction",
"The bidirectional-readiness audit reports no errors; layout uses logical CSS and every physical exception is documented and verified",
"Every visible or interactive component is classified, and the planned review scope is reconciled with the actual source and rendered application",
"Every applicable state and viewport of direction-aware, direction-fixed, and unknown or third-party components has verification evidence",
"Localized form labels, placeholders, hints, helper text, validation messages, prefixes, suffixes, and overlays follow the active UI direction; only classified machine-oriented values remain LTR",
"Representative routes work in the selected direction and a pseudo-opposite direction at desktop and narrow widths without console errors, broken reading order, or broken focus order",
"Unknown-direction content uses bdi or dir=auto, while URLs, email addresses, code, paths, GUIDs, and identifiers are explicitly isolated",
"Dates, numbers, currency, percentages, and relative time use locale-aware Intl formatting",
"Typography covers the selected script and expanded or opposite-direction text does not clip or overlap",
"Directional components, interactions, icons, and assets behave according to their reviewed mirror, preserve, or replace classification",
"Every route has zero critical or serious axe-core accessibility violations"
],
"makerReview": [
"Language, terminology, tone, regional wording, and regulated content are appropriate",
"Visual hierarchy and reading flow feel natural in the selected direction and the opposite-direction preview",
"Typography remains readable and preserves the intended brand character",
"Icon and image direction choices are culturally and semantically appropriate",
"Text wrapping, expansion, and narrow-screen layouts remain visually acceptable"
]
}
Translate these as complete criteria rather than copying the English wording. The two groups communicate responsibility:
agentChecks are objective commitments the agent must execute and report
during Phases 5–7. They are not instructions for the maker.makerReview contains linguistic, cultural, brand, and visual judgments that
automation cannot approve. The maker reviews them in the live preview during
Phase 7.Do not move subjective maker judgments into agentChecks, and do not ask the
maker to repeat deterministic source, browser, or accessibility checks.
Pick an output path under <PROJECT_ROOT>/docs/. Default is create-site-plan.html; if that file already exists, pick a descriptive variant like create-site-plan-v2.html (the render script refuses to overwrite existing files).
node "${PLUGIN_ROOT}/scripts/render-createsite-plan.js" --output "<PROJECT_ROOT>/docs/create-site-plan.html" --data - <<'PLAN'
<plan JSON>
PLAN
Send the plan JSON on stdin as shown, so no temp file is written and nothing in it - such as the brand URL the user typed - is parsed by the shell; the quoted 'PLAN' delimiter turns off expansion.
The script prints {"status":"ok","output":"<path>"} on success. Capture and use that actual output path for the next step.
Open <OUTPUT_PATH> in the default browser using the platform-appropriate file opener for the current environment. For example, use open on macOS, xdg-open on Linux, or the equivalent default-browser opener available on Windows.
Keep the terminal message short — the full plan lives in the HTML file now. Include:
Do NOT dump the full plan contents into the terminal — that defeats the purpose of the HTML view.
The user may still be looking at the full-screen scaffold loader when you ask for approval. Write <PROJECT_ROOT>/public/scaffold-status.json:
{ "message": "Ready to build", "awaitingInput": true, "inputPrompt": "Plan approval needed — review the plan in your browser and answer in the terminal." }
Immediately after the user answers, Write the same file again with "awaitingInput": false.
🚦 Gate (plan · create-site:4.7.plan-approval): Final sign-off on the rendered HTML plan before Phase 5 starts replacing the scaffold with real pages, components, and design tokens.
Trigger: Phase 4.3 rendered
docs/create-site-plan.html; Phase 4.4 opened it in the browser. Why we ask: Phase 5 rewrites the entire scaffold (theme.css, Layout, Home page, components, routes) — undoing that touches every commit in the implementation phase. Cancel leaves: Nothing destructive — the scaffold itself can be deleted with the project directory; no Dataverse / deploy fired.
Use AskUserQuestion:
| Question | Header | Options |
|---|---|---|
| Does this plan look good? | Plan | Approve and start building (Recommended), I'd like to make changes |
Output: Approved implementation plan, with an HTML copy committed alongside the project for the user to reference during and after implementation.
Goal: Build all pages, components, and design elements with the design thesis applied from the start, then prove the result with a visual design critique
Prerequisite: The dev server MUST already be running and verified via Playwright (completed in Phase 2). If it is not, go back and complete Phase 2.
Design references: Build from
${PLUGIN_ROOT}/references/design-aesthetics.md(the design system) and${PLUGIN_ROOT}/references/page-blueprints.md(page narratives, hero patterns, copy). Read them now if they are not already in context. All pages and components carry the chosen typography, color tokens, imagery, motion, and backgrounds from the start - do NOT build with neutral styling first and redesign later.
Actions:
Before writing any code, use TaskCreate to create a todo for every piece of work. This gives the user full visibility into what will be built:
/contact)", "Create Dashboard page (/dashboard)"Each todo should have a clear subject, activeForm, and description that includes the file path and what the page/component does. Then work through the todos in order, marking each in_progress → completed.
The scaffold is a temporary loading screen — it must be completely replaced during this phase. Do NOT build on top of it or try to modify the loading animation into a real page. Start fresh with the user's chosen design.
Narrate progress in the loader: Before each of the steps below, update
<PROJECT_ROOT>/public/scaffold-status.jsonso the user — who may still be watching the Home page loader — sees what's actually happening instead of the hardcoded placeholder cycle. Use a short present-participlemessage(e.g.,"Creating Navbar component","Creating Contact page"). Include any useful grouping context inline in the message itself. The loader picks up changes within ~1.5 seconds. Updates become no-ops once step 4 replaces the Home page.
Design foundations - Completely rewrite theme.css (or styles.css for Angular) from scratch with the full token set from design-aesthetics.md section 3, base element styles, the interaction-state styles from section 8, a prefers-reduced-motion block, background treatments, and logical CSS properties. The selected font stack must cover SITE_LOCALE's resolved script. Do not use physical left/right layout as the default, even for an initially LTR-only site. Add the chosen Google Fonts to the entry HTML's font <link> (index.html, or Layout.astro for Astro) alongside the scaffold's DM Sans + Outfit, which the loader still uses until step 4. The scaffold's loading-screen CSS is discarded entirely. Commit after this step. Before starting, set the loader status to { "message": "Applying design tokens" }.
Layout - Rewrite the Layout component (and Header/Footer for Astro) with proper navigation, header, and footer that reflect the chosen design, with a link for every route in the approved plan so the header is final before the first-impression review. The scaffold's passthrough Layout is replaced with a real layout structure. Set status to { "message": "Rewriting Layout" }.
Shared components - Build reusable components (Navbar, Footer, ContactForm, etc.) that pages will use, each with every state in the design-aesthetics.md state table that applies to it. For each component, set status to { "message": "Creating <Component> component" }.
Pages - Create route components for each requested page, replacing the scaffold Home page and About placeholder entirely. Build Home first, and its hero first, following the hero concept from the brief. As soon as Home is built, run a first-impression review of / (see "Running a review" in 5.7) and fix the hero until it passes, for at most three rounds - every later page inherits that foundation. Build each page's sections in the order of its planned narrative beats. Each page component must update document.title on mount to reflect the current page (e.g., "Contact — Contoso Portal"). Use the framework's idiomatic lifecycle hook: useEffect (React), onMounted (Vue), ngOnInit (Angular), or a <title> tag in the frontmatter (Astro). Format: "<Page Name> — <Site Name>", with the home page using just "<Site Name>". For each page, set status to { "message": "Creating <Page> page" } before writing the file. The loader disappears when the Home page itself is replaced - no further status updates are needed after that.
Router — Register all new routes (the scaffold only has / and /about — add all requested routes)
Navigation - Confirm every Layout/Header link resolves to a registered route, and mark the current item with aria-current="page"
Entry HTML - Remove the scaffold's DM Sans + Outfit from the font <link> in index.html (or Layout.astro for Astro) so only the chosen families load, and set <meta name="theme-color"> to the --color-bg value
Reserve AI summary slots — only if AI_SUMMARY_PLACEMENTS from Phase 3 is non-empty. For each placement, insert a single comment marker in the target page source at the intended insertion point. No visible placeholder UI, no stub components, no extra routes — just a grep-able anchor that /add-ai-webapi will later find and replace. Syntax depends on the framework:
| Framework | Marker syntax |
|---|---|
| React (JSX) | {/* POWERPAGES:AI-SLOT kind=<pick> */} inside the component's return JSX |
| Vue (SFC template) | <!-- POWERPAGES:AI-SLOT kind=<pick> --> inside <template> |
| Angular (HTML template) | <!-- POWERPAGES:AI-SLOT kind=<pick> --> inside the component template |
| Astro | <!-- POWERPAGES:AI-SLOT kind=<pick> --> inside the component's HTML |
Where <pick> is one of search-summary, data-summarization — verbatim from the placement record.
Placement within the page:
data-summarization (record detail): directly after the page heading, above the detail content — this is where a Copilot-style summary card naturally reads in the reading order.data-summarization (list page): directly above the list / table, below the page heading and any filter bar.search-summary: directly above the search-results list, below the search input — the summary paragraph reads before the keyword hits.One marker per placement, exactly as defined in the marker field of the AI_SUMMARY_PLACEMENTS record. Do NOT add stub components (<CopilotSummaryCard />, etc.), CSS classes, or empty <aside> elements — the slot is just a comment. The site must ship as if AI is not a consideration; the follow-up skill does the real work.
Content-language requirements:
SITE_LANGUAGE; do not author an English site and
mechanically translate it afterward./services), while navigation labels and page titles use
SITE_LANGUAGE. Do not translate paths during single-language site creation;
stable routes avoid breaking bookmarks, analytics, integrations, tests, and
a later /add-localization setup. Use localized paths only when the maker
explicitly requests them for a permanently single-language SEO strategy.row-reverse to simulate
RTL. Initial RTL sites must also be visually verified in RTL.dir: the
active UI direction while empty, auto while populated, and the active UI
direction again after clearing. Preserve or add dirname where submitted
direction metadata is appropriate. Keep only
classified machine-oriented values such as email addresses, telephone
numbers, URLs, code, paths, GUIDs, and identifiers LTR; add the adjacent
/* bidi-fixed: <specific reason>; verify=ltr,rtl */ or equivalent HTML
directive required by the shared standard. Their surrounding field UI
remains direction-aware.<bdi> or dir="auto". Keep URLs, email,
code, file paths, GUIDs, and other machine values explicitly isolated,
normally LTR. Free-form native controls must not use permanent dir="auto";
bind their native direction to value emptiness and the active UI direction.Intl
APIs rather than concatenating locale-sensitive punctuation or symbols./* bidi-physical: <specific reason>; verify=ltr,rtl */ directive and
successful browser verification in both directions.Important: Build real, functional UI with the design thesis applied - not placeholder "coming soon" pages, and not generic unstyled markup. Every page and component reflects the thesis from the moment it's created. The scaffold loading screen should be completely gone after this phase - no trace of the Power Pages branded animation should remain.
Follow the visuals order in design-aesthetics.md section 6: the site's own UI composed as a product moment first, bespoke inline SVG second, photography third. Do NOT use placeholder services (e.g., placeholder.com, placehold.co), broken <img> tags, or empty image slots.
Finding photography:
WebSearch to search Unsplash for the specific subject the section needs (e.g., site:unsplash.com city clerk helping resident at counter) - specific to the audience and the job, never a generic "business meeting".https://images.unsplash.com/photo-{id}?w={width}&h={height}&fit=crop (w=800 for cards, w=1600 for heroes and full-bleed bands).<img> descriptive alt text (or alt="" when decorative), explicit width and height, and loading="lazy" below the fold.Commit after every individual page and component so breaking changes can be reverted. Each page and each component gets its own commit — do NOT batch multiple pages or components into a single commit.
git add -A
git commit -m "<short description of what was added/changed>"
Every tool call re-sends the whole conversation, so save calls without merging commits: write a component's files (e.g., Navbar.tsx and Navbar.css) in one turn, and when several components are written in one turn, make their separate commits in one command (git add src/components/Navbar.* && git commit -m "Add Navbar component" && git add src/components/Footer.* && git commit -m "Add Footer component").
When to commit:
If something breaks, revert to the last good commit:
git revert HEAD
After each significant change (new page or component), browse the site via Playwright to confirm structure and content:
mcp__plugin_power-pages_playwright__browser_navigate to reload or navigate to the updated pagemcp__plugin_power-pages_playwright__browser_snapshot to verify the page structure and content are correctVisual judgement - screenshots at desktop and mobile widths - happens only in the first-impression review (step 4 of 5.2) and the critique pass (5.7). Screenshots stay in the conversation and are re-sent with every later call, so per-page checks during the build use browser_snapshot, not the capture script. The user is previewing in their own browser via the dev server URL shared in Phase 2.7.
Run the deterministic readiness audit after all pages and components exist:
node "${PLUGIN_ROOT}/scripts/audit-bidirectional-readiness.js" --projectRoot "<PROJECT_ROOT>"
The command prints structured JSON and exits nonzero when deterministic errors
exist; parse the JSON even on that expected failure path. Fix every error
finding. Review every geometry, scrolling, visual-order, and content-expansion
finding in the live site. For an intentionally physical product requirement,
keep the declaration, add the validated adjacent bidi-physical directive,
and verify that component in both LTR and RTL. For intentionally fixed
machine-oriented text, add the adjacent bidi-fixed directive and verify the
surrounding UI in both directions. Use pseudo-opposite-direction content to
check wrapping, navigation, forms, mixed names/identifiers, icons, calendars,
and narrow/mobile layout even when no second real locale exists yet. For every
free-form native input or textarea, verify empty, RTL-valued, LTR-valued,
clear-after-RTL, and clear-after-LTR states under both UI directions. Each
clear state must enter the corresponding value before clearing it; empty and
cleared states follow the UI locale, while populated states follow the entered
value.
Reconcile BIDIRECTIONAL_REVIEW_DATA against the completed source and rendered
routes. Add every implemented visible or interactive component that was not
known during planning, and add every applicable state or viewport discovered
during implementation. Verify the selected direction and pseudo-opposite
direction for every site: LTR sites receive pseudo-RTL coverage, and RTL sites
receive pseudo-LTR coverage. Direction-neutral components need an inheritance
check; direction-aware components need all applicable states in both
directions; direction-fixed components need a semantic reason plus surrounding
UI checks; unknown/third-party components need rendered checks including
dialogs, menus, autocomplete panels, tooltips, teleports, portals, overlays,
Shadow DOM, or iframes they open.
Build the ephemeral rendered-verification specification described in
${PLUGIN_ROOT}/references/rendered-bidirectional-verification.md. Derive it
from the reconciled component scope and actual selectors; do not commit it as a
component manifest. Give unstable but important verification surfaces a
data-bidi-id attribute. Include every applicable state and viewport, compound
form parts, body-mounted overlays, explicit focus sequences, non-overlap pairs,
and direction-specific computed-style expectations. Use the real configured
LTR and RTL locales when both exist; otherwise add a browser-only
pseudo-opposite locale. Real locales must be activated through the application
and prove representative localized content; only pseudo locales may use the
browser-only set-document action. If runtime localization was added, include both
default -> locale -> default and locale -> default -> locale transition
sequences for every real non-default locale, with route, form-state,
application-state, and focus preservation checks. Pseudo locales do not
participate in application-switch transitions. Every real locale, including
the default, needs a reusable application activation action; use-current
cannot restore a locale during a round trip. For a newly added runtime locale,
use the localization verification transaction defined by add-localization:
temporarily expose only the recorded target through the normal selector, run
Playwright against a loopback dev server, keep it available on success, and
restore fail-closed availability on failure. Exclude pre-existing unavailable
locales from activation and declare them in unavailableLocaleChecks. Do not
complete creation or offer deployment while the verification transaction
exists. Bare preserve selectors are only for form controls; use explicit
text, attribute, or property preservation entries for tabs, panels, counters,
and other application state.
Retain evidence for every applicable REVIEW_DATA.agentChecks item: audit
result, routes/viewports exercised, direction used, mixed-content fixtures,
font/text-expansion observations, component/asset classifications, console
result, and any remediations. Do not mark an agent-owned criterion complete
based only on visual inspection by the maker.
Once the scaffold loader is gone, public/scaffold-status.json is just dead weight that would ship with the deployed site. Delete the file from <PROJECT_ROOT>/public/ and commit the removal alongside the final implementation.
Judge the site from screenshots against ${PLUGIN_ROOT}/references/design-critique.md (read it once, at the first review). Every tool call re-sends the whole conversation, so the review is built to take few calls: one script call captures every route at both widths and runs the automated checks, and one turn opens all the screenshots in parallel. Single-step browser calls (navigate, resize, screenshot, evaluate per page) cost many times more for the same review.
Running a review (used for the first-impression review in 5.2 step 4 and for each critique round):
Capture by sending the request on stdin, as design-critique.md's Capture section shows - the dev server URL and the routes never go on the command line. Nothing needs installing: the script uses the plugin's pinned Playwright.
node "${PLUGIN_ROOT}/scripts/capture-design-review.js" --input - <<'REQUEST'
{"url": "<DEV_SERVER_URL>", "routes": ["/", "/about"]}
REQUEST
Open every path in summary.images in one turn, and read the automated checks from the JSON.
Judge as design-critique.md describes and write the compact scorecard and fix list it defines.
Apply every fix from the scorecard, highest impact first, and commit. Capture again only at the start of the next round - one capture per round, after all of its fixes - rather than after each individual fix.
The critique pass: review every route, then fix and review again, capturing only the routes that changed plus /, until one of these holds:
{"cleanup": "<outputDir>"} to the same script the same way.🚦 Gate (progress · create-site:5.7.critique-blocked): A critical design gate still fails after three critique rounds.
Trigger: Phase 5.7 round three ends with at least one critical gate from
design-critique.mdfailing. Why we ask: A critical gate means a broken first screen, mobile layout, accessible flow, or honest-proof problem; moving on silently would carry it into the audit and the deployed site. Cancel leaves: Nothing - site files and commits stay on disk.
Use AskUserQuestion, naming each failing critical gate and its evidence in the question:
| Question | Header | Options |
|---|---|---|
| (failing gate and evidence) - how should I proceed? | Design gate | Keep fixing (Recommended), Continue and record it as a known issue, Stop here |
GATE: Do NOT proceed to Phase 6 until ALL customization is complete and the 5.7 critique pass is complete. All requested pages and features exist, the design tokens live in the theme file, the chosen Google Fonts pass the font check, no critical gate fails unless the user chose to continue past it, and the scorecard is recorded.
Output: All pages, components, and design elements implemented, critiqued, and verified
Query the centralized add-localization availability before offering the child workflow:
node "${PLUGIN_ROOT}/scripts/lib/localization-config.js" mode-availability --framework "<react|vue|angular|astro>"
If availableModes is empty, explain that localization is temporarily
unavailable for the selected framework using the returned reason, set
LOCALIZATION_REQUESTED=false, and do not ask the additional-languages
question. This currently applies to Astro. Do not hardcode a separate
framework availability list in create-site.
Ask whether more languages should be added now:
<!-- not-a-gate: this selects whether to enter the child workflow, whose Phase 3 gate approves every localization write -->Would you like to add more languages now? The site is currently single-language in
<SITE_LANGUAGE>. This adds SPA localization only; Dataverse and Power Pages system messages remain English.
Use AskUserQuestion with this exact wording and these exact options:
| Question | Header | Options |
|---|---|---|
Would you like to add more languages now?<br><br>The site is currently single-language in <SITE_LANGUAGE>. This adds SPA localization only; Dataverse and Power Pages system messages remain English. | Additional languages | Yes — add more languages now, No — keep the site in <SITE_LANGUAGE> only |
Record the answer as LOCALIZATION_REQUESTED=true|false. Ask at this point even
when $ARGUMENTS mentioned localization, so the maker's answer immediately
determines whether the child workflow starts.
When LOCALIZATION_REQUESTED=true, invoke:
/power-pages:add-localization <PROJECT_ROOT> [FROM_CREATE_SITE]
Wait for the skill to complete before accessibility verification. The child
skill owns locale/package questions, translation generation, selector wiring,
its implementation review, build, and localization checks. The
[FROM_CREATE_SITE] context prevents a duplicate deployment prompt; control
returns here so create-site can run accessibility verification, final review,
and its existing deployment prompt.
When LOCALIZATION_REQUESTED=false, skip the child workflow.
Run the create-site validator explicitly with the approved language context.
Use only the canonical SITE_LOCALE returned by resolve-locale and the
validated SITE_DIRECTION enum; never pass the maker's original free-text
answer. Run the command with its working directory set to PROJECT_ROOT:
node "${PLUGIN_ROOT}/skills/create-site/scripts/validate-site.js" \
--expectedLocale "<SITE_LOCALE>" \
--expectedDirection "<SITE_DIRECTION>"
Treat exit code 2 as blocking. Repair the reported document or
localization-default mismatch and rerun the command until it passes. The
automatic skill validator runs without these arguments and still checks that
the detected document or localization context is internally valid. The
explicit invocation verifies that it also matches the locale and direction
approved in this create-site session without temporary or persistent metadata.
GATE: Do NOT proceed to Phase 6 until the Phase 5.7 design critique is complete, any requested localization has completed, and the approved site language validation passes. The finished design must use script-capable typography and logical direction-neutral layout.
Output: All pages, components, design elements, and requested localization implemented and verified
Goal: Verify the site meets WCAG 2.2 AA standards using axe-core automated testing and fix any violations
Prerequisite: All pages and components must be fully implemented (Phase 5 complete). The dev server MUST be running.
Actions:
The dev server must be running. Nothing needs installing: the audit uses the plugin's pinned Playwright with the system-installed browser (Edge or Chrome), and verifies the axe-core script against a pinned hash before running it.
Run the audit script via Bash, sending the dev server URL and every site route as a JSON request on stdin, so neither is parsed by the shell:
node "${PLUGIN_ROOT}/scripts/axe-audit.js" --input - <<'REQUEST'
{"url": "<DEV_SERVER_URL>", "routes": ["/", "/about", "/services", "/contact"]}
REQUEST
Parse the returned JSON array of per-route results. Each result contains violations (with id, impact, description, helpUrl, and affected nodes), passes count, and incomplete count. A nonzero exit means at least one critical or serious violation was found, or a route could not be audited - its result has error instead of violations, so fix the cause (a broken route, or the dev server down) and audit it again.
Parse the JSON output and record all violations.
Read ${PLUGIN_ROOT}/references/rendered-bidirectional-verification.md. If
add-localization returned a successful report, first validate and reuse it:
node "${PLUGIN_ROOT}/scripts/audit-rendered-bidirectional-readiness.js" \
--projectRoot "<PROJECT_ROOT>" \
--reuse-report "<ADD_LOCALIZATION_REPORT_PATH>"
This succeeds only when the child report came from a complete standard or
extensive run that passed, and source, resources, dependencies, build
configuration, and locale availability still match it. A targeted repair
report is never reused. Readiness-only metadata does not invalidate it. If reuse
succeeds, do not build a specification or repeat the matrix. If reuse is
stale—such as after accessibility remediation changed relevant source—build
the component/state/viewport specification from the current implementation and
run it:
node "${PLUGIN_ROOT}/scripts/audit-rendered-bidirectional-readiness.js" \
--url "<DEV_SERVER_URL>" \
--projectRoot "<PROJECT_ROOT>" \
--spec "<TEMP_SPEC_PATH>" \
--evidence-dir "<PROJECT_ROOT>/docs/bidirectional-evidence/<RUN_ID>" \
--output "<PROJECT_ROOT>/docs/bidirectional-evidence/<RUN_ID>/report.json"
The command prints JSON and exits 1 when blocking rendered findings exist;
parse stdout on that expected path. Exit 2 is a runner/specification failure
and must be fixed before continuing. Delete the temporary specification after
the report is written.
Fix every rendered error and rerun the complete affected matrix. Review
findings require screenshot-backed disposition; they are not automatic passes.
The audit must cover computed lang/dir, component and compound-field
direction, clipping, page overflow, viewport escape, declared non-overlap,
focus order, portals/overlays, unknown/third-party surfaces, and runtime
round-trip preservation when applicable. A visible opaque external surface is
blocking unless it is replaced or intentionally unavailable for the affected
locale.
Use the rendered-verification reference's grouped execution model:
maxConcurrency: 3 by default, reload state isolation unless a deterministic
reset exists, resettable only with explicit reset actions, and isolated
for global or destructive state. Independent route/viewport/locale groups may
run concurrently; locale-transition sequences remain serial.
GATE: Do NOT proceed to Phase 7 while the create-site report contains an error affecting an available locale or the available site experience, or an untriaged review finding. Every review item must have evidence and a proposed resolution, judgment-based pass, or usable-limitation disposition for maker review. An add-localization blocker may continue only when its affected locale is verifiably unavailable at every activation boundary; keep that child report as pending evidence and audit the remaining available experience with a real locale plus pseudo-opposite direction. A screenshot documents what rendered; it does not override a deterministic failure.
For each review finding, record whether it was resolved, accepted as a
judgment-based pass, or proposed as a usable limitation. A usable limitation
must name the affected component/page, user impact, and report or screenshot
evidence. Never offer an error finding for maker override. If the
add-localization child returned pending-remediation, verify that every
affected locale remains excluded from selectors, detection, metadata, and
production output before continuing with the available site experience.
For each violation found, identify the source file and apply the fix:
| Violation | Fix |
|---|---|
Missing alt text on images | Add descriptive alt attributes to <img> tags |
| Insufficient color contrast | Adjust CSS color variables to meet 4.5:1 (normal text) or 3:1 (large text) ratios |
| Missing form labels | Add <label> elements or aria-label attributes |
| Missing landmark regions | Wrap content in <main>, <nav>, <header>, <footer> |
| Skipped heading levels | Correct heading hierarchy (h1 → h2 → h3, no gaps) |
| Missing link text | Add descriptive text or aria-label to links |
Missing or incorrect lang/dir attributes | Set lang="<SITE_LOCALE>" and dir="<SITE_DIRECTION>" on the root <html> element |
| Inadequate focus indicators | Add visible outline styles to interactive elements |
After fixing each group of related violations, commit:
git add -A
git commit -m "Fix accessibility: <violation description>"
After all fixes are applied, re-run the audit script (same command as 6.2) to confirm violations are resolved:
critical and serious violations)Present a summary table to the user:
| Page | Route | Violations Found | Violations Fixed | Status |
|------|-------|-----------------|-----------------|--------|
| Home | / | 3 | 3 | Pass |
| About | /about | 1 | 1 | Pass |
| Contact | /contact | 2 | 2 | Pass |
| **Total** | | **6** | **6** | **All passing** |
GATE: Do NOT proceed to Phase 7 until all pages pass axe-core with zero
criticalandseriousviolations. Minor and moderate violations should also be fixed where possible, but are not blocking.
Record the final axe-core result against the corresponding
REVIEW_DATA.agentChecks item. Accessibility remains agent-owned; the maker is
not asked to reproduce the automated audit.
Output: Accessibility-verified site with zero critical/serious axe-core violations
Goal: Ensure the site meets user expectations and all pages work correctly
<!-- gate: create-site:7.review | category=plan | cancel-leaves=nothing -->🚦 Gate (plan · create-site:7.review): Live-site review — last chance to request changes before the deploy prompt. Cancel branch lets the user keep iterating. Fires at step 5 of the action list below.
Trigger: Phase 7 has verified all pages render via Playwright. Why we ask: User loses the chance to spot UI issues before deploy; broken pages get pushed. Cancel leaves: Nothing — site files stay as-is on disk.
Actions:
Confirm every route still loads cleanly with one call - the capture request from 5.7 with every route and "checksOnly": true - and fix any route listed under summary.pageErrors, summary.overflow, or summary.captureErrors. The visual review already happened in Phase 5.7
Present a summary of what was built, with the design scorecard from Phase 5.7:
| Component Type | Count | Details |
|---------------------|-------|---------|
| Pages | 4 | Home (/), About (/about), Services (/services), Contact (/contact) |
| Shared Components | 3 | Navbar, Footer, ContactForm |
| Design Elements | 5 | Schibsted Grotesk + Public Sans, 15 color tokens, product-moment hero, status-timeline signature moment, paper-grain backgrounds |
| Git Commits | 9 | scaffold + 8 feature and critique commits |
Follow it with the design thesis in one sentence and the scorecard
(category, score, one-line evidence), including any category recorded below
3 and why. Include SITE_LANGUAGE, SITE_LOCALE, and SITE_DIRECTION.
Present the reconciled bidirectional review scope with each component's classification and the applicable states/viewports exercised. Report direction-aware, direction-fixed, and unknown/third-party evidence separately; do not treat the pre-implementation plan as proof that the implemented component was verified.
Link each scope entry to its rendered report cases and any captured screenshots. Show case totals for passed, review, and failed. Do not expose temporary specification internals as project configuration.
Then report the REVIEW_DATA.agentChecks criteria separately with the
evidence gathered in Phases 5–6. Every criterion must be reported as
Passed or Blocked, never as maker verification. Fix blocked
technical criteria before requesting approval.
Present REVIEW_DATA.makerReview as the maker's live-preview checklist.
Ask the maker to assess linguistic correctness, terminology, tone, regional
wording, regulated content, visual hierarchy and reading flow, brand
typography, directional imagery/icons, and text expansion. Automated checks
cannot approve these judgment-based criteria.
Finally, list the sample content still in the site - search src for
SAMPLE CONTENT markers and give each marker's file and what it stands in
for - so the user can supply real content in this review.
Share the dev server URL with the user and list all available routes
Classify bidirectional readiness as:
Ask the user to review using AskUserQuestion:
"The site is ready for review at
<dev server URL>. Please check it out in your browser. Would you like any changes?" For Ready, offer Accept changes and Request revisions. For proposed usable limitations, offer Fix before handoff, Accept with documented limitations, and Request revisions. Acceptance applies only to review-severity limitations; record the approval timestamp, impact, and evidence path in the final summary. If localization is configured, finalize the same disposition in.powerpages-localization.json, update every selector, detection, metadata, and static-output availability boundary, then rerun the localization validator, project build, and affected real-locale activation cases before proceeding.
If the user requests changes, apply them and repeat the applicable static, rendered, accessibility, and browser checks before returning to this gate. Re-run the axe-core audit (6.2) on every affected route because a new control, form field, or restructured section can add violations without any visual change. When a change affects appearance, also run another design review round on the affected routes.
After the user-approved source changes and all normal validation are complete, run:
node "${PLUGIN_ROOT}/scripts/validate-site-integrity.js" --projectRoot "<PROJECT_ROOT>"
Fix every blocking error before proceeding. Inspect and report review findings in the relevant directions and with expanded content. If review causes further source changes, rerun this gate. Do not enter the deployment phase until it exits successfully.
Output: User-approved site ready for deployment
Goal: Deploy the site and suggest enhancements
<!-- gate: create-site:8.deploy | category=plan | cancel-leaves=nothing -->This phase is MANDATORY. Do NOT end the session without asking about deployment.
🚦 Gate (plan · create-site:8.deploy): Deploy prompt — invokes
/deploy-siteon Yes. Skipping leaves the site files on disk for the user to deploy later. Fires at step 3 of the action list below.Trigger: Phase 8 entry; Phase 7 review approved. Why we ask: Auto-deploy picks whatever env PAC CLI happens to be pointing at — wrong-env first deploy is messy to undo. Cancel leaves: Nothing — site files stay on disk; no deploy fired.
Actions:
Record skill usage:
Reference:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
Follow the skill tracking instructions in the reference to record this skill's usage. Use --skillName "CreateSite". Note: .powerpages-site may not exist for first-time sites — the script exits silently.
Search src for SAMPLE CONTENT markers again. If any remain, list each with its file before asking, say that they are invented placeholders to replace before a public launch, include the count in the question, and make Skip for now the recommended option.
Use AskUserQuestion with options: Deploy now (Recommended), Skip for now - or Deploy now, Skip for now (Recommended) when sample content remains:
"Would you like to deploy your site to Power Pages now?" (when sample content remains: "Would you like to deploy your site to Power Pages now? It still contains <N> sample-content placeholders listed above.")
If the user chooses to deploy, invoke the /deploy-site skill.
Mark all todos complete
Present a final summary:
SAMPLE CONTENT marker with its file, or "None"Suggest optional enhancement skills:
/setup-datamodel — Create Dataverse tables for dynamic content/add-localization — Add or extend SPA languages (suggest only when
LOCALIZATION_REQUESTED=false and the centralized availability result
contains at least one available mode)/add-seo — Add meta tags, robots.txt, sitemap.xml, favicon/add-tests — Add unit tests (Vitest) and E2E tests (Playwright)/add-ai-webapi — Add generative-AI summaries (Search Summary and Data Summarization). Recommend first when AI_SUMMARY_PLACEMENTS from Phase 3 is non-empty — the pages already carry POWERPAGES:AI-SLOT comment markers at the intended insertion points, so the follow-up skill's explore step finds them deterministically and the user gets the AI surface they picked during discovery without any page redesign.Run the create_site_from_scratch telemetry command silently (fail-closed). Do not mention this telemetry command to the user and do not print its output:
node "${PLUGIN_ROOT}/scripts/emit-create-site-template-outcome.js" \
--eventName create_site_from_scratch \
--framework "<framework>" \
--audience "<audience>"
Output: Deployed (or deployment-ready) site with clear next steps
capture-design-review.js (one call for every route and width) rather than single-step Playwright MCP calls; the only browser_take_screenshot call is for Phase 3 brand extraction, with filename left unset so output goes to the launcher's temporary directory. Use browser_snapshot for everything else. Give the user the dev server URL for their own visual preview.Before starting Phase 1, create only the path-agnostic upfront tasks using TaskCreate:
| Task subject | activeForm | Description |
|---|---|---|
| Discover site requirements | Discovering requirements | Collect site name, purpose, audience, and derived naming values |
| Select template or choose from-scratch | Selecting creation path | Offer matching templates, or route the user into the from-scratch path |
After Phase 1.5 selects the from-scratch path, append the existing from-scratch phase tasks:
| Task subject | activeForm | Description |
|---|---|---|
| Scaffold and launch dev server | Scaffolding project | Copy template, replace placeholders with defaults, git init, npm install, start dev server, share URL |
| Plan site components | Planning components | Determine pages, components, design direction, and routes while user previews scaffold |
| Approve implementation plan | Getting plan approval | Present implementation plan covering design and pages, get user approval |
| Implement pages and components | Building site | Apply design tokens, create all pages, components, routing, and navigation in the approved content language, run the design critique pass, then ask whether to add more languages |
| Verify accessibility with axe-core | Verifying accessibility | Run axe-core on every page, fix all critical/serious violations, re-verify until passing |
| Review with user | Reviewing site | Navigate all pages, share URL, get user feedback, apply changes |
| Deploy and wrap up | Deploying site | Ask about deployment, present summary, suggest next steps |
After Phase 1.5 selects the template path, append the pre-install tasks immediately so the user can see each environment check:
| Task subject | activeForm | Description |
|---|---|---|
| Choose local template directory | Choosing project location | Ask where the local template project should be cloned and require a new or empty destination |
| Resolve target environment | Resolving environment | Resolve the active PAC/Azure target environment and token before any environment preflight |
| Confirm target environment | Confirming environment | Ask whether the resolved environment is the one the user wants for the template install |
| Validate CLI tenant alignment | Checking CLI tenants | Verify PAC CLI and Azure CLI are authenticated to the same tenant before installation |
| Validate JavaScript unblock requirement | Checking JavaScript setting | Check blockedattachments for .js and, with consent, remove only js before site upload |
| Validate Dataverse language requirements | Checking language availability | Call RetrieveAvailableLanguages and require every LCID listed in the selected template's requiredDataverseLanguages |
| Confirm template install | Confirming template install | Show the selected template and target environment, then ask for final install consent |
After the reinstall policy chooses a normal import, update, or import-anyway path, append:
When TEMPLATE_SOLUTIONS_TO_IMPORT contains exactly one entry, use the task subject Import template supporting solution and active form Importing supporting solution. Use the plural forms in the table for two or more entries.
| Task subject | activeForm | Description |
|---|---|---|
| Import template supporting solutions | Importing supporting solutions | Import each required unmanaged supporting solution in deterministic order and poll every async job to completion |
| Clone, build, and upload template site | Creating template site | Clone the packaged SPA source into the selected local directory, install dependencies, build and verify the configured compiled output, then upload the resulting code site |
| Apply template seed data | Applying seed data | In parallel with site creation, insert optional template seed records using the deterministic seed-data script; failures do not block activation |
| Show inactive template site | Showing template site | After site creation and seeding join, use the Website Record ID written by pac pages clone to .powerpages-site/website.yml and tell the user the uploaded site is not activated yet |
| Activate template site | Activating template site | Invoke activate-site with the resolved site name and Website Record ID |
| Show live template site | Showing live site | Open the activated site URL in the browser and invite the user to continue customizing |
If the user chooses to customize the live template, append:
| Task subject | activeForm | Description |
|---|---|---|
| Plan template customizations | Planning customizations | Ask what the user wants changed and plan edits against the existing cloned project |
When every supporting solution is already installed at the same or newer version, append the same list without Import template supporting solutions. The packaged SPA clone/upload, site discovery, seed, activation, and live-preview tasks still run.
Mark each task in_progress when starting it and completed when done via TaskUpdate. This gives the user visibility into progress and keeps the workflow deterministic while avoiding permanently skipped tasks on future non-from-scratch branches.
Every site must meet these standards before completion:
design-critique.md); a critical gate the user chose to continue past is recorded as a known issueSITE_LOCALESITE_LOCALE and SITE_DIRECTION"Create a partner portal for our consultants"
partner-portal in current directoryhttp://localhost:5173DESIGN_DIRECTION_DATAdocs/create-site-plan.html via render-createsite-plan.jsbrowser_evaluatehttp://localhost:5173, requested minor color adjustment/deploy-siteBegin with Phase 1: Discovery
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Activates and provisions a Power Pages website in a Power Platform environment via the Power Platform REST API. Use when the user wants to activate, provision, turn on, or enable a Power Pages website or portal.
日本語の概要は準備中です。原文の説明を表示しています。
Integrates Power Pages generative-AI summarization APIs (PREVIEW) into a Single Page Application (SPA) site — the Search Summary API and the Data Summarization API — on any record-detail or list page. Generates per-target service code (CSRF-handled) and AI site settings; delegates Web API settings, table permissions, and web roles to `/integrate-webapi` and `/create-webroles`. Use whenever a user wants AI/Copilot output that condenses Dataverse content on a Power Pages site — an AI summary, AI-generated overview or "key insights" across a record or list, a search-results summary, a case/incident summary, or recommendation-chip refinement — even when phrased as "AI-generated paragraph", "insights", or "overview". Do NOT use for: generative pages in model-driven apps (use the model-apps `genpage` skill), Copilot Studio agents/chatbots, summarizing documents or PDFs, Power BI dashboards, plain keyword search with no AI summary, or plain Dataverse CRUD (use `/integrate-webapi`).
日本語の概要は準備中です。原文の説明を表示しています。
Adds Azure DevOps connector to a Power Apps code app. Use when querying work items, creating bugs, managing pipelines, or making ADO API calls.
日本語の概要は準備中です。原文の説明を表示しています。
Internal implementation skill invoked by /add-native for camera, image picker, barcode scanner, QR scanner, and camera/gallery Dataverse artifact workflows.
日本語の概要は準備中です。原文の説明を表示しています。
Integrates Power Automate cloud flows into a Power Pages site. Lists available flows, suggests relevant ones based on intent, identifies scenarios and web roles, creates metadata files, and generates client-side code to call flows. Handles both new flow registration and adding already-registered flows to additional pages without re-creating metadata. Use when the user wants to add, connect, register, or link a Power Automate cloud flow to their site.
日本語の概要は準備中です。原文の説明を表示しています。
Adds any Power Platform connector to a Power Apps code app. Generic fallback for connectors not covered by a specific skill.
日本語の概要は準備中です。原文の説明を表示しています。