Skip to content

Commit 7695d50

Browse files
committed
feat(ai): design-mode awareness + sharper takeScreenshot rule + getEditorState fallback hint
Bundled set of editor-tool refinements driven by support traces: - getEditorState now reports inDesignMode (true when the code editor is hidden and the live preview is expanded full-bleed). The model needs this to interpret "I can't see your code edits" cases — design mode hides the code view, and that's a separate fact from the active file. - getEditorState response now appends a fallback hint pointing the model at takeScreenshot (no selector) for any UI question the JSON doesn't answer (e.g. "what's in the Problems panel" / "what does this sidebar say" — UI panels not represented in the state object). - takeScreenshot description rewritten to a clean binary rule — "rendered live preview" → selector='#panel-live-preview-frame', "anything else (Problems panel, file tree, toolbar, any Phoenix UI)" → no selector. Earlier "ALMOST ALWAYS pass a selector" framing was over-discouraging the legit no-selector case where the user is asking about Phoenix UI. - controlEditor gains a toggleDesignMode operation with an `enabled` boolean. The op description spells out what design mode is (full live preview, code editor hidden, content-focused browser-like view) so the model picks it for the right intents. - System prompt: explains what the live preview actually is (the rendered view of the active HTML/CSS/JS/SVG/Markdown file), so Claude has a mental model before reading the per-tool sections.
1 parent c2cf5fd commit 7695d50

2 files changed

Lines changed: 40 additions & 24 deletions

File tree

src-node/claude-code-agent.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,8 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale,
685685
"<img> tags over div background-image so the user can swap, inspect, and resize " +
686686
"them in the editor — only fall back to background-image when an effect (parallax, " +
687687
"cover-with-overlay, repeating tile) genuinely requires it." +
688+
"\n\nThe live preview is the rendered view of the HTML/CSS/JS/SVG or Markdown file " +
689+
"currently active in the editor." +
688690
"\n\nYou ALWAYS have live visibility into the editor through the phoenix-editor tools " +
689691
"listed below. NEVER tell the user you can't see what's open / what they're looking " +
690692
"at / what file they're on / what's selected / what's in the live preview — call " +
@@ -701,14 +703,13 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale,
701703
"These tools are for active iteration, not just final verification:" +
702704
"\n- takeScreenshot: see the rendered HTML preview, the rendered Markdown preview, " +
703705
"the editor, or any panel. Use it to confirm visual output, diagnose layout/styling " +
704-
"bugs, or check that HTML or Markdown rendered as expected. For 'how does the page " +
705-
"look' / 'check the preview' questions pass selector='#panel-live-preview-frame' " +
706-
"instead of capturing the full editor window — the targeted shot is far easier to " +
707-
"reason about. Pass reload=true to force-reload the preview before capturing " +
708-
"(useful after JS edits) — saves a tool call vs. reloading separately. As a last " +
709-
"resort when the user asks about something in the editor that you can't identify " +
710-
"from getEditorState or other tools, take a screenshot of the editor (no selector) " +
711-
"to see what they're looking at." +
706+
"bugs, or check that HTML or Markdown rendered as expected. Simple selector rule: " +
707+
"if the question is about the rendered live preview pass " +
708+
"selector='#panel-live-preview-frame' (targeted shot is easier to reason about); for " +
709+
"anything else — Problems panel, file tree, toolbar, any other Phoenix UI, or just " +
710+
"\"what is the user looking at\" — omit the selector and capture the full editor " +
711+
"window. Pass reload=true to force-reload the preview before capturing (useful after " +
712+
"JS edits) — saves a tool call vs. reloading separately." +
712713
"\n- execJsInLivePreview: run JS inside the HTML preview iframe to read the DOM, " +
713714
"query computed styles, click elements, or capture console output. Use it to debug " +
714715
"behavior, not just to verify." +

src-node/mcp-editor-tools.js

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,27 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
109109
"getEditorState",
110110
"Get the current Phoenix editor state: active file, working set (open files with isDirty flag), live preview file, " +
111111
"cursor/selection info (current line text with surrounding context, or selected text), " +
112-
"and the currently selected element in the live preview (tag, selector, text preview) if any. " +
112+
"the currently selected element in the live preview (tag, selector, text preview) if any, " +
113+
"and inDesignMode (true when the code editor is hidden and the live preview is expanded " +
114+
"to fill the workspace — full-bleed, content-focused view). " +
113115
"The live preview selected element may differ from the editor cursor — use execJsInLivePreview to inspect it further. " +
114116
"Long lines are trimmed to 200 chars and selections to 10K chars — use the Read tool for full content.",
115117
{},
116118
async function () {
117119
let result;
118120
try {
119121
const state = await _execPeerWithTimeout(nodeConnector, "getEditorState", {}, "getEditorState");
122+
// Append a fallback hint so the model has a clear next step if the
123+
// state alone doesn't answer the user's question — e.g. they're
124+
// pointing at a UI panel (Problems, search, sidebar) that's
125+
// visible on screen but not represented in this JSON.
126+
const hint = "\n\nIf this state isn't enough to identify what the user is " +
127+
"asking about (e.g. they're pointing at a Phoenix UI panel like the " +
128+
"Problems panel, search bar, or sidebar that isn't represented here), " +
129+
"call takeScreenshot with no selector to capture the full editor window " +
130+
"and see what's on their screen.";
120131
result = {
121-
content: [{ type: "text", text: JSON.stringify(state) }]
132+
content: [{ type: "text", text: JSON.stringify(state) + hint }]
122133
};
123134
} catch (err) {
124135
result = {
@@ -140,16 +151,13 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
140151
"panel shows a WYSIWYG markdown editor/viewer). " +
141152
"Returns the screenshot as an inline PNG image; if filePath is specified, saves to that file " +
142153
"and returns the path instead. " +
143-
"ALMOST ALWAYS pass a selector — capturing the full editor returns a busy image full of editor " +
144-
"chrome that's hard to reason about. Use:" +
145-
"\n- '#panel-live-preview-frame' to see the rendered preview (HTML or markdown) — this is what " +
146-
"you want when verifying visual output, debugging a layout/styling bug, or confirming the page " +
147-
"renders as expected. Use this for ANY 'how does it look', 'is the page rendering', or " +
148-
"'check the preview' question." +
149-
"\n- '.editor-holder' to see just the code editor area." +
150-
"\nOnly omit the selector when you need to see the full editor application layout itself — " +
151-
"e.g. the user is asking about Phoenix's UI, panels, toolbar, or you can't otherwise figure " +
152-
"out what they're talking about and need to see what's on their screen. " +
154+
"Simple rule for the selector parameter:" +
155+
"\n- If the question is about the rendered live preview (\"how does it look\", \"is the page " +
156+
"rendering\", \"check the preview\", layout/styling/markdown verification): pass " +
157+
"selector='#panel-live-preview-frame'. The targeted shot is far easier to reason about than the " +
158+
"full editor." +
159+
"\n- For anything else — Problems panel, file tree, toolbar, search bar, any editor UI, or " +
160+
"\"what is the user looking at\" — omit the selector and capture the full editor window. " +
153161
"Note: live preview screenshots may include Phoenix toolbox overlays on selected elements. " +
154162
"Use purePreview=true to temporarily hide these overlays and render the page as it would appear in a real browser. " +
155163
"Use reload=true to force-reload the live preview before capturing — useful after editing JS, " +
@@ -249,22 +257,29 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
249257
"- openInWorkingSet: Open a file and pin it to the working set. Params: filePath\n" +
250258
"- setSelection: Open a file and select a range. Params: filePath, startLine, startCh, endLine, endCh\n" +
251259
"- setCursorPos: Open a file and set cursor position. Params: filePath, line, ch\n" +
252-
"- toggleLivePreview: Show or hide the live preview panel. Params: show (boolean)\n" +
260+
"- toggleLivePreview: Show or hide the live preview panel. Params: showPreview (boolean)\n" +
261+
"- toggleDesignMode: Switch design mode on or off. Design mode hides the code editor and " +
262+
"expands the live preview to fill the workspace, giving the user a content-focused, " +
263+
"browser-like view of their page. Use it when the user wants to see how the page looks " +
264+
"without code chrome (e.g. presenting a draft, polishing visuals); turn it off when " +
265+
"switching back to code editing. Params: enabled (boolean — true for design mode on, " +
266+
"false to return to the code editor + side-by-side preview).\n" +
253267
"- reloadLivePreview: Force-reload the live preview iframe (and any popped-out preview tabs). " +
254268
"Use after editing JS that doesn't appear to have hot-reloaded. Note: if you're about to call " +
255269
"takeScreenshot anyway, prefer takeScreenshot({ reload: true }) — it reloads and captures in " +
256270
"one step. No params.",
257271
{
258272
operations: z.array(z.object({
259-
operation: z.enum(["open", "close", "openInWorkingSet", "setSelection", "setCursorPos", "toggleLivePreview", "reloadLivePreview"]),
260-
filePath: z.string().optional().describe("Absolute path to the file (not required for toggleLivePreview)"),
273+
operation: z.enum(["open", "close", "openInWorkingSet", "setSelection", "setCursorPos", "toggleLivePreview", "toggleDesignMode", "reloadLivePreview"]),
274+
filePath: z.string().optional().describe("Absolute path to the file (not required for toggleLivePreview / toggleDesignMode / reloadLivePreview)"),
261275
startLine: z.number().optional().describe("Start line (1-based) for setSelection"),
262276
startCh: z.number().optional().describe("Start column (1-based) for setSelection"),
263277
endLine: z.number().optional().describe("End line (1-based) for setSelection"),
264278
endCh: z.number().optional().describe("End column (1-based) for setSelection"),
265279
line: z.number().optional().describe("Line number (1-based) for setCursorPos"),
266280
ch: z.number().optional().describe("Column (1-based) for setCursorPos"),
267-
showPreview: z.boolean().optional().describe("true to show, false to hide live preview (for toggleLivePreview)")
281+
showPreview: z.boolean().optional().describe("true to show, false to hide live preview (for toggleLivePreview)"),
282+
enabled: z.boolean().optional().describe("true to turn design mode on (full live preview, code editor hidden), false to return to code editor view (for toggleDesignMode)")
268283
})).describe("Array of editor operations to execute sequentially")
269284
},
270285
async function (args) {

0 commit comments

Comments
 (0)