feat: skip discovery questions for unmodified example prompts (#3257)

* feat: skip discovery questions for unmodified example prompts

When a user selects a curated example prompt from the gallery and sends
it without modification, the agent now skips all discovery/clarification
questions and directly generates the artifact at full quality.

Adds an `examplePrompt` metadata flag that injects a dedicated system
prompt override instructing the agent to treat the prompt as a complete
creative brief requiring showcase-quality generation.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>

* feat: enrich example prompt override with title and pre-filled brief

- Pass example prompt title and artifact-type-specific discovery brief
  through metadata so the agent receives full context without asking
- Only skip discovery on the user's first example prompt usage
  (localStorage gated), subsequent uses follow normal discovery flow
- Convert EXAMPLE_PROMPT_OVERRIDE from static string to dynamic builder
  that interpolates title and brief key-value pairs

* fix: prevent handlePromptChange from clearing examplePromptInfoRef

The ref is now only cleared by onExamplePromptStatusChange(null) which
HomeHero triggers when the user actually edits the text or clicks clear.
This prevents the race where usePromptExample sets the ref and then
onPromptChange immediately clears it in the same tick.

* fix: honor example-prompt discovery skip in API/BYOK mode

API/BYOK runs compose their system prompt through @open-design/contracts,
whose ProjectMetadata and composeSystemPrompt only knew about
skipDiscoveryBrief. Unmodified gallery example prompts therefore skipped
discovery in daemon-backed runs but still asked discovery questions in
API mode, making the feature mode-dependent. Mirror the daemon's
examplePrompt/examplePromptTitle/examplePromptBrief metadata and
buildExamplePromptOverride into contracts so both composers behave
identically.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
elihahah666
2026-06-03 12:56:44 +00:00
committed by GitHub
co-authored by qiongyu1999 Claude Opus 4
parent 8f9ec58c26
commit 4aea5fdb01
8 changed files with 196 additions and 4 deletions
+36 -1
View File
@@ -129,6 +129,9 @@ type ProjectMetadata = {
platformTargets?: string[] | null;
inspirationDesignSystemIds?: string[];
skipDiscoveryBrief?: boolean | null;
examplePrompt?: boolean | null;
examplePromptTitle?: string | null;
examplePromptBrief?: Record<string, string> | null;
imageModel?: string | null;
imageAspect?: string | null;
imageStyle?: string | null;
@@ -278,6 +281,35 @@ printf '%s\\n' "\$last"
For the best fal image model use \`--model flux-pro-ultra\`. For video use \`--model veo-3-fal\` or \`--model wan-2.1-t2v\`. Always pass \`--surface\` explicitly (\`image\`, \`video\`, or \`audio\`). Any \`fal-ai/*\` path (e.g. \`fal-ai/flux/schnell\`, \`fal-ai/wan-i2v\`) is also a valid \`--model\` value for image/video — pass it through as-is without substitution.`;
export function buildExamplePromptOverride(
title?: string | null,
brief?: Record<string, string> | null,
): string {
let text = `# Example prompt mode — full-quality direct generation
The user selected a curated example prompt from the gallery and sent it without modification. This prompt is a complete, self-contained creative brief that has been carefully designed to produce a showcase-quality artifact.`;
if (title) {
text += `\n\nSelected example: "${title}"`;
}
if (brief && Object.keys(brief).length > 0) {
text += `\n\nPre-filled creative brief (treat as if the user already answered all discovery questions):`;
for (const [key, value] of Object.entries(brief)) {
text += `\n- ${key.replace(/_/g, ' ')}: ${value}`;
}
}
text += `\n\nRules:
1. Do NOT emit \`<question-form id="discovery">\`, do NOT show "Quick brief — 30 seconds", and do NOT ask any clarifying questions.
2. Treat the user's message as the FULL specification — it contains all visual direction, content themes, and structural intent needed.
3. Generate the artifact at your absolute highest quality. This is a showcase piece — match or exceed the standard of a hand-crafted design.
4. Infer any unspecified details (copy, layout choices, imagery descriptions) in a way that is maximally coherent with the stated creative direction.
5. Proceed directly to planning and building. Output your TodoWrite plan and then the artifact immediately.`;
return text;
}
const ACTIVE_DESIGN_SYSTEM_VISUAL_DIRECTION_OVERRIDE = `
---
@@ -510,7 +542,10 @@ export function composeSystemPrompt({
metadata?.kind === 'video' ||
metadata?.kind === 'audio';
if (metadata?.skipDiscoveryBrief === true) {
if (metadata?.examplePrompt === true) {
parts.push(buildExamplePromptOverride(metadata.examplePromptTitle, metadata.examplePromptBrief));
parts.push('\n\n---\n\n');
} else if (metadata?.skipDiscoveryBrief === true) {
parts.push(SKIP_DISCOVERY_BRIEF_OVERRIDE);
parts.push('\n\n---\n\n');
}
+5
View File
@@ -526,6 +526,11 @@ export function EntryShell({
? { contextConnectors: payload.contextConnectors }
: {}),
...(payload.workingDir ? { userWorkingDir: payload.workingDir } : {}),
...(payload.examplePromptContext ? {
examplePrompt: true,
examplePromptTitle: payload.examplePromptContext.title,
examplePromptBrief: payload.examplePromptContext.brief,
} : {}),
};
onCreateProject({
name,
+51
View File
@@ -61,6 +61,12 @@ export interface HomeHeroSubmitHandler {
(): void;
}
export interface ExamplePromptInfo {
title: string;
artifactType: string;
brief: Record<string, string>;
}
interface Props {
prompt: string;
onPromptChange: (value: string) => void;
@@ -107,6 +113,7 @@ interface Props {
contextItemCount: number;
error: string | null;
showActivePluginChip?: boolean;
onExamplePromptStatusChange?: (info: ExamplePromptInfo | null) => void;
}
interface HomeHeroDesignSystemOption {
@@ -192,6 +199,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
contextItemCount,
error,
showActivePluginChip = true,
onExamplePromptStatusChange,
},
ref,
) {
@@ -541,6 +549,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
function clearSelectedPromptExample() {
if (selectedPromptExample) {
onPromptChange('');
onExamplePromptStatusChange?.(null);
}
setSelectedPromptExample(null);
}
@@ -550,6 +559,11 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
label: promptExampleChipLabel(example),
promptText: example,
});
onExamplePromptStatusChange?.({
title: promptExampleChipLabel(example),
artifactType: activeChipId ?? 'prototype',
brief: briefForChipId(activeChipId ?? 'prototype'),
});
onPromptChange(example);
setSelectedIndex(0);
requestAnimationFrame(() => {
@@ -567,6 +581,11 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
label: record.title,
promptText,
});
onExamplePromptStatusChange?.({
title: record.title,
artifactType: chipId,
brief: briefForPluginPreset(record, chipId),
});
onPickExamplePlugin(record, chipId, promptText);
}
@@ -826,6 +845,7 @@ export const HomeHero = forwardRef<HTMLTextAreaElement, Props>(function HomeHero
onPromptChange(e.target.value);
if (selectedPromptExample && e.target.value !== selectedPromptExample.promptText) {
setSelectedPromptExample(null);
onExamplePromptStatusChange?.(null);
}
setSelectedIndex(0);
}}
@@ -3070,3 +3090,34 @@ function homeHeroChipPromptExamples(chipId: string, locale: Locale): string[] {
function isChineseLocale(locale: Locale): boolean {
return locale === 'zh-CN' || locale === 'zh-TW';
}
function briefForChipId(chipId: string): Record<string, string> {
switch (chipId) {
case 'prototype':
return { artifact_type: 'web prototype', audience: 'product evaluators', fidelity: 'high-fidelity' };
case 'deck':
return { artifact_type: 'pitch deck / presentation', audience: 'decision makers', slide_count: '10-15 pages' };
case 'image':
return { artifact_type: 'image', style: 'cinematic, high-quality, on-brand' };
case 'video':
return { artifact_type: 'video', style: 'cinematic, high-quality, on-brand' };
case 'hyperframes':
return { artifact_type: 'motion graphic / animated sequence', style: 'cinematic, polished transitions' };
case 'audio':
return { artifact_type: 'audio', style: 'professional, polished, brand-appropriate' };
default:
return { artifact_type: chipId };
}
}
function briefForPluginPreset(record: InstalledPluginRecord, chipId: string): Record<string, string> {
const brief: Record<string, string> = { ...briefForChipId(chipId) };
const fields = record.manifest?.od?.inputs ?? [];
for (const field of fields) {
const value = field.default ?? field.placeholder;
if (value != null && typeof value === 'string' && value.trim()) {
brief[field.name] = value;
}
}
return brief;
}
+14 -2
View File
@@ -7,7 +7,7 @@
// surface by lifting its plugin orchestration up here so the prompt
// textarea can live centered in the hero.
import { useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type {
ApplyResult,
ConnectorDetail,
@@ -51,7 +51,7 @@ import type {
SkillSummary,
} from '../types';
import { inlineMentionToken } from '../utils/inlineMentions';
import { HomeHero } from './HomeHero';
import { HomeHero, type ExamplePromptInfo } from './HomeHero';
import { findChip, HOME_HERO_CHIPS, type HomeHeroChip } from './home-hero/chips';
import {
buildHomeMediaComposer,
@@ -240,6 +240,10 @@ export function HomeView({
const [mcpLoading, setMcpLoading] = useState(true);
const [prompt, setPrompt] = useState('');
const [promptEditedByUser, setPromptEditedByUser] = useState(false);
const examplePromptInfoRef = useRef<ExamplePromptInfo | null>(null);
const handleExamplePromptStatusChange = useCallback((info: ExamplePromptInfo | null) => {
examplePromptInfoRef.current = info;
}, []);
const [error, setError] = useState<string | null>(null);
const [designSystemLogoById, setDesignSystemLogoById] = useState<Record<string, string>>({});
const [elevenLabsVoices, setElevenLabsVoices] = useState<AudioVoiceOption[]>([]);
@@ -1271,6 +1275,13 @@ export function HomeView({
contextMcpServers,
contextConnectors,
attachments: stagedFiles,
...(() => {
if (!examplePromptInfoRef.current) return {};
const key = 'od:example-prompt-used';
if (localStorage.getItem(key)) return {};
localStorage.setItem(key, '1');
return { examplePromptContext: examplePromptInfoRef.current };
})(),
});
}
@@ -1330,6 +1341,7 @@ export function HomeView({
onPickChip={pickChip}
contextItemCount={contextItemCount}
error={error}
onExamplePromptStatusChange={handleExamplePromptStatusChange}
/>
<RecentProjectsStrip
@@ -44,6 +44,7 @@ export interface PluginLoopSubmit {
// Files staged on Home before the project exists. App uploads them
// into the created project's Design Files before the first auto-send.
attachments?: File[];
examplePromptContext?: { title: string; artifactType: string; brief: Record<string, string> };
}
interface Props {
+8
View File
@@ -153,6 +153,14 @@ export interface ProjectMetadata {
// Batch/API-created projects can opt out of the initial discovery form so
// the first agent turn builds immediately from the submitted brief.
skipDiscoveryBrief?: boolean;
// Set when the user submits an unmodified curated example prompt from the
// gallery. Skips discovery AND requests full-quality direct generation,
// treating the curated title/brief as the complete creative brief. Honored
// by both the daemon and contracts (API/BYOK) system-prompt composers so the
// feature is not mode-dependent.
examplePrompt?: boolean;
examplePromptTitle?: string;
examplePromptBrief?: Record<string, string>;
// Plugins selected through @ mentions on Home. These are additive
// context references; the explicit "Use plugin" snapshot, when present,
// remains the primary executable plugin for the run.
+33 -1
View File
@@ -124,6 +124,35 @@ export const SKIP_DISCOVERY_BRIEF_OVERRIDE = `# Automated project mode — skip
This project was created through the daemon API with \`skipDiscoveryBrief: true\`. Override the discovery rules below: do NOT emit \`<question-form id="discovery">\`, do NOT show "Quick brief — 30 seconds", and do NOT ask a first-turn clarification form. Do not call AskUserQuestion, do not emit any question form or choice card, and do not wait for user input. Treat the user's first message and project metadata as the brief, choose reasonable defaults for any missing details, then proceed directly to planning/building under the normal artifact workflow.`;
export function buildExamplePromptOverride(
title?: string | null,
brief?: Record<string, string> | null,
): string {
let text = `# Example prompt mode — full-quality direct generation
The user selected a curated example prompt from the gallery and sent it without modification. This prompt is a complete, self-contained creative brief that has been carefully designed to produce a showcase-quality artifact.`;
if (title) {
text += `\n\nSelected example: "${title}"`;
}
if (brief && Object.keys(brief).length > 0) {
text += `\n\nPre-filled creative brief (treat as if the user already answered all discovery questions):`;
for (const [key, value] of Object.entries(brief)) {
text += `\n- ${key.replace(/_/g, ' ')}: ${value}`;
}
}
text += `\n\nRules:
1. Do NOT emit \`<question-form id="discovery">\`, do NOT show "Quick brief — 30 seconds", and do NOT ask any clarifying questions.
2. Treat the user's message as the FULL specification — it contains all visual direction, content themes, and structural intent needed.
3. Generate the artifact at your absolute highest quality. This is a showcase piece — match or exceed the standard of a hand-crafted design.
4. Infer any unspecified details (copy, layout choices, imagery descriptions) in a way that is maximally coherent with the stated creative direction.
5. Proceed directly to planning and building. Output your TodoWrite plan and then the artifact immediately.`;
return text;
}
const ACTIVE_DESIGN_SYSTEM_VISUAL_DIRECTION_OVERRIDE = `
---
@@ -247,7 +276,10 @@ export function composeSystemPrompt({
parts.push('\n\n---\n\n');
}
if (metadata?.skipDiscoveryBrief === true) {
if (metadata?.examplePrompt === true) {
parts.push(buildExamplePromptOverride(metadata.examplePromptTitle, metadata.examplePromptBrief));
parts.push('\n\n---\n\n');
} else if (metadata?.skipDiscoveryBrief === true) {
parts.push(SKIP_DISCOVERY_BRIEF_OVERRIDE);
parts.push('\n\n---\n\n');
}
@@ -134,4 +134,52 @@ describe('composeSystemPrompt — API mode (#313)', () => {
expect(prompt).toContain('choose reasonable defaults for any missing details');
});
});
// Regression coverage for #3257 — example-prompt discovery skip must be
// honored in API/BYOK mode (which composes prompts through this contracts
// composer), not only in daemon-backed runs. Without the examplePrompt
// handling here, the same unmodified gallery prompt skipped discovery in
// daemon mode but still asked discovery questions in API mode.
describe('example prompt mode (#3257)', () => {
it('injects the example-prompt override and skips discovery when metadata.examplePrompt is true', () => {
const prompt = composeSystemPrompt({
metadata: { kind: 'prototype', examplePrompt: true },
});
expect(prompt).toContain('Example prompt mode — full-quality direct generation');
expect(prompt).toMatch(/do NOT emit `?<question-form id="discovery">`?/i);
});
it('interpolates the curated title and pre-filled brief', () => {
const prompt = composeSystemPrompt({
metadata: {
kind: 'prototype',
examplePrompt: true,
examplePromptTitle: 'Neon dashboard',
examplePromptBrief: { target_audience: 'developers', fidelity: 'high' },
},
});
expect(prompt).toContain('Selected example: "Neon dashboard"');
expect(prompt).toContain('target audience: developers');
expect(prompt).toContain('fidelity: high');
});
it('pins the example-prompt override above the discovery layer in API mode', () => {
const prompt = composeSystemPrompt({
streamFormat: 'plain',
metadata: { kind: 'prototype', examplePrompt: true },
});
const overrideIdx = prompt.indexOf('Example prompt mode — full-quality direct generation');
const discoveryIdx = prompt.indexOf('# OD core directives');
expect(overrideIdx).toBeGreaterThanOrEqual(0);
expect(overrideIdx).toBeLessThan(discoveryIdx);
});
it('prefers the example-prompt override over the plain skip-discovery override', () => {
const prompt = composeSystemPrompt({
metadata: { kind: 'prototype', examplePrompt: true, skipDiscoveryBrief: true },
});
expect(prompt).toContain('Example prompt mode — full-quality direct generation');
expect(prompt).not.toContain(SKIP_DISCOVERY_BRIEF_OVERRIDE);
});
});
});