Skip to content

Commit e7babd7

Browse files
committed
fix(core): ask before optional tweak work
1 parent 27a71b0 commit e7babd7

6 files changed

Lines changed: 45 additions & 20 deletions

File tree

packages/core/src/generate.test.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ describe('composeSystemPrompt()', () => {
181181
const prompt = composeSystemPrompt({ mode: 'create' });
182182
for (const guardrail of [
183183
'Section/content beats needed to avoid sparse output',
184-
'Palette, type ladder, and tweakable tokens',
184+
'Palette, type ladder, candidate tweakable tokens',
185185
'No hotlinked stock or placeholder images',
186186
'Content must be domain-specific',
187187
'#0E0E10',
@@ -236,6 +236,18 @@ describe('composeSystemPrompt()', () => {
236236
expect(prompt).toContain('TWEAK_DEFAULTS');
237237
});
238238

239+
it('create mode asks before high-impact ambiguity and treats tweaks as optional', () => {
240+
const prompt = composeSystemPrompt({ mode: 'create' });
241+
expect(prompt).toContain('ask before editing instead of guessing');
242+
expect(prompt).toContain('optional feature would add meaningful work');
243+
expect(prompt).toContain('Tweak controls would require extra design-token work');
244+
expect(prompt).toContain('Expose tweaks selectively');
245+
expect(prompt).toContain('Skip tweak work for narrow edits');
246+
expect(prompt).toContain('they can ask for controls in a later turn');
247+
expect(prompt).toContain('Empty `{}` is valid');
248+
expect(prompt).toContain('user did not want tweak controls');
249+
});
250+
239251
it('create mode defines concrete DESIGN.md promotion triggers', () => {
240252
const prompt = composeSystemPrompt({ mode: 'create' });
241253
expect(prompt).toContain('Before a second screen');
@@ -317,13 +329,13 @@ describe('composeSystemPrompt()', () => {
317329
expect(p).toContain('not a standalone HTML export');
318330
});
319331

320-
it('requires staged file generation instead of one huge initial artifact write', () => {
332+
it('allows a coherent first file pass before preview', () => {
321333
const p = composeSystemPrompt({ mode: 'create' });
322-
expect(p).toContain('First file scaffold');
323-
expect(p).toContain('Do not put the whole finished page into the first write');
334+
expect(p).toContain('First file pass');
335+
expect(p).toContain('create `App.jsx` when you have a coherent first pass');
324336
expect(p).toContain('Preview the complete pass');
325-
expect(p).toContain('Implement the first complete pass');
326-
expect(p).toContain('do not call `preview` while the file is still only a scaffold');
337+
expect(p).toContain('Implement and polish');
338+
expect(p).toContain('Do not call `preview` while the file is still only a scaffold');
327339
});
328340

329341
it('asks the agent to interleave concise progress notes with tool phases', () => {

packages/core/src/prompts/sections/editmode-protocol.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# EDITMODE protocol
22

3-
Declare user-tweakable visual parameters near the top of the design source:
3+
When useful, declare user-tweakable visual parameters near the top of the design source:
44

55
```js
66
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
@@ -18,6 +18,6 @@ Rules:
1818
- The preview runtime exposes each key as `--ocd-tweak-<kebab-key>` on `:root` before the design renders, for example `accentColor` becomes `--ocd-tweak-accent-color`.
1919
- Consume tweakable visual values through those CSS custom properties in styles, such as `background: "var(--ocd-tweak-accent-color)"` or `padding: "calc(var(--ocd-tweak-density) * 1rem)"`, so the tweak panel can update the rendered design instantly without re-running React or Babel.
2020
- Do not wire tweakable colors, spacing, radius, opacity, or typography directly from `TWEAK_DEFAULTS` into one-time rendered inline values when a CSS custom property can represent the same value.
21-
- Pick 2-6 values that materially change the design.
22-
- Empty `{}` is valid when no useful controls exist yet.
21+
- Pick 2-5 values that materially change the design.
22+
- Do not invent controls just to fill the panel. Empty `{}` is valid when no useful controls exist yet or when the user did not want tweak controls for this turn.
2323
- In revise mode, preserve an existing EDITMODE block unless the user explicitly asks to change it.

packages/core/src/prompts/sections/pre-flight.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Before writing, silently decide:
77
3. Section/content beats needed to avoid sparse output.
88
4. Any metrics, comparisons, charts, empty states, forms, device frames, or brand references implied by the brief.
99
5. Which manifest resources to load with `skill()` or `scaffold()`.
10-
6. Palette, type ladder, and tweakable tokens.
11-
7. The first file action sequence: for a fresh workspace, `set_todos`, compact scaffold `create App.jsx`, incremental edits to a complete first pass, then `preview(App.jsx)`; for existing source, `set_todos`, `view`, then edit.
10+
6. Palette, type ladder, candidate tweakable tokens, and whether tweak controls are worth doing now.
11+
7. The first file action sequence: for a fresh workspace, optional `set_todos` when the work has multiple steps, `create App.jsx`, focused edits to a complete first pass, then `preview(App.jsx)`; for existing source, optional `set_todos`, `view`, then edit.
1212

13-
If a decision is still materially unclear, call `ask()` instead of guessing.
13+
If a decision is still materially unclear, or if optional tweak/control work may not be valuable for this user, call `ask()` instead of guessing.

packages/core/src/prompts/sections/workflow.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
Work in a visible loop:
44

5-
1. **Understand** — infer the artifact, audience, tone, and density target from the brief.
6-
2. **Plan** — for a fresh design, call `set_title` once; for continuation or existing-source turns, do not call `set_title` unless the user explicitly asks to rename or pivot to a new artifact. Call `set_todos` before any `create`, `str_replace`, or `insert` file edit. This is required for fresh single-file designs too.
5+
1. **Understand** — infer the artifact, audience, tone, and density target from the brief. If the brief leaves a high-impact direction open, ask before editing instead of guessing.
6+
2. **Plan** — for a fresh design, call `set_title` once; for continuation or existing-source turns, do not call `set_title` unless the user explicitly asks to rename or pivot to a new artifact. Use `set_todos` for multi-step or ambiguous work, but do not delay a ready file edit solely to add todos.
77
3. **Load resources** — use the resource manifest. Call `skill(name)` for matching method guidance, call `skill("brand:<slug>")` for reference-only brand DESIGN.md, and call `scaffold({kind, destPath})` for concrete starter/source files. If you need copyable JSX component patterns, view virtual `skills/*.jsx` snippets and adapt them; do not confuse those snippets with markdown `skill(name)` rules.
8-
4. **First file scaffold** — for a fresh artifact, create a compact `App.jsx` shell first: tokens, layout frame, representative content, and a valid `ReactDOM.createRoot(...)` end line. Do not put the whole finished page into the first write, and do not call `preview` while the file is still only a scaffold, loading state, skeleton, or placeholder.
9-
5. **Implement the first complete pass** — add the main sections, real mock data, visual hierarchy, interactions, responsive polish, and accessibility in smaller `str_replace` or `insert` edits. Do not paste source code in chat.
8+
4. **First file pass** — for a fresh artifact, create `App.jsx` when you have a coherent first pass: tokens, layout frame, representative content, and a valid `ReactDOM.createRoot(...)` end line. Do not call `preview` while the file is still only a scaffold, loading state, skeleton, or placeholder.
9+
5. **Implement and polish** — add or refine the main sections, real mock data, visual hierarchy, interactions, responsive polish, and accessibility with focused `str_replace` or `insert` edits. Do not paste source code in chat.
1010
6. **Preview the complete pass** — call `preview(App.jsx)` only after the artifact can stand on its own without "Loading", "Generating", gray skeleton blocks, placeholder cards, or empty lower sections, unless the user explicitly asked for a loading-state design.
1111
7. **Design baton** — create, repair, or update the workspace `DESIGN.md` for substantive artifacts, multi-screen work, adopted brand refs, or stable reusable tokens.
12-
8. **Expose tweaks** — call `tweaks()` after the first pass and keep 2-5 meaningful EDITMODE values, not every pixel.
12+
8. **Expose tweaks selectively** — call `tweaks()` only when the user asked for controls, answered that controls would help, or the artifact has 2-5 obvious high-leverage values. Skip tweak work for narrow edits, throwaway sketches, or when the user declines; they can ask for controls in a later turn.
1313
9. **Finish** — call `done(path)`. After it succeeds, answer with 1-2 concise sentences and no code.
1414

1515
## Visible progress
@@ -18,8 +18,15 @@ Interleave tool groups with short assistant text so the user understands the wor
1818

1919
## Ask
2020

21-
If the brief is genuinely ambiguous, call `ask({questions:[...]})` before writing. Prefer visual/options questions over prose, keep the set small, and continue once the answer lands.
21+
If the brief is genuinely ambiguous or an optional feature would add meaningful work, call `ask({questions:[...]})` before writing. Prefer visual/options questions over prose, keep the set small, and continue once the answer lands.
22+
23+
Good ask moments:
24+
- The user has not chosen a visual direction, artifact type, content source, or target audience.
25+
- Tweak controls would require extra design-token work and the brief does not imply the user wants them.
26+
- You are choosing between a quick one-off artifact and a reusable design system surface.
27+
28+
Ask at most 1-3 questions. Do not ask about details you can infer safely or revise cheaply later.
2229

2330
## Revision workflow
2431

25-
For revise-mode, continuation, or inline-comment work, call `set_todos`, re-read the current artifact with `view`, make the minimum coherent change, preserve the existing visual system unless asked, then call `done`.
32+
For revise-mode, continuation, or inline-comment work, re-read the current artifact with `view`, use `set_todos` only when the change has multiple steps, make the minimum coherent change, preserve the existing visual system unless asked, then call `done`.

packages/core/src/tools/ask.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ describe('validateAskInput', () => {
5656
});
5757

5858
describe('makeAskTool', () => {
59+
it('describes optional tweak controls as a valid reason to ask', () => {
60+
const tool = makeAskTool(async () => ({ status: 'answered', answers: [] }));
61+
expect(tool.description).toContain('optional work such as tweak controls');
62+
});
63+
5964
it('routes valid input through the bridge and surfaces the answers', async () => {
6065
const canned: AskResult = {
6166
status: 'answered',

packages/core/src/tools/ask.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,8 @@ export function makeAskTool(askBridge: AskBridge): AgentTool<typeof AskInput, As
281281
'Render a structured questionnaire (1–25 questions, 5 types: text-options / ' +
282282
'svg-options / slider / file / freeform) to the user and wait for answers. ' +
283283
'Use BEFORE implementing when the request is ambiguous or when aesthetic / ' +
284-
"content direction is unclear. Returns `{status: 'answered', answers}` or " +
284+
'content direction is unclear, including optional work such as tweak controls. ' +
285+
"Returns `{status: 'answered', answers}` or " +
285286
"`{status: 'cancelled', answers: []}`.",
286287
parameters: AskInput,
287288
async execute(_toolCallId, params): Promise<AgentToolResult<AskResult>> {

0 commit comments

Comments
 (0)