From 8044301e6e33e80c27c5783964da60ebc60bb278 Mon Sep 17 00:00:00 2001 From: Nathan Turinski Date: Mon, 8 Jun 2026 10:48:54 -0700 Subject: [PATCH 001/190] Remove Copilot on Rails feature Removes the Copilot on Rails webviews, commands, agents, tree contributions, and build wiring. The feature is preserved on the feat/CoR branch for future development. --- .vscode/tasks.json | 19 +- esbuild.copilotOnRailsViews.mjs | 70 -- eslint.config.mjs | 10 - package.json | 43 +- package.nls.json | 8 +- resources/agents/azure-deploy.agent.md | 62 -- resources/agents/azure-local-debug.agent.md | 58 -- .../agents/azure-project-scaffold.agent.md | 81 -- .../copilotOnRails/openChatWithAgent.ts | 41 - src/commands/registerCommands.ts | 18 - src/extension.ts | 5 - .../WorkspaceResourceTreeDataProvider.ts | 6 - .../CreateProjectViewController.ts | 46 -- .../DeploymentPlanViewController.ts | 68 -- .../controllers/LocalPlanViewController.ts | 70 -- .../controllers/ScaffoldPlanViewController.ts | 70 -- .../extension/copilotOnRailsBundleLocation.ts | 20 - .../extension/createProjectWithCopilot.ts | 69 -- .../extension/openDeploymentPlanView.ts | 167 ----- .../extension/openLocalPlanView.ts | 116 --- .../extension/openScaffoldPlanView.ts | 122 --- .../views/CreateProjectView.tsx | 75 -- .../views/DeploymentPlanView.tsx | 701 ------------------ .../copilotOnRails/views/LocalPlanView.tsx | 516 ------------- .../copilotOnRails/views/ScaffoldPlanView.tsx | 670 ----------------- .../copilotOnRails/views/WebviewRegistry.ts | 16 - .../copilotOnRails/views/react-shim.js | 8 - src/webviews/copilotOnRails/views/scss.d.ts | 9 - .../views/styles/createProjectView.scss | 183 ----- .../views/styles/deploymentPlanView.scss | 481 ------------ .../views/styles/localPlanView.scss | 590 --------------- .../views/styles/scaffoldPlanView.scss | 571 -------------- .../copilotOnRails/views/tsconfig.json | 25 - .../views/utils/deploymentPlanTypes.ts | 29 - .../utils/parseDeploymentPlanMarkdown.ts | 176 ----- .../views/utils/parseLocalPlanMarkdown.ts | 201 ----- .../views/utils/parseScaffoldPlanMarkdown.ts | 330 --------- .../views/utils/viewConfigTypes.ts | 60 -- .../copilotOnRails/views/webviewEntry.tsx | 13 - tsconfig.json | 3 +- 40 files changed, 4 insertions(+), 5822 deletions(-) delete mode 100644 esbuild.copilotOnRailsViews.mjs delete mode 100644 resources/agents/azure-deploy.agent.md delete mode 100644 resources/agents/azure-local-debug.agent.md delete mode 100644 resources/agents/azure-project-scaffold.agent.md delete mode 100644 src/commands/copilotOnRails/openChatWithAgent.ts delete mode 100644 src/webviews/copilotOnRails/extension/controllers/CreateProjectViewController.ts delete mode 100644 src/webviews/copilotOnRails/extension/controllers/DeploymentPlanViewController.ts delete mode 100644 src/webviews/copilotOnRails/extension/controllers/LocalPlanViewController.ts delete mode 100644 src/webviews/copilotOnRails/extension/controllers/ScaffoldPlanViewController.ts delete mode 100644 src/webviews/copilotOnRails/extension/copilotOnRailsBundleLocation.ts delete mode 100644 src/webviews/copilotOnRails/extension/createProjectWithCopilot.ts delete mode 100644 src/webviews/copilotOnRails/extension/openDeploymentPlanView.ts delete mode 100644 src/webviews/copilotOnRails/extension/openLocalPlanView.ts delete mode 100644 src/webviews/copilotOnRails/extension/openScaffoldPlanView.ts delete mode 100644 src/webviews/copilotOnRails/views/CreateProjectView.tsx delete mode 100644 src/webviews/copilotOnRails/views/DeploymentPlanView.tsx delete mode 100644 src/webviews/copilotOnRails/views/LocalPlanView.tsx delete mode 100644 src/webviews/copilotOnRails/views/ScaffoldPlanView.tsx delete mode 100644 src/webviews/copilotOnRails/views/WebviewRegistry.ts delete mode 100644 src/webviews/copilotOnRails/views/react-shim.js delete mode 100644 src/webviews/copilotOnRails/views/scss.d.ts delete mode 100644 src/webviews/copilotOnRails/views/styles/createProjectView.scss delete mode 100644 src/webviews/copilotOnRails/views/styles/deploymentPlanView.scss delete mode 100644 src/webviews/copilotOnRails/views/styles/localPlanView.scss delete mode 100644 src/webviews/copilotOnRails/views/styles/scaffoldPlanView.scss delete mode 100644 src/webviews/copilotOnRails/views/tsconfig.json delete mode 100644 src/webviews/copilotOnRails/views/utils/deploymentPlanTypes.ts delete mode 100644 src/webviews/copilotOnRails/views/utils/parseDeploymentPlanMarkdown.ts delete mode 100644 src/webviews/copilotOnRails/views/utils/parseLocalPlanMarkdown.ts delete mode 100644 src/webviews/copilotOnRails/views/utils/parseScaffoldPlanMarkdown.ts delete mode 100644 src/webviews/copilotOnRails/views/utils/viewConfigTypes.ts delete mode 100644 src/webviews/copilotOnRails/views/webviewEntry.tsx diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f134236f8..9d0b2120d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,8 +6,7 @@ { "label": "Watch: ESBuild", "dependsOn": [ - "Watch: ESBuild (extension)", - "Watch: ESBuild (webviews)" + "Watch: ESBuild (extension)" ], "problemMatcher": [], "presentation": { @@ -33,22 +32,6 @@ }, "isBackground": true, }, - { - "label": "Watch: ESBuild (webviews)", - "type": "shell", - "command": "npm", - "args": [ - "run", - "build:webviews", - "--", - "--watch", - ], - "problemMatcher": "$esbuild-watch", - "presentation": { - "reveal": "silent", - }, - "isBackground": true, - }, { "label": "Watch: Check Types", "type": "shell", diff --git a/esbuild.copilotOnRailsViews.mjs b/esbuild.copilotOnRailsViews.mjs deleted file mode 100644 index 403d5fc67..000000000 --- a/esbuild.copilotOnRailsViews.mjs +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { isAutoDebug, isAutoWatch } from '@microsoft/vscode-azext-eng/esbuild'; -import esbuild from 'esbuild'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const outdir = path.resolve(__dirname, 'dist', 'copilotOnRails'); -const entryDir = path.resolve(__dirname, 'src', 'webviews', 'copilotOnRails', 'views'); - -/** @type {import('esbuild').BuildOptions} */ -const commonConfig = { - entryPoints: { - views: path.resolve(entryDir, 'webviewEntry.tsx'), - }, - bundle: true, - outdir, - format: 'esm', - platform: 'browser', - target: 'es2022', - sourcemap: isAutoWatch, - minify: !isAutoWatch, - metafile: isAutoDebug, - splitting: false, - - inject: [path.resolve(entryDir, 'react-shim.js')], - - loader: { - '.ts': 'ts', - '.tsx': 'tsx', - '.css': 'css', - '.scss': 'css', - '.ttf': 'dataurl', - '.woff': 'dataurl', - '.woff2': 'dataurl', - }, - - plugins: [ - { - name: 'sass', - setup(build) { - build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => { - const sass = await import('sass'); - const result = sass.compile(args.path); - return { - contents: result.css, - loader: 'css', - }; - }); - }, - }, - ], - logLevel: 'info', -}; - -const ctx = await esbuild.context(commonConfig); -await ctx.rebuild(); - -if (isAutoWatch) { - await ctx.watch(); - console.log('Watching Copilot on Rails webview bundle...'); -} else { - await ctx.dispose(); -} diff --git a/eslint.config.mjs b/eslint.config.mjs index d2ee49bf3..b694c728b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -12,7 +12,6 @@ export default defineConfig([ ignores: [ 'api/dist/**', 'api/out/**', - 'src/webviews/copilotOnRails/views/react-shim.js', ], }, { @@ -27,13 +26,4 @@ export default defineConfig([ }], }, }, - { - // The Copilot on Rails React views use display strings as object keys (e.g. "Static Web Apps") - // for lookup maps and the React-required `__html` property in `dangerouslySetInnerHTML`. - // These don't fit camelCase/PascalCase but are intentional. - files: ['src/webviews/copilotOnRails/views/**/*.{ts,tsx}'], - rules: { - '@typescript-eslint/naming-convention': 'off', - }, - }, ]); diff --git a/package.json b/package.json index 6ddf69484..c2d901139 100644 --- a/package.json +++ b/package.json @@ -98,17 +98,6 @@ ] } ], - "chatAgents": [ - { - "path": "resources/agents/azure-project-scaffold.agent.md" - }, - { - "path": "resources/agents/azure-local-debug.agent.md" - }, - { - "path": "resources/agents/azure-deploy.agent.md" - } - ], "languageModelTools": [ { "displayName": "Azure Resources: Get Azure Activity Log", @@ -375,26 +364,6 @@ { "command": "azureResourceGroups.askAgentAboutResource", "title": "%azureResourceGroups.askAzure%" - }, - { - "command": "azureResourceGroups.createProjectWithCopilot", - "title": "%azureResourceGroups.createProjectWithCopilot%", - "category": "Azure" - }, - { - "command": "azureResourceGroups.openPlanView", - "title": "%azureResourceGroups.openPlanView%", - "category": "Azure" - }, - { - "command": "azureResourceGroups.openLocalPlanView", - "title": "%azureResourceGroups.openLocalPlanView%", - "category": "Azure" - }, - { - "command": "azureResourceGroups.openDeployPlanView", - "title": "%azureResourceGroups.openDeployPlanView%", - "category": "Azure" } ], "viewsContainers": { @@ -431,11 +400,6 @@ "name": "Workspace", "visibility": "visible" }, - { - "id": "azureProject", - "name": "%azureProject.viewName%", - "visibility": "visible" - }, { "id": "azureTenantsView", "name": "Accounts & Tenants", @@ -472,10 +436,6 @@ "contents": "No local workspace resource providers exist.", "when": "azureWorkspace.state == 'noWorkspaceResourceProviders'" }, - { - "view": "azureProject", - "contents": "%azureProject.welcomeContent%" - }, { "view": "azureResourceGroups", "contents": "Please sign in to a specific tenant (directory) to continue. \n [Sign in to Tenant (Directory)...](command:azureResourceGroups.signInToTenant)\n[View Accounts & Tenants](command:azureTenantsView.focus)", @@ -937,9 +897,8 @@ ] }, "scripts": { - "build": "npm run build:esbuild && npm run build:webviews && npm run build:check", + "build": "npm run build:esbuild && npm run build:check", "build:esbuild": "node esbuild.mjs", - "build:webviews": "node esbuild.copilotOnRailsViews.mjs", "build:check": "tsc --noEmit", "package": "vsce package --githubBranch main", "lint": "eslint --max-warnings 0", diff --git a/package.nls.json b/package.nls.json index e6d7b382c..9d67654dd 100644 --- a/package.nls.json +++ b/package.nls.json @@ -63,11 +63,5 @@ "Do not translate '@azure'; it is the syntax for addressing the Azure agent.", "Do not translate any of the words in the command URL." ] - }, - "azureResourceGroups.createProjectWithCopilot": "Create Project with Copilot", - "azureResourceGroups.openPlanView": "Open Scaffold Plan View", - "azureResourceGroups.openLocalPlanView": "Open Local Plan View", - "azureResourceGroups.openDeployPlanView": "Open Deploy Plan View", - "azureProject.viewName": "Azure Project", - "azureProject.welcomeContent": "Use Copilot to plan and create a complete application that's configured for Azure services and setup for local development.\n[Create New Project With Copilot](command:azureResourceGroups.createProjectWithCopilot)" + } } diff --git a/resources/agents/azure-deploy.agent.md b/resources/agents/azure-deploy.agent.md deleted file mode 100644 index 69194ff52..000000000 --- a/resources/agents/azure-deploy.agent.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: azure-deploy -description: Prepare an Azure-centric project for deployment — generate Bicep/Terraform infrastructure, `azure.yaml`, Dockerfiles, and any other artifacts required by `azd up` / `terraform apply`. Run after the local development environment is set up. WHEN: "deploy to Azure", "prepare for deployment", "generate infra", "generate Bicep", "generate Terraform", "create azure.yaml", "ship to Azure", "host on Azure", "create and deploy". -tools: [vscode, run_vscode_command, tool_search, execute, read, agent, browser, edit, search, web, azure-mcp/search, todo] ---- - -# Azure Deploy Agent - -## Critical workflow rules (read first, do not skip) - -The phases below are **strictly ordered**. You **must not** start a later phase until the earlier one has completed: - -1. Write `.azure/deployment-plan.md` (the `azure-prepare` skill calls this the deployment plan). -2. **Step A** — open the deployment plan preview (see below). Mandatory. -3. **Step B** — wait for the user's explicit approval of the deployment plan. Mandatory. -4. Generate the deployment artifacts (infra, `azure.yaml`, Dockerfiles, etc.) as directed by the `azure-prepare` skill. - -### Step A — open the deployment plan preview (MANDATORY, do not skip) - -**Trigger:** the instant the `azure-prepare` skill finishes writing `.azure/deployment-plan.md` to disk. This must happen **before** the skill's approval gate (before you summarize the plan or ask for approval). - -**Action — call `run_vscode_command` immediately, before any other output:** - -```json -{ "commandId": "azureResourceGroups.openDeployPlanView", "name": "Open Deploy Plan View" } -``` - -`run_vscode_command` is a deferred tool. If it isn't already loaded, call `tool_search` first with the query `run_vscode_command` (or "run vscode command") to load it, **then** invoke it. Both `tool_search` and `run_vscode_command` are listed in this agent's `tools:` frontmatter — they are available in this session. Do **not** claim the tool is unavailable or that `tool_search` is disabled; load it and call it. There is no file-watcher fallback — if you skip this call, the user will not see the plan preview. - -This is not optional and not conditional. Do not summarize the plan, do not ask the user a question, do not begin generating infrastructure, and do not move on until this command has been called. If `run_vscode_command` returns an error, report it verbatim — but still attempt the call first. - -### Step B — require explicit user approval before generating artifacts - -After Step A, **stop and wait** for explicit user approval of the deployment plan. Do **not** begin generating Bicep/Terraform/`azure.yaml`/Dockerfiles until the user confirms. Treat anything other than a clear approval (e.g. questions, edits, "looks good but…") as not-yet-approved. - ---- - -You are the **Project Deployer** in a guided Azure-project workflow: - -**Plan → Scaffold → Verify → Local Dev → Deploy** - -## Your job - -Follow the authoritative guidance in the `azure-prepare` skill: - -📖 **Read and follow:** `.agents/skills/azure-prepare/SKILL.md` - -That skill is the canonical, mandatory source for this phase. Treat it as your operating manual — do not improvise or substitute steps. **Exception:** the "Critical workflow rules" above govern preview-opening and approval gating — always route through the matching `run_vscode_command` call, never bypass it. - -## Your deliverable - -A workspace ready to deploy to Azure: - -- `.azure/deployment-plan.md` (approved) -- Infrastructure as code (Bicep or Terraform under `infra/`) -- `azure.yaml` for the Azure Developer CLI (`azd`) -- Dockerfiles where required -- Any environment files / parameter files referenced by the plan - -## Prerequisites - -A scaffolded project with a working local development environment. If the workspace has not yet been scaffolded, stop and direct the user to run the `azure-project-scaffold` agent first. If the local development environment has not yet been set up, stop and direct the user to run the `azure-local-debug` agent first. diff --git a/resources/agents/azure-local-debug.agent.md b/resources/agents/azure-local-debug.agent.md deleted file mode 100644 index 595d877f1..000000000 --- a/resources/agents/azure-local-debug.agent.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: azure-local-debug -description: Set up the local development environment — Azure emulators, docker-compose, VS Code launch/tasks, and F5 debugging — for an Azure-centric project. -tools: [vscode, run_vscode_command, tool_search, execute, read, agent, browser, edit, search, web, azure-mcp/search, todo] ---- - -# Azure Local Development Agent - -## Critical workflow rules (read first, do not skip) - -### Step A — open the local-dev plan preview (MANDATORY, do not skip) - -The **moment** you finish writing `local-development-plan.md` — before you say anything else, before you ask the user for approval, before any handoff — you **must** call the `run_vscode_command` tool with: - -```json -{ "commandId": "azureResourceGroups.openLocalPlanView", "name": "Open Local Development Plan View" } -``` - -`run_vscode_command` is a deferred tool. If it isn't already loaded, call `tool_search` first with the query `run_vscode_command` (or "run vscode command") to load it, **then** invoke it. Both `tool_search` and `run_vscode_command` are listed in this agent's `tools:` frontmatter — they are available in this session. Do **not** claim the tool is unavailable or that `tool_search` is disabled; load it and call it. There is no file-watcher fallback — if you skip this call, the user will not see the plan preview. - -This is a hard requirement of this agent. The user cannot review the local-dev plan without it. If you skip this step, the workflow is broken. Do not ask the user whether to do it — just do it as the very next tool call after the file write completes. - -### Step B — hand off to deployment when local-dev setup is complete - -Once local development setup is finished and verified (emulators running, F5 debugging working, the user confirms they're ready to move on), **do not** print plain-text suggestions and **do not** start the deployment phase yourself. Call `run_vscode_command` with: - -```json -{ - "commandId": "azureResourceGroups.startDeployment", - "name": "Start Deployment", - "skipCheck": true, - "args": ["Prepare the project for deployment to Azure — generate `.azure/deployment-plan.md`, then the infrastructure (Bicep or Terraform), `azure.yaml`, and any Dockerfiles needed for `azd up`."] -} -``` - -This command exists — do not say it isn't registered. If `run_vscode_command` returns an error, report it to the user verbatim, but still attempt the call first. Do not skip the call. - ---- - -You are the **Local Development Setter-Upper** in a guided Azure-project workflow: - -**Plan → Scaffold → Verify → Local Dev → Deploy** - -## Your job - -Follow the authoritative guidance in the `azure-local-debug` skill: - -📖 **Read and follow:** `.agents/skills/azure-local-debug/SKILL.md` - -That skill is the canonical, mandatory source for this phase. Treat it as your operating manual — do not improvise or substitute steps. **Exception:** the "Critical workflow rules" above override anything in the skill regarding preview-opening and the deployment hand-off — always run `azureResourceGroups.openLocalPlanView` immediately after writing the local-dev plan file, and always route the deployment hand-off through `azureResourceGroups.startDeployment`. - -## Your deliverable - -A workspace configured for one-keystroke local debugging — `docker-compose.yml` for Azure emulators, `.vscode/launch.json` and `.vscode/tasks.json` wired up, and a `local-development-plan.md` documenting the setup. - -## Prerequisites - -A scaffolded project. If the workspace has not yet been scaffolded, stop and direct the user to run the `azure-project-scaffold` agent first. diff --git a/resources/agents/azure-project-scaffold.agent.md b/resources/agents/azure-project-scaffold.agent.md deleted file mode 100644 index 80a3e3e11..000000000 --- a/resources/agents/azure-project-scaffold.agent.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -name: azure-project-scaffold -description: Plan and scaffold a NEW Azure-centric project end-to-end — gather requirements, produce an approved `.azure/project-plan.md`, then scaffold the frontend preview, backend services, database, and API routes. -tools: [vscode, run_vscode_command, tool_search, execute, read, agent, browser, edit, search, web, azure-mcp/search, todo] ---- - -# Azure Project Scaffold Agent - -## Critical workflow rules (read first, do not skip) - -The phases below are **strictly ordered**. You **must not** start a later phase until the earlier one has completed: - -1. Write `.azure/project-plan.md`. -2. **Step A** — open the plan preview (see below). Mandatory. -3. **Step B** — wait for the user's explicit approval of the plan. Mandatory. -4. Scaffold the project. -5. **Step C** — hand off to the next phase based on the user's "Next Step" answer. - -### Step A — open the plan preview (MANDATORY, do not skip) - -**Trigger:** the instant the skill finishes Step P3 — i.e. as soon as `.azure/project-plan.md` has been written/saved to disk with `Status: Planning`. This must happen **before** the skill's Step P4 approval gate (before you summarize the plan or ask for approval). - -**Action — call `run_vscode_command` immediately, before any other output:** - -```json -{ "commandId": "azureResourceGroups.openPlanView", "name": "Open Plan View" } -``` - -`run_vscode_command` is a deferred tool. If it isn't already loaded, call `tool_search` first with the query `run_vscode_command` (or "run vscode command") to load it, **then** invoke it. Both `tool_search` and `run_vscode_command` are listed in this agent's `tools:` frontmatter — they are available in this session. Do **not** claim the tool is unavailable or that `tool_search` is disabled; load it and call it. There is no file-watcher fallback — if you skip this call, the user will not see the plan preview. - -This is not optional and not conditional. Do not summarize the plan, do not ask the user a question, do not begin scaffolding, and do not move on until this command has been called. The skill's Step P4 ("Present plan" / "Ask explicitly") only runs **after** this command. If `run_vscode_command` returns an error, report it verbatim — but still attempt the call first. - -### Step B — require explicit user approval before scaffolding - -After Step A, **stop and wait** for explicit user approval of the plan. Do **not** begin scaffolding until the user confirms. Treat anything other than a clear approval (e.g. questions, edits, "looks good but…") as not-yet-approved. - -### Step C — handle the "Next Step" answer at the end of scaffolding - -After the user answers the **"Next Step"** question that the skill asks at the end of scaffolding, **do not** print plain-text suggestions and **do not** start the next phase yourself. Call `run_vscode_command` with the matching command: - -**Answer "Verify project"** → - -```json -{ - "commandId": "azureResourceGroups.startProjectTest", - "name": "Start Project Test", - "skipCheck": true, - "args": ["Scaffolding is complete. Add test coverage and runtime validation to the scaffolded project."] -} -``` - -**Answer "Set up local dev"** → - -```json -{ - "commandId": "azureResourceGroups.startLocalDevelopment", - "name": "Start Local Development", - "skipCheck": true, - "args": ["The project has been scaffolded. Now set up the local development environment so the user can start building and testing."] -} -``` - -These commands exist — do not say they aren't registered. If `run_vscode_command` returns an error, report it to the user verbatim, but still attempt the call first. Do not skip the call. - ---- - -You are the **Project Planner & Scaffolder** in a guided Azure-project workflow: - -**Plan → Scaffold → Verify → Local Dev** - -## Your job - -Follow the authoritative guidance in the `azure-project-scaffold` skill: - -📖 **Read and follow:** [`.agents/skills/azure-project-scaffold/SKILL.md`] - -That skill is the canonical, mandatory source for both the planning and scaffolding phases. Treat it as your operating manual — do not improvise or substitute steps. **Exception:** the "Critical workflow rules" above govern preview-opening, approval gating, and the final hand-off — always route through the matching `run_vscode_command` call, never start the next phase inline. - -## Your deliverable - -An approved `.azure/project-plan.md` together with a fully scaffolded, buildable Azure project — frontend preview, backend services, database setup, and API routes all wired together and ready for local development or deployment. diff --git a/src/commands/copilotOnRails/openChatWithAgent.ts b/src/commands/copilotOnRails/openChatWithAgent.ts deleted file mode 100644 index 8ee3e11ea..000000000 --- a/src/commands/copilotOnRails/openChatWithAgent.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; - -const COPILOT_CHAT_EXTENSION_ID = 'GitHub.copilot-chat'; - -/** - * Ensure the GitHub Copilot Chat extension is installed and activated before invoking - * `workbench.action.chat.open`. Custom chat agents contributed via `package.json` - * (`chatAgents`) are not registered until that extension activates, so opening chat - * with a `mode` referring to one of them silently no-ops if we don't wait. - */ -export async function ensureCopilotChatReady(): Promise { - const ext = vscode.extensions.getExtension(COPILOT_CHAT_EXTENSION_ID); - if (!ext) { - void vscode.window.showErrorMessage( - vscode.l10n.t('GitHub Copilot Chat is required to continue. Please install the GitHub Copilot Chat extension and try again.'), - ); - return false; - } - if (!ext.isActive) { - await vscode.window.withProgress( - { location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Starting GitHub Copilot Chat...') }, - async () => { await ext.activate(); }, - ); - } - return true; -} - -export async function openChatWithAgent(agentName: string, prompt: string): Promise { - if (!(await ensureCopilotChatReady())) { - return; - } - await vscode.commands.executeCommand('workbench.action.chat.open', { - mode: agentName, - query: prompt, - }); -} diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 93e8ee060..08e9aec77 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -17,14 +17,9 @@ import { ResourceGroupsItem } from '../tree/ResourceGroupsItem'; import { ActivityItem } from '../tree/activityLog/ActivityItem'; import { GroupingItem } from '../tree/azure/grouping/GroupingItem'; import { TenantTreeItem } from '../tree/tenants/TenantTreeItem'; -import { createProjectWithCopilot } from '../webviews/copilotOnRails/extension/createProjectWithCopilot'; -import { openDeploymentPlanViewFromWorkspace } from '../webviews/copilotOnRails/extension/openDeploymentPlanView'; -import { openLocalPlanViewFromWorkspace } from '../webviews/copilotOnRails/extension/openLocalPlanView'; -import { openPlanViewFromWorkspace } from '../webviews/copilotOnRails/extension/openScaffoldPlanView'; import { logIn } from './accounts/logIn'; import { SelectSubscriptionOptions, selectSubscriptions } from './accounts/selectSubscriptions'; import { clearActivities } from './activities/clearActivities'; -import { openChatWithAgent } from './copilotOnRails/openChatWithAgent'; import { createResource } from './createResource'; import { createResourceGroup } from './createResourceGroup'; import { deleteResourceGroupV2 } from './deleteResourceGroup/v2/deleteResourceGroupV2'; @@ -161,19 +156,6 @@ export function registerCommands(): void { registerCommand("azureResourceGroups.askAgentAboutActivityLog", async (context: IActionContext, _node: ActivityItem) => await askAgentAboutActivityLog(context)); registerCommandWithTreeNodeUnwrapping("azureResourceGroups.askAgentAboutActivityLogItem", askAgentAboutActivityLog); registerCommandWithTreeNodeUnwrapping<{ id?: string }>("azureResourceGroups.askAgentAboutResource", (context, node) => askAgentAboutResource(context, node)); - - registerCommand('azureResourceGroups.createProjectWithCopilot', createProjectWithCopilot); - registerCommand('azureResourceGroups.openPlanView', openPlanViewFromWorkspace); - registerCommand('azureResourceGroups.openLocalPlanView', openLocalPlanViewFromWorkspace); - registerCommand('azureResourceGroups.openDeployPlanView', openDeploymentPlanViewFromWorkspace); - - // Hand-off commands - registerCommand('azureResourceGroups.startProjectScaffold', (_context: IActionContext, prompt?: string) => - openChatWithAgent('azure-project-scaffold', prompt ?? 'Plan and scaffold a new Azure project: gather requirements, produce `.azure/project-plan.md`, require explicit user approval, then scaffold the frontend preview, backend services, database, and API routes.')); - registerCommand('azureResourceGroups.startLocalDevelopment', (_context: IActionContext, prompt?: string) => - openChatWithAgent('azure-local-debug', prompt ?? 'The project has been scaffolded. Now set up the local development environment so the user can start building and testing.')); - registerCommand('azureResourceGroups.startDeployment', (_context: IActionContext, prompt?: string) => - openChatWithAgent('azure-deploy', prompt ?? 'Prepare the project for deployment to Azure — generate `.azure/deployment-plan.md`, then the infrastructure (Bicep or Terraform), `azure.yaml`, and any Dockerfiles needed for `azd up`.')); } async function handleAzExtTreeItemRefresh(context: IActionContext, node?: ResourceGroupsItem): Promise { diff --git a/src/extension.ts b/src/extension.ts index 2ead8d3ce..5d32cbd21 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -165,11 +165,6 @@ export async function activate(context: vscode.ExtensionContext, perfStats: { lo refreshEvent: refreshWorkspaceTreeEmitter.event, }); - context.subscriptions.push(vscode.window.registerTreeDataProvider('azureProject', { - getChildren: () => [], - getTreeItem: () => { throw new Error('azureProject view has no tree items.'); }, - })); - const tenantResourcesBranchDataItemCache = new BranchDataItemCache(); registerTenantTree(context, { tenantResourceProviderManager, diff --git a/src/tree/workspace/WorkspaceResourceTreeDataProvider.ts b/src/tree/workspace/WorkspaceResourceTreeDataProvider.ts index 2472f1d8b..aa5e2fd1e 100644 --- a/src/tree/workspace/WorkspaceResourceTreeDataProvider.ts +++ b/src/tree/workspace/WorkspaceResourceTreeDataProvider.ts @@ -46,12 +46,6 @@ export class WorkspaceResourceTreeDataProvider extends ResourceTreeDataProviderB } } - children.push(new GenericItem(localize('createProjectWithCopilot', 'Create Project with Copilot...'), { - id: 'azureResourceGroups.createProjectWithCopilot', - iconPath: new vscode.ThemeIcon('sparkle'), - commandId: 'azureResourceGroups.createProjectWithCopilot' - })); - // NOTE: Returning zero children indicates to VS Code that is should display a "welcome view". // The one chosen for display depends on the context set above. return children.sort((a, b) => a.id.localeCompare(b.id)); diff --git a/src/webviews/copilotOnRails/extension/controllers/CreateProjectViewController.ts b/src/webviews/copilotOnRails/extension/controllers/CreateProjectViewController.ts deleted file mode 100644 index f84e7e2ed..000000000 --- a/src/webviews/copilotOnRails/extension/controllers/CreateProjectViewController.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -import { WebviewController } from "@microsoft/vscode-azext-webview"; -import * as vscode from "vscode"; -import { ViewColumn } from "vscode"; -import { ensureCopilotChatReady } from "../../../../commands/copilotOnRails/openChatWithAgent"; -import { ext } from "../../../../extensionVariables"; -import { type CreateProjectViewControllerType } from "../../views/utils/viewConfigTypes"; -import { getCopilotOnRailsBundleLocation } from "../copilotOnRailsBundleLocation"; - -export type { CreateProjectViewControllerType }; - -export class CreateProjectViewController extends WebviewController { - constructor(viewConfig: CreateProjectViewControllerType) { - super(ext.context, viewConfig.title, 'createProjectView', viewConfig, ViewColumn.Active, undefined, getCopilotOnRailsBundleLocation()); - - this.panel.webview.onDidReceiveMessage( - (message: { command: string; prompt?: string }) => { - switch (message.command) { - case 'plan': - if (message.prompt) { - void this.openChatWithQuery(message.prompt); - } else { - this.panel.dispose(); - } - break; - } - } - ); - } - - private async openChatWithQuery(query: string): Promise { - if (!(await ensureCopilotChatReady())) { - return; - } - this.panel.dispose(); - await vscode.commands.executeCommand('workbench.action.chat.newChat'); - await vscode.commands.executeCommand("workbench.action.chat.open", { - mode: 'azure-project-scaffold', - query, - }); - } -} diff --git a/src/webviews/copilotOnRails/extension/controllers/DeploymentPlanViewController.ts b/src/webviews/copilotOnRails/extension/controllers/DeploymentPlanViewController.ts deleted file mode 100644 index 94163aaf6..000000000 --- a/src/webviews/copilotOnRails/extension/controllers/DeploymentPlanViewController.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { WebviewController } from "@microsoft/vscode-azext-webview"; -import * as vscode from "vscode"; -import { ViewColumn } from "vscode"; -import { ext } from "../../../../extensionVariables"; -import { type DeploymentPlanData } from "../../views/utils/deploymentPlanTypes"; -import { type DeploymentPlanViewConfiguration, type DeploymentPlanViewStrings } from "../../views/utils/viewConfigTypes"; -import { getCopilotOnRailsBundleLocation } from "../copilotOnRailsBundleLocation"; - -export type { DeploymentPlanViewConfiguration, DeploymentPlanViewStrings }; - -export class DeploymentPlanViewController extends WebviewController { - private sourceFileUri: vscode.Uri | undefined; - - constructor(planData: DeploymentPlanData, strings: DeploymentPlanViewStrings, sourceFileUri?: vscode.Uri) { - super(ext.context, strings.title, 'deploymentPlanView', { strings }, ViewColumn.Active, undefined, getCopilotOnRailsBundleLocation()); - - this.sourceFileUri = sourceFileUri; - - this.panel.webview.onDidReceiveMessage((message: { command: string; data?: unknown; prompt?: string }) => { - switch (message.command) { - case 'ready': - void this.panel.webview.postMessage({ command: 'setDeploymentPlanData', data: planData }); - break; - case 'approve': - this.panel.dispose(); - break; - case 'submitPlanFeedback': { - const query = message.prompt?.trim(); - if (!query) { - return; - } - void vscode.commands.executeCommand('workbench.action.chat.open', { - mode: 'agent', - query, - }); - void this.panel.webview.postMessage({ command: 'revisionInProgress' }); - break; - } - case 'openSourceFile': - this.openSourceFile(); - break; - } - }); - } - - updateDeploymentPlanData(planData: DeploymentPlanData, sourceFileUri?: vscode.Uri): void { - if (sourceFileUri) { - this.sourceFileUri = sourceFileUri; - } - void this.panel.webview.postMessage({ command: 'setDeploymentPlanData', data: planData }); - void this.panel.webview.postMessage({ command: 'revisionComplete' }); - } - - private openSourceFile(): void { - if (!this.sourceFileUri) { - void vscode.window.showWarningMessage( - vscode.l10n.t('The plan file location is unknown. Locate it manually in the workspace.'), - ); - return; - } - void vscode.commands.executeCommand('vscode.open', this.sourceFileUri); - } -} diff --git a/src/webviews/copilotOnRails/extension/controllers/LocalPlanViewController.ts b/src/webviews/copilotOnRails/extension/controllers/LocalPlanViewController.ts deleted file mode 100644 index 7dc3f0e0d..000000000 --- a/src/webviews/copilotOnRails/extension/controllers/LocalPlanViewController.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { WebviewController } from "@microsoft/vscode-azext-webview"; -import * as vscode from "vscode"; -import { ViewColumn } from "vscode"; -import { ext } from "../../../../extensionVariables"; -import { type LocalPlanData } from "../../views/utils/parseLocalPlanMarkdown"; -import { getCopilotOnRailsBundleLocation } from "../copilotOnRailsBundleLocation"; - -export class LocalPlanViewController extends WebviewController> { - private sourceFileUri: vscode.Uri | undefined; - - constructor(planData: LocalPlanData, sourceFileUri?: vscode.Uri) { - super(ext.context, 'Local Dev Plan', 'localPlanView', {}, ViewColumn.Active, undefined, getCopilotOnRailsBundleLocation()); - - this.sourceFileUri = sourceFileUri; - - this.panel.webview.onDidReceiveMessage((message: { command: string; data?: LocalPlanData; prompt?: string }) => { - switch (message.command) { - case 'ready': - void this.panel.webview.postMessage({ command: 'setLocalPlanData', data: planData }); - break; - case 'approvePlan': - void vscode.commands.executeCommand('azureProjectCreation.completeStep', 'projectCreation/localDevelopment/defineLocalPlan'); - void vscode.commands.executeCommand('workbench.action.chat.open', { - mode: 'agent', - query: 'I approve the local dev plan.', - }); - this.panel.dispose(); - break; - case 'submitPlanFeedback': { - const query = message.prompt?.trim(); - if (!query) { - return; - } - void vscode.commands.executeCommand('workbench.action.chat.open', { - mode: 'agent', - query, - }); - void this.panel.webview.postMessage({ command: 'revisionInProgress' }); - break; - } - case 'openSourceFile': - this.openSourceFile(); - break; - } - }); - } - - updatePlanData(planData: LocalPlanData, sourceFileUri?: vscode.Uri): void { - if (sourceFileUri) { - this.sourceFileUri = sourceFileUri; - } - void this.panel.webview.postMessage({ command: 'setLocalPlanData', data: planData }); - void this.panel.webview.postMessage({ command: 'revisionComplete' }); - } - - private openSourceFile(): void { - if (!this.sourceFileUri) { - void vscode.window.showWarningMessage( - vscode.l10n.t('The plan file location is unknown. Locate it manually in the workspace.'), - ); - return; - } - void vscode.commands.executeCommand('vscode.open', this.sourceFileUri); - } -} diff --git a/src/webviews/copilotOnRails/extension/controllers/ScaffoldPlanViewController.ts b/src/webviews/copilotOnRails/extension/controllers/ScaffoldPlanViewController.ts deleted file mode 100644 index 84aeb80c9..000000000 --- a/src/webviews/copilotOnRails/extension/controllers/ScaffoldPlanViewController.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { WebviewController } from "@microsoft/vscode-azext-webview"; -import * as vscode from "vscode"; -import { ViewColumn } from "vscode"; -import { ext } from "../../../../extensionVariables"; -import { type PlanData } from "../../views/utils/parseScaffoldPlanMarkdown"; -import { getCopilotOnRailsBundleLocation } from "../copilotOnRailsBundleLocation"; - -export class ScaffoldPlanViewController extends WebviewController> { - private sourceFileUri: vscode.Uri | undefined; - - constructor(planData: PlanData, sourceFileUri?: vscode.Uri) { - super(ext.context, 'Project Plan', 'scaffoldPlanView', {}, ViewColumn.Active, undefined, getCopilotOnRailsBundleLocation()); - - this.sourceFileUri = sourceFileUri; - - this.panel.webview.onDidReceiveMessage((message: { command: string; data?: PlanData; prompt?: string }) => { - switch (message.command) { - case 'ready': - void this.panel.webview.postMessage({ command: 'setPlanData', data: planData }); - break; - case 'approvePlan': - void vscode.commands.executeCommand('azureProjectCreation.completeStep', 'projectCreation/plan/definePlan'); - void vscode.commands.executeCommand('workbench.action.chat.open', { - mode: 'azure-project-scaffold', - query: 'I approve the plan.', - }); - this.panel.dispose(); - break; - case 'submitPlanFeedback': { - const query = message.prompt?.trim(); - if (!query) { - return; - } - void vscode.commands.executeCommand('workbench.action.chat.open', { - mode: 'agent', - query, - }); - void this.panel.webview.postMessage({ command: 'revisionInProgress' }); - break; - } - case 'openSourceFile': - this.openSourceFile(); - break; - } - }); - } - - updatePlanData(planData: PlanData, sourceFileUri?: vscode.Uri): void { - if (sourceFileUri) { - this.sourceFileUri = sourceFileUri; - } - void this.panel.webview.postMessage({ command: 'setPlanData', data: planData }); - void this.panel.webview.postMessage({ command: 'revisionComplete' }); - } - - private openSourceFile(): void { - if (!this.sourceFileUri) { - void vscode.window.showWarningMessage( - vscode.l10n.t('The plan file location is unknown. Locate it manually in the workspace.'), - ); - return; - } - void vscode.commands.executeCommand('vscode.open', this.sourceFileUri); - } -} diff --git a/src/webviews/copilotOnRails/extension/copilotOnRailsBundleLocation.ts b/src/webviews/copilotOnRails/extension/copilotOnRailsBundleLocation.ts deleted file mode 100644 index f596f8d63..000000000 --- a/src/webviews/copilotOnRails/extension/copilotOnRailsBundleLocation.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { type WebviewBundleLocation } from "@microsoft/vscode-azext-webview"; -import * as path from "path"; -import { ext } from "../../../extensionVariables"; - -/** - * Returns the location of the bundled Copilot on Rails webview script and stylesheet - * (produced by `esbuild.copilotOnRailsViews.mjs`). - */ -export function getCopilotOnRailsBundleLocation(): WebviewBundleLocation { - return { - distDir: path.join(ext.context.extensionPath, 'dist', 'copilotOnRails'), - scriptFileName: 'views.js', - styleFileName: 'views.css', - }; -} diff --git a/src/webviews/copilotOnRails/extension/createProjectWithCopilot.ts b/src/webviews/copilotOnRails/extension/createProjectWithCopilot.ts deleted file mode 100644 index dfbeb97dc..000000000 --- a/src/webviews/copilotOnRails/extension/createProjectWithCopilot.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) Microsoft Corporation. All rights reserved. -* Licensed under the MIT License. See License.md in the project root for license information. -*--------------------------------------------------------------------------------------------*/ - -import { type IActionContext } from "@microsoft/vscode-azext-utils"; -import * as vscode from 'vscode'; -import { CreateProjectViewController } from "./controllers/CreateProjectViewController"; - -const localDev = vscode.l10n.t('Local Development'); -const deploy = vscode.l10n.t('Deploy'); - -export async function createProjectWithCopilot(_context: IActionContext): Promise { - switch (true) { - // Local Development => Deploy - case await hasCompletedPhase('**/local-development-plan.md', 'implemented'): { - const choice = await vscode.window.showInformationMessage( - vscode.l10n.t('We detected a previous Copilot session with a completed local debug configuration. Would you like to deploy this project?'), - { modal: true }, - deploy, - ); - - if (choice === deploy) { - await vscode.commands.executeCommand('azureResourceGroups.startDeployment'); - } - break; - } - - // Create => Debug | Deploy - case await hasCompletedPhase('**/project-plan.md', 'scaffolded'): { - const choice = await vscode.window.showInformationMessage( - vscode.l10n.t('We detected a previous Copilot session with a fully scaffolded project. How would you like to proceed?'), - { modal: true }, - localDev, - deploy, - ); - - if (choice === localDev) { - await vscode.commands.executeCommand('azureResourceGroups.startLocalDevelopment'); - } else if (choice === deploy) { - await vscode.commands.executeCommand('azureResourceGroups.startDeployment'); - } - break; - } - - default: { - const controller = new CreateProjectViewController({ - title: vscode.l10n.t('Create with Copilot'), - heading: vscode.l10n.t('What would you like to build?'), - subtitle: vscode.l10n.t('Describe your project and Copilot will help you build and deploy it to Azure.'), - promptPlaceholder: vscode.l10n.t('Describe your project...'), - hint: vscode.l10n.t('Ctrl+Enter to plan'), - planButtonLabel: vscode.l10n.t('Plan'), - }); - controller.revealToForeground(); - } - } -} - -async function hasCompletedPhase(filePath: string, expectedStatus: string): Promise { - const files = await vscode.workspace.findFiles(filePath); - if (!files.length) { - return false; - } - - const content = Buffer.from(await vscode.workspace.fs.readFile(files[0])).toString('utf-8'); - // [*_~]* allows markdown formatting (bold, italic, strikethrough) around "status" - return new RegExp(`status[*_~]*\\s*:\\s*${expectedStatus}`, 'i').test(content); -} diff --git a/src/webviews/copilotOnRails/extension/openDeploymentPlanView.ts b/src/webviews/copilotOnRails/extension/openDeploymentPlanView.ts deleted file mode 100644 index b2ff5a349..000000000 --- a/src/webviews/copilotOnRails/extension/openDeploymentPlanView.ts +++ /dev/null @@ -1,167 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from "vscode"; -import type { DeploymentPlanData } from "../views/utils/deploymentPlanTypes"; -import { parseDeploymentPlanMarkdown } from "../views/utils/parseDeploymentPlanMarkdown"; -import { DeploymentPlanViewController, type DeploymentPlanViewStrings } from "./controllers/DeploymentPlanViewController"; - -let currentDeploymentPlanViewController: DeploymentPlanViewController | undefined; -let currentDeploymentPlanWatcher: vscode.Disposable | undefined; - -function getDeploymentPlanViewStrings(): DeploymentPlanViewStrings { - return { - title: vscode.l10n.t('Azure Deployment Plan'), - loading: vscode.l10n.t('Loading deployment plan...'), - subscriptionLabel: vscode.l10n.t('Subscription'), - locationLabel: vscode.l10n.t('Location'), - selectSubscriptionPlaceholder: vscode.l10n.t('Select a subscription...'), - selectLocationPlaceholder: vscode.l10n.t('Select a location...'), - architectureDiagramHeading: vscode.l10n.t('Architecture Diagram'), - workspaceScanHeading: vscode.l10n.t('Workspace Scan'), - decisionsHeading: vscode.l10n.t('Decisions'), - azureResourcesHeading: vscode.l10n.t('Azure Resources'), - approveButton: vscode.l10n.t('Approve'), - feedbackButtonAriaLabel: vscode.l10n.t('Feedback'), - revisingBanner: vscode.l10n.t('Copilot is revising the plan…'), - requestChangesHeading: vscode.l10n.t('Request changes'), - feedbackDrawerAriaLabel: vscode.l10n.t('Plan feedback'), - closeFeedbackAriaLabel: vscode.l10n.t('Close feedback'), - drawerHint: vscode.l10n.t('Change a SKU in the Azure Resources table to capture a suggested edit here, or add a free-form note below.'), - freeformPlaceholder: vscode.l10n.t('Add a note for Copilot (e.g. "Use a Premium plan for the Functions App")'), - addNoteButton: vscode.l10n.t('Add note'), - discardAllButton: vscode.l10n.t('Discard all'), - submitFeedbackButton: vscode.l10n.t('Submit feedback'), - removeFeedbackItemAriaLabel: vscode.l10n.t('Remove feedback item'), - submitEditsDialogTitle: vscode.l10n.t('Submit edits to Copilot?'), - pendingEditsSingularMessage: vscode.l10n.t('You have {0} pending edit. Would you like to submit it to Copilot to revise the plan?'), - pendingEditsPluralMessage: vscode.l10n.t('You have {0} pending edits. Would you like to submit them to Copilot to revise the plan?'), - editsMadeFallbackMessage: vscode.l10n.t('Edits were made. Would you like to submit those edits to Copilot?'), - cancelButton: vscode.l10n.t('Cancel'), - submitEditsButton: vscode.l10n.t('Submit'), - noDiagramAvailable: vscode.l10n.t('No diagram available'), - parseFailureTitle: vscode.l10n.t("We couldn't render this plan"), - parseFailureFallbackMessage: vscode.l10n.t("The deployment plan couldn't be rendered as a structured view. The generated markdown didn't match the expected layout."), - parseFailureFileLabel: vscode.l10n.t('Plan file'), - openPlanFileButton: vscode.l10n.t('Open plan file'), - }; -} - -export function isDeploymentPlanViewOpen(): boolean { - return currentDeploymentPlanViewController !== undefined; -} - -export function openDeploymentPlanView(uri: vscode.Uri): void { - void openDeploymentPlanViewAsync(uri); -} - -export function openDeploymentPlanViewWithContent(content: string, sourceFileUri?: vscode.Uri): void { - const planData = tryParseDeploymentPlan(content, sourceFileUri); - - if (currentDeploymentPlanViewController) { - currentDeploymentPlanViewController.updateDeploymentPlanData(planData, sourceFileUri); - currentDeploymentPlanViewController.revealToForeground(vscode.ViewColumn.Active); - return; - } - - currentDeploymentPlanViewController = new DeploymentPlanViewController(planData, getDeploymentPlanViewStrings(), sourceFileUri); - currentDeploymentPlanViewController.revealToForeground(vscode.ViewColumn.Active); - currentDeploymentPlanViewController.panel.onDidDispose(() => { - currentDeploymentPlanViewController = undefined; - currentDeploymentPlanWatcher?.dispose(); - currentDeploymentPlanWatcher = undefined; - }); -} - -function tryParseDeploymentPlan(content: string, sourceFileUri: vscode.Uri | undefined): DeploymentPlanData { - let parsed: DeploymentPlanData | undefined; - let errorMessage: string | undefined; - try { - parsed = parseDeploymentPlanMarkdown(content); - } catch (err) { - errorMessage = err instanceof Error ? err.message : String(err); - } - - if (errorMessage - || !parsed - || (parsed.resources.rows.length === 0 - && parsed.decisions.rows.length === 0 - && parsed.workspaceScan.rows.length === 0 - && !parsed.mermaidDiagram)) { - return { - status: parsed?.status ?? 'Unknown', - mode: parsed?.mode ?? 'Unknown', - subscription: parsed?.subscription ?? '', - availableSubscriptions: parsed?.availableSubscriptions, - location: parsed?.location ?? '', - locationCode: parsed?.locationCode ?? '', - availableLocations: parsed?.availableLocations, - mermaidDiagram: parsed?.mermaidDiagram ?? '', - workspaceScan: parsed?.workspaceScan ?? { headers: [], rows: [] }, - decisions: parsed?.decisions ?? { headers: [], rows: [] }, - resources: parsed?.resources ?? { headers: [], rows: [] }, - parseError: { - message: errorMessage ?? vscode.l10n.t("The deployment plan couldn't be rendered as a structured view. The generated markdown didn't match the expected layout."), - fileLabel: sourceFileUri ? vscode.workspace.asRelativePath(sourceFileUri) : undefined, - }, - }; - } - return parsed; -} - -export async function openDeploymentPlanViewFromWorkspace(): Promise { - const files = await vscode.workspace.findFiles('**/.azure/plan.md', '**/node_modules/**', 10); - if (files.length === 0) { - void vscode.window.showInformationMessage(vscode.l10n.t('No deployment plan markdown files found in the workspace.')); - return; - } - - let selected: vscode.Uri; - if (files.length === 1) { - selected = files[0]; - } else { - const picked = await vscode.window.showQuickPick( - files.map((f) => ({ label: vscode.workspace.asRelativePath(f), uri: f })), - { placeHolder: vscode.l10n.t('Select a deployment plan file to open') }, - ); - if (!picked) { - return; - } - selected = picked.uri; - } - - await openDeploymentPlanViewAsync(selected); -} - -async function openDeploymentPlanViewAsync(uri: vscode.Uri): Promise { - const content = Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf-8'); - openDeploymentPlanViewWithContent(content, uri); - watchDeploymentPlanFile(uri); -} - -/** - * Watch the deployment plan markdown file so the webview auto-refreshes when - * Copilot finishes revising it on disk. - */ -function watchDeploymentPlanFile(uri: vscode.Uri): void { - currentDeploymentPlanWatcher?.dispose(); - - const pattern = new vscode.RelativePattern( - vscode.Uri.file(uri.fsPath.replace(/[/\\][^/\\]+$/, '')), - uri.fsPath.replace(/^.*[/\\]/, ''), - ); - const watcher = vscode.workspace.createFileSystemWatcher(pattern); - const reload = async () => { - try { - const content = Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf-8'); - openDeploymentPlanViewWithContent(content, uri); - } catch { - // File may have been deleted or be momentarily unavailable; ignore. - } - }; - watcher.onDidChange(() => void reload()); - watcher.onDidCreate(() => void reload()); - currentDeploymentPlanWatcher = watcher; -} diff --git a/src/webviews/copilotOnRails/extension/openLocalPlanView.ts b/src/webviews/copilotOnRails/extension/openLocalPlanView.ts deleted file mode 100644 index 5565bfc44..000000000 --- a/src/webviews/copilotOnRails/extension/openLocalPlanView.ts +++ /dev/null @@ -1,116 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from "vscode"; -import { type LocalPlanData, parseLocalPlanMarkdown } from "../views/utils/parseLocalPlanMarkdown"; -import { LocalPlanViewController } from "./controllers/LocalPlanViewController"; - -let currentLocalPlanViewController: LocalPlanViewController | undefined; -let currentLocalPlanWatcher: vscode.Disposable | undefined; - -export function isLocalPlanViewOpen(): boolean { - return currentLocalPlanViewController !== undefined; -} - -export function openLocalPlanView(uri: vscode.Uri): void { - void openLocalPlanViewAsync(uri); -} - -export function openLocalPlanViewWithContent(content: string, sourceFileUri?: vscode.Uri): void { - const planData = tryParseLocalPlan(content, sourceFileUri); - - if (currentLocalPlanViewController) { - currentLocalPlanViewController.updatePlanData(planData, sourceFileUri); - currentLocalPlanViewController.revealToForeground(vscode.ViewColumn.Active); - return; - } - - currentLocalPlanViewController = new LocalPlanViewController(planData, sourceFileUri); - currentLocalPlanViewController.revealToForeground(vscode.ViewColumn.Active); - currentLocalPlanViewController.panel.onDidDispose(() => { - currentLocalPlanViewController = undefined; - currentLocalPlanWatcher?.dispose(); - currentLocalPlanWatcher = undefined; - }); -} - -function tryParseLocalPlan(content: string, sourceFileUri: vscode.Uri | undefined): LocalPlanData { - let parsed: LocalPlanData | undefined; - let errorMessage: string | undefined; - try { - parsed = parseLocalPlanMarkdown(content); - } catch (err) { - errorMessage = err instanceof Error ? err.message : String(err); - } - - if (errorMessage || !parsed || parsed.sections.length === 0) { - return { - title: parsed?.title ?? 'Local Development Plan', - status: parsed?.status ?? 'Unknown', - headerNote: parsed?.headerNote ?? '', - sections: parsed?.sections ?? [], - parseError: { - message: errorMessage ?? vscode.l10n.t("The plan file couldn't be rendered as a structured view. The generated markdown didn't match the expected layout."), - fileLabel: sourceFileUri ? vscode.workspace.asRelativePath(sourceFileUri) : undefined, - }, - }; - } - return parsed; -} - -export async function openLocalPlanViewFromWorkspace(): Promise { - const files = await vscode.workspace.findFiles('**/local-development-plan.md', '**/node_modules/**', 10); - if (files.length === 0) { - void vscode.window.showInformationMessage(vscode.l10n.t('No local plan markdown files found in the workspace.')); - return; - } - - let selected: vscode.Uri; - if (files.length === 1) { - selected = files[0]; - } else { - const picked = await vscode.window.showQuickPick( - files.map((f) => ({ label: vscode.workspace.asRelativePath(f), uri: f })), - { placeHolder: vscode.l10n.t('Select a local plan file to open') }, - ); - if (!picked) { - return; - } - selected = picked.uri; - } - - await openLocalPlanViewAsync(selected); -} - -async function openLocalPlanViewAsync(uri: vscode.Uri): Promise { - const content = Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf-8'); - openLocalPlanViewWithContent(content, uri); - watchLocalPlanFile(uri); -} - -/** - * Watch the local plan markdown file so the webview auto-refreshes whenever - * Copilot (or anyone else) edits it on disk. - */ -function watchLocalPlanFile(uri: vscode.Uri): void { - currentLocalPlanWatcher?.dispose(); - - const pattern = new vscode.RelativePattern( - vscode.Uri.file(uri.fsPath.replace(/[/\\][^/\\]+$/, '')), - uri.fsPath.replace(/^.*[/\\]/, ''), - ); - const watcher = vscode.workspace.createFileSystemWatcher(pattern); - const reload = async () => { - try { - const content = Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf-8'); - openLocalPlanViewWithContent(content, uri); - } catch { - // File may have been deleted or be momentarily unavailable; ignore. - } - }; - watcher.onDidChange(() => void reload()); - watcher.onDidCreate(() => void reload()); - currentLocalPlanWatcher = watcher; -} diff --git a/src/webviews/copilotOnRails/extension/openScaffoldPlanView.ts b/src/webviews/copilotOnRails/extension/openScaffoldPlanView.ts deleted file mode 100644 index 80c06d8f2..000000000 --- a/src/webviews/copilotOnRails/extension/openScaffoldPlanView.ts +++ /dev/null @@ -1,122 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from "vscode"; -import { type PlanData, parseScaffoldPlanMarkdown } from "../views/utils/parseScaffoldPlanMarkdown"; -import { ScaffoldPlanViewController } from "./controllers/ScaffoldPlanViewController"; - -let currentPlanViewController: ScaffoldPlanViewController | undefined; -let currentPlanWatcher: vscode.Disposable | undefined; - -export function isPlanViewOpen(): boolean { - return currentPlanViewController !== undefined; -} - -export function openPlanView(uri: vscode.Uri): void { - void openPlanViewAsync(uri); -} - -export function openPlanViewWithContent(content: string, sourceFileUri?: vscode.Uri): void { - const planData = tryParseScaffoldPlan(content, sourceFileUri); - - if (currentPlanViewController) { - currentPlanViewController.updatePlanData(planData, sourceFileUri); - currentPlanViewController.revealToForeground(vscode.ViewColumn.Active); - return; - } - - currentPlanViewController = new ScaffoldPlanViewController(planData, sourceFileUri); - currentPlanViewController.revealToForeground(vscode.ViewColumn.Active); - currentPlanViewController.panel.onDidDispose(() => { - currentPlanViewController = undefined; - currentPlanWatcher?.dispose(); - currentPlanWatcher = undefined; - }); -} - -/** - * Parse the plan markdown, returning a placeholder PlanData with a parseError - * flag if parsing fails or yields no usable content. The view uses the flag to - * render a warning banner with an "Open file" button. - */ -function tryParseScaffoldPlan(content: string, sourceFileUri: vscode.Uri | undefined): PlanData { - let parsed: PlanData | undefined; - let errorMessage: string | undefined; - try { - parsed = parseScaffoldPlanMarkdown(content); - } catch (err) { - errorMessage = err instanceof Error ? err.message : String(err); - } - - if (errorMessage || !parsed || parsed.sections.length === 0) { - return { - status: parsed?.status ?? 'Unknown', - created: parsed?.created ?? 'Unknown', - mode: parsed?.mode ?? 'Unknown', - sections: parsed?.sections ?? [], - parseError: { - message: errorMessage ?? vscode.l10n.t("The plan file couldn't be rendered as a structured view. The generated markdown didn't match the expected layout."), - fileLabel: sourceFileUri ? vscode.workspace.asRelativePath(sourceFileUri) : undefined, - }, - }; - } - return parsed; -} - -export async function openPlanViewFromWorkspace(): Promise { - const files = await vscode.workspace.findFiles('**/project-plan.md', '**/node_modules/**', 10); - if (files.length === 0) { - void vscode.window.showInformationMessage(vscode.l10n.t('No plan markdown files found in the workspace.')); - return; - } - - let selected: vscode.Uri; - if (files.length === 1) { - selected = files[0]; - } else { - const picked = await vscode.window.showQuickPick( - files.map((f) => ({ label: vscode.workspace.asRelativePath(f), uri: f })), - { placeHolder: vscode.l10n.t('Select a plan file to open') }, - ); - if (!picked) { - return; - } - selected = picked.uri; - } - - await openPlanViewAsync(selected); -} - -async function openPlanViewAsync(uri: vscode.Uri): Promise { - const content = Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf-8'); - openPlanViewWithContent(content, uri); - watchPlanFile(uri); -} - -/** - * Watch the plan markdown file for changes (e.g. Copilot finishes revising it) - * and push the updated content to the webview so the user doesn't get stuck on - * a stale view. - */ -function watchPlanFile(uri: vscode.Uri): void { - currentPlanWatcher?.dispose(); - - const pattern = new vscode.RelativePattern( - vscode.Uri.file(uri.fsPath.replace(/[/\\][^/\\]+$/, '')), - uri.fsPath.replace(/^.*[/\\]/, ''), - ); - const watcher = vscode.workspace.createFileSystemWatcher(pattern); - const reload = async () => { - try { - const content = Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf-8'); - openPlanViewWithContent(content, uri); - } catch { - // File may have been deleted or be momentarily unavailable; ignore. - } - }; - watcher.onDidChange(() => void reload()); - watcher.onDidCreate(() => void reload()); - currentPlanWatcher = watcher; -} diff --git a/src/webviews/copilotOnRails/views/CreateProjectView.tsx b/src/webviews/copilotOnRails/views/CreateProjectView.tsx deleted file mode 100644 index 006fc1151..000000000 --- a/src/webviews/copilotOnRails/views/CreateProjectView.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Button, Textarea } from '@fluentui/react-components'; -import { ClipboardTaskListLtrRegular } from '@fluentui/react-icons'; -import { useConfiguration, WebviewContext } from '@microsoft/vscode-azext-webview/webview'; -import * as React from 'react'; -import { useContext, useState, type JSX } from 'react'; -import './styles/createProjectView.scss'; -import { type CreateProjectViewControllerType } from './utils/viewConfigTypes'; - -export const CreateProjectView = (): JSX.Element => { - const [prompt, setPrompt] = useState(''); - const { vscodeApi } = useContext(WebviewContext); - const config = useConfiguration(); - - const planClicked = () => { - if (!prompt.trim()) { - return; - } - vscodeApi.postMessage({ - command: 'plan', - prompt: prompt.trim(), - }); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { - planClicked(); - } - }; - - return ( -
-
-
-
-
-
-

{config.heading}

-

- {config.subtitle} -

-
- -
- +
+
+ + +
+ +``` + +> Render **one field per real field** of this form's entity (from Section 5) — not a fixed three. Use input types that fit (`text`, `email`, `number`, `date`, `select`, `textarea`). Keep `Cancel`; tailor the submit label to the action (e.g. "Save", "Create", "Send"). + +### `table` +```html +
+ + + + + + + + + + + + + + + + + + + + + + + + +
{Field 1}{Field 2}{Field 3}{Field 4}
{record 1 value}{value}{State}{value}
{record 2 value}{value}{State}{value}
{record 3 value}{value}{State}{value}
+
+``` + +> Columns are the primary entity's real fields (as many as the entity has — not a fixed four); rows are the page's records from Section 5's Sample Content (one `` per record). + +### `actions` / `action-bar` +```html +
+ + +
+``` + +### `tabs` +```html +
+
{Tab 1}
+
{Tab 2}
+
{Tab 3}
+
+``` + +> Use the tab names the page's purpose implies — as many as it needs. + +### `modal` (rendered inline as a preview — no overlay backdrop in the sketch) +```html +
+

{Confirm action}

+

+ {Body copy — describe what the dialog confirms or asks. The real app will mount this above an overlay.} +

+
+ + +
+
+``` + +### `empty` (empty-state panel — use for tabs with no items) +```html +
+

{No items yet}

+

{1–2 lines explaining how to add the first item.}

+ +
+``` + +### `footer` +```html +
+ © {App Name} + v0.1.0 · preview +
+``` + +### Compound: `split(a|b)` (1:2 columns) +```html +
+
{snippet for a}
+
{snippet for b}
+
+``` + +### Compound: `two-column(a+b)` (1:1 columns) +```html +
+
{snippet for a}
+
{snippet for b}
+
+``` + +--- + +## Adapting sizing to the domain + +The snippets above show a *shape*; the **counts and proportions** must follow the plan's actual data, or every preview collapses back into the same generic four-tile / three-card layout. Match these to the domain: + +| Knob | Default in the snippets | Adapt it to… | +|------|------------------------|--------------| +| **KPI tiles** | 4 | One per metric the dashboard tracks (a 2-metric app shows 2). The row auto-fits. | +| **Grid / list cards** | 3 | One per record in this page's Sample Content. The grid auto-fills columns. | +| **Table rows** | 3 | One `` per Sample Content record; columns = the entity's real fields. | +| **Form fields** | 3 | One per real field of the entity (a sign-up form may have 6, a search box 1). | +| **Nav / sidebar links** | 3 | One per page in Section 5's Pages table. | +| **Tabs** | 3 | As many as the page's purpose implies. | +| **Content density** | medium | A list-heavy admin tool packs rows tight; a marketing landing page leans hero + few cards. Let Section 5's Style Direction steer this. | + +These are the **only** things that should vary per page. The **design tokens** (`--space-*` scale, `--radius-*`, `--text-*`, the `.preview-*` class definitions) stay fixed — they are the shared contract the parent webview's palette/typography editors key off. Customize *what* and *how many*, never the spacing scale or class CSS. + +--- + +## Wrapping a full page + +Wrap the page body in `
` so the header → shell → footer flow lays out vertically. When the page uses a `sidebar`, wrap `