Skip to content
Merged
13 changes: 13 additions & 0 deletions ui/desktop/forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ if (process.env['APPLE_ID'] === undefined) {
module.exports = {
packagerConfig: cfg,
rebuildConfig: {},
publishers: [
{
name: '@electron-forge/publisher-github',
config: {
repository: {
owner: 'block',
name: 'goose'
},
prerelease: false,
draft: true
}
}
],
makers: [
{
name: '@electron-forge/maker-zip',
Expand Down
79 changes: 70 additions & 9 deletions ui/desktop/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ui/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@
"ai": "^3.4.33",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"compare-versions": "^6.1.1",
"cors": "^2.8.5",
"cronstrue": "^2.48.0",
"dotenv": "^16.4.5",
"electron-log": "^5.2.2",
"electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.6.2",
"electron-window-state": "^5.0.3",
"express": "^4.21.1",
"framer-motion": "^11.11.11",
Expand Down
53 changes: 53 additions & 0 deletions ui/desktop/scripts/generate-update-icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const { createCanvas, loadImage } = require('canvas');
const fs = require('fs');
const path = require('path');

async function generateUpdateIcon() {
// Load the original icon
const iconPath = path.join(__dirname, '../src/images/iconTemplate.png');
const icon = await loadImage(iconPath);

// Create canvas
const canvas = createCanvas(22, 22);
const ctx = canvas.getContext('2d');

// Draw the original icon
ctx.drawImage(icon, 0, 0);

// Add red dot in top-right corner
ctx.fillStyle = '#FF0000';
ctx.beginPath();
ctx.arc(18, 4, 3, 0, 2 * Math.PI);
ctx.fill();

// Save the new icon
const outputPath = path.join(__dirname, '../src/images/iconTemplateUpdate.png');
const buffer = canvas.toBuffer('image/png');
fs.writeFileSync(outputPath, buffer);

console.log('Generated update icon at:', outputPath);

// Also generate @2x version
const canvas2x = createCanvas(44, 44);
const ctx2x = canvas2x.getContext('2d');

// Load and draw @2x version
const icon2xPath = path.join(__dirname, '../src/images/[email protected]');
const icon2x = await loadImage(icon2xPath);
ctx2x.drawImage(icon2x, 0, 0);

// Add red dot in top-right corner (scaled)
ctx2x.fillStyle = '#FF0000';
ctx2x.beginPath();
ctx2x.arc(36, 8, 6, 0, 2 * Math.PI);
ctx2x.fill();

// Save the @2x version
const output2xPath = path.join(__dirname, '../src/images/[email protected]');
const buffer2x = canvas2x.toBuffer('image/png');
fs.writeFileSync(output2xPath, buffer2x);

console.log('Generated @2x update icon at:', output2xPath);
}

generateUpdateIcon().catch(console.error);
12 changes: 10 additions & 2 deletions ui/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,16 @@ export default function App() {
console.log('Setting up view change handler');
const handleSetView = (_event: IpcRendererEvent, ...args: unknown[]) => {
const newView = args[0] as View;
console.log(`Received view change request to: ${newView}`);
setView(newView);
const section = args[1] as string | undefined;
console.log(
`Received view change request to: ${newView}${section ? `, section: ${section}` : ''}`
);

if (section && newView === 'settings') {
setView(newView, { section });
} else {
setView(newView);
}
};
const urlParams = new URLSearchParams(window.location.search);
const viewFromUrl = urlParams.get('view');
Expand Down
3 changes: 2 additions & 1 deletion ui/desktop/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import MoreMenuLayout from '../more_menu/MoreMenuLayout';
export type SettingsViewOptions = {
deepLinkConfig?: ExtensionConfig;
showEnvVars?: boolean;
section?: string;
};

export default function SettingsView({
Expand Down Expand Up @@ -55,7 +56,7 @@ export default function SettingsView({
{/* Tool Selection Strategy */}
<ToolSelectionStrategySection setView={setView} />
{/* App Settings */}
<AppSettingsSection />
<AppSettingsSection scrollToSection={viewOptions.section} />
</div>
</div>
</div>
Expand Down
25 changes: 23 additions & 2 deletions ui/desktop/src/components/settings/app/AppSettingsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { Switch } from '../../ui/switch';
import UpdateSection from './UpdateSection';

export default function AppSettingsSection() {
interface AppSettingsSectionProps {
scrollToSection?: string;
}

export default function AppSettingsSection({ scrollToSection }: AppSettingsSectionProps) {
const [menuBarIconEnabled, setMenuBarIconEnabled] = useState(true);
const [dockIconEnabled, setDockIconEnabled] = useState(true);
const [isMacOS, setIsMacOS] = useState(false);
const [isDockSwitchDisabled, setIsDockSwitchDisabled] = useState(false);
const updateSectionRef = useRef<HTMLDivElement>(null);

// Check if running on macOS
useEffect(() => {
setIsMacOS(window.electron.platform === 'darwin');
}, []);

// Handle scrolling to update section
useEffect(() => {
if (scrollToSection === 'update' && updateSectionRef.current) {
// Use a timeout to ensure the DOM is ready
setTimeout(() => {
updateSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 100);
}
}, [scrollToSection]);
Comment on lines +22 to +29
Copy link

@surreptus surreptus Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not required, but consider using useLayoutEffect here instead of depending on the timeout.

Suggested change
useEffect(() => {
if (scrollToSection === 'update' && updateSectionRef.current) {
// Use a timeout to ensure the DOM is ready
setTimeout(() => {
updateSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 100);
}
}, [scrollToSection]);
useLayoutEffect(() => {
if (scrollToSection === 'update' && updateSectionRef.current) {
updateSectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, [scrollToSection]);


// Load menu bar and dock icon states
useEffect(() => {
window.electron.getMenuBarIconState().then((enabled) => {
Expand Down Expand Up @@ -106,6 +122,11 @@ export default function AppSettingsSection() {
</div>
)}
</div>

{/* Update Section */}
<div ref={updateSectionRef} className="mt-8 pt-8 border-t border-gray-200">
<UpdateSection />
</div>
</div>
</section>
);
Expand Down
Loading
Loading