diff --git a/.github/workflows/desktop-macos-branch-build.yml b/.github/workflows/desktop-macos-branch-build.yml index d1bc98fc..0038b1d1 100644 --- a/.github/workflows/desktop-macos-branch-build.yml +++ b/.github/workflows/desktop-macos-branch-build.yml @@ -69,7 +69,7 @@ jobs: cat release/SHASUMS256.txt - name: Upload branch build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ steps.artifact.outputs.name }} path: | diff --git a/.github/workflows/desktop-windows-branch-build.yml b/.github/workflows/desktop-windows-branch-build.yml index 0528d7a4..362c1025 100644 --- a/.github/workflows/desktop-windows-branch-build.yml +++ b/.github/workflows/desktop-windows-branch-build.yml @@ -53,7 +53,7 @@ jobs: cat release/SHASUMS256.txt - name: Upload branch build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ steps.artifact.outputs.name }} path: | diff --git a/electron/desktopWindow.js b/electron/desktopWindow.js index 606f5f16..1f1143fa 100644 --- a/electron/desktopWindow.js +++ b/electron/desktopWindow.js @@ -60,6 +60,7 @@ export class DesktopWindowManager { this.tabs = tabs; this.mainWindow = null; + this.settingsWindow = null; this.tray = null; this.launcherLoaded = false; this.activeContentView = null; @@ -205,8 +206,13 @@ export class DesktopWindowManager { } emitDesktopState() { - if (!this.mainWindow || this.mainWindow.webContents.isDestroyed()) return; - this.mainWindow.webContents.send('cloudcli-desktop:state-updated', this.getDesktopState()); + const state = this.getDesktopState(); + if (this.mainWindow && !this.mainWindow.webContents.isDestroyed()) { + this.mainWindow.webContents.send('cloudcli-desktop:state-updated', state); + } + if (this.settingsWindow && !this.settingsWindow.webContents.isDestroyed()) { + this.settingsWindow.webContents.send('cloudcli-desktop:state-updated', state); + } } emitLauncherCommand(command) { @@ -214,6 +220,64 @@ export class DesktopWindowManager { this.mainWindow.webContents.send('cloudcli-desktop:launcher-command', command); } + emitSettingsCommand(command) { + if (!this.settingsWindow || this.settingsWindow.webContents.isDestroyed()) return; + this.settingsWindow.webContents.send('cloudcli-desktop:launcher-command', command); + } + + syncSettingsWindowBounds() { + if (!this.mainWindow || !this.settingsWindow || this.settingsWindow.isDestroyed()) return; + this.settingsWindow.setBounds(this.mainWindow.getBounds()); + } + + async ensureSettingsWindow(sheet = 'desktop-settings') { + if (!this.mainWindow) return null; + + if (this.settingsWindow && !this.settingsWindow.isDestroyed()) { + this.syncSettingsWindowBounds(); + this.emitSettingsCommand({ type: 'open-sheet', sheet }); + this.settingsWindow.focus(); + return this.settingsWindow; + } + + this.settingsWindow = new BrowserWindow({ + parent: this.mainWindow, + modal: true, + show: false, + frame: false, + transparent: true, + hasShadow: false, + resizable: false, + minimizable: false, + maximizable: false, + fullscreenable: false, + movable: false, + skipTaskbar: true, + backgroundColor: '#00000000', + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + preload: this.getPreloadPath(), + }, + }); + this.syncSettingsWindowBounds(); + this.configureChildWebContents(this.settingsWindow.webContents); + this.settingsWindow.once('ready-to-show', () => this.settingsWindow?.show()); + this.settingsWindow.on('closed', () => { + this.settingsWindow = null; + }); + await this.settingsWindow.loadFile(this.getLauncherPath(), { + query: { modal: '1', sheet }, + }); + return this.settingsWindow; + } + + closeSettingsWindow() { + if (!this.settingsWindow || this.settingsWindow.isDestroyed()) return; + this.settingsWindow.close(); + } + async showTarget(target, { trackTab = true } = {}) { if (!this.mainWindow) return; if (trackTab) { @@ -372,8 +436,8 @@ export class DesktopWindowManager { label: 'Services', submenu: [ { - label: 'Computer Access', - click: () => void this.actions.showComputerAccess(), + label: 'Computer Use', + click: () => void this.showDesktopSettings(), }, ], }, @@ -572,8 +636,13 @@ export class DesktopWindowManager { async showDesktopSettings() { if (!this.mainWindow) return this.getDesktopState(); - await this.showLauncher(); - this.emitLauncherCommand({ type: 'open-sheet', sheet: 'app-settings' }); + await this.ensureSettingsWindow('desktop-settings'); + return this.getDesktopState(); + } + + async showLocalSettings() { + if (!this.mainWindow) return this.getDesktopState(); + await this.ensureSettingsWindow('local-settings'); return this.getDesktopState(); } @@ -666,11 +735,17 @@ export class DesktopWindowManager { if (this.activeContentView) { this.activeContentView.setBounds(this.getContentViewBounds()); } + this.syncSettingsWindowBounds(); + }); + + this.mainWindow.on('move', () => { + this.syncSettingsWindowBounds(); }); this.mainWindow.on('closed', () => { this.tabViews.clear(); this.activeContentView = null; + this.settingsWindow = null; this.mainWindow = null; this.launcherLoaded = false; }); diff --git a/electron/launcher/launcher.css b/electron/launcher/launcher.css index edd18acc..b361cd18 100644 --- a/electron/launcher/launcher.css +++ b/electron/launcher/launcher.css @@ -8,6 +8,11 @@ body { height: 100%; } +html.cc-modal-window, +body.cc-modal-window { + background: transparent; +} + :root { --bg: #111315; --s1: #171a1d; diff --git a/electron/launcher/launcher.js b/electron/launcher/launcher.js index 0d5f2aa3..ca47890b 100644 --- a/electron/launcher/launcher.js +++ b/electron/launcher/launcher.js @@ -21,6 +21,7 @@ window.__MOCK_STATE__ = { var MOCK = window.__MOCK_STATE__ || {}; var VERSION = window.__APP_VERSION__ || ''; var LOGO_URL = new URL('../../public/logo-32.png', window.location.href).toString(); + var SEARCH = new URLSearchParams(window.location.search || ''); function clone(value) { return JSON.parse(JSON.stringify(value)); @@ -48,7 +49,9 @@ window.__MOCK_STATE__ = { showComputerAccess: function () { return Promise.resolve(clone(mockState)); }, showEnvironmentPicker: function () { return Promise.resolve(clone(mockState)); }, showLauncher: function () { return Promise.resolve(clone(mockState)); }, + showLocalSettings: function () { return Promise.resolve(clone(mockState)); }, showDesktopSettings: function () { return Promise.resolve(clone(mockState)); }, + closeSettingsWindow: function () { return Promise.resolve(clone(mockState)); }, showActiveEnvironmentActionsMenu: function () { return Promise.resolve(clone(mockState)); }, openCloudDashboard: function () { return Promise.resolve(clone(mockState)); }, runActiveEnvironmentAction: function () { return Promise.resolve(clone(mockState)); }, @@ -70,9 +73,6 @@ window.__MOCK_STATE__ = { mockState.computerUse.running = mockState.computerUse.enabled; return Promise.resolve(clone(mockState)); }, - showComputerAccessPermissions: function () { - return Promise.resolve(clone(mockState)); - }, openEnvironment: function (id) { var env = (mockState.environments || []).filter(function (item) { return item.id === id; })[0]; if (env) { @@ -171,12 +171,12 @@ window.__MOCK_STATE__ = { function computerUseStatus(state) { var computerUse = state && state.computerUse ? state.computerUse : {}; if (!computerUse.enabled) { - return { label: 'Off', tone: 'idle', detail: 'CloudCLI cannot use this computer.' }; + return { label: 'Disabled', tone: 'idle', detail: 'CloudCLI cannot use this computer.' }; } if (computerUse.consentMode === 'auto') { - return { label: 'Ready · Unattended', tone: 'warn', detail: 'Trusted agents can use this computer without a local approval prompt.' }; + return { label: 'Unattended access', tone: 'warn', detail: 'Trusted agents can use this computer without a local approval prompt.' }; } - return { label: 'Ready · Ask first', tone: 'ok', detail: 'CloudCLI can use this computer. Agents need approval before control starts.' }; + return { label: 'Ask before each session', tone: 'ok', detail: 'Agents need approval before control starts.' }; } var CC = { @@ -198,6 +198,7 @@ window.__MOCK_STATE__ = { _reg: {}, _wired: false, _poll: null, + modalMode: SEARCH.get('modal') === '1', }; window.CC = CC; @@ -206,11 +207,14 @@ window.__MOCK_STATE__ = { var overlay; CC.setState = function (state) { + var currentSheet = CC.ui.openSheet || (CC.modalMode ? (CC.ui.initialSheet || 'desktop-settings') : null); + var sheetBody = overlay ? overlay.querySelector('.cc-sheet-body') : null; + var scrollTop = sheetBody ? sheetBody.scrollTop : 0; if (state && typeof state === 'object') CC.state = state; CC.applyTheme(CC.state); CC.render(CC.state); - if (CC.ui.openSheet) { - CC.openSheet(CC.ui.openSheet); + if (currentSheet) { + CC.openSheet(currentSheet, { scrollTop: scrollTop }); } }; @@ -334,11 +338,16 @@ window.__MOCK_STATE__ = { consentMode: current.consentMode === 'auto' ? 'auto' : 'ask', }); }); - case 'computer-permissions': - return CC.run('Opening system permissions...', function () { return bridge.showComputerAccessPermissions(); }); case 'settings-toggle': - CC.openSheet('app-settings'); - return; + return CC.run('Opening desktop settings...', function () { return bridge.showDesktopSettings(); }); + case 'desktop-settings-toggle': + return CC.run('Opening desktop settings...', function () { return bridge.showDesktopSettings(); }); + case 'local-settings-toggle': + return CC.run('Opening local settings...', function () { return bridge.showLocalSettings(); }); + case 'computer-settings-toggle': + return CC.run('Opening desktop settings...', function () { return bridge.showDesktopSettings(); }); + case 'settings-close': + return CC.closeSheet(); case 'dashboard': return CC.run('Opening CloudCLI dashboard...', function () { return bridge.openCloudDashboard(); }); case 'env-action': @@ -347,15 +356,6 @@ window.__MOCK_STATE__ = { return CC.run('Opening environment actions...', function () { return bridge.showActiveEnvironmentActionsMenu(); }); case 'env-row-menu': return CC.run('Opening environment actions...', function () { return bridge.showEnvironmentActionsMenu(node.getAttribute('data-cc-environment-id')); }); - case 'local-settings-toggle': - CC.openSheet('local-settings'); - return; - case 'computer-settings-toggle': - CC.openSheet('computer-access'); - return; - case 'settings-close': - CC.closeSheet(); - return; default: return; } @@ -430,19 +430,26 @@ window.__MOCK_STATE__ = { ''; }; - CC.openSheet = function (sheet) { - if (sheet === 'app-settings') { - CC.renderAppSettings(); - } else if (sheet === 'computer-access') { - CC.renderComputerAccess(); + CC.openSheet = function (sheet, options) { + options = options || {}; + if (sheet === 'desktop-settings') { + CC.renderDesktopSettings(); } else { CC.renderLocalSettings(); } CC.ui.openSheet = sheet; overlay.classList.add('open'); + if (typeof options.scrollTop === 'number') { + var body = overlay.querySelector('.cc-sheet-body'); + if (body) body.scrollTop = options.scrollTop; + } }; CC.closeSheet = function () { + if (CC.modalMode && bridge.closeSettingsWindow) { + CC.ui.openSheet = null; + return bridge.closeSettingsWindow(); + } CC.ui.openSheet = null; overlay.classList.remove('open'); }; @@ -478,46 +485,19 @@ window.__MOCK_STATE__ = { ); }; - CC.buildComputerAccessSections = function (state, options) { - options = options || {}; + CC.buildComputerUseSection = function (state) { var computerUse = state.computerUse || {}; - var status = computerUseStatus(state); - var sections = []; - - if (options.includeStatus !== false) { - sections.push(CC.renderSection(options.statusEyebrow || 'STATUS', status.label, '' + - '
Run the open-source app on this machine. No account required.