Skip to content
Merged
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
8 changes: 8 additions & 0 deletions src/assets/default-project/en/Newly_added_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ A new tools drawer brings Git, Terminal, Problems, and more into one place. Swit

![Image](https://docs-images.phcode.dev/in-app/bottom-panel.png)

## New Linux Platform

`Added in April 2026`

Phoenix Code for Linux has been rebuilt from the ground up.

The previous Linux app was harder to install and didn’t match the experience we wanted to deliver. We heard that feedback loud and clear. This release is powered by a brand-new Linux platform, bringing faster performance, easier installation, and a desktop experience that now stands alongside Windows and macOS.

## [Phoenix Neo Themes](https://docs.phcode.dev/app-links/themes)

`Added in April 2026`
Expand Down
68 changes: 11 additions & 57 deletions src/extensionsIntegrated/Phoenix/phoenix-tour.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*
*/

/*global PhStore */
/*global PhStore, logger */

/**
* One-shot, app-lifetime onboarding tour that introduces the design-mode
Expand All @@ -32,6 +32,7 @@ define(function (require, exports, module) {
const Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
Metrics = require("utils/Metrics"),
BootGreetings = require("utils/BootGreetings"),
SidebarView = require("project/SidebarView"),
SidebarTabs = require("view/SidebarTabs"),
ProjectManager = require("project/ProjectManager"),
Expand All @@ -41,12 +42,6 @@ define(function (require, exports, module) {
WorkspaceManager = require("view/WorkspaceManager"),
CentralControlBar = require("view/CentralControlBar");

// Capture the kernel trust ring at module-load time — it's deleted from
// `window` shortly after boot. Treated as optional: community-edition
// builds without the pro trial flow won't expose `loginService` and the
// tour will simply proceed without waiting.
const _LoginService = (window.KernalModeTrust && window.KernalModeTrust.loginService) || null;

const TOUR_STORAGE_KEY = "phoenixOnboardingTourState";
const CURRENT_TOUR_VERSION = 1;

Expand Down Expand Up @@ -287,6 +282,7 @@ define(function (require, exports, module) {
_ensureSidebarVisible();
const $btn = $("#ccbCollapseEditorBtn");
if (!$btn.length) {
logger.reportError(new Error("phoenix-tour: #ccbCollapseEditorBtn missing at step 1"));
_markComplete();
_teardown();
return;
Expand Down Expand Up @@ -337,7 +333,7 @@ define(function (require, exports, module) {
_ensureSidebarVisible();
const $tab = $('.sidebar-tab[data-tab-id="ai"]');
if (!$tab.length) {
// No AI tab in this build — skip ahead to the next step.
logger.reportError(new Error("phoenix-tour: AI sidebar tab missing at step 2"));
_runStep3();
return;
}
Expand Down Expand Up @@ -369,8 +365,7 @@ define(function (require, exports, module) {
_ensureSidebarVisible();
const $newBtn = $("#newProject");
if (!$newBtn.length) {
// No new-project button — skip to the live-preview step instead
// of giving up on the tour entirely.
logger.reportError(new Error("phoenix-tour: #newProject missing at step 3"));
_runStep4();
return;
}
Expand Down Expand Up @@ -457,8 +452,7 @@ define(function (require, exports, module) {

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.
logger.reportError(new Error("phoenix-tour: #previewModeLivePreviewButton missing at step 4"));
_markComplete();
_teardown();
return;
Expand Down Expand Up @@ -495,61 +489,21 @@ define(function (require, exports, module) {
if (Phoenix.isTestWindow || Phoenix.isSpecRunnerWindow) {
return false;
}
if (CentralControlBar.isEditorCollapsed && CentralControlBar.isEditorCollapsed()) {
// User has already discovered design mode in some other way.
return false;
}
if (!$("#ccbCollapseEditorBtn").length) {
return false;
}
return true;
}

/**
* 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() {
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() {
if (!_shouldRun()) {
return;
}
_ranThisSession = true;
Metrics.countEvent(Metrics.EVENT_TYPE.GUIDE, "tour", "start");

_waitForTrialStartDialogDismissed().then(function () {
// Re-check primary preconditions after the wait — the user may
// have already discovered design mode while a trial dialog was
// up, or the button may have been torn down.
if (!$("#ccbCollapseEditorBtn").length) {
_markComplete();
_teardown();
return;
}
if (CentralControlBar.isEditorCollapsed && CentralControlBar.isEditorCollapsed()) {
_markComplete();
_teardown();
return;
}
_timers.push(setTimeout(function () {
if (!$("#ccbCollapseEditorBtn").length) {
_markComplete();
_teardown();
return;
}
_runStep1();
}, STEP_START_DELAY_MS));
// Wait until every boot-time greeting dialog (auto/manual update
// "What's New", pro trial start, paid-Pro "What's New") has been
// dismissed.
BootGreetings.allDismissed().then(function () {
_timers.push(setTimeout(_runStep1, STEP_START_DELAY_MS));
});
}

Expand Down
21 changes: 19 additions & 2 deletions src/extensionsIntegrated/appUpdater/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,17 @@ define(function (require, exports, module) {
TaskManager = require("features/TaskManager"),
StringUtils = require("utils/StringUtils"),
NativeApp = require("utils/NativeApp"),
BootGreetings = require("utils/BootGreetings"),
PreferencesManager = require("preferences/PreferencesManager");

// Reserve a slot in the boot-greeting coordinator so the tour can wait
// until the updater has either shown its "What's New" dialog (auto or
// manual update) or decided not to. Unblocked once per boot.
const UPDATER_GATE = "updater-tauri";
BootGreetings.registerBlocker(UPDATER_GATE);
function _unblockUpdaterGate() {
BootGreetings.unblockBlocker(UPDATER_GATE);
}
let updaterWindow, updateTask, updatePendingRestart, updateFailed;

const TAURI_UPDATER_WINDOW_LABEL = "updater",
Expand Down Expand Up @@ -519,14 +529,18 @@ define(function (require, exports, module) {
let updateInstalledDialogShown = false, updateFailedDialogShown = false;
AppInit.appReady(function () {
if(Phoenix.isTestWindow) {
_unblockUpdaterGate();
return;
}
if(window.__ELECTRON__) {
// Electron updates handled by update-electron.js
// Electron updates handled by update-electron.js — that
// module owns its own blocker, so this one is a no-op.
_unblockUpdaterGate();
return;
}
if(!window.__TAURI__) {
// app updates are only for desktop builds
_unblockUpdaterGate();
return;
}
if (brackets.platform === "mac") {
Expand Down Expand Up @@ -616,13 +630,16 @@ define(function (require, exports, module) {
const lastUpdateDetails = PreferencesManager.getViewState(KEY_LAST_UPDATE_DESCRIPTION);
if(lastUpdateDetails && (lastUpdateDetails.updateVersion === Phoenix.metadata.apiVersion)) {
let markdownHtml = marked.parse(lastUpdateDetails.releaseNotesMarkdown || "");
Dialogs.showInfoDialog(Strings.UPDATE_WHATS_NEW, markdownHtml);
Dialogs.showInfoDialog(Strings.UPDATE_WHATS_NEW, markdownHtml)
.done(_unblockUpdaterGate);
PreferencesManager.setViewState(KEY_LAST_UPDATE_DESCRIPTION, null);
PreferencesManager.setViewState(KEY_UPDATE_AVAILABLE, false);
// hide the update available icon as we are showing what's new dialog. In edge cases, there can be an update
// at this time if the user opened phcode after an update, but a new update was just published or the user
// didn't open phcode after last update, which a new update was published.
$("#update-notification").addClass("forced-hidden");
} else {
_unblockUpdaterGate();
}
// check for updates at boot
let lastUpdateCheckTime = PreferencesManager.getViewState(KEY_LAST_UPDATE_CHECK_TIME);
Expand Down
17 changes: 16 additions & 1 deletion src/extensionsIntegrated/appUpdater/update-electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ define(function (require, exports, module) {
NotificationUI = require("widgets/NotificationUI"),
TaskManager = require("features/TaskManager"),
NativeApp = require("utils/NativeApp"),
BootGreetings = require("utils/BootGreetings"),
PreferencesManager = require("preferences/PreferencesManager");

// Reserve a slot in the boot-greeting coordinator so the tour can wait
// until the updater has either shown its "What's New" dialog (auto or
// manual update) or decided not to. Unblocked once per boot.
const UPDATER_GATE = "updater-electron";
BootGreetings.registerBlocker(UPDATER_GATE);
function _unblockUpdaterGate() {
BootGreetings.unblockBlocker(UPDATER_GATE);
}

let updateTask, updatePendingRestart, updateFailed;

const KEY_LAST_UPDATE_CHECK_TIME = "PH_LAST_UPDATE_CHECK_TIME",
Expand Down Expand Up @@ -448,11 +458,13 @@ define(function (require, exports, module) {

AppInit.appReady(async function () {
if(!window.__ELECTRON__ || Phoenix.isTestWindow) {
_unblockUpdaterGate();
return;
}
// Electron updates only supported on Linux currently
if (brackets.platform !== "linux") {
console.error("App updates not yet implemented on this platform in Electron builds!");
_unblockUpdaterGate();
return;
}
// Check if another window already scheduled an update (multi-window state persistence)
Expand Down Expand Up @@ -496,10 +508,13 @@ define(function (require, exports, module) {
const lastUpdateDetails = PreferencesManager.getViewState(KEY_LAST_UPDATE_DESCRIPTION);
if(lastUpdateDetails && (lastUpdateDetails.updateVersion === Phoenix.metadata.apiVersion)) {
let markdownHtml = marked.parse(lastUpdateDetails.releaseNotesMarkdown || "");
Dialogs.showInfoDialog(Strings.UPDATE_WHATS_NEW, markdownHtml);
Dialogs.showInfoDialog(Strings.UPDATE_WHATS_NEW, markdownHtml)
.done(_unblockUpdaterGate);
PreferencesManager.setViewState(KEY_LAST_UPDATE_DESCRIPTION, null);
PreferencesManager.setViewState(KEY_UPDATE_AVAILABLE, false);
$("#update-notification").addClass("forced-hidden");
} else {
_unblockUpdaterGate();
}
// check for updates at boot
let lastUpdateCheckTime = PreferencesManager.getViewState(KEY_LAST_UPDATE_CHECK_TIME);
Expand Down
2 changes: 2 additions & 0 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2084,6 +2084,8 @@ define({
// promos
"PROMO_UPGRADE_TITLE": "You’ve been upgraded to {0}",
"PROMO_UPGRADE_MESSAGE": "Enjoy free access to these premium features for the next {0} days:",
"PROMO_PRO_WHATS_NEW_TITLE": "New in {0}",
"PROMO_PRO_WHATS_NEW_MESSAGE": "Thanks for being a {0} member. Here’s what’s new in this update:",
"PROMO_CARD_1": "Edit In Live Preview",
"PROMO_CARD_1_MESSAGE": "Edit text, update images, change links, drag elements, and more. Your code updates as you go.",
"PROMO_CARD_2": "Try ideas, build pages, and fix issues with AI",
Expand Down
22 changes: 11 additions & 11 deletions src/styles/CentralControlBar.less
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
editor on the right. A solid box-shadow with zero blur paints CCB-colored
pixels into those gaps without shifting any geometry. */
box-shadow:
-2px 0 0 0 #222,
2px 0 0 0 #222;
-2px 0 0 0 #222,
2px 0 0 0 #222;

.ccb-group {
display: flex;
Expand Down Expand Up @@ -160,13 +160,12 @@
.ccb-file-name {
flex: 1 1 auto;
min-height: 0;
/* `sideways-lr` rotates each glyph 90° CCW so the text reads
bottom-up naturally. Using this instead of `vertical-rl` +
`transform: rotate(180deg)` avoids the blurry sub-pixel
rasterization that the transform path produced on linux
electron, because Chromium can take its fast vertical-text
path for glyph layout and skip the rotated bitmap upscale. */
writing-mode: sideways-lr;
/* Base: vertical-rl + rotate(180deg) gives the same bottom-to-top
reading direction as sideways-lr and works in all engines
(including WebKit/Tauri on macOS where sideways-lr is unsupported
and silently falls back to horizontal-tb). */
writing-mode: vertical-rl;
text-orientation: sideways;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
Expand All @@ -175,8 +174,9 @@
font-weight: 500;
/* Promote to its own compositing layer + force AA — keeps the
rotated glyphs crisp even when the system falls back to the
slow text path. */
transform: translateZ(0);
slow text path. rotate(180deg) flips vertical-rl top-to-bottom
into bottom-to-top reading order. */
transform: rotate(180deg) translateZ(0);
backface-visibility: hidden;
-webkit-font-smoothing: antialiased;
text-rendering: geometricPrecision;
Expand Down
Loading
Loading