@@ -60,25 +57,21 @@ export function StepPromptSection({
))}
{
+ if (readOnly) return;
+ onLocalPromptChange(value);
+ debouncedUpdatePrompt(value);
+ }}
+ language="markdown"
+ readOnly={readOnly}
+ placeholders={placeholders}
+ promptReferences
+ testId={`workflow-step-prompt-${step.id}`}
+ isDirty={!savedStep || localPrompt !== (savedStep.prompt ?? "")}
+ dirtyLevel="container"
+ />
{t("workflows:stepPromptUsageHint", {
open: "{{",
diff --git a/apps/web/e2e/helpers/api-client.ts b/apps/web/e2e/helpers/api-client.ts
index 7a384a5462..6c79d934e3 100644
--- a/apps/web/e2e/helpers/api-client.ts
+++ b/apps/web/e2e/helpers/api-client.ts
@@ -679,6 +679,13 @@ export class ApiClient {
await this.request("DELETE", `/api/v1/prompts/${promptId}`);
}
+ async resetGitHubActionPresets(workspaceId: string): Promise {
+ await this.request(
+ "POST",
+ `/api/v1/github/action-presets/reset?workspace_id=${encodeURIComponent(workspaceId)}`,
+ );
+ }
+
async createTaskWithAgent(
workspaceId: string,
title: string,
diff --git a/apps/web/e2e/helpers/settings-prompt-editor.ts b/apps/web/e2e/helpers/settings-prompt-editor.ts
new file mode 100644
index 0000000000..bd6f577292
--- /dev/null
+++ b/apps/web/e2e/helpers/settings-prompt-editor.ts
@@ -0,0 +1,95 @@
+import { type Locator, type Page, expect } from "@playwright/test";
+
+type PromptEditorOptions = {
+ touch?: boolean;
+};
+
+type CompletionOptions = {
+ keyboard?: boolean;
+ touch?: boolean;
+};
+
+/** Return the Monaco host inside a SettingsPromptEditor wrapper. */
+export function monacoPromptEditor(editor: Locator): Locator {
+ return editor.locator(".monaco-editor");
+}
+
+/** Focus Monaco's EditContext target, which is the input surface in this build. */
+export async function focusPromptEditor(
+ editor: Locator,
+ { touch = false }: PromptEditorOptions = {},
+): Promise {
+ const monaco = monacoPromptEditor(editor);
+ await expect(monaco).toBeVisible({ timeout: 10_000 });
+ if (touch) await monaco.tap();
+ else await monaco.click();
+ const editContext = monaco.locator(".native-edit-context");
+ await expect(editContext).toBeFocused({ timeout: 5_000 });
+ return monaco;
+}
+
+export async function replacePromptEditor(
+ page: Page,
+ editor: Locator,
+ value: string,
+ options?: PromptEditorOptions,
+): Promise {
+ const monaco = await focusPromptEditor(editor, options);
+ await page.keyboard.press("Control+A");
+ await page.keyboard.press("Backspace");
+ await page.keyboard.type(value);
+ return monaco;
+}
+
+export async function waitForPromptInStore(page: Page, promptName: string): Promise {
+ await page.waitForFunction(
+ (name) => {
+ const store = (
+ window as Window & {
+ __KANDEV_E2E_STORE__?: {
+ getState: () => {
+ prompts: {
+ loaded: boolean;
+ items: Array<{ name: string }>;
+ };
+ };
+ };
+ }
+ ).__KANDEV_E2E_STORE__;
+ const prompts = store?.getState().prompts;
+ return prompts?.loaded === true && prompts.items.some((prompt) => prompt.name === name);
+ },
+ promptName,
+ {
+ timeout: 10_000,
+ message: `saved prompt ${promptName} was not loaded into the settings store`,
+ },
+ );
+}
+
+export async function acceptPromptCompletion(
+ page: Page,
+ label: string,
+ { keyboard = false, touch = false }: CompletionOptions = {},
+): Promise {
+ const suggestWidget = page.locator(".suggest-widget:visible").last();
+ await expect(suggestWidget).toBeVisible({ timeout: 5_000 });
+ const suggestion = suggestWidget.locator(".monaco-list-row").filter({ hasText: label }).first();
+ await expect(suggestion).toBeVisible({ timeout: 5_000 });
+ if (!keyboard) {
+ if (touch) await suggestion.tap();
+ else await suggestion.click();
+ return;
+ }
+
+ const index = Number((await suggestion.getAttribute("data-index")) ?? "0");
+ await page.keyboard.press("Home");
+ for (let step = 0; step < index; step += 1) {
+ await page.keyboard.press("ArrowDown");
+ }
+ await page.keyboard.press("Enter");
+}
+
+export function promptEditorText(editor: Locator): Locator {
+ return monacoPromptEditor(editor).locator(".view-lines");
+}
diff --git a/apps/web/e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts b/apps/web/e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts
new file mode 100644
index 0000000000..df69709b7b
--- /dev/null
+++ b/apps/web/e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts
@@ -0,0 +1,71 @@
+import { test, expect } from "../../fixtures/test-base";
+import {
+ acceptPromptCompletion,
+ promptEditorText,
+ replacePromptEditor,
+ waitForPromptInStore,
+} from "../../helpers/settings-prompt-editor";
+
+test.describe("GitHub quick action prompt autocomplete", () => {
+ test("inserts saved prompt mentions and GitHub action placeholders, then persists them", async ({
+ testPage,
+ apiClient,
+ seedData,
+ prCapture,
+ }) => {
+ test.setTimeout(90_000);
+ const prompt = await apiClient.createPrompt(
+ `e2e-github-action-prompt-${Date.now()}`,
+ "Reusable review guidance",
+ );
+
+ try {
+ const settingsPath = `/settings/workspaces/${seedData.workspaceId}/integrations/github`;
+ await testPage.goto(settingsPath);
+
+ const quickActions = testPage
+ .getByRole("heading", { name: "Quick actions", exact: true })
+ .locator("xpath=ancestor::section[1]");
+ await expect(quickActions).toBeVisible();
+ await quickActions.getByRole("tab", { name: "Pull requests" }).click();
+ await quickActions.getByRole("button", { name: "Edit prompt" }).first().click();
+ await waitForPromptInStore(testPage, prompt.name);
+
+ const editor = quickActions.locator('[data-testid^="github-action-prompt-editor-"]').first();
+ await replacePromptEditor(testPage, editor, "Review @");
+ await acceptPromptCompletion(testPage, prompt.name);
+ await testPage.keyboard.type(" {{");
+ await acceptPromptCompletion(testPage, "{{title}}");
+
+ await expect(promptEditorText(editor)).toContainText(`@${prompt.name}`);
+ await expect(promptEditorText(editor)).toContainText("{{title}}");
+ await expect(testPage.getByTestId("settings-floating-save")).toBeVisible();
+ await testPage
+ .getByTestId("settings-floating-save")
+ .getByRole("button", { name: "Save changes" })
+ .click();
+ await expect(testPage.getByTestId("settings-floating-save")).not.toBeVisible({
+ timeout: 15_000,
+ });
+
+ await testPage.goto(settingsPath);
+ const reloadedQuickActions = testPage
+ .getByRole("heading", { name: "Quick actions", exact: true })
+ .locator("xpath=ancestor::section[1]");
+ await reloadedQuickActions.getByRole("tab", { name: "Pull requests" }).click();
+ await reloadedQuickActions.getByRole("button", { name: "Edit prompt" }).first().click();
+ await waitForPromptInStore(testPage, prompt.name);
+ const reloadedEditor = reloadedQuickActions
+ .locator('[data-testid^="github-action-prompt-editor-"]')
+ .first();
+ await expect(promptEditorText(reloadedEditor)).toContainText(`@${prompt.name}`);
+ await expect(promptEditorText(reloadedEditor)).toContainText("{{title}}");
+ await prCapture.screenshot("github-action-prompt-editor", {
+ caption: "GitHub quick action prompt editor with saved-prompt and placeholder completions.",
+ });
+ } finally {
+ await apiClient.deletePrompt(prompt.id).catch(() => undefined);
+ await apiClient.resetGitHubActionPresets(seedData.workspaceId).catch(() => undefined);
+ }
+ });
+});
diff --git a/apps/web/e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts b/apps/web/e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts
new file mode 100644
index 0000000000..f000d72fbc
--- /dev/null
+++ b/apps/web/e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts
@@ -0,0 +1,58 @@
+import { test, expect } from "../../fixtures/test-base";
+import {
+ acceptPromptCompletion,
+ promptEditorText,
+ replacePromptEditor,
+ waitForPromptInStore,
+} from "../../helpers/settings-prompt-editor";
+
+test.describe("GitHub quick action prompt autocomplete on mobile", () => {
+ test("supports touch editing without horizontal document overflow", async ({
+ testPage,
+ apiClient,
+ seedData,
+ prCapture,
+ }) => {
+ test.setTimeout(90_000);
+ const prompt = await apiClient.createPrompt(
+ `Daily Summary ${Date.now()}`,
+ "Reusable mobile review guidance",
+ );
+
+ try {
+ const settingsPath = `/settings/workspaces/${seedData.workspaceId}/integrations/github`;
+ await testPage.goto(settingsPath);
+
+ const quickActions = testPage
+ .getByRole("heading", { name: "Quick actions", exact: true })
+ .locator("xpath=ancestor::section[1]");
+ await expect(quickActions).toBeVisible();
+ await quickActions.getByRole("tab", { name: "Pull requests" }).tap();
+ await quickActions.getByRole("button", { name: "Edit prompt" }).first().tap();
+ await waitForPromptInStore(testPage, prompt.name);
+
+ const editor = quickActions.locator('[data-testid^="github-action-prompt-editor-"]').first();
+ await replacePromptEditor(testPage, editor, "Review @Daily ", { touch: true });
+ await acceptPromptCompletion(testPage, prompt.name, { touch: true });
+ await testPage.keyboard.type(" {{");
+ await acceptPromptCompletion(testPage, "{{title}}", { touch: true });
+
+ await expect(promptEditorText(editor)).toContainText(`@${prompt.name}`);
+ await expect(promptEditorText(editor)).toContainText("{{title}}");
+
+ const editorBox = await editor.boundingBox();
+ expect(editorBox).not.toBeNull();
+ expect(editorBox!.width).toBeLessThanOrEqual(testPage.viewportSize()!.width);
+ expect(
+ await testPage.evaluate(
+ () => document.documentElement.scrollWidth <= document.documentElement.clientWidth,
+ ),
+ ).toBe(true);
+ await prCapture.screenshot("mobile-github-action-prompt-editor", {
+ caption: "Mobile GitHub quick action prompt editor with touch completion controls.",
+ });
+ } finally {
+ await apiClient.deletePrompt(prompt.id).catch(() => undefined);
+ }
+ });
+});
diff --git a/apps/web/e2e/tests/settings/prompts-settings.spec.ts b/apps/web/e2e/tests/settings/prompts-settings.spec.ts
index 430bb4f2cb..e35e88dda5 100644
--- a/apps/web/e2e/tests/settings/prompts-settings.spec.ts
+++ b/apps/web/e2e/tests/settings/prompts-settings.spec.ts
@@ -1,4 +1,10 @@
import { test, expect } from "../../fixtures/test-base";
+import {
+ acceptPromptCompletion,
+ promptEditorText,
+ replacePromptEditor,
+ waitForPromptInStore,
+} from "../../helpers/settings-prompt-editor";
// Custom prompts share a UNIQUE name index with built-in prompts. Prior to the
// 409-mapping fix, posting a duplicate name returned 500 and the frontend
@@ -28,7 +34,7 @@ test.describe("Prompts settings — duplicate name handling", () => {
const form = testPage.getByTestId("prompt-create-form");
await expect(form).toBeVisible();
await form.getByTestId("prompt-name-input").fill("dupe-prompt");
- await form.getByTestId("prompt-content-input").fill("second content");
+ await replacePromptEditor(testPage, form.getByTestId("prompt-content-input"), "second content");
await testPage
.getByTestId("settings-floating-save")
.getByRole("button", { name: "Save changes" })
@@ -87,7 +93,7 @@ test.describe("Prompts settings — duplicate name handling", () => {
const form = testPage.getByTestId("prompt-create-form");
await form.getByTestId("prompt-name-input").fill("e2e-fresh-prompt");
- await form.getByTestId("prompt-content-input").fill("hello world");
+ await replacePromptEditor(testPage, form.getByTestId("prompt-content-input"), "hello world");
await testPage
.getByTestId("settings-floating-save")
.getByRole("button", { name: "Save changes" })
@@ -99,4 +105,36 @@ test.describe("Prompts settings — duplicate name handling", () => {
// Form should be reset / closed on success.
await expect(testPage.getByTestId("prompt-create-form")).toHaveCount(0);
});
+
+ test("does not suggest the prompt currently being edited", async ({ testPage, apiClient }) => {
+ test.setTimeout(60_000);
+
+ const current = await apiClient.createPrompt("e2e-current-prompt", "Current prompt content");
+ const other = await apiClient.createPrompt("e2e-other-prompt", "Reusable prompt content");
+
+ try {
+ await testPage.goto("/settings/prompts");
+ await waitForPromptInStore(testPage, other.name);
+ const row = testPage.locator(
+ '[data-testid="prompt-list-item"][data-prompt-name="e2e-current-prompt"]',
+ );
+ await expect(row).toBeVisible();
+ await row.getByTestId("prompt-edit-button").click();
+
+ const editor = row.getByTestId("prompt-content-input");
+ await replacePromptEditor(testPage, editor, "Use ");
+ await testPage.keyboard.type("@");
+
+ const suggestWidget = testPage.locator(".suggest-widget:visible").last();
+ await expect(suggestWidget).toBeVisible({ timeout: 5_000 });
+ await expect(suggestWidget).toContainText(other.name);
+ await expect(suggestWidget).not.toContainText(current.name);
+
+ await acceptPromptCompletion(testPage, other.name);
+ await expect(promptEditorText(editor)).toContainText(`@${other.name}`);
+ } finally {
+ await apiClient.deletePrompt(current.id).catch(() => undefined);
+ await apiClient.deletePrompt(other.id).catch(() => undefined);
+ }
+ });
});
diff --git a/apps/web/e2e/tests/workflow/workflow-duplication.spec.ts b/apps/web/e2e/tests/workflow/workflow-duplication.spec.ts
index 6204fec1c1..246dab9517 100644
--- a/apps/web/e2e/tests/workflow/workflow-duplication.spec.ts
+++ b/apps/web/e2e/tests/workflow/workflow-duplication.spec.ts
@@ -1,5 +1,6 @@
import { expect } from "@playwright/test";
import { test } from "../../fixtures/test-base";
+import { promptEditorText } from "../../helpers/settings-prompt-editor";
import { WorkflowSettingsPage } from "../../pages/workflow-settings-page";
import { seedWorkflowDuplication } from "./workflow-duplication-helpers";
@@ -31,7 +32,7 @@ test.describe("workflow duplication", () => {
await expect(copyCard.getByTestId("workflow-description-input")).toHaveValue(
"Copied workflow description",
);
- await expect(copyCard.getByTestId("workflow-prompt-input")).toHaveValue(
+ await expect(promptEditorText(copyCard.getByTestId("workflow-prompt-input"))).toContainText(
"Copied workflow prompt",
);
await expect(copyCard.getByText("Review", { exact: true })).toBeVisible();
diff --git a/apps/web/e2e/tests/workflow/workflow-settings.spec.ts b/apps/web/e2e/tests/workflow/workflow-settings.spec.ts
index 28b67b0179..69082fe8f1 100644
--- a/apps/web/e2e/tests/workflow/workflow-settings.spec.ts
+++ b/apps/web/e2e/tests/workflow/workflow-settings.spec.ts
@@ -1,5 +1,6 @@
import type { Locator } from "@playwright/test";
import { test, expect } from "../../fixtures/test-base";
+import { promptEditorText } from "../../helpers/settings-prompt-editor";
import { WorkflowSettingsPage } from "../../pages/workflow-settings-page";
import { dwell } from "../../helpers/causal-waits";
@@ -566,9 +567,10 @@ test.describe("Workflow settings", () => {
await expect(descriptionInput).toHaveValue("New description from another tab");
await expect(descriptionInput).toHaveAttribute("data-settings-dirty", "false");
const promptInput = card.getByTestId("workflow-prompt-input");
+ const promptEditor = promptInput.locator("..");
await expect(promptInput).toBeVisible();
- await expect(promptInput).toHaveValue("New prompt from another tab");
- await expect(promptInput).toHaveAttribute("data-settings-dirty", "false");
+ await expect(promptEditorText(promptInput)).toContainText("New prompt from another tab");
+ await expect(promptEditor).toHaveAttribute("data-settings-dirty", "false");
// The unsaved local name edit was not clobbered by the cross-tab sync.
await expect(nameInput).toHaveValue("Unsaved local name");
diff --git a/apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts b/apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts
index 4949cd97d7..e62010ef54 100644
--- a/apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts
+++ b/apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts
@@ -43,12 +43,45 @@ test.describe("Workflow step prompt autocomplete", () => {
await expect(suggestion).toContainText("task_prompt");
});
+ test("does not duplicate closing braces when completing inside a token", async ({
+ testPage,
+ seedData,
+ }) => {
+ const page = new WorkflowSettingsPage(testPage);
+ await page.goto(seedData.workspaceId);
+
+ const card = await page.findWorkflowCard("E2E Workflow");
+ await expect(card).toBeVisible();
+
+ const stepNodes = card.locator(".group.relative");
+ await stepNodes.first().click();
+
+ const monacoEditor = card.locator(".monaco-editor");
+ await expect(monacoEditor).toBeVisible({ timeout: 10_000 });
+ await monacoEditor.click();
+ await expect(monacoEditor.locator(".native-edit-context")).toBeFocused({ timeout: 5_000 });
+
+ await testPage.keyboard.insertText("{{}}");
+ await testPage.keyboard.press("ArrowLeft");
+ await testPage.keyboard.press("ArrowLeft");
+ await testPage.keyboard.press("Control+Space");
+
+ const suggestWidget = testPage.locator(".monaco-editor .suggest-widget");
+ await expect(suggestWidget).toBeVisible({ timeout: 5_000 });
+ const suggestion = suggestWidget.locator(".monaco-list-row").filter({ hasText: "task_prompt" });
+ await expect(suggestion.first()).toBeVisible();
+ await suggestion.first().click();
+
+ await expect(monacoEditor).toContainText("{{task_prompt}}");
+ await expect(monacoEditor).not.toContainText("{{task_prompt}}}}");
+ });
+
test("shows and inserts a saved prompt mention when typing @ in step prompt editor", async ({
testPage,
seedData,
apiClient,
}) => {
- const promptName = "e2e-mention-prompt";
+ const promptName = `Daily Summary ${Date.now()}`;
await apiClient.createPrompt(promptName, "Some reusable prompt content for e2e mentions.");
try {
@@ -72,8 +105,8 @@ test.describe("Workflow step prompt autocomplete", () => {
// textarea. See the note in the first test.
await expect(monacoEditor.locator(".native-edit-context")).toBeFocused({ timeout: 5_000 });
- // Type @ to trigger the prompt-mention autocomplete
- await testPage.keyboard.type("@");
+ // Type a multi-word name prefix to trigger and filter the prompt-mention autocomplete
+ await testPage.keyboard.type("@Daily ");
// The Monaco suggest widget should appear with the seeded prompt
const suggestWidget = testPage.locator(".monaco-editor .suggest-widget");
diff --git a/apps/web/hooks/domains/settings/use-custom-prompts.test.tsx b/apps/web/hooks/domains/settings/use-custom-prompts.test.tsx
new file mode 100644
index 0000000000..a99704b11a
--- /dev/null
+++ b/apps/web/hooks/domains/settings/use-custom-prompts.test.tsx
@@ -0,0 +1,57 @@
+import { cleanup, render, screen, waitFor } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ listPrompts: vi.fn(),
+}));
+
+vi.mock("@/lib/api", () => ({
+ listPrompts: mocks.listPrompts,
+}));
+
+import { StateProvider } from "@/components/state-provider";
+import { useCustomPrompts } from "./use-custom-prompts";
+
+function PromptEditorLoaders() {
+ const first = useCustomPrompts();
+ const second = useCustomPrompts();
+
+ return (
+
+ );
+}
+
+describe("useCustomPrompts", () => {
+ afterEach(() => {
+ cleanup();
+ mocks.listPrompts.mockReset();
+ });
+
+ it("deduplicates the prompt request for two mounted editors", async () => {
+ let resolveRequest!: (response: { prompts: [] }) => void;
+ mocks.listPrompts.mockReturnValue(
+ new Promise<{ prompts: [] }>((resolve) => {
+ resolveRequest = resolve;
+ }),
+ );
+
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => expect(mocks.listPrompts).toHaveBeenCalledTimes(1));
+ await waitFor(() =>
+ expect(screen.getByTestId("prompt-editor-loaders").textContent).toBe("true:true"),
+ );
+
+ resolveRequest({ prompts: [] });
+
+ await waitFor(() =>
+ expect(screen.getByTestId("prompt-editor-loaders").textContent).toBe("false:false"),
+ );
+ });
+});
diff --git a/apps/web/hooks/domains/settings/use-custom-prompts.ts b/apps/web/hooks/domains/settings/use-custom-prompts.ts
index 8fffab1b28..566bd4a1d9 100644
--- a/apps/web/hooks/domains/settings/use-custom-prompts.ts
+++ b/apps/web/hooks/domains/settings/use-custom-prompts.ts
@@ -3,7 +3,31 @@
import { useEffect } from "react";
import { listPrompts } from "@/lib/api";
import { useAppStore } from "@/components/state-provider";
-export function useCustomPrompts() {
+import type { CustomPrompt } from "@/lib/types/http";
+
+type UseCustomPromptsOptions = {
+ enabled?: boolean;
+};
+
+let inFlightPromptsRequest: Promise | null = null;
+
+function requestPrompts() {
+ if (inFlightPromptsRequest) return inFlightPromptsRequest;
+
+ const request = listPrompts({ cache: "no-store" }).then((response) => response.prompts ?? []);
+ inFlightPromptsRequest = request;
+ void request.then(
+ () => {
+ if (inFlightPromptsRequest === request) inFlightPromptsRequest = null;
+ },
+ () => {
+ if (inFlightPromptsRequest === request) inFlightPromptsRequest = null;
+ },
+ );
+ return request;
+}
+
+export function useCustomPrompts({ enabled = true }: UseCustomPromptsOptions = {}) {
const prompts = useAppStore((state) => state.prompts.items);
const loaded = useAppStore((state) => state.prompts.loaded);
const loading = useAppStore((state) => state.prompts.loading);
@@ -11,11 +35,11 @@ export function useCustomPrompts() {
const setPromptsLoading = useAppStore((state) => state.setPromptsLoading);
useEffect(() => {
- if (loaded || loading) return;
+ if (!enabled || loaded || loading) return;
setPromptsLoading(true);
- listPrompts({ cache: "no-store" })
- .then((response) => {
- setPrompts(response.prompts ?? []);
+ requestPrompts()
+ .then((nextPrompts) => {
+ setPrompts(nextPrompts);
})
.catch(() => {
setPrompts([]);
@@ -23,7 +47,7 @@ export function useCustomPrompts() {
.finally(() => {
setPromptsLoading(false);
});
- }, [loaded, loading, setPrompts, setPromptsLoading]);
+ }, [enabled, loaded, loading, setPrompts, setPromptsLoading]);
return {
prompts,
diff --git a/apps/web/src/locales/en/azuredevops.json b/apps/web/src/locales/en/azuredevops.json
index 29debf47b3..b81683a547 100644
--- a/apps/web/src/locales/en/azuredevops.json
+++ b/apps/web/src/locales/en/azuredevops.json
@@ -146,6 +146,13 @@
"personalAccessToken": "Personal Access Token",
"placeholderTitleDescription": "Title of the Azure DevOps work item or pull request",
"placeholderUrlDescription": "URL of the Azure DevOps work item or pull request",
+ "placeholderDescriptionBody": "Description body",
+ "placeholderIdDescription": "Work item or pull request ID",
+ "placeholderProjectDescription": "Project name",
+ "placeholderSourceBranchDescription": "Source branch",
+ "placeholderStateDescription": "Current state",
+ "placeholderTargetBranchDescription": "Target branch",
+ "placeholderTypeDescription": "Work item type",
"planningAndEffort": "Planning and effort",
"policiesLabelled": "Policies: {{state}}",
"pollIntervalSeconds": "Poll interval (seconds)",
@@ -153,7 +160,7 @@
"previousPullRequestPage": "Previous pull request page",
"project": "Project",
"prompt": "Prompt",
- "promptPlaceholdersHint": "Type {{open}} to see available placeholders. <3>{{url}}3> and <6>{{title}}6> are substituted when the action runs.",
+ "promptPlaceholdersHint": "Type {{open}} to see available placeholders. <4>{{url}}4> and <6>{{title}}6> are substituted when the action runs.",
"pullRequestAny": "any",
"pullRequestFeedback": "Pull request feedback",
"pullRequestFilter": "Pull-request filter",
diff --git a/apps/web/src/locales/en/gitlab.json b/apps/web/src/locales/en/gitlab.json
index d8c8ec0157..e08bd02c6d 100644
--- a/apps/web/src/locales/en/gitlab.json
+++ b/apps/web/src/locales/en/gitlab.json
@@ -205,6 +205,12 @@
"projectPaths": "Project paths",
"projects": "Projects",
"promptSentToTheSelectedAgent": "Prompt sent to the selected agent profile for each new match.",
+ "placeholderActionTitle": "Title of the merge request or issue",
+ "placeholderActionUrl": "URL of the merge request or issue",
+ "placeholderDescription": "Description body",
+ "placeholderIssueIid": "Issue IID",
+ "placeholderMergeRequestIid": "Merge request IID",
+ "placeholderProjectPath": "Project path",
"promptUsingUrlAndTitle": "Prompt using {{url}} and {{title}}",
"query": "Query",
"quickActions": "Quick actions",
diff --git a/apps/web/src/locales/en/settings.json b/apps/web/src/locales/en/settings.json
index d9bab2843e..a613e50a7b 100644
--- a/apps/web/src/locales/en/settings.json
+++ b/apps/web/src/locales/en/settings.json
@@ -492,7 +492,10 @@
"programming": "Programming",
"profileStartModelNotAdvertisedOnHost": "The host probe did not advertise {{model}}. The selected executor will decide the model at launch.",
"promptAdd": "Add prompt",
- "promptContentPlaceholder": "Prompt content",
+ "promptEditorAriaLabel": "Prompt editor",
+ "promptEditorBothHint": "Type {{placeholder}} for available placeholders or {{mention}} for saved prompts.",
+ "promptEditorPlaceholderHint": "Type {{placeholder}} to see available placeholders.",
+ "promptEditorPromptReferenceHint": "Type {{mention}} to see saved prompts.",
"promptCreateFailed": "Couldn't create prompt",
"promptDelete": "Delete prompt",
"promptDeleteDescription": "This will permanently remove <2>{{name}}2>. This action cannot be undone.",
diff --git a/apps/web/src/locales/pseudo/azuredevops.json b/apps/web/src/locales/pseudo/azuredevops.json
index ebb0417a28..43101e3141 100644
--- a/apps/web/src/locales/pseudo/azuredevops.json
+++ b/apps/web/src/locales/pseudo/azuredevops.json
@@ -146,6 +146,13 @@
"personalAccessToken": "Ƥēŕśōńàĺ Àććēśś Ţōķēń",
"placeholderTitleDescription": "Ţĩţĺē ōƒ ţĥē Àźũŕē ĎēvŌƥś ŵōŕķ ĩţēḿ ōŕ ƥũĺĺ ŕēqũēśţ",
"placeholderUrlDescription": "ŨŔĹ ōƒ ţĥē Àźũŕē ĎēvŌƥś ŵōŕķ ĩţēḿ ōŕ ƥũĺĺ ŕēqũēśţ",
+ "placeholderDescriptionBody": "Ďēśćŕĩƥţĩōń ƀōďŷ",
+ "placeholderIdDescription": "Ŵōŕķ ĩţēḿ ōŕ ƥũĺĺ ŕēqũēśţ ĨĎ",
+ "placeholderProjectDescription": "Ƥŕōĵēćţ ńàḿē",
+ "placeholderSourceBranchDescription": "Śōũŕćē ƀŕàńćĥ",
+ "placeholderStateDescription": "Ćũŕŕēńţ śţàţē",
+ "placeholderTargetBranchDescription": "Ţàŕĝēţ ƀŕàńćĥ",
+ "placeholderTypeDescription": "Ŵōŕķ ĩţēḿ ţŷƥē",
"planningAndEffort": "Ƥĺàńńĩńĝ àńď ēƒƒōŕţ",
"policiesLabelled": "Ƥōĺĩćĩēś: {{state}}",
"pollIntervalSeconds": "Ƥōĺĺ ĩńţēŕvàĺ (śēćōńďś)",
@@ -153,7 +160,7 @@
"previousPullRequestPage": "Ƥŕēvĩōũś ƥũĺĺ ŕēqũēśţ ƥàĝē",
"project": "Ƥŕōĵēćţ",
"prompt": "Ƥŕōḿƥţ",
- "promptPlaceholdersHint": "Ţŷƥē {{open}} ţō śēē àvàĩĺàƀĺē ƥĺàćēĥōĺďēŕś. <3>{{url}}3> àńď <6>{{title}}6> àŕē śũƀśţĩţũţēď ŵĥēń ţĥē àćţĩōń ŕũńś.",
+ "promptPlaceholdersHint": "Ţŷƥē {{open}} ţō śēē àvàĩĺàƀĺē ƥĺàćēĥōĺďēŕś. <4>{{url}}4> àńď <6>{{title}}6> àŕē śũƀśţĩţũţēď ŵĥēń ţĥē àćţĩōń ŕũńś.",
"pullRequestAny": "àńŷ",
"pullRequestFeedback": "Ƥũĺĺ ŕēqũēśţ ƒēēďƀàćķ",
"pullRequestFilter": "Ƥũĺĺ-ŕēqũēśţ ƒĩĺţēŕ",
diff --git a/apps/web/src/locales/pseudo/gitlab.json b/apps/web/src/locales/pseudo/gitlab.json
index 0f02e9aaad..92cd3123db 100644
--- a/apps/web/src/locales/pseudo/gitlab.json
+++ b/apps/web/src/locales/pseudo/gitlab.json
@@ -205,6 +205,12 @@
"projectPaths": "Ƥŕōĵēćţ ƥàţĥś",
"projects": "Ƥŕōĵēćţś",
"promptSentToTheSelectedAgent": "Ƥŕōḿƥţ śēńţ ţō ţĥē śēĺēćţēď àĝēńţ ƥŕōƒĩĺē ƒōŕ ēàćĥ ńēŵ ḿàţćĥ.",
+ "placeholderActionTitle": "Ţĩţĺē ōƒ ţĥē ḿēŕĝē ŕēqũēśţ ōŕ ĩśśũē",
+ "placeholderActionUrl": "ŨŔĹ ōƒ ţĥē ḿēŕĝē ŕēqũēśţ ōŕ ĩśśũē",
+ "placeholderDescription": "Ďēśćŕĩƥţĩōń ƀōďŷ",
+ "placeholderIssueIid": "Ĩśśũē ĨĨĎ",
+ "placeholderMergeRequestIid": "Ḿēŕĝē ŕēqũēśţ ĨĨĎ",
+ "placeholderProjectPath": "Ƥŕōĵēćţ ƥàţĥ",
"promptUsingUrlAndTitle": "Ƥŕōḿƥţ ũśĩńĝ {{url}} àńď {{title}}",
"query": "Qũēŕŷ",
"quickActions": "Qũĩćķ àćţĩōńś",
diff --git a/apps/web/src/locales/pseudo/settings.json b/apps/web/src/locales/pseudo/settings.json
index 140cf35eab..e3bb1a43d4 100644
--- a/apps/web/src/locales/pseudo/settings.json
+++ b/apps/web/src/locales/pseudo/settings.json
@@ -492,7 +492,10 @@
"programming": "Ƥŕōĝŕàḿḿĩńĝ",
"profileStartModelNotAdvertisedOnHost": "Ţĥē ĥōśţ ƥŕōƀē ďĩď ńōţ àďvēŕţĩśē {{model}}. Ţĥē śēĺēćţēď ēxēćũţōŕ ŵĩĺĺ ďēćĩďē ţĥē ḿōďēĺ àţ ĺàũńćĥ.",
"promptAdd": "Àďď ƥŕōḿƥţ",
- "promptContentPlaceholder": "Ƥŕōḿƥţ ćōńţēńţ",
+ "promptEditorAriaLabel": "Ƥŕōḿƥţ ēďĩţōŕ",
+ "promptEditorBothHint": "Ţŷƥē {{placeholder}} ƒōŕ àvàĩĺàƀĺē ƥĺàćēĥōĺďēŕś ōŕ {{mention}} ƒōŕ śàvēď ƥŕōḿƥţś.",
+ "promptEditorPlaceholderHint": "Ţŷƥē {{placeholder}} ţō śēē àvàĩĺàƀĺē ƥĺàćēĥōĺďēŕś.",
+ "promptEditorPromptReferenceHint": "Ţŷƥē {{mention}} ţō śēē śàvēď ƥŕōḿƥţś.",
"promptCreateFailed": "Ćōũĺďń'ţ ćŕēàţē ƥŕōḿƥţ",
"promptDelete": "Ďēĺēţē ƥŕōḿƥţ",
"promptDeleteDescription": "Ţĥĩś ŵĩĺĺ ƥēŕḿàńēńţĺŷ ŕēḿōvē <2>{{name}}2>. Ţĥĩś àćţĩōń ćàńńōţ ƀē ũńďōńē.",
diff --git a/apps/web/src/locales/pt-pt/azuredevops.json b/apps/web/src/locales/pt-pt/azuredevops.json
index 3dddb55aad..2399eeb0e5 100644
--- a/apps/web/src/locales/pt-pt/azuredevops.json
+++ b/apps/web/src/locales/pt-pt/azuredevops.json
@@ -146,6 +146,13 @@
"personalAccessToken": "Token de acesso pessoal",
"placeholderTitleDescription": "Título do item de trabalho ou do pull request do Azure DevOps",
"placeholderUrlDescription": "URL do item de trabalho ou do pull request do Azure DevOps",
+ "placeholderDescriptionBody": "Corpo da descrição",
+ "placeholderIdDescription": "ID do item de trabalho ou do pull request",
+ "placeholderProjectDescription": "Nome do projeto",
+ "placeholderSourceBranchDescription": "Ramo de origem",
+ "placeholderStateDescription": "Estado atual",
+ "placeholderTargetBranchDescription": "Ramo de destino",
+ "placeholderTypeDescription": "Tipo de item de trabalho",
"planningAndEffort": "Planeamento e esforço",
"policiesLabelled": "Políticas: {{state}}",
"pollIntervalSeconds": "Intervalo de sondagem (segundos)",
@@ -153,7 +160,7 @@
"previousPullRequestPage": "Página anterior de pull requests",
"project": "Projeto",
"prompt": "Prompt",
- "promptPlaceholdersHint": "Escreva {{open}} para ver os marcadores disponíveis. <3>{{url}}3> e <6>{{title}}6> são substituídos quando a ação é executada.",
+ "promptPlaceholdersHint": "Escreva {{open}} para ver os marcadores disponíveis. <4>{{url}}4> e <6>{{title}}6> são substituídos quando a ação é executada.",
"pullRequestAny": "qualquer",
"pullRequestFeedback": "Comentários do pull request",
"pullRequestFilter": "Filtro de pull requests",
diff --git a/apps/web/src/locales/pt-pt/gitlab.json b/apps/web/src/locales/pt-pt/gitlab.json
index 3e6640fb15..baa8ed77c5 100644
--- a/apps/web/src/locales/pt-pt/gitlab.json
+++ b/apps/web/src/locales/pt-pt/gitlab.json
@@ -205,6 +205,12 @@
"projectPaths": "Caminhos dos projetos",
"projects": "Projetos",
"promptSentToTheSelectedAgent": "Prompt enviado ao perfil de agente selecionado por cada nova correspondência.",
+ "placeholderActionTitle": "Título do merge request ou problema",
+ "placeholderActionUrl": "URL do merge request ou problema",
+ "placeholderDescription": "Corpo da descrição",
+ "placeholderIssueIid": "IID do problema",
+ "placeholderMergeRequestIid": "IID do merge request",
+ "placeholderProjectPath": "Caminho do projeto",
"promptUsingUrlAndTitle": "Prompt com {{url}} e {{title}}",
"query": "Consulta",
"quickActions": "Ações rápidas",
diff --git a/apps/web/src/locales/pt-pt/settings.json b/apps/web/src/locales/pt-pt/settings.json
index 2ea7e55f4a..8aa783d1cf 100644
--- a/apps/web/src/locales/pt-pt/settings.json
+++ b/apps/web/src/locales/pt-pt/settings.json
@@ -478,7 +478,10 @@
"profileForTasksCreatedByAgents": "Perfil das tarefas criadas por agentes",
"programming": "Programação",
"promptAdd": "Adicionar prompt",
- "promptContentPlaceholder": "Conteúdo do prompt",
+ "promptEditorAriaLabel": "Editor de prompts",
+ "promptEditorBothHint": "Escreva {{placeholder}} para ver os marcadores disponíveis ou {{mention}} para ver prompts guardados.",
+ "promptEditorPlaceholderHint": "Escreva {{placeholder}} para ver os marcadores disponíveis.",
+ "promptEditorPromptReferenceHint": "Escreva {{mention}} para ver os prompts guardados.",
"promptCreateFailed": "Não foi possível criar o prompt",
"promptDelete": "Eliminar prompt",
"promptDeleteDescription": "Isto remove permanentemente <2>{{name}}2>. Esta ação não pode ser anulada.",
diff --git a/apps/web/src/locales/zh-cn/azuredevops.json b/apps/web/src/locales/zh-cn/azuredevops.json
index 363ac4077a..491383595d 100644
--- a/apps/web/src/locales/zh-cn/azuredevops.json
+++ b/apps/web/src/locales/zh-cn/azuredevops.json
@@ -146,6 +146,13 @@
"personalAccessToken": "个人访问令牌",
"placeholderTitleDescription": "Azure DevOps 工作项或拉取请求的标题",
"placeholderUrlDescription": "Azure DevOps 工作项或拉取请求的 URL",
+ "placeholderDescriptionBody": "描述正文",
+ "placeholderIdDescription": "工作项或拉取请求 ID",
+ "placeholderProjectDescription": "项目名称",
+ "placeholderSourceBranchDescription": "源分支",
+ "placeholderStateDescription": "当前状态",
+ "placeholderTargetBranchDescription": "目标分支",
+ "placeholderTypeDescription": "工作项类型",
"planningAndEffort": "规划与工作量",
"policiesLabelled": "策略:{{state}}",
"pollIntervalSeconds": "轮询间隔(秒)",
@@ -153,7 +160,7 @@
"previousPullRequestPage": "上一页拉取请求",
"project": "项目",
"prompt": "提示词",
- "promptPlaceholdersHint": "输入 {{open}} 可查看可用占位符。运行操作时会替换 <3>{{url}}3> 和 <6>{{title}}6>。",
+ "promptPlaceholdersHint": "输入 {{open}} 可查看可用占位符。运行操作时会替换 <4>{{url}}4> 和 <6>{{title}}6>。",
"pullRequestAny": "任意",
"pullRequestFeedback": "拉取请求反馈",
"pullRequestFilter": "拉取请求筛选条件",
diff --git a/apps/web/src/locales/zh-cn/gitlab.json b/apps/web/src/locales/zh-cn/gitlab.json
index 27e262906e..dbefa93512 100644
--- a/apps/web/src/locales/zh-cn/gitlab.json
+++ b/apps/web/src/locales/zh-cn/gitlab.json
@@ -205,6 +205,12 @@
"projectPaths": "项目路径",
"projects": "项目",
"promptSentToTheSelectedAgent": "为每个新匹配项发送给所选智能体配置的提示词。",
+ "placeholderActionTitle": "合并请求或问题的标题",
+ "placeholderActionUrl": "合并请求或问题的 URL",
+ "placeholderDescription": "描述正文",
+ "placeholderIssueIid": "问题 IID",
+ "placeholderMergeRequestIid": "合并请求 IID",
+ "placeholderProjectPath": "项目路径",
"promptUsingUrlAndTitle": "使用 {{url}} 和 {{title}} 的提示词",
"query": "查询",
"quickActions": "快捷操作",
diff --git a/apps/web/src/locales/zh-cn/settings.json b/apps/web/src/locales/zh-cn/settings.json
index 36e09f334e..f8aacd2f6f 100644
--- a/apps/web/src/locales/zh-cn/settings.json
+++ b/apps/web/src/locales/zh-cn/settings.json
@@ -491,7 +491,10 @@
"profileForTasksCreatedByAgents": "智能体创建任务时使用的配置",
"programming": "编程",
"promptAdd": "添加提示词",
- "promptContentPlaceholder": "提示词内容",
+ "promptEditorAriaLabel": "提示词编辑器",
+ "promptEditorBothHint": "输入 {{placeholder}} 查看可用占位符,或输入 {{mention}} 查看已保存的提示词。",
+ "promptEditorPlaceholderHint": "输入 {{placeholder}} 查看可用占位符。",
+ "promptEditorPromptReferenceHint": "输入 {{mention}} 查看已保存的提示词。",
"promptCreateFailed": "无法创建提示词",
"promptDelete": "删除提示词",
"promptDeleteDescription": "这将永久移除 <2>{{name}}2>。此操作无法撤销。",
diff --git a/apps/web/src/locales/zh-hk/azuredevops.json b/apps/web/src/locales/zh-hk/azuredevops.json
index 03d6bb5844..bffd13de33 100644
--- a/apps/web/src/locales/zh-hk/azuredevops.json
+++ b/apps/web/src/locales/zh-hk/azuredevops.json
@@ -146,6 +146,13 @@
"personalAccessToken": "個人存取權杖",
"placeholderTitleDescription": "Azure DevOps 工作項或拉取要求的標題",
"placeholderUrlDescription": "Azure DevOps 工作項或拉取要求的 URL",
+ "placeholderDescriptionBody": "描述正文",
+ "placeholderIdDescription": "工作項或拉取要求 ID",
+ "placeholderProjectDescription": "項目名稱",
+ "placeholderSourceBranchDescription": "來源分支",
+ "placeholderStateDescription": "目前狀態",
+ "placeholderTargetBranchDescription": "目標分支",
+ "placeholderTypeDescription": "工作項類型",
"planningAndEffort": "規劃與工作量",
"policiesLabelled": "策略:{{state}}",
"pollIntervalSeconds": "輪詢間隔(秒)",
@@ -153,7 +160,7 @@
"previousPullRequestPage": "上一頁拉取要求",
"project": "項目",
"prompt": "提示詞",
- "promptPlaceholdersHint": "輸入 {{open}} 可查看可用佔位符。執行操作時會替換 <3>{{url}}3> 和 <6>{{title}}6>。",
+ "promptPlaceholdersHint": "輸入 {{open}} 可查看可用佔位符。執行操作時會替換 <4>{{url}}4> 和 <6>{{title}}6>。",
"pullRequestAny": "任意",
"pullRequestFeedback": "拉取要求反饋",
"pullRequestFilter": "拉取要求篩選條件",
diff --git a/apps/web/src/locales/zh-hk/gitlab.json b/apps/web/src/locales/zh-hk/gitlab.json
index 0ce39512b0..2a5a60f20a 100644
--- a/apps/web/src/locales/zh-hk/gitlab.json
+++ b/apps/web/src/locales/zh-hk/gitlab.json
@@ -205,6 +205,12 @@
"projectPaths": "項目路徑",
"projects": "項目",
"promptSentToTheSelectedAgent": "為每個新匹配項傳送給所選代理程式設定檔的提示詞。",
+ "placeholderActionTitle": "合併請求或問題的標題",
+ "placeholderActionUrl": "合併請求或問題的 URL",
+ "placeholderDescription": "描述正文",
+ "placeholderIssueIid": "問題 IID",
+ "placeholderMergeRequestIid": "合併請求 IID",
+ "placeholderProjectPath": "項目路徑",
"promptUsingUrlAndTitle": "使用 {{url}} 和 {{title}} 的提示詞",
"query": "查詢",
"quickActions": "快捷操作",
diff --git a/apps/web/src/locales/zh-hk/settings.json b/apps/web/src/locales/zh-hk/settings.json
index 24a2a69419..f878ce1cd3 100644
--- a/apps/web/src/locales/zh-hk/settings.json
+++ b/apps/web/src/locales/zh-hk/settings.json
@@ -491,7 +491,10 @@
"profileForTasksCreatedByAgents": "代理程式建立任務時使用的設定",
"programming": "編程",
"promptAdd": "新增提示詞",
- "promptContentPlaceholder": "提示詞內容",
+ "promptEditorAriaLabel": "提示詞編輯器",
+ "promptEditorBothHint": "輸入 {{placeholder}} 查看可用佔位符,或輸入 {{mention}} 查看已儲存的提示詞。",
+ "promptEditorPlaceholderHint": "輸入 {{placeholder}} 查看可用佔位符。",
+ "promptEditorPromptReferenceHint": "輸入 {{mention}} 查看已儲存的提示詞。",
"promptCreateFailed": "無法建立提示詞",
"promptDelete": "刪除提示詞",
"promptDeleteDescription": "這將永久移除 <2>{{name}}2>。此操作無法撤銷。",
diff --git a/apps/web/src/locales/zh-tw/azuredevops.json b/apps/web/src/locales/zh-tw/azuredevops.json
index d42b8c234b..2617411eff 100644
--- a/apps/web/src/locales/zh-tw/azuredevops.json
+++ b/apps/web/src/locales/zh-tw/azuredevops.json
@@ -146,6 +146,13 @@
"personalAccessToken": "個人存取權杖",
"placeholderTitleDescription": "Azure DevOps 工作項或提取要求的標題",
"placeholderUrlDescription": "Azure DevOps 工作項或提取要求的 URL",
+ "placeholderDescriptionBody": "描述內容",
+ "placeholderIdDescription": "工作項或提取要求 ID",
+ "placeholderProjectDescription": "專案名稱",
+ "placeholderSourceBranchDescription": "來源分支",
+ "placeholderStateDescription": "目前狀態",
+ "placeholderTargetBranchDescription": "目標分支",
+ "placeholderTypeDescription": "工作項類型",
"planningAndEffort": "規劃與工作量",
"policiesLabelled": "策略:{{state}}",
"pollIntervalSeconds": "輪詢間隔(秒)",
@@ -153,7 +160,7 @@
"previousPullRequestPage": "上一頁提取要求",
"project": "專案",
"prompt": "提示詞",
- "promptPlaceholdersHint": "輸入 {{open}} 可檢視可用佔位符。執行操作時會替換 <3>{{url}}3> 和 <6>{{title}}6>。",
+ "promptPlaceholdersHint": "輸入 {{open}} 可檢視可用佔位符。執行操作時會替換 <4>{{url}}4> 和 <6>{{title}}6>。",
"pullRequestAny": "任意",
"pullRequestFeedback": "提取要求意見回饋",
"pullRequestFilter": "提取要求篩選條件",
diff --git a/apps/web/src/locales/zh-tw/gitlab.json b/apps/web/src/locales/zh-tw/gitlab.json
index ee72855915..6730ebd4bd 100644
--- a/apps/web/src/locales/zh-tw/gitlab.json
+++ b/apps/web/src/locales/zh-tw/gitlab.json
@@ -205,6 +205,12 @@
"projectPaths": "專案路徑",
"projects": "專案",
"promptSentToTheSelectedAgent": "為每個新匹配項傳送給所選代理程式設定檔的提示詞。",
+ "placeholderActionTitle": "合併請求或問題的標題",
+ "placeholderActionUrl": "合併請求或問題的 URL",
+ "placeholderDescription": "描述內容",
+ "placeholderIssueIid": "問題 IID",
+ "placeholderMergeRequestIid": "合併請求 IID",
+ "placeholderProjectPath": "專案路徑",
"promptUsingUrlAndTitle": "使用 {{url}} 和 {{title}} 的提示詞",
"query": "查詢",
"quickActions": "快捷操作",
diff --git a/apps/web/src/locales/zh-tw/settings.json b/apps/web/src/locales/zh-tw/settings.json
index fbcd6cc099..4f1674418c 100644
--- a/apps/web/src/locales/zh-tw/settings.json
+++ b/apps/web/src/locales/zh-tw/settings.json
@@ -491,7 +491,10 @@
"profileForTasksCreatedByAgents": "代理程式建立任務時使用的設定",
"programming": "程式設計",
"promptAdd": "新增提示詞",
- "promptContentPlaceholder": "提示詞內容",
+ "promptEditorAriaLabel": "提示詞編輯器",
+ "promptEditorBothHint": "輸入 {{placeholder}} 查看可用佔位符,或輸入 {{mention}} 查看已儲存的提示詞。",
+ "promptEditorPlaceholderHint": "輸入 {{placeholder}} 查看可用佔位符。",
+ "promptEditorPromptReferenceHint": "輸入 {{mention}} 查看已儲存的提示詞。",
"promptCreateFailed": "無法建立提示詞",
"promptDelete": "刪除提示詞",
"promptDeleteDescription": "這將永久移除 <2>{{name}}2>。此操作無法撤銷。",
diff --git a/docs/decisions/2026-08-20-settings-prompt-editor-provider-ownership.md b/docs/decisions/2026-08-20-settings-prompt-editor-provider-ownership.md
new file mode 100644
index 0000000000..173d94ed57
--- /dev/null
+++ b/docs/decisions/2026-08-20-settings-prompt-editor-provider-ownership.md
@@ -0,0 +1,44 @@
+# ADR-2026-08-20-settings-prompt-editor-provider-ownership: Settings Prompt Editors Own Monaco Completion Providers
+
+**Status:** accepted
+**Date:** 2026-08-20
+**Area:** frontend
+
+## Context
+
+Monaco registers completion providers by language, but Settings can mount more
+than one prompt editor with different placeholder and saved-prompt catalogs.
+The previous language-wide singleton allowed the last mounted editor to replace
+the provider used by another open editor. Saved-prompt catalogs also load
+asynchronously, so registration must handle data arriving before or after
+Monaco mounts.
+
+## Decision
+
+Each `ScriptEditor` instance owns its primary and saved-prompt completion
+registrations. The registrations wrap their providers with a model-identity
+check and return no suggestions for another model. Provider disposables are
+updated and cleaned up per instance. The shared `SettingsPromptEditor` enables
+saved-prompt registration only for fields whose runtime expands `@name`
+references, and its completion widget is layered above the mobile Settings Save
+bar.
+
+## Consequences
+
+Open editors no longer share mutable provider state, so placeholder and saved-
+prompt suggestions remain local to the editor that requested them. Each editor
+performs its own registration and disposal work, and provider changes replace
+only that editor's registration. The shared prompt editor must retain the
+latest registration callback because prompt data may load before Monaco's
+one-shot mount callback.
+
+## Alternatives Considered
+
+- Keep one language-wide provider per slot. This is simpler, but it reintroduces
+ cross-editor catalog leakage and makes unmount order observable.
+- Keep a global provider and route requests through the active editor. This
+ requires focus and model bookkeeping outside Monaco and is fragile when
+ several editors are mounted or a completion request races a focus change.
+- Give every editor a distinct Monaco language identifier. This avoids some
+ collisions, but multiplies language registrations and does not improve the
+ provider lifecycle or async catalog handling.
diff --git a/docs/decisions/INDEX.md b/docs/decisions/INDEX.md
index 47b1c0af37..4f618cbe17 100644
--- a/docs/decisions/INDEX.md
+++ b/docs/decisions/INDEX.md
@@ -179,5 +179,6 @@ Read individual ADRs for full context. Create new ones via `/record decision` or
| 2026-08-17-separate-task-activity-from-summary-freshness | [Separate Task Activity From Summary Freshness](2026-08-17-separate-task-activity-from-summary-freshness.md) | accepted | backend, frontend, protocol | 2026-08-17 |
| 2026-08-17-release-pr-ruleset-bypass | [Give Stable Release PRs an Administrator Token Bypass](2026-08-17-release-pr-ruleset-bypass.md) | accepted | infra, workflow, security | 2026-08-17 |
| 2026-08-18-never-started-agent-stall-terminal | [Treat Never-Started Agent Stalls as Terminal](2026-08-18-never-started-agent-stall-terminal.md) | accepted | backend, frontend, protocol | 2026-08-18 |
+| 2026-08-20-settings-prompt-editor-provider-ownership | [Settings Prompt Editors Own Monaco Completion Providers](2026-08-20-settings-prompt-editor-provider-ownership.md) | accepted | frontend | 2026-08-20 |
| 2026-08-14-kandev-native-agent-rich-output | [Keep Agent Rich Output Host Native](2026-08-14-kandev-native-agent-rich-output.md) | accepted | backend, frontend, protocol, security | 2026-08-14 |
| 2026-08-20-acp-client-non-underscore-extension-methods | [Route Non-Underscore Inbound Client Methods to the Extension Handler](2026-08-20-acp-client-non-underscore-extension-methods.md) | accepted | backend, protocol | 2026-08-20 |
diff --git a/docs/plans/settings-prompt-completion-regressions/plan.md b/docs/plans/settings-prompt-completion-regressions/plan.md
new file mode 100644
index 0000000000..93b4590134
--- /dev/null
+++ b/docs/plans/settings-prompt-completion-regressions/plan.md
@@ -0,0 +1,142 @@
+---
+spec: docs/specs/ui/settings-prompt-editor.md
+created: 2026-08-20
+status: done
+---
+
+# Fix Plan: Settings Prompt Completion Regressions
+
+## Overview
+
+Correct completion metadata and loading races in the shared Monaco prompt
+editor. Placeholder completion must account for an unfinished name suffix
+before existing closing braces. Saved-prompt completion must expose the prompt
+name as its filtering value, keep the `@name` display label, and continue
+filtering across spaces in valid multi-word names. Multiple mounted editors
+must share one in-flight prompt request. The repair remains frontend-only and
+preserves the existing plain-text and save contracts.
+
+## Confirmed Root Cause
+
+- For `{{|}}`, the placeholder provider uses the cursor-only replacement range
+ and always inserts `${key}}}`. The existing `}}` remains after the cursor,
+ so selection produces `{{key}}}}`. The same range bug leaves a suffix such
+ as `_prompt}}` behind when the cursor is inside `{{ta_prompt}}`.
+- For `@c`, the saved-prompt provider uses an `@name` label but its
+ replacement range starts after `@`. Monaco filters the typed name prefix
+ against the label when `filterText` is absent, so a valid prefix such as
+ `c` does not match the label's first character, `@`.
+- The `[\w-]*` mention-prefix pattern stops at a space. A valid prompt named
+ `Daily Summary` therefore loses its mention start when the user types
+ `@Daily `.
+- Every mounted editor can observe `loading=false` in the same render and
+ start its own request. A late failure can then overwrite the shared prompt
+ list with an empty result.
+
+## Frontend
+
+### Completion provider behavior
+
+Update `apps/web/components/settings/profile-edit/script-editor-completions.ts`:
+
+- Make placeholder completion insert only the placeholder name when the text
+ after the cursor contains the remaining placeholder name followed by `}}`.
+ Extend the replacement range across that suffix.
+- Keep the existing insertion of one closing pair for an unfinished token.
+- Set each saved-prompt completion item's `filterText` to `prompt.name` while
+ retaining the `@${prompt.name}` display label and the existing insertion
+ value.
+- Find the whitespace-bounded mention start directly, use the complete
+ substring after `@` as the active prefix, and retrigger completion after a
+ space.
+- Share an in-flight `listPrompts` request across mounted hook instances while
+ applying the settled result to each subscribing store consumer.
+
+No component API, backend contract, persistence format, or mobile composition
+changes are required.
+
+## Tests
+
+- **What:** Placeholder selection does not duplicate an existing closing pair
+ and still closes an unfinished token.
+ **File:** `apps/web/components/settings/profile-edit/script-editor-completions.test.ts`.
+ **How:** Call the provider with `{{}}` and the cursor between the pairs, then
+ with `{{` at the end, and assert the exact `insertText` values.
+- **What:** Saved-prompt suggestions remain filterable by the typed name.
+ **File:** `apps/web/components/settings/profile-edit/script-editor-completions.test.ts`.
+ **How:** Assert each suggestion exposes the prompt name as `filterText` while
+ its label remains prefixed with `@`.
+
+## E2E Tests
+
+- **Scenario:** **GIVEN** a saved prompt named `changes-walkthrough`, **WHEN**
+ the user types `@c` in a shared Settings prompt editor, **THEN** the matching
+ suggestion remains visible and can be selected.
+ **Files:** `apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts`
+ and the existing GitHub quick-action autocomplete coverage if needed.
+- **Scenario:** **GIVEN** the editor contains `{{}}` with the cursor inside,
+ **WHEN** the user selects a placeholder, **THEN** the persisted draft contains
+ one balanced token rather than duplicated closing braces.
+ **Files:** `apps/web/e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts`
+ and its mobile counterpart.
+
+## Review fixup scope
+
+The review follow-up extends the original repair with these cases:
+
+- A partially typed `{{ta_prompt}}` replaces the remaining name suffix before
+ existing `}}` and inserts only the selected key.
+- A whitespace-bounded mention such as `@Daily ` keeps a valid multi-word
+ prompt name available. Space retriggers use the full text after `@` as the
+ active replacement range.
+- Two mounted prompt editors share one in-flight `listPrompts` request, and
+ each consumer receives the settled result.
+- Desktop and mobile fixtures use unique prompt names. The GitHub quick-action
+ fixture resets action presets in `finally` after it saves a draft.
+
+The focused provider suite and hook suite cover the first three cases. The
+existing desktop and mobile completion scenarios cover browser filtering and
+cleanup.
+
+## Verification Results
+
+- RED: `pnpm --filter @kandev/web test -- --run components/settings/profile-edit/script-editor-completions.test.ts` failed as expected with 2 regression failures: duplicated placeholder braces and missing saved-prompt `filterText`.
+- GREEN: the same focused provider suite passed 10/10 tests.
+- `make build-backend build-web-e2e build-e2e-plugin-package` completed successfully; only existing build warnings were emitted.
+- `pnpm e2e:raw --project=chromium e2e/tests/workflow/workflow-step-autocomplete.spec.ts e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts` passed 5/5.
+- `pnpm e2e:raw --project=mobile-chrome e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts` passed 1/1.
+- `pnpm run typecheck` passed.
+- Focused ESLint passed for the changed completion source and unit test.
+- `git diff --check` passed. No temporary source or browser fixture files remain.
+- Review fixup RED: provider regressions failed for the partial placeholder and
+ multi-word mention, while the two-editor hook regression observed two prompt
+ requests.
+- Review fixup GREEN: focused provider, hook, shared-editor, and Monaco suites
+ passed 21 tests; web typecheck and focused ESLint passed.
+- Review fixup GREEN: `make build-backend build-web-e2e
+ build-e2e-plugin-package` passed; Chromium coverage passed 5/5 and mobile
+ coverage passed 1/1. The GitHub test completed its prompt and preset cleanup.
+- Review fixup GREEN: `git diff --check` passed.
+
+## Implementation Waves And Parallel Candidates
+
+Wave 1:
+
+- [x] [task-01-completion-provider-regressions](task-01-completion-provider-regressions.md)
+
+The task is sequential because it changes one shared provider and its unit and
+browser regression coverage.
+
+## Out of Scope
+
+- Changes to saved-prompt expansion, placeholder interpolation, or backend APIs.
+- Changes to Monaco theme, layout, mobile composition, or completion contents.
+- New public documentation, because this repair restores the behavior already
+ promised by the shipped prompt-editor spec.
+
+## Risks
+
+- Completion insertion must distinguish a cursor before existing `}}` from an
+ unfinished token without changing replacement ranges for typed names.
+- `filterText` must affect Monaco's filtering only; the visible `@name` label
+ and inserted `name` value must remain unchanged.
diff --git a/docs/plans/settings-prompt-completion-regressions/task-01-completion-provider-regressions.md b/docs/plans/settings-prompt-completion-regressions/task-01-completion-provider-regressions.md
new file mode 100644
index 0000000000..b282f0d627
--- /dev/null
+++ b/docs/plans/settings-prompt-completion-regressions/task-01-completion-provider-regressions.md
@@ -0,0 +1,106 @@
+---
+id: "01-completion-provider-regressions"
+title: "Fix prompt completion insertion and filtering"
+status: done
+wave: 1
+depends_on: []
+plan: "plan.md"
+spec: "../../specs/ui/settings-prompt-editor.md"
+---
+
+# Task 01: Fix prompt completion insertion and filtering
+
+## Acceptance
+
+- Selecting a placeholder at `{{|}}` produces exactly `{{key}}` and does not
+ duplicate the existing closing braces.
+- Selecting a placeholder after `ta` in `{{ta_prompt}}` replaces the full
+ partial token name and produces exactly `{{key}}`.
+- Selecting a placeholder at an unfinished `{{|` still produces exactly
+ `{{key}}`.
+- Saved-prompt completion labels remain `@name`, while typing a name prefix
+ keeps matching suggestions available and selection still inserts `@name`.
+- Saved prompts with spaces remain filterable after typing a prefix such as
+ `@Daily `.
+- Two mounted prompt editors share one in-flight saved-prompt request.
+- Desktop and mobile shared prompt-editor flows retain the same behavior.
+
+## Verification
+
+Follow TDD. Add the provider regression tests first and confirm they fail for
+the current insertion metadata and missing `filterText`.
+
+From `apps`:
+
+```bash
+pnpm --filter @kandev/web test -- --run components/settings/profile-edit/script-editor-completions.test.ts
+pnpm --filter @kandev/web test -- --run hooks/domains/settings/use-custom-prompts.test.tsx
+```
+
+After the unit fix, rebuild the production web/backend artifacts required by
+the E2E fixture and run the focused desktop and mobile scenarios:
+
+```bash
+make build-backend build-web-e2e build-e2e-plugin-package
+cd apps/web && pnpm e2e:raw --project=chromium e2e/tests/workflow/workflow-step-autocomplete.spec.ts e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts
+cd apps/web && pnpm e2e:raw --project=mobile-chrome e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts
+```
+
+Also run `cd apps/web && pnpm run typecheck`, the focused ESLint command for
+the changed files, and `git diff --check` before marking the task done.
+
+## Files likely touched
+
+- `apps/web/components/settings/profile-edit/script-editor-completions.ts`
+- `apps/web/components/settings/profile-edit/script-editor-completions.test.ts`
+- `apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts`
+- `apps/web/e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts`
+- `apps/web/e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts`
+- `docs/specs/ui/settings-prompt-editor.md`
+- `docs/plans/settings-prompt-completion-regressions/plan.md`
+- `docs/plans/settings-prompt-completion-regressions/task-01-completion-provider-regressions.md`
+
+## Dependencies
+
+None.
+
+## Parallelism
+
+Sequential. The provider and all regression tests share the same completion
+contract.
+
+## Inputs
+
+- The repaired behavior and scenarios in `docs/specs/ui/settings-prompt-editor.md`.
+- The confirmed root causes and exact source files in `plan.md`.
+- Existing Monaco provider tests and the shared prompt-editor E2E helpers.
+
+## Output contract
+
+Report the RED failures, implementation result, files changed, exact commands
+and outcomes, any browser screenshot or cleanup evidence, and synchronized task
+and plan status.
+
+## Review fixup scope
+
+- Cover the partial `{{ta_prompt}}` replacement range and multi-word `@Daily `
+ filtering contract.
+- Deduplicate prompt loading for two mounted editors and deliver one settled
+ result to both consumers.
+- Use unique prompt fixtures and restore GitHub action presets in `finally`.
+
+## Results
+
+- RED: `pnpm --filter @kandev/web test -- --run components/settings/profile-edit/script-editor-completions.test.ts` failed with the expected two assertions: `filterText` was undefined and `{{}}` completion returned `task_prompt}}`.
+- `apps/web/components/settings/profile-edit/script-editor-completions.ts` now preserves an existing trailing `}}` and sets saved-prompt `filterText` to the prompt name.
+- Added unit regressions for balanced and unfinished placeholder tokens and `@` name filtering.
+- Updated desktop workflow E2E coverage to complete inside `{{}}` and type `@c`; prompt fixtures use unique `c-` names to avoid seeded-data collisions.
+- Updated the mobile GitHub quick-action E2E flow to type `@c` and verify touch filtering.
+- GREEN: focused provider suite passed 10/10.
+- `make build-backend build-web-e2e build-e2e-plugin-package` completed successfully.
+- Desktop Playwright command passed 5/5; mobile Playwright command passed 1/1.
+- `pnpm run typecheck`, focused ESLint, and `git diff --check` passed.
+- No temporary browser artifacts were added; generated E2E failure artifacts were produced only under the existing ignored test-results directory.
+- Review fixup GREEN: the partial placeholder, multi-word mention, and
+ two-editor request regressions passed. Chromium passed 5/5 and mobile passed
+ 1/1 after the fixture cleanup changes.
diff --git a/docs/plans/settings-prompt-editor/plan.md b/docs/plans/settings-prompt-editor/plan.md
new file mode 100644
index 0000000000..ff4fe39b34
--- /dev/null
+++ b/docs/plans/settings-prompt-editor/plan.md
@@ -0,0 +1,192 @@
+---
+spec: docs/specs/ui/settings-prompt-editor.md
+created: 2026-08-20
+status: done
+---
+
+# Implementation Plan: Settings Prompt Editor
+
+## Overview
+
+Add a prompt-specific Settings editor above the existing Monaco `ScriptEditor`. First, scope completion providers to each Monaco model.
+
+Then migrate all prompt-authoring settings fields to the shared editor.
+
+The change preserves all stored strings and current save APIs. It adds frontend behavior, focused tests, desktop and mobile E2E coverage, and public documentation.
+
+## Audit
+
+The current prompt editors have four different states:
+
+- Workflow step and GitHub review-watch prompts support placeholders and saved-prompt references.
+- GitHub, Jira, and Azure DevOps quick actions support placeholders only.
+- Several watcher, workflow, automation, and utility prompts use Monaco with only part of the available completion contract.
+- GitLab quick actions, GitLab watches, Azure DevOps watches, workflow prompts, and custom prompts use plain textareas.
+
+`ScriptEditor` registers one completion provider per language and slot. The last mounted editor can replace the provider for another mounted editor.
+
+## Backend
+
+No backend, database, API, or interpolation change is required.
+
+Task descriptions already resolve saved-prompt references during workflow prompt assembly. Utility prompts use a separate sessionless template engine and do not resolve these references.
+
+## Frontend
+
+### Shared prompt editor
+
+Create `apps/web/components/settings/settings-prompt-editor.tsx` with a controlled plain-text contract:
+
+- `value` and `onChange`
+- `placeholders?: ScriptPlaceholder[]`
+- `promptReferences?: boolean`
+- `excludedPromptIds?: string[]`
+- `language?: "markdown" | "plaintext"`
+- `height`, `minLines`, `readOnly`, `ariaLabel`, `testId`, and dirty-state props
+- optional supplemental help content
+
+The component reads saved prompts through `useCustomPrompts` when prompt references are enabled. It filters excluded prompts before it passes them to `ScriptEditor`.
+
+The component owns the border, completion hint, loading-safe prompt list, and content-based default height. Consumers keep field labels and context-specific placeholder descriptions.
+
+Add localized completion hints to the `settings` catalogs. Use one hint for placeholders, one for prompt references, and one for both completion types.
+
+### Monaco completion ownership
+
+Update `apps/web/components/settings/profile-edit/script-editor.tsx`. Replace the language-wide singleton registrations with per-instance disposables.
+
+Wrap each completion provider with a model identity check. A provider returns no items when Monaco calls it for a different model.
+
+Dispose both providers when the editor unmounts. Re-register only the changed provider when placeholders or saved prompts change.
+
+Expose stable accessibility and test attributes from `ScriptEditor`. Keep the existing script-editor consumers compatible.
+
+### Quick actions
+
+Use `SettingsPromptEditor` in these files:
+
+- `apps/web/components/github/action-presets-section.tsx`
+- `apps/web/components/gitlab/action-presets-section.tsx`
+- `apps/web/components/jira/task-presets-section.tsx`
+- `apps/web/components/azure-devops/azure-devops-quick-actions.tsx`
+
+Enable saved-prompt references for every quick action. Keep each provider's current `{{...}}` tokens.
+
+Add GitLab `{{url}}` and `{{title}}` definitions. Keep the current quick-action data model and shared save contributor.
+
+### Watch prompts
+
+Use `SettingsPromptEditor` for GitHub, GitLab, Jira, Linear, Sentry, and Azure DevOps watch prompts. Enable saved-prompt references for all watch prompts.
+
+Reuse existing placeholder definitions for GitHub, GitLab, Jira, Linear, and Sentry. Add typed Azure DevOps definitions for work-item and pull-request watch tokens.
+
+Do not migrate provider query fields such as WIQL or JQL. These fields are not agent prompts.
+
+### Other Settings prompts
+
+Use `SettingsPromptEditor` for these settings fields:
+
+- workflow-level prompts
+- workflow step prompts
+- automation instructions
+- custom prompt content
+- utility agent prompt templates
+
+Enable saved-prompt references for workflow, automation, and custom prompt fields. Exclude the current custom prompt from its own completion list.
+
+Disable saved-prompt references for utility prompts. Keep utility template variables enabled.
+
+Update existing tests that assume a native textarea. Use the stable editor test ID and controlled-value assertions instead.
+
+### Mobile design contract
+
+- **Desktop outcome and mobile entry:** both viewports use the existing Settings routes and existing quick-action expansion control.
+- **Nearest mobile exemplars:** `mobile-github-integration-layout.spec.ts` and `mobile-workflow-settings.spec.ts` define the settings-page composition and overflow rules.
+- **Hierarchy:** the field label and completion hint remain above and below the inline editor. The route-level Save changes control remains primary.
+- **Presentation:** keep the editor inline. A drawer adds no value because prompt editing is the primary content of the expanded settings row.
+- **Scroll owner:** the Settings page remains the page scroll owner. Monaco owns vertical editor scroll after the content-height cap.
+- **Touch and geometry:** completion items support touch selection. The editor and suggestion widget must stay inside the phone viewport.
+- **Shared logic:** editor value, completion providers, dirty state, and save behavior are identical across viewports.
+
+## Tests
+
+- **What:** two mounted editors keep separate placeholder and saved-prompt providers.
+ **Files:** `apps/web/components/settings/profile-edit/script-editor.test.tsx` and `script-editor-completions.test.ts`.
+ **How:** use Monaco stubs with distinct models. Make sure that each provider rejects the other model and disposes on unmount.
+- **What:** the shared editor selects the correct completion modes and excludes the open custom prompt.
+ **File:** `apps/web/components/settings/settings-prompt-editor.test.tsx`.
+ **How:** mock `ScriptEditor` and `useCustomPrompts`. Assert props, dynamic prompt updates, dirty state, help text, and no prompt fetch error effect.
+- **What:** quick actions preserve drafts, placeholders, reset behavior, and save coordination while enabling prompt references.
+ **Files:** focused tests beside the four quick-action sections.
+ **How:** mock the shared editor and assert provider tokens, prompt-reference mode, and unchanged save payloads.
+- **What:** watcher and core Settings prompts use the shared editor with the correct completion contract.
+ **Files:** existing watcher, workflow, and custom-prompt component tests. Add focused tests where no consumer test exists.
+ **How:** assert component props and controlled updates. Keep persistence tests on the existing save coordinator.
+
+## E2E Tests
+
+- **Scenario:** **GIVEN** a saved prompt and GitHub quick action, **WHEN** the user selects `@name` and `{{title}}`, **THEN** both references persist after reload.
+ **File:** `apps/web/e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts`.
+ **What to verify:** both Monaco menus, keyboard or pointer selection, route save, reload persistence, and no automatic save on selection.
+- **Scenario:** **GIVEN** GitHub settings on a phone, **WHEN** the user selects both completion types, **THEN** the editor remains usable without page overflow.
+ **File:** `apps/web/e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts`.
+ **What to verify:** touch entry, suggestion selection, viewport containment, internal editor scrolling, and no document horizontal overflow.
+- **Scenario:** **GIVEN** a custom prompt editor, **WHEN** the user types `@`, **THEN** other prompts appear and the current prompt is absent.
+ **File:** `apps/web/e2e/tests/settings/prompts-settings.spec.ts`.
+ **What to verify:** create and edit paths, selection, save, and reload persistence.
+- **Scenario:** existing workflow step completion behavior remains available through the shared editor.
+ **File:** `apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts`.
+ **What to verify:** existing placeholder and saved-prompt cases remain green.
+
+## Public documentation
+
+Update these user guides:
+
+- `docs/public/integrations.md` for quick-action and watch prompt completion.
+- `docs/public/workflow-tips.md` for workflow-level and step prompt completion.
+- `docs/public/developer-tools.md` for nested saved prompts and the utility prompt exception.
+
+These pages remain how-to or explanation pages. No new navigation entry is required.
+
+## Verification Results
+
+- Focused unit/component suites pass for the shared editor, Monaco ownership, quick actions, watch prompts, and core Settings prompts.
+- `pnpm run typecheck` and `pnpm exec tsc --noEmit --incremental false` pass.
+- `pnpm run i18n:check` and `pnpm run i18n:ratchet` pass.
+- `node --test scripts/validate-public-docs.test.mjs` passes (61 tests), and `node scripts/validate-public-docs.mjs` validates 41 published pages.
+- Desktop and mobile managed Playwright runs pass for the new completion flows, custom-prompt exclusion, and existing workflow autocomplete.
+- `git diff --check` passes.
+
+## Implementation Waves And Parallel Candidates
+
+Wave 1:
+
+- [x] [task-01-shared-editor](task-01-shared-editor.md)
+
+Wave 2:
+
+- [x] [task-02-quick-actions](task-02-quick-actions.md)
+
+Wave 3:
+
+- [x] [task-03-watch-prompts](task-03-watch-prompts.md)
+- [x] [task-04-core-settings-prompts](task-04-core-settings-prompts.md)
+
+Wave 4:
+
+- [x] [task-05-browser-coverage](task-05-browser-coverage.md)
+- [x] [task-06-public-documentation](task-06-public-documentation.md)
+
+Tasks 03 and 04 are parallel-safe after Task 01 because their production files are disjoint.
+
+Tasks 05 and 06 are parallel-safe after Tasks 02 through 04.
+
+The primary conversation runs tasks sequentially by default. These labels do not authorize subagents.
+
+## Risks
+
+- Monaco completion providers are language-global. Model scoping must prevent cross-editor suggestions without breaking existing script completions.
+- Migrated native textareas change E2E input mechanics. Tests must focus Monaco's `native-edit-context` before they type.
+- GitLab and Azure DevOps watcher placeholders exist in backend interpolators. Frontend lists must match those exact tokens.
+- A saved-prompt suggestion is valid only when the runtime expands references. Utility prompts must not enable that completion mode.
+- Several settings routes can mount more than one editor. The component must avoid repeated prompt requests and provider churn.
diff --git a/docs/plans/settings-prompt-editor/task-01-shared-editor.md b/docs/plans/settings-prompt-editor/task-01-shared-editor.md
new file mode 100644
index 0000000000..5896e940d1
--- /dev/null
+++ b/docs/plans/settings-prompt-editor/task-01-shared-editor.md
@@ -0,0 +1,66 @@
+---
+id: "01-shared-editor"
+title: "Build shared prompt editor"
+status: done
+wave: 1
+depends_on: []
+plan: "plan.md"
+spec: "../../specs/ui/settings-prompt-editor.md"
+---
+
+# Task 01: Build shared prompt editor
+
+## Acceptance
+
+- `SettingsPromptEditor` provides controlled plain text, configured placeholders, optional saved-prompt references, exclusions, accessibility, dirty state, and localized hints.
+- Two mounted Monaco editors return completion items only for their own model.
+- Provider updates and unmounts dispose the correct Monaco registrations without changing existing script-editor behavior.
+
+## Verification
+
+Follow TDD. Add the model-isolation and shared-component tests first. Make sure that they fail against the language-wide provider ownership.
+
+```bash
+cd apps && pnpm install --frozen-lockfile && pnpm --filter @kandev/web test -- --run components/settings/settings-prompt-editor.test.tsx components/settings/profile-edit/script-editor.test.tsx components/settings/profile-edit/script-editor-completions.test.ts && cd web && pnpm run typecheck && pnpm run i18n:check && pnpm run i18n:ratchet && pnpm exec eslint components/settings/settings-prompt-editor.tsx components/settings/settings-prompt-editor.test.tsx components/settings/profile-edit/script-editor.tsx components/settings/profile-edit/script-editor.test.tsx components/settings/profile-edit/script-editor-completions.ts components/settings/profile-edit/script-editor-completions.test.ts
+```
+
+## Files likely touched
+
+- `apps/web/components/settings/settings-prompt-editor.tsx`
+- `apps/web/components/settings/settings-prompt-editor.test.tsx`
+- `apps/web/components/settings/profile-edit/script-editor.tsx`
+- `apps/web/components/settings/profile-edit/script-editor.test.tsx`
+- `apps/web/components/settings/profile-edit/script-editor-completions.ts`
+- `apps/web/components/settings/profile-edit/script-editor-completions.test.ts`
+- `apps/web/src/locales/en/settings.json`
+- `apps/web/src/locales/pt-pt/settings.json`
+- `apps/web/src/locales/zh-cn/settings.json`
+- `apps/web/src/locales/zh-hk/settings.json`
+- `apps/web/src/locales/zh-tw/settings.json`
+
+## Dependencies
+
+None.
+
+## Parallelism
+
+Sequential. All later tasks depend on this component and its provider ownership contract.
+
+## Inputs
+
+- Spec: `What`, `Failure modes`, and the model-isolation scenarios.
+- Plan: `Shared prompt editor`, `Monaco completion ownership`, and `Risks`.
+- Existing patterns: `ScriptEditor`, `useCustomPrompts`, and `script-editor-completions.test.ts`.
+
+## Output contract
+
+Report the RED error, final API, model-isolation method, files changed, exact command results, blockers, risks, and synchronized task and plan status.
+
+## Results
+
+- The initial browser run exposed a lifecycle race: saved prompts loaded before Monaco mounted, but the one-shot `onMount` callback still held an empty prompt list. The editor now keeps the latest registration callbacks in refs and registers per-model providers.
+- `ScriptEditor` owns one primary and one saved-prompt registration per mounted model, scopes both through `scopeCompletionProviderToModel`, and disposes them independently.
+- `SettingsPromptEditor` owns the controlled plain-text contract, localized completion hints, dirty attributes, stable editor attributes, prompt loading, and current-prompt exclusions.
+- Focused shared-editor/component/completion tests pass: 3 files, 13 tests; the regression test covers prompt data loading before Monaco mount.
+- `pnpm run typecheck` and `pnpm exec tsc --noEmit --incremental false` pass.
+- `pnpm run i18n:check` and `pnpm run i18n:ratchet` pass.
diff --git a/docs/plans/settings-prompt-editor/task-02-quick-actions.md b/docs/plans/settings-prompt-editor/task-02-quick-actions.md
new file mode 100644
index 0000000000..f8b1561e48
--- /dev/null
+++ b/docs/plans/settings-prompt-editor/task-02-quick-actions.md
@@ -0,0 +1,62 @@
+---
+id: "02-quick-actions"
+title: "Migrate quick action prompts"
+status: done
+wave: 2
+depends_on: ["01-shared-editor"]
+plan: "plan.md"
+spec: "../../specs/ui/settings-prompt-editor.md"
+---
+
+# Task 02: Migrate quick action prompts
+
+## Acceptance
+
+- GitHub, GitLab, Jira, and Azure DevOps quick actions use `SettingsPromptEditor`.
+- Each provider offers its exact placeholders and saved-prompt references.
+- Draft, reset, dirty-state, validation, and shared Save changes behavior remain unchanged.
+
+## Verification
+
+Follow TDD. Add consumer assertions for completion modes and tokens before each migration.
+
+```bash
+cd apps && pnpm install --frozen-lockfile && pnpm --filter @kandev/web test -- --run components/github/action-presets-section.test.tsx components/gitlab/action-presets-section.test.tsx components/jira/task-presets-section.test.tsx components/azure-devops/azure-devops-quick-actions.test.tsx && cd web && pnpm run i18n:ratchet && pnpm exec eslint components/github/action-presets-section.tsx components/github/action-presets-section.test.tsx components/gitlab/action-presets-section.tsx components/gitlab/action-presets-section.test.tsx components/jira/task-presets-section.tsx components/jira/task-presets-section.test.tsx components/azure-devops/azure-devops-quick-actions.tsx components/azure-devops/azure-devops-quick-actions.test.tsx
+```
+
+## Files likely touched
+
+- `apps/web/components/github/action-presets-section.tsx`
+- `apps/web/components/github/action-presets-section.test.tsx`
+- `apps/web/components/gitlab/action-presets-section.tsx`
+- `apps/web/components/gitlab/action-presets-section.test.tsx`
+- `apps/web/components/jira/task-presets-section.tsx`
+- `apps/web/components/jira/task-presets-section.test.tsx`
+- `apps/web/components/azure-devops/azure-devops-quick-actions.tsx`
+- `apps/web/components/azure-devops/azure-devops-quick-actions.test.tsx`
+- GitLab locale catalogs only if new provider-specific placeholder descriptions are required
+
+## Dependencies
+
+Task 01.
+
+## Parallelism
+
+Sequential. All four consumers share the new component contract and quick-action E2E surface.
+
+## Inputs
+
+- Spec: `Settings surfaces` and the GitHub quick-action scenarios.
+- Plan: `Quick actions` and `Shared prompt editor`.
+- Existing patterns: GitHub and Azure DevOps expandable prompt rows, Jira placeholder definitions, and each save contributor.
+
+## Output contract
+
+Report the RED error, provider token lists, save-payload evidence, files changed, exact command results, blockers, risks, and synchronized task and plan status.
+
+## Results
+
+- GitHub, GitLab, Jira, and Azure DevOps quick-action prompt fields use `SettingsPromptEditor` with saved-prompt references and their provider-specific placeholder maps.
+- GitLab now exposes localized `{{url}}` and `{{title}}` descriptions. Existing draft, reset, dirty-state, and save-contributor paths remain unchanged.
+- Focused consumer tests pass: 7 tests across 4 files.
+- `pnpm run i18n:ratchet` passes with no new hardcoded-copy violations.
diff --git a/docs/plans/settings-prompt-editor/task-03-watch-prompts.md b/docs/plans/settings-prompt-editor/task-03-watch-prompts.md
new file mode 100644
index 0000000000..7f28f3669a
--- /dev/null
+++ b/docs/plans/settings-prompt-editor/task-03-watch-prompts.md
@@ -0,0 +1,69 @@
+---
+id: "03-watch-prompts"
+title: "Migrate watch prompts"
+status: done
+wave: 3
+depends_on: ["01-shared-editor"]
+plan: "plan.md"
+spec: "../../specs/ui/settings-prompt-editor.md"
+---
+
+# Task 03: Migrate watch prompts
+
+## Acceptance
+
+- GitHub, GitLab, Jira, Linear, Sentry, and Azure DevOps watch prompts use `SettingsPromptEditor`.
+- Each watch offers backend-supported placeholders and saved-prompt references.
+- Query fields, watcher validation, responsive dialog composition, and persistence remain unchanged.
+
+## Verification
+
+Follow TDD. Add focused assertions for GitLab and Azure DevOps token lists before the textarea migrations.
+
+```bash
+cd apps && pnpm install --frozen-lockfile && pnpm --filter @kandev/web test -- --run components/github/review-watch-dialog.test.tsx components/gitlab/review-watch-dialog.test.tsx components/gitlab/issue-watch-dialog.test.tsx components/azure-devops/azure-devops-watch-settings.test.tsx components/sentry/sentry-issue-watch-dialog.test.tsx && cd web && pnpm run i18n:ratchet && pnpm exec eslint components/github/issue-watch-dialog.tsx components/github/review-watch-prompt-field.tsx components/gitlab/watch-dialog.tsx components/gitlab/review-watch-placeholders.ts components/gitlab/issue-watch-placeholders.ts components/jira/jira-issue-watch-dialog.tsx components/linear/linear-issue-watch-dialog.tsx components/sentry/sentry-issue-watch-dialog.tsx components/azure-devops/azure-devops-watch-settings.tsx
+```
+
+## Files likely touched
+
+- `apps/web/components/github/issue-watch-dialog.tsx`
+- `apps/web/components/github/review-watch-prompt-field.tsx`
+- `apps/web/components/github/review-watch-dialog.test.tsx`
+- `apps/web/components/gitlab/watch-dialog.tsx`
+- `apps/web/components/gitlab/review-watch-placeholders.ts`
+- `apps/web/components/gitlab/issue-watch-placeholders.ts`
+- `apps/web/components/gitlab/review-watch-dialog.test.tsx`
+- `apps/web/components/gitlab/issue-watch-dialog.test.tsx`
+- `apps/web/components/jira/jira-issue-watch-dialog.tsx`
+- `apps/web/components/linear/linear-issue-watch-dialog.tsx`
+- `apps/web/components/sentry/sentry-issue-watch-dialog.tsx`
+- `apps/web/components/sentry/sentry-issue-watch-dialog.test.tsx`
+- `apps/web/components/azure-devops/azure-devops-watch-settings.tsx`
+- `apps/web/components/azure-devops/azure-devops-watch-placeholders.ts`
+- `apps/web/components/azure-devops/azure-devops-watch-settings.test.tsx`
+- Provider locale catalogs for new placeholder descriptions and completion help
+
+## Dependencies
+
+Task 01.
+
+## Parallelism
+
+Parallel-safe with Task 04 after Task 01. The tasks use disjoint production and test files.
+
+## Inputs
+
+- Spec: the watch-prompt row in `Settings surfaces` and the saved-prompt failure scenario.
+- Plan: `Watch prompts` and `Mobile design contract`.
+- Backend token authority: `source_gitlab.go`, `source_azuredevops.go`, and existing provider placeholder modules.
+
+## Output contract
+
+Report the RED error, exact token maps, responsive behavior, files changed, command results, blockers, risks, and synchronized task and plan status.
+
+## Results
+
+- GitHub, GitLab, Jira, Linear, Sentry, and Azure DevOps watch prompt fields use `SettingsPromptEditor`; query fields such as WIQL and JQL remain native query inputs.
+- GitLab placeholder descriptions are localized and Azure DevOps work-item and pull-request token maps are typed against the backend-supported fields.
+- Focused watch tests pass: 13 tests across 7 files, plus 5 Azure DevOps watch-settings tests.
+- No watcher save, validation, dialog, or persistence contract changed.
diff --git a/docs/plans/settings-prompt-editor/task-04-core-settings-prompts.md b/docs/plans/settings-prompt-editor/task-04-core-settings-prompts.md
new file mode 100644
index 0000000000..96bf2c2d32
--- /dev/null
+++ b/docs/plans/settings-prompt-editor/task-04-core-settings-prompts.md
@@ -0,0 +1,64 @@
+---
+id: "04-core-settings-prompts"
+title: "Migrate core settings prompts"
+status: done
+wave: 3
+depends_on: ["01-shared-editor"]
+plan: "plan.md"
+spec: "../../specs/ui/settings-prompt-editor.md"
+---
+
+# Task 04: Migrate core settings prompts
+
+## Acceptance
+
+- Workflow, automation, custom-prompt, and utility-agent fields use `SettingsPromptEditor`.
+- Workflow, automation, and custom prompts offer saved-prompt references. The open custom prompt is excluded.
+- Utility prompts offer only utility variables. Existing dirty, save, reset, read-only, and cross-tab behavior remains unchanged.
+
+## Verification
+
+Follow TDD. Update native-textarea assumptions only after the shared-editor assertions fail.
+
+```bash
+cd apps && pnpm install --frozen-lockfile && pnpm --filter @kandev/web test -- --run components/settings/workflow-prompt-section.test.tsx components/settings/workflow-step-prompt-section.test.tsx components/settings/prompts-settings.test.tsx components/settings/utility-agent-dialog.test.tsx components/automations/prompt-section.test.tsx && cd web && pnpm run typecheck && pnpm run i18n:check && pnpm run i18n:ratchet && pnpm exec eslint components/settings/workflow-prompt-section.tsx components/settings/workflow-prompt-section.test.tsx components/settings/workflow-step-prompt-section.tsx components/settings/workflow-step-prompt-section.test.tsx components/settings/prompts-settings.tsx components/settings/prompts-settings.test.tsx components/settings/utility-agent-dialog.tsx components/settings/utility-agent-dialog.test.tsx components/automations/prompt-section.tsx components/automations/prompt-section.test.tsx
+```
+
+## Files likely touched
+
+- `apps/web/components/settings/workflow-prompt-section.tsx`
+- `apps/web/components/settings/workflow-prompt-section.test.tsx`
+- `apps/web/components/settings/workflow-step-prompt-section.tsx`
+- `apps/web/components/settings/workflow-step-prompt-section.test.tsx`
+- `apps/web/components/settings/prompts-settings.tsx`
+- `apps/web/components/settings/prompts-settings.test.tsx`
+- `apps/web/components/settings/utility-agent-dialog.tsx`
+- `apps/web/components/settings/utility-agent-dialog.test.tsx`
+- `apps/web/components/automations/prompt-section.tsx`
+- `apps/web/components/automations/prompt-section.test.tsx`
+- Relevant locale catalogs when existing help text changes
+
+## Dependencies
+
+Task 01.
+
+## Parallelism
+
+Parallel-safe with Task 03 after Task 01. The tasks use disjoint production and test files.
+
+## Inputs
+
+- Spec: `Settings surfaces`, `Failure modes`, and the custom-prompt and utility scenarios.
+- Plan: `Other Settings prompts` and `Shared prompt editor`.
+- Existing patterns: workflow step prompt references, prompt save coordination, automation placeholders, and utility variables.
+
+## Output contract
+
+Report the RED error, enabled completion modes, textarea-test migrations, files changed, command results, blockers, risks, and synchronized task and plan status.
+
+## Results
+
+- Workflow, workflow-step, automation, custom-prompt, and utility-agent fields use `SettingsPromptEditor`.
+- Workflow, automation, and custom prompts enable saved-prompt references; the open custom prompt is excluded. Utility prompts keep only their template-variable completion mode.
+- Focused core tests pass: 10 tests across 5 files.
+- Existing save, reset, read-only, dirty-state, and controlled-value behavior remains owned by each consumer.
diff --git a/docs/plans/settings-prompt-editor/task-05-browser-coverage.md b/docs/plans/settings-prompt-editor/task-05-browser-coverage.md
new file mode 100644
index 0000000000..d4b40a5663
--- /dev/null
+++ b/docs/plans/settings-prompt-editor/task-05-browser-coverage.md
@@ -0,0 +1,63 @@
+---
+id: "05-browser-coverage"
+title: "Add browser coverage"
+status: done
+wave: 4
+depends_on: ["02-quick-actions", "03-watch-prompts", "04-core-settings-prompts"]
+plan: "plan.md"
+spec: "../../specs/ui/settings-prompt-editor.md"
+---
+
+# Task 05: Add browser coverage
+
+## Acceptance
+
+- Desktop E2E proves GitHub quick-action placeholder and saved-prompt completion, save coordination, and reload persistence.
+- Custom-prompt E2E proves that an editor offers other prompts but omits itself.
+- Mobile E2E proves the same GitHub completion value with touch selection and no viewport overflow.
+
+## Verification
+
+Follow E2E TDD. Run each new scenario before the production migration and record the missing-menu or textarea failure.
+
+```bash
+cd apps && pnpm install --frozen-lockfile && cd web && pnpm e2e:run tests/integrations/github-quick-action-prompt-autocomplete.spec.ts tests/settings/prompts-settings.spec.ts tests/workflow/workflow-step-autocomplete.spec.ts && pnpm e2e:run --project mobile-chrome tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts
+```
+
+## Files likely touched
+
+- `apps/web/e2e/helpers/settings-prompt-editor.ts`
+- `apps/web/e2e/tests/integrations/github-quick-action-prompt-autocomplete.spec.ts`
+- `apps/web/e2e/tests/integrations/mobile-github-quick-action-prompt-autocomplete.spec.ts`
+- `apps/web/e2e/tests/settings/prompts-settings.spec.ts`
+- `apps/web/e2e/tests/workflow/workflow-step-autocomplete.spec.ts`
+- `apps/web/e2e/tests/workflow/workflow-settings.spec.ts`
+- `apps/web/e2e/tests/workflow/workflow-duplication.spec.ts`
+- `apps/web/e2e/pages/workflow-settings-page.ts` only if a reusable editor locator belongs there
+
+## Dependencies
+
+Tasks 02, 03, and 04.
+
+## Parallelism
+
+Parallel-safe with Task 06 after Tasks 02 through 04. This task owns only browser helpers and E2E files.
+
+## Inputs
+
+- Spec: all Scenarios, with emphasis on quick actions, custom prompts, persistence, and phone behavior.
+- Plan: `E2E Tests` and `Mobile design contract`.
+- Existing patterns: `workflow-step-autocomplete.spec.ts`, `mobile-github-integration-layout.spec.ts`, and Monaco's `native-edit-context` focus target.
+
+## Output contract
+
+Report RED failures, discovered test counts, desktop and mobile results, screenshots or traces, cleanup evidence, overflow measurements, blockers, risks, and synchronized task and plan status.
+
+## Results
+
+- The first desktop run failed because the saved-prompt provider was not registered when prompt data loaded before Monaco's one-shot mount callback. The fix added latest-callback registration and a focused regression test.
+- A mobile run then exposed Monaco's default `z-index: 40` competing with the mobile floating Save bar. The Settings prompt editor raises its suggestion widget to `z-index: 50`, allowing real touch selection.
+- Desktop GitHub quick-action completion and reload persistence pass: 1 test.
+- Mobile GitHub quick-action completion passes with touch editor entry, touch completion selection, viewport containment, and no document horizontal overflow: 1 test.
+- Existing workflow completion coverage passes: 3 tests. Custom-prompt exclusion and existing prompt settings coverage passes: 4 tests.
+- Fresh desktop and mobile PR asset captures were generated and visually inspected for the migrated GitHub quick-action editor.
diff --git a/docs/plans/settings-prompt-editor/task-06-public-documentation.md b/docs/plans/settings-prompt-editor/task-06-public-documentation.md
new file mode 100644
index 0000000000..3368684066
--- /dev/null
+++ b/docs/plans/settings-prompt-editor/task-06-public-documentation.md
@@ -0,0 +1,54 @@
+---
+id: "06-public-documentation"
+title: "Update public documentation"
+status: done
+wave: 4
+depends_on: ["02-quick-actions", "03-watch-prompts", "04-core-settings-prompts"]
+plan: "plan.md"
+spec: "../../specs/ui/settings-prompt-editor.md"
+---
+
+# Task 06: Update public documentation
+
+## Acceptance
+
+- Integration documentation explains placeholder and saved-prompt completion for quick actions and watch prompts.
+- Workflow and developer-tool documentation explain the shared completion behavior and utility-agent exception.
+- Public documentation validation passes without a new navigation entry.
+
+## Verification
+
+```bash
+rg -n "quick actions|saved prompt|workflow prompt|utility" docs/public/integrations.md docs/public/workflow-tips.md docs/public/developer-tools.md && node --test scripts/validate-public-docs.test.mjs && node scripts/validate-public-docs.mjs
+```
+
+## Files likely touched
+
+- `docs/public/integrations.md`
+- `docs/public/workflow-tips.md`
+- `docs/public/developer-tools.md`
+
+## Dependencies
+
+Tasks 02, 03, and 04.
+
+## Parallelism
+
+Parallel-safe with Task 05 after Tasks 02 through 04. This task owns only public documentation files.
+
+## Inputs
+
+- Spec: `What`, `Settings surfaces`, and `Out of scope`.
+- Plan: `Public documentation`.
+- Current docs: existing saved-prompt, workflow, watcher, quick-action, and utility sections.
+
+## Output contract
+
+Report pages changed, each page's primary Diátaxis type, exact command results, link impact, blockers, risks, and synchronized task and plan status.
+
+## Results
+
+- Updated `docs/public/integrations.md`, `docs/public/workflow-tips.md`, and `docs/public/developer-tools.md`.
+- The pages explain placeholder and saved-prompt completion, route-level save/reset behavior, nested references, current-prompt exclusion, and the utility-agent exception.
+- `node --test scripts/validate-public-docs.test.mjs` passes: 61 tests.
+- `node scripts/validate-public-docs.mjs` passes: 41 published pages validated.
diff --git a/docs/public/developer-tools.md b/docs/public/developer-tools.md
index 765621610a..c4d42212af 100644
--- a/docs/public/developer-tools.md
+++ b/docs/public/developer-tools.md
@@ -95,7 +95,7 @@ Open **Settings > Prompts** (`/settings/prompts`) to add, edit, or delete reusab
Type `@` in the task chat composer and select a prompt. The visible message keeps the `@name`; Kandev expands the prompt content into hidden system context for the agent. References are recognized only at the start of the text or after whitespace and must match the stored name. Prompt content can reference other saved prompts. Expansion stops at a depth of eight, skips cycles, and includes each prompt only once.
-The same `@name` reference works in a workflow step's Prompt field and in a GitHub Review Watch's prompt; see [Saved prompt references in step prompts](workflow-tips.md#saved-prompt-references-in-step-prompts).
+The Settings prompt editor also offers the same `@name` completion when you edit a saved prompt, a workflow prompt, a workflow step, an automation instruction, a quick action, or a provider watch. The prompt being edited is excluded from its own completion list, so selecting a reference cannot create a direct self-reference by accident. The same `@name` reference works in a workflow step's Prompt field and in a GitHub Review Watch's prompt; see [Saved prompt references in step prompts](workflow-tips.md#saved-prompt-references-in-step-prompts).
Kandev seeds these built-ins:
@@ -111,6 +111,8 @@ A saved prompt is an instruction, not an authorization or policy boundary. Execu
+Utility-agent prompt templates use a separate template engine. Type `{{` to select the utility variables shown by the editor, but do not use `@name` saved-prompt references there. Utility calls are sessionless and do not resolve saved prompts at runtime. Put reusable nested instructions in a saved prompt used by a task, workflow, automation, or integration prompt instead.
+
## Voice Mode
diff --git a/docs/public/integrations.md b/docs/public/integrations.md
index 53d65c5a46..c1951085d7 100644
--- a/docs/public/integrations.md
+++ b/docs/public/integrations.md
@@ -43,6 +43,14 @@ GitHub, GitLab, Azure DevOps, Jira, and Linear configuration are workspace-speci
Provider secrets saved by these forms use Kandev's encrypted secret store. The backend must still decrypt them to make API requests. Limit access to settings and the Kandev data directory, and use the narrowest provider scope that works.
+### Author prompt fields
+
+Quick actions and provider watch prompts use the same inline prompt editor. Type `{{` to open the provider's placeholder list, or type `@` after whitespace to find a [saved prompt](developer-tools.md#saved-prompts). Select an item to insert it into the draft. The menu shows the exact tokens supported by that provider and prompt type.
+
+Common quick-action tokens are `{{url}}` and `{{title}}`. Jira quick actions also provide `{{key}}` and `{{description}}`. Watch prompts expose provider data such as `pr.*`, `issue.*`, `mr.*`, `work_item.*`, or `pull_request.*`, depending on the integration and watch type.
+
+Selecting a completion changes the local draft only. Use the route-level **Save changes** action to persist it; **Reset** restores the provider defaults and **Cancel** or discard returns to the last saved draft. Saved-prompt references are expanded when the watch or action runs, so later edits to the referenced prompt apply without changing each integration setting.
+
### The Enabled switch
Jira, Linear, and Sentry pages show an **Enabled** switch. It is a browser-local preference, saved per installation in that browser and on by default. It controls some client-side entry points, availability checks, and configuration fetches; settings pages can still poll provider health. It does not delete backend configuration and does not stop a server-side watch. Pause/delete watches or remove the provider configuration when processing must stop.
diff --git a/docs/public/workflow-tips.md b/docs/public/workflow-tips.md
index 46de0927e6..93f56d0a7a 100644
--- a/docs/public/workflow-tips.md
+++ b/docs/public/workflow-tips.md
@@ -146,6 +146,12 @@ promotion.
Pull candidates are selected by board position, then priority, queue time, creation time, and ID. A candidate that cannot be moved is skipped. Pulling runs for every limited step; a feeder is only needed for overflow created outside the destination step.
+### Complete prompts while editing
+
+Workflow and step prompt fields use the inline prompt editor. Type `@` after whitespace to select a saved prompt. In a step prompt, type `{{` to select `{{task_prompt}}` and other tokens supported by that step. The completion menu inserts the reference into the draft; it does not save the workflow. Use **Save changes** when the prompt is ready.
+
+The workflow-level prompt supports saved-prompt references but does not expand step-only variables. `{{task_prompt}}` is available in a step prompt because it is replaced with the task description when that step runs.
+
## Events and actions
diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md
index e733b3b4ff..253dedec9a 100644
--- a/docs/specs/INDEX.md
+++ b/docs/specs/INDEX.md
@@ -219,6 +219,7 @@ Subscription quota tracking and per-agent cheap-model profile routing.
| [message-queue-send-now](ui/message-queue-send-now.md) | shipped |
| [settings-manual-save](ui/settings-manual-save.md) | shipped |
| [settings-discovery](ui/settings-discovery.md) | shipped |
+| [settings-prompt-editor](ui/settings-prompt-editor.md) | shipped |
| [settings-typography](settings-typography/spec.md) | draft |
| [executor-settings-card-spacing](ui/executor-settings-card-spacing.md) | shipped |
| [quick-chat-elevation](ui/quick-chat-elevation.md) | building |
diff --git a/docs/specs/ui/settings-prompt-editor.md b/docs/specs/ui/settings-prompt-editor.md
new file mode 100644
index 0000000000..b27f8abe28
--- /dev/null
+++ b/docs/specs/ui/settings-prompt-editor.md
@@ -0,0 +1,106 @@
+---
+status: shipped
+created: 2026-08-20
+owner: Kandev
+---
+
+# Settings Prompt Editor
+
+## Why
+
+Prompt fields in Settings use different editors and completion rules. Users cannot reliably discover placeholders or reference saved prompts across these fields.
+
+## What
+
+- Kandev provides one shared prompt editor for prompt-authoring fields in Settings.
+- The editor stores plain text and preserves each existing settings and save contract.
+- Typing `{{` shows only the placeholders that the current prompt context supports.
+- Selecting a placeholder inside an existing `{{...}}` pair replaces only the
+ placeholder name and preserves the pair's closing braces. Selecting a
+ placeholder after an unfinished `{{` adds exactly one closing pair.
+- Typing `@` shows saved prompts when the runtime resolves saved-prompt references for that field.
+- Typing a name prefix after `@` keeps saved prompts whose names match that
+ prefix available for selection.
+- Prompt names may contain spaces. The active mention starts at a
+ whitespace-bounded `@`, and the full text after it remains part of the
+ filtering range while the user types.
+- A selected saved prompt inserts its `@name` reference. The editor does not inline or copy the saved prompt content.
+- The saved-prompt list updates when a prompt is added, edited, or removed.
+- A custom prompt can reference other saved prompts. Its editor does not suggest the prompt that is currently open.
+- Keyboard, pointer, and touch users can open, filter, and select completion items.
+- The editor keeps completion providers scoped to their editor model. One open editor cannot show completion items from another editor.
+- Each field keeps its existing dirty state, validation, reset behavior, save coordinator, and persisted text format.
+- Desktop and mobile settings routes provide the same completion functions.
+
+### Settings surfaces
+
+| Surface | Placeholder completion | Saved-prompt completion |
+|---|---|---|
+| GitHub, GitLab, Jira, and Azure DevOps quick actions | Provider placeholders | Yes |
+| GitHub, GitLab, Jira, Linear, Sentry, and Azure DevOps watch prompts | Provider placeholders when available | Yes |
+| Workflow prompt | Workflow placeholders | Yes |
+| Workflow step prompt | Step placeholders | Yes |
+| Automation instructions | Trigger placeholders | Yes |
+| Custom prompt content | None | Yes, excluding the open prompt |
+| Utility agent prompt | Utility template variables | No |
+
+Utility prompts run through the sessionless utility template engine. That engine does not resolve saved-prompt references.
+
+The shared editor does not replace script, query, JSON, or description fields. Those fields have different languages and completion contracts.
+
+## Failure modes
+
+- If saved prompts fail to load, placeholder completion continues to work. The `@` menu shows no saved prompts.
+- If a field has no configured placeholders, `{{` shows no placeholder items.
+- If Monaco loads slowly, the field shows the existing localized editor loading state. The controlled draft remains unchanged.
+- If two prompt editors are mounted together, each editor uses only its own placeholders and saved-prompt rules.
+- Concurrent mounted editors share one in-flight saved-prompt request. A
+ failed request cannot replace a successful result from another editor.
+
+## Persistence guarantees
+
+This feature adds no new persistence. Each settings surface continues to store its current plain-text value through its current API.
+
+Selecting a completion item changes only the local draft. The existing settings save action remains the persistence boundary.
+
+## Scenarios
+
+- **GIVEN** a saved prompt named `review-helper`, **WHEN** a user types `@rev` in a GitHub quick action, **THEN** the editor offers `@review-helper`.
+- **GIVEN** that suggestion, **WHEN** the user selects it, **THEN** the draft contains `@review-helper` and no save starts.
+- **GIVEN** a GitHub quick action editor, **WHEN** a user types `{{`, **THEN** the editor offers `{{url}}` and `{{title}}`.
+- **GIVEN** an editor contains `{{}}` with the cursor between the brace pairs,
+ **WHEN** the user selects `{{task_prompt}}`, **THEN** the draft contains
+ exactly `{{task_prompt}}` with no duplicated closing braces.
+- **GIVEN** an editor contains an unfinished `{{` token, **WHEN** the user
+ selects `{{task_prompt}}`, **THEN** the draft contains exactly
+ `{{task_prompt}}` with one closing brace pair.
+- **GIVEN** an editor contains `{{ta_prompt}}` with the cursor after `ta`,
+ **WHEN** the user selects `{{task_prompt}}`, **THEN** the partial name and
+ its existing suffix are replaced and the draft contains exactly
+ `{{task_prompt}}`.
+- **GIVEN** a saved prompt named `changes-walkthrough`, **WHEN** a user types
+ `@c`, **THEN** the completion list keeps `@changes-walkthrough` available.
+- **GIVEN** a saved prompt named `Daily Summary`, **WHEN** a user types
+ `@Daily `, **THEN** the completion list keeps `@Daily Summary` available
+ and selecting it inserts the complete mention.
+- **GIVEN** two open editors with different placeholder lists, **WHEN** a user types `{{` in either editor, **THEN** only that editor's placeholders appear.
+- **GIVEN** an open workflow prompt, **WHEN** another route adds a saved prompt, **THEN** the next `@` list includes it.
+- **GIVEN** a custom prompt named `release`, **WHEN** the user edits `release` and types `@`, **THEN** the list omits `@release` and includes other saved prompts.
+- **GIVEN** a utility agent prompt, **WHEN** the user types `{{`, **THEN** utility variables appear and no saved-prompt completion is offered.
+- **GIVEN** a failed saved-prompt request, **WHEN** the user types a supported placeholder prefix, **THEN** placeholder completion still works.
+- **GIVEN** an unsaved prompt edit, **WHEN** the user leaves the route, **THEN** the existing settings navigation guard still handles the draft.
+- **GIVEN** GitHub quick actions on a phone, **WHEN** the user selects both completion types, **THEN** no horizontal page overflow occurs.
+- **GIVEN** a saved quick action that contains a placeholder and saved-prompt reference, **WHEN** the page reloads, **THEN** the editor shows the same stored text.
+
+## Out of scope
+
+- Rich-text formatting, Markdown preview, or Tiptap document storage.
+- Changes to saved-prompt expansion, placeholder interpolation, or backend APIs.
+- Prompt composers outside Settings, such as task creation and task chat.
+- Completion for scripts, WIQL, JQL, JSON, descriptions, or confirmation fields.
+
+## Implementation plan
+
+See [`../../plans/settings-prompt-editor/plan.md`](../../plans/settings-prompt-editor/plan.md).
+The completion regression repair is tracked in
+[`../../plans/settings-prompt-completion-regressions/plan.md`](../../plans/settings-prompt-completion-regressions/plan.md).