fix(custom-widgets): make embedded prompt standalone

This commit is contained in:
Thomas Camlong committed 2026-07-20 18:55:22 +02:00
1 parent 80d364c050
commit 48f73d919d
6 files changed
+35 -7

No files matched your search

@@ -10,8 +10,14 @@ metadata:
Create one widget at a time. Repeat the workflow when the user asks for several widgets; do not invent packs or shared connections.
## Standalone workflow
When Homarr MCP tools and `homarr://` resources are unavailable, continue offline using this skill and the supplied authoring prompt. Do not search repeatedly for missing tools, stop, or ask whether to proceed. Produce the requested credential-free manifest and JSX directly, without claiming they were validated, previewed, or installed. The user will paste them into Homarr and return any diagnostics for correction.
## Connected workflow
Use this workflow only when the Homarr MCP tools and resources are actually available:
1. Get `homarr-custom-widget-author`, then read `homarr://custom-widgets/schema` and `homarr://custom-widgets/components`.
2. Read the API documentation supplied by the user. Ask for missing base URLs and credentials only when needed.
3. Build one `homarr-custom-widget-v2` document. Keep credentials outside the document.
@@ -20,14 +20,14 @@ The **Schema** action beside Undo, Redo, and Format expands the generated JSON S
## ChatGPT copy/paste
Enter a short request and optional API documentation URL, then select **Copy AI prompt**. Its dropdown also offers a self-contained version with the Custom Widget skill embedded for models that cannot discover skills or MCP resources. Paste either prompt into ChatGPT or another model. The model must return exactly:
Enter a short request and optional API documentation URL, then select **Copy AI prompt**. Its dropdown also offers a self-contained version with the Custom Widget skill embedded for models that cannot discover skills or MCP resources. The copied text starts with your request and repeats it as the final instruction so it cannot be lost behind the authoring reference. The embedded version explicitly uses a standalone workflow: the model must author immediately without searching for Homarr MCP tools or claiming that it validated the result. Paste either prompt into ChatGPT or another model. The model must return exactly:
1. A fenced `json` block with one complete widget and `template: "__HOMARR_TEMPLATE__"`.
2. A fenced `jsx` block with the readable template.
Use **Paste AI response** to fill the workbench. Enter credentials yourself, test, and preview. If saving fails, select or copy the validation errors shown by Homarr and give that plain text to the model. Homarr does not generate a separate fix prompt or assume the model can access local skill files.
The prompt is under 8,000 characters. It includes the installed Mantine version and capability rules but not a complete prop list, MCP workflow, server URL, or credentials.
The normal prompt is under 8,000 characters. It includes the installed Mantine version and capability rules but not a complete prop list, MCP workflow, server URL, or credentials. The embedded-skill variant is longer because it includes the portable offline instructions.
## Options
+12 -3
View File
@@ -50,16 +50,25 @@ export function buildCustomWidgetAiPrompt(
documentationUrl?: string | null,
embeddedSkill?: string,
) {
const sections = [AUTHORING_PROMPT];
const requestedWidget = redactText(request?.trim() || "Describe the widget you want to create.");
const sections = [`# User request\n\n${requestedWidget}`];
if (documentationUrl) sections.push(`API documentation: ${redactUrl(documentationUrl)}`);
sections.push(`Requested widget: ${redactText(request?.trim() || "Describe the widget you want to create.")}`);
if (embeddedSkill) {
sections.push(`Use this embedded Homarr Custom Widget skill while authoring:\n\n${embeddedSkill}`);
sections.push(`## Execution mode: standalone copy/paste
You do not have to connect to Homarr MCP or access any \`homarr://\` resource to complete this request. Do not search for unavailable Homarr tools, do not stop because they are unavailable, and do not ask whether to proceed. Author the best valid widget you can from the instructions embedded below. Do not claim that you validated, previewed, or installed it; the user will paste your two output blocks into Homarr for validation.`);
}
sections.push(AUTHORING_PROMPT);
if (embeddedSkill) sections.push(`## Embedded Homarr Custom Widget skill\n\n${embeddedSkill}`);
if (rawResponse) sections.push(`Sample API response:\n\n\`\`\`json\n${redactResponse(rawResponse)}\n\`\`\``);
if (currentConfig) {
sections.push(`Current widget:\n\n\`\`\`json\n${JSON.stringify(redactValue(currentConfig), null, 2)}\n\`\`\``);
}
sections.push(`## Produce the response now
Create this widget: ${requestedWidget}
Return exactly one complete \`json\` fenced block and one complete \`jsx\` fenced block as specified above. Do not respond with a plan, a refusal, a tool-access warning, or a question.`);
const prompt = sections.join("\n\n");
return embeddedSkill ? prompt : prompt.slice(0, 8_000);
}
@@ -25,8 +25,14 @@ metadata:
Create one widget at a time. Repeat the workflow when the user asks for several widgets; do not invent packs or shared connections.
## Standalone workflow
When Homarr MCP tools and \`homarr://\` resources are unavailable, continue offline using this skill and the supplied authoring prompt. Do not search repeatedly for missing tools, stop, or ask whether to proceed. Produce the requested credential-free manifest and JSX directly, without claiming they were validated, previewed, or installed. The user will paste them into Homarr and return any diagnostics for correction.
## Connected workflow
Use this workflow only when the Homarr MCP tools and resources are actually available:
1. Get \`homarr-custom-widget-author\`, then read \`homarr://custom-widgets/schema\` and \`homarr://custom-widgets/components\`.
2. Read the API documentation supplied by the user. Ask for missing base URLs and credentials only when needed.
3. Build one \`homarr-custom-widget-v2\` document. Keep credentials outside the document.
@@ -15,11 +15,16 @@ describe("buildCustomWidgetAiPrompt", () => {
CUSTOM_WIDGET_SKILL_MD,
);
expect(prompt).toContain("Use this embedded Homarr Custom Widget skill while authoring:");
expect(prompt.startsWith("# User request\n\nCreate a status widget")).toBe(true);
expect(prompt).toContain("## Execution mode: standalone copy/paste");
expect(prompt).toContain("Do not search for unavailable Homarr tools");
expect(prompt).toContain("## Embedded Homarr Custom Widget skill");
expect(prompt).toContain("# Homarr Custom Widget");
expect(prompt).toContain("When Homarr MCP tools and `homarr://` resources are unavailable, continue offline");
expect(prompt).toContain("do not assume access to repository-relative files");
expect(prompt).not.toContain("references/schema.md");
expect(prompt.endsWith(CUSTOM_WIDGET_SKILL_MD)).toBe(true);
expect(prompt.endsWith("Do not respond with a plan, a refusal, a tool-access warning, or a question.")).toBe(true);
expect(prompt.match(/Create a status widget/g)).toHaveLength(2);
});
test("is concise and describes the one-widget v2 contract", () => {
@@ -37,6 +42,7 @@ describe("buildCustomWidgetAiPrompt", () => {
expect(prompt).toContain('"optionsBinding"');
expect(prompt).toContain("Never infer a binding from matching names");
expect(prompt).toContain("Create a Portainer widget");
expect(prompt.startsWith("# User request\n\nCreate a Portainer widget")).toBe(true);
expect(prompt.length).toBeLessThanOrEqual(8_000);
});
@@ -14,6 +14,7 @@ describe("custom widget Agent Skill", () => {
expect(getCustomWidgetSkill().skillMd).toBe(source);
expect(source).not.toContain("references/schema.md");
expect(source).toContain("do not assume access to repository-relative files");
expect(source).toContain("When Homarr MCP tools and `homarr://` resources are unavailable, continue offline");
});
test("publishes repository-based skills.sh installation metadata", () => {