Skip to content

Commit f8b8ec8

Browse files
authored
Merge branch 'main' into pluto/btm-panel-tabbed
2 parents 32401db + c88eb5f commit f8b8ec8

95 files changed

Lines changed: 4303 additions & 476 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/API-Reference/document/Document.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ Given a character index within the document text (assuming \n newlines),
248248
returns the corresponding {line, ch} position. Works whether or not
249249
a master editor is attached.
250250

251-
**Kind**: instance method of [<code>Document</code>](#Document)
251+
**Kind**: instance method of [<code>Document</code>](#Document)
252252

253253
| Param | Type | Description |
254254
| --- | --- | --- |

src-node/claude-code-agent.js

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,25 @@ exports.sendPrompt = async function (params) {
147147

148148
// Prepend selection context to the prompt if available
149149
let enrichedPrompt = prompt;
150-
if (selectionContext && selectionContext.selectedText) {
151-
enrichedPrompt =
152-
"The user has selected the following text in " + selectionContext.filePath +
153-
" (lines " + selectionContext.startLine + "-" + selectionContext.endLine + "):\n" +
154-
"```\n" + selectionContext.selectedText + "\n```\n\n" + prompt;
150+
if (selectionContext) {
151+
if (selectionContext.selectedText) {
152+
enrichedPrompt =
153+
"The user has selected the following text in " + selectionContext.filePath +
154+
" (lines " + selectionContext.startLine + "-" + selectionContext.endLine + "):\n" +
155+
"```\n" + selectionContext.selectedText + "\n```\n\n" + prompt;
156+
} else {
157+
let previewSnippet = "";
158+
if (selectionContext.selectionPreview) {
159+
previewSnippet = "\nPreview of selection:\n```\n" +
160+
selectionContext.selectionPreview + "\n```\n";
161+
}
162+
enrichedPrompt =
163+
"The user has selected lines " + selectionContext.startLine + "-" +
164+
selectionContext.endLine + " in " + selectionContext.filePath +
165+
". Use the Read tool with offset=" + (selectionContext.startLine - 1) +
166+
" and limit=" + (selectionContext.endLine - selectionContext.startLine + 1) +
167+
" to read the selected content if needed." + previewSnippet + "\n" + prompt;
168+
}
155169
}
156170

157171
// Run the query asynchronously — don't await here so we return requestId immediately
@@ -222,7 +236,9 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
222236
"mcp__phoenix-editor__getEditorState",
223237
"mcp__phoenix-editor__takeScreenshot",
224238
"mcp__phoenix-editor__execJsInLivePreview",
225-
"mcp__phoenix-editor__controlEditor"
239+
"mcp__phoenix-editor__controlEditor",
240+
"mcp__phoenix-editor__resizeLivePreview",
241+
"mcp__phoenix-editor__wait"
226242
],
227243
mcpServers: { "phoenix-editor": editorMcpServer },
228244
permissionMode: "acceptEdits",
@@ -253,21 +269,33 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
253269
newText: input.tool_input.new_string
254270
};
255271
editCount++;
272+
let editResult;
256273
try {
257-
await nodeConnector.execPeer("applyEditToBuffer", edit);
274+
editResult = await nodeConnector.execPeer("applyEditToBuffer", edit);
258275
} catch (err) {
259276
console.warn("[Phoenix AI] Failed to apply edit to buffer:", err.message);
277+
editResult = { applied: false, error: err.message };
260278
}
261279
nodeConnector.triggerPeer("aiToolEdit", {
262280
requestId: requestId,
263281
toolId: myToolId,
264282
edit: edit
265283
});
284+
let reason;
285+
if (editResult && editResult.applied === false) {
286+
reason = "Edit FAILED: " + (editResult.error || "unknown error");
287+
} else {
288+
reason = "Edit applied successfully via Phoenix editor.";
289+
if (editResult && editResult.isLivePreviewRelated) {
290+
reason += " The edited file is part of the active live preview." +
291+
" Reload when ready with execJsInLivePreview: `location.reload()`";
292+
}
293+
}
266294
return {
267295
hookSpecificOutput: {
268296
hookEventName: "PreToolUse",
269297
permissionDecision: "deny",
270-
permissionDecisionReason: "Edit applied successfully via Phoenix editor."
298+
permissionDecisionReason: reason
271299
}
272300
};
273301
}
@@ -326,21 +354,33 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale)
326354
newText: input.tool_input.content
327355
};
328356
editCount++;
357+
let writeResult;
329358
try {
330-
await nodeConnector.execPeer("applyEditToBuffer", edit);
359+
writeResult = await nodeConnector.execPeer("applyEditToBuffer", edit);
331360
} catch (err) {
332361
console.warn("[Phoenix AI] Failed to apply write to buffer:", err.message);
362+
writeResult = { applied: false, error: err.message };
333363
}
334364
nodeConnector.triggerPeer("aiToolEdit", {
335365
requestId: requestId,
336366
toolId: myToolId,
337367
edit: edit
338368
});
369+
let reason;
370+
if (writeResult && writeResult.applied === false) {
371+
reason = "Write FAILED: " + (writeResult.error || "unknown error");
372+
} else {
373+
reason = "Write applied successfully via Phoenix editor.";
374+
if (writeResult && writeResult.isLivePreviewRelated) {
375+
reason += " The written file is part of the active live preview." +
376+
" Reload when ready with execJsInLivePreview: `location.reload()`";
377+
}
378+
}
339379
return {
340380
hookSpecificOutput: {
341381
hookEventName: "PreToolUse",
342382
permissionDecision: "deny",
343-
permissionDecisionReason: "Write applied successfully via Phoenix editor."
383+
permissionDecisionReason: reason
344384
}
345385
};
346386
}

src-node/mcp-editor-tools.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,57 @@ function createEditorMcpServer(sdkModule, nodeConnector) {
170170
}
171171
);
172172

173+
const resizeLivePreviewTool = sdkModule.tool(
174+
"resizeLivePreview",
175+
"Resize the live preview panel to a specific width for responsive testing. " +
176+
"Provide a width in pixels based on the target device (e.g. 390 for a phone, 768 for a tablet, 1440 for desktop).",
177+
{
178+
width: z.number().describe("Target width in pixels")
179+
},
180+
async function (args) {
181+
try {
182+
const result = await nodeConnector.execPeer("resizeLivePreview", {
183+
width: args.width
184+
});
185+
if (result.error) {
186+
return {
187+
content: [{ type: "text", text: "Error: " + result.error }],
188+
isError: true
189+
};
190+
}
191+
return {
192+
content: [{ type: "text", text: JSON.stringify(result) }]
193+
};
194+
} catch (err) {
195+
return {
196+
content: [{ type: "text", text: "Error resizing live preview: " + err.message }],
197+
isError: true
198+
};
199+
}
200+
}
201+
);
202+
203+
const waitTool = sdkModule.tool(
204+
"wait",
205+
"Wait for a specified number of seconds before continuing. " +
206+
"Useful for waiting after DOM changes, animations, live preview updates, or resize operations " +
207+
"before taking a screenshot or inspecting state. Maximum 60 seconds.",
208+
{
209+
seconds: z.number().min(0.1).max(60).describe("Number of seconds to wait (0.1–60)")
210+
},
211+
async function (args) {
212+
const ms = Math.round(args.seconds * 1000);
213+
await new Promise(function (resolve) { setTimeout(resolve, ms); });
214+
return {
215+
content: [{ type: "text", text: "Waited " + args.seconds + " seconds." }]
216+
};
217+
}
218+
);
219+
173220
return sdkModule.createSdkMcpServer({
174221
name: "phoenix-editor",
175-
tools: [getEditorStateTool, takeScreenshotTool, execJsInLivePreviewTool, controlEditorTool]
222+
tools: [getEditorStateTool, takeScreenshotTool, execJsInLivePreviewTool, controlEditorTool,
223+
resizeLivePreviewTool, waitTool]
176224
});
177225
}
178226

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ function RemoteFunctions(config = {}) {
548548
if (this.trigger) {
549549
_trigger(this.elements[i], "highlight", 0);
550550
}
551-
clearElementBackground(this.elements[i]);
551+
clearElementHoverHighlight(this.elements[i]);
552552
}
553553

554554
this.elements = [];
@@ -583,19 +583,12 @@ function RemoteFunctions(config = {}) {
583583
return getHighlightMode() !== "click";
584584
}
585585

586-
// helper function to clear element background highlighting
587-
function clearElementBackground(element) {
588-
if (element._originalBackgroundColor !== undefined) {
589-
element.style.backgroundColor = element._originalBackgroundColor;
590-
} else {
591-
// only clear background if it's currently a highlight color, not if it's an original user style
592-
const currentBg = element.style.backgroundColor;
593-
if (currentBg === "rgba(0, 162, 255, 0.2)" || currentBg.includes("rgba(0, 162, 255")) {
594-
element.style.backgroundColor = "";
595-
}
596-
// if it's some other color, we just leave it as is - it's likely a user-defined style
586+
// helper function to clear element hover outline highlighting
587+
function clearElementHoverHighlight(element) {
588+
if (element._originalHoverOutline !== undefined) {
589+
element.style.outline = element._originalHoverOutline;
597590
}
598-
delete element._originalBackgroundColor;
591+
delete element._originalHoverOutline;
599592
}
600593

601594
function onElementHover(event) {
@@ -622,9 +615,10 @@ function RemoteFunctions(config = {}) {
622615
if (_hoverHighlight && shouldShowHighlightOnHover()) {
623616
_hoverHighlight.clear();
624617

625-
// Store original background color to restore on hover out
626-
element._originalBackgroundColor = element.style.backgroundColor;
627-
element.style.backgroundColor = "rgba(0, 162, 255, 0.2)";
618+
// Store original outline to restore on hover out, then apply a blue border
619+
element._originalHoverOutline = element.style.outline;
620+
const outlineColor = element.hasAttribute(GLOBALS.DATA_BRACKETS_ID_ATTR) ? "#4285F4" : "#3C3F41";
621+
element.style.outline = `1px solid ${outlineColor}`;
628622

629623
_hoverHighlight.add(element, false);
630624

@@ -646,7 +640,7 @@ function RemoteFunctions(config = {}) {
646640
// this is to check the user's settings, if they want to show the elements highlights on hover or click
647641
if (_hoverHighlight && shouldShowHighlightOnHover()) {
648642
_hoverHighlight.clear();
649-
clearElementBackground(element);
643+
clearElementHoverHighlight(element);
650644
// dismiss the info box
651645
const infoBoxHandler = LivePreviewView.getToolHandler("InfoBox");
652646
if (infoBoxHandler) {

0 commit comments

Comments
 (0)