Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gulpfile.js/validate-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const DEV_MAX_TOTAL_SIZE_MB = 100;
// Custom size limits for known large files (size in MB) For development builds
const LARGE_FILE_LIST_DEV = {
'dist/thirdparty/no-minify/language-worker.js.map': 10,
'dist/brackets-min.js': 15
'dist/brackets-min.js': 20
};

// Size limits for production/staging builds (in MB)
Expand Down
37 changes: 28 additions & 9 deletions src-node/claude-code-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,15 +663,34 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale,
"controlEditor). Never use relative paths." +
"\n\nWhen a tool response mentions the user has typed a clarification, immediately " +
"call getUserClarification to read it and incorporate the user's feedback into your current work." +
"\n\nYou are running inside Phoenix Code, a web-focused code editor with a built-in " +
"live preview for HTML/CSS/JS. When the user asks to create mockups, prototypes, " +
"or web pages, prefer vanilla HTML/CSS/JS so the live preview can render and " +
"edit them — unless the user specifically requests a framework. " +
"Build responsive layouts by default for web content." +
"\n\nWhen planning, consider if verification is needed. takeScreenshot can " +
"capture the full editor, specific panels, the code area, or the live preview. " +
"For HTML/CSS/JS with live preview, execJsInLivePreview can run JS in the " +
"browser to confirm behavior." +
"\n\nYou are running inside Phoenix Code, a web-focused code editor with built-in " +
"live preview for both HTML/CSS/JS/SVG and Markdown. When the user asks to create " +
"mockups, prototypes, or web pages, prefer vanilla HTML/CSS/JS so the live preview " +
"can render and edit them — unless the user specifically requests a framework. " +
"Build responsive layouts by default for web content. For images, prefer real " +
"<img> tags over div background-image so the user can swap, inspect, and resize " +
"them in the editor — only fall back to background-image when an effect (parallax, " +
"cover-with-overlay, repeating tile) genuinely requires it." +
"\n\nYou can debug and inspect the live preview directly — these tools are for " +
"active iteration, not just final verification:" +
"\n- takeScreenshot: see the rendered HTML preview, the rendered Markdown preview, " +
"the editor, or any panel. Use it to confirm visual output, diagnose layout/styling " +
"bugs, or check that HTML or Markdown rendered as expected. Pass reload=true to " +
"force-reload the preview before capturing (useful after JS edits) — saves a tool " +
"call vs. reloading separately." +
"\n- execJsInLivePreview: run JS inside the HTML preview iframe to read the DOM, " +
"query computed styles, click elements, or capture console output. Use it to debug " +
"behavior, not just to verify." +
"\n- resizeLivePreview: change the preview viewport width to test responsive " +
"breakpoints." +
"\n- controlEditor: open files, move the cursor, change selection, toggle the live " +
"preview panel, or reload it (reloadLivePreview operation — use after JS edits if " +
"you're not also taking a screenshot)." +
"\n- getEditorState: report active file, working set, cursor/selection, and the " +
"livePreviewFile. The live preview normally follows the active editor file, so " +
"assume that. Rarely the user pins the preview to a specific file — if a " +
"screenshot doesn't match the file you just edited, check " +
"getEditorState.livePreviewFile to rule that out." +
"\n\nUse your best judgement for when to enter plan mode. Use it when the task " +
"involves creating new applications, extensive modifications, or architectural " +
"changes — propose a plan for user approval before writing code." +
Expand Down
14 changes: 11 additions & 3 deletions src-node/mcp-editor-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,13 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
"or '.editor-holder' to capture only the code editor area. " +
"Only omit the selector when you need to see the full editor application layout. " +
"Note: live preview screenshots may include Phoenix toolbox overlays on selected elements. " +
"Use purePreview=true to temporarily hide these overlays and render the page as it would appear in a real browser.",
"Use purePreview=true to temporarily hide these overlays and render the page as it would appear in a real browser. " +
"Use reload=true to force-reload the live preview before capturing — useful after editing JS, " +
"and saves a tool call vs. calling controlEditor.reloadLivePreview separately.",
{
selector: z.string().optional().describe("CSS selector to capture a specific element. Use '#panel-live-preview-frame' for the preview panel (HTML live preview or markdown preview), '.editor-holder' for the code editor."),
purePreview: z.boolean().optional().describe("When true, temporarily switches to preview mode to hide element highlight overlays and toolboxes before capturing, then restores the previous mode."),
reload: z.boolean().optional().describe("When true, force-reloads the live preview before capturing. Use this instead of a separate reloadLivePreview call when you're about to screenshot anyway."),
filePath: z.string().optional().describe("Absolute path to save the screenshot as a PNG file. If specified, returns the file path instead of inline image data.")
},
async function (args) {
Expand All @@ -140,6 +143,7 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
const result = await _execPeerWithTimeout(nodeConnector, "takeScreenshot", {
selector: args.selector || undefined,
purePreview: args.purePreview || false,
reload: args.reload || false,
filePath: args.filePath || undefined
}, "takeScreenshot");
if (result.filePath) {
Expand Down Expand Up @@ -212,10 +216,14 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors)
"- openInWorkingSet: Open a file and pin it to the working set. Params: filePath\n" +
"- setSelection: Open a file and select a range. Params: filePath, startLine, startCh, endLine, endCh\n" +
"- setCursorPos: Open a file and set cursor position. Params: filePath, line, ch\n" +
"- toggleLivePreview: Show or hide the live preview panel. Params: show (boolean)",
"- toggleLivePreview: Show or hide the live preview panel. Params: show (boolean)\n" +
"- reloadLivePreview: Force-reload the live preview iframe (and any popped-out preview tabs). " +
"Use after editing JS that doesn't appear to have hot-reloaded. Note: if you're about to call " +
"takeScreenshot anyway, prefer takeScreenshot({ reload: true }) — it reloads and captures in " +
"one step. No params.",
{
operations: z.array(z.object({
operation: z.enum(["open", "close", "openInWorkingSet", "setSelection", "setCursorPos", "toggleLivePreview"]),
operation: z.enum(["open", "close", "openInWorkingSet", "setSelection", "setCursorPos", "toggleLivePreview", "reloadLivePreview"]),
filePath: z.string().optional().describe("Absolute path to the file (not required for toggleLivePreview)"),
startLine: z.number().optional().describe("Start line (1-based) for setSelection"),
startCh: z.number().optional().describe("Start column (1-based) for setSelection"),
Expand Down
167 changes: 126 additions & 41 deletions src/extensionsIntegrated/Phoenix/phoenix-tour.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
const Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
Metrics = require("utils/Metrics"),
SidebarView = require("project/SidebarView"),
ProjectManager = require("project/ProjectManager"),
EditorManager = require("editor/EditorManager"),
CommandManager = require("command/CommandManager"),
Commands = require("command/Commands"),
WorkspaceManager = require("view/WorkspaceManager"),
CentralControlBar = require("view/CentralControlBar");

// Capture the kernel trust ring at module-load time — it's deleted from
Expand All @@ -46,13 +52,6 @@
const STEP_START_DELAY_MS = 2500;
const STEP1_INVITE_MS = 1800;
const STEP1_DESIGN_MODE_HOLD_MS = 2000;
// Hard cap on how long we'll wait for the pro trial start dialog to be
// dismissed before starting the tour. The dialog is shown on every fresh
// first-run boot (where this tour also runs), so under normal conditions
// the wait is bounded by the user dismissing it. The cap protects edge
// cases where the dialog isn't shown at all (e.g. user already has a
// subscription / a prior expired trial).
const TRIAL_DIALOG_WAIT_TIMEOUT_MS = 60000;

function _loadState() {
const raw = PhStore.getItem(TOUR_STORAGE_KEY);
Expand Down Expand Up @@ -101,7 +100,7 @@
}
}

const TOTAL_STEPS = 3;
const TOTAL_STEPS = 4;

function _ensureOverlay() {
if ($overlay) {
Expand Down Expand Up @@ -189,7 +188,21 @@
update();
}

/**
* Make sure the sidebar is showing before each step. Upgrade flows can
* boot Phoenix with the sidebar hidden (the user's last-session state),
* which would hide the AI tab and the new-project button this tour
* points at. Cheap to call when already visible — SidebarView.show()
* is a no-op then.
*/
function _ensureSidebarVisible() {
if (SidebarView && SidebarView.isVisible && !SidebarView.isVisible()) {

Check warning on line 199 in src/extensionsIntegrated/Phoenix/phoenix-tour.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ3ZTfla384hUvdeTZG4&open=AZ3ZTfla384hUvdeTZG4&pullRequest=2868
SidebarView.show();
}
}

function _runStep1() {
_ensureSidebarVisible();
const $btn = $("#ccbCollapseEditorBtn");
if (!$btn.length) {
_markComplete();
Expand Down Expand Up @@ -235,6 +248,7 @@
}

function _runStep2() {
_ensureSidebarVisible();
const $tab = $('.sidebar-tab[data-tab-id="ai"]');
if (!$tab.length) {
// No AI tab in this build — skip ahead to the next step.
Expand All @@ -260,18 +274,108 @@
}

function _runStep3() {
_ensureSidebarVisible();
const $newBtn = $("#newProject");
if (!$newBtn.length) {
// No new-project button — tour is effectively done.
_markComplete();
_teardown();
// No new-project button — skip to the live-preview step instead
// of giving up on the tour entirely.
_runStep4();
return;
}
_ensureOverlay();
_trackTarget($newBtn, "right");
_setStep(3);
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step3");
_setText(Strings.PHOENIX_TOUR_NEW_PROJECT);
_setActions([
{
label: Strings.PHOENIX_TOUR_NEXT_BTN,
kind: "primary",
onClick: function () {
_runStep4();
}
}
]);
// Intentionally do NOT advance on a real click of the target — only
// the Next button advances.
}

/**
* Bring the workspace to a state where the live-preview Edit Mode button
* is visible and meaningful: the welcome project must be open, the
* active editor file must be one the preview can render (the welcome
* project's index.html or phoenix-pro.html), and the LP panel must be
* showing. Each branch is a no-op when already true.
*/
async function _ensureLivePreviewReady() {
const welcomeRoot = ProjectManager.getWelcomeProjectPath();

// 1. Switch to the welcome project if we're not already there.
const currentRoot = ProjectManager.getProjectRoot();
if (!currentRoot || currentRoot.fullPath !== welcomeRoot) {
await new Promise(function (resolve) {
ProjectManager.openProject(welcomeRoot)
.done(resolve).fail(resolve);
});
}

// 2. Make sure the active file is one the LP can render. Prefer
// phoenix-pro.html (Pro flow) and fall back to index.html.
const proPath = welcomeRoot + "phoenix-pro.html";
const indexPath = welcomeRoot + "index.html";
const editor = EditorManager.getActiveEditor();
const currentFile = editor && editor.document && editor.document.file

Check warning on line 327 in src/extensionsIntegrated/Phoenix/phoenix-tour.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ3ZTfla384hUvdeTZG5&open=AZ3ZTfla384hUvdeTZG5&pullRequest=2868
? editor.document.file.fullPath : null;
if (currentFile !== proPath && currentFile !== indexPath) {
let target = null;
try {
if (await Phoenix.VFS.existsAsync(proPath)) {
target = proPath;
} else if (await Phoenix.VFS.existsAsync(indexPath)) {
target = indexPath;
}
} catch (e) { /* fall through with target=null */ }

Check warning on line 337 in src/extensionsIntegrated/Phoenix/phoenix-tour.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ3ZTfla384hUvdeTZG6&open=AZ3ZTfla384hUvdeTZG6&pullRequest=2868
if (target) {
await new Promise(function (resolve) {
CommandManager.execute(Commands.FILE_OPEN, { fullPath: target })
.done(resolve).fail(resolve);
});
}
}

// 3. Open the live-preview panel if it isn't visible.
const lpPanel = WorkspaceManager.getPanelForID("live-preview-panel");
if (!lpPanel || !lpPanel.isVisible()) {

Check warning on line 348 in src/extensionsIntegrated/Phoenix/phoenix-tour.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ3ZTfla384hUvdeTZG7&open=AZ3ZTfla384hUvdeTZG7&pullRequest=2868
await new Promise(function (resolve) {
CommandManager.execute(Commands.FILE_LIVE_FILE_PREVIEW)
.done(resolve).fail(resolve);
});
}
}

async function _runStep4() {
_ensureSidebarVisible();
try {
await _ensureLivePreviewReady();
} catch (e) { /* best-effort prep — proceed and let the rect-zero
RAF gate sort out a missing target */ }

Check warning on line 361 in src/extensionsIntegrated/Phoenix/phoenix-tour.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ3ZTfla384hUvdeTZG8&open=AZ3ZTfla384hUvdeTZG8&pullRequest=2868

const $btn = $("#previewModeLivePreviewButton");
if (!$btn.length) {
// LP panel never came up (custom server, unsupported file, etc.)
// — finalize the tour rather than stalling on a missing target.
_markComplete();
_teardown();
return;
}
_ensureOverlay();
// LP panel sits on the right edge; keep the default tooltip
// placement (lower-right of the ring) so the tooltip extends back
// into the panel area rather than off the right edge of the viewport.
_trackTarget($btn, "left");
_setStep(4);
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "step4");
_setText(Strings.PHOENIX_TOUR_EDIT_MODE);
_setActions([
{
label: Strings.PHOENIX_TOUR_DISMISS_BTN,
Expand All @@ -283,8 +387,6 @@
}
}
]);
// Intentionally do NOT end on a real click of the target — only the
// Dismiss button ends the tour.
}

function _shouldRun() {
Expand All @@ -308,36 +410,19 @@
}

/**
* Resolves once the pro trial start dialog has been dismissed, or after
* TRIAL_DIALOG_WAIT_TIMEOUT_MS as a fallback for builds/runs where the
* dialog isn't shown.
* Resolves once the pro trial start dialog has been dismissed. The
* dialog is guaranteed to fire `proTrialStartDialogDismissed` on every
* boot path (including builds where the dialog isn't shown), so we
* just await it without a timeout fallback.
*/
function _waitForTrialStartDialogDismissed() {
return new Promise(function (resolve) {
const dismissed = _LoginService && _LoginService.proTrialStartDialogDismissed;
if (!dismissed) {
// No pro trial flow exposed — proceed immediately.
resolve();
return;
}
let settled = false;
const fallback = setTimeout(function () {
if (settled) {
return;
}
settled = true;
resolve();
}, TRIAL_DIALOG_WAIT_TIMEOUT_MS);
// jQuery deferred or native promise — both implement .then
Promise.resolve(dismissed).then(function () {
if (settled) {
return;
}
settled = true;
clearTimeout(fallback);
resolve();
});
});
const dismissed = _LoginService && _LoginService.proTrialStartDialogDismissed;
// Community-edition builds expose no login service at all — skip
// the wait so the tour still works there.
if (!dismissed) {
return Promise.resolve();
}
return Promise.resolve(dismissed);
}

function startTour() {
Expand Down
1 change: 1 addition & 0 deletions src/htmlContent/about-dialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ <h2>{{APP_NAME_ABOUT_BOX}}</h2>
<a href="https://github.com/abose" target="_blank" rel="noopener" >Arun Bose</a>,&nbsp;
<a href="https://github.com/charlypa" target="_blank" rel="noopener" >Charly P Abraham</a>,&nbsp;
<a href="https://github.com/devvaannsh" target="_blank" rel="noopener" >Devansh Agarwal</a>,&nbsp;
<a href="https://github.com/AnsuKoshy" target="_blank" rel="noopener" >Ansu Ann Koshy</a>,&nbsp;
<a href="https://github.com/Electrofist" target="_blank" rel="noopener" >Krrish Parmar</a>
</p>

Expand Down
Loading
Loading