diff --git a/electron/main.js b/electron/main.js
index 1303894e..101ed172 100644
--- a/electron/main.js
+++ b/electron/main.js
@@ -3114,6 +3114,59 @@ ipcMain.handle('open-application', (_event, name) => {
return true;
});
+// The user's REAL desktop wallpaper for the dashboard background, read at runtime and never
+// bundled (Apple's images can't ship in an open-source repo). Any failure on any platform
+// returns null and the renderer keeps its SVG scenery, so this can only ever add.
+let desktopWallpaperCache;
+ipcMain.handle('get-desktop-wallpaper', async () => {
+ if (desktopWallpaperCache !== undefined) return desktopWallpaperCache;
+ const { execFile } = require('child_process');
+ const run = (cmd, args) => new Promise((resolve, reject) => {
+ execFile(cmd, args, { timeout: 8000 }, (err, stdout) => (err ? reject(err) : resolve(String(stdout).trim())));
+ });
+ try {
+ if (process.platform === 'darwin') {
+ let srcPath = await run('/usr/bin/osascript', ['-e', 'tell application "System Events" to get picture of current desktop']).catch(() => '');
+ if (!srcPath || !fs.existsSync(srcPath)) {
+ // Dynamic/aerial wallpapers report a reaped temp frame; the wallpaper store may still hold a real image path.
+ srcPath = '';
+ try {
+ const storePath = path.join(app.getPath('home'), 'Library', 'Application Support', 'com.apple.wallpaper', 'Store', 'Index.plist');
+ const raw = fs.readFileSync(storePath, 'latin1');
+ const candidates = [...raw.matchAll(/file:\/\/(\/[ -~]+?\.(?:jpg|jpeg|png|heic|tiff))/gi)].map((m) => decodeURIComponent(m[1]));
+ srcPath = candidates.find((c) => fs.existsSync(c)) || '';
+ } catch (_) { /* fall through to the SVG scenery */ }
+ }
+ if (!srcPath) { desktopWallpaperCache = null; return null; }
+ // sips normalizes HEIC sources to jpeg and downscales so the data URL stays small.
+ const outPath = path.join(app.getPath('userData'), 'wallpaper-cache.jpg');
+ await run('/usr/bin/sips', ['-s', 'format', 'jpeg', '--resampleHeightWidthMax', '2400', srcPath, '--out', outPath]);
+ desktopWallpaperCache = `data:image/jpeg;base64,${fs.readFileSync(outPath).toString('base64')}`;
+ return desktopWallpaperCache;
+ }
+ if (process.platform === 'win32') {
+ // Windows keeps the active wallpaper pre-transcoded as a JPEG; the registry path is the fallback.
+ const transcoded = path.join(app.getPath('appData'), 'Microsoft', 'Windows', 'Themes', 'TranscodedWallpaper');
+ let srcPath = fs.existsSync(transcoded) ? transcoded : null;
+ if (!srcPath) {
+ const out = await run('reg', ['query', 'HKCU\\Control Panel\\Desktop', '/v', 'WallPaper']);
+ const match = out.match(/WallPaper\s+REG_SZ\s+(.+)$/m);
+ if (match && fs.existsSync(match[1].trim())) srcPath = match[1].trim();
+ }
+ if (!srcPath) { desktopWallpaperCache = null; return null; }
+ const ext = path.extname(srcPath).toLowerCase();
+ const mime = ext === '.png' ? 'image/png' : ext === '.bmp' ? 'image/bmp' : 'image/jpeg';
+ desktopWallpaperCache = `data:${mime};base64,${fs.readFileSync(srcPath).toString('base64')}`;
+ return desktopWallpaperCache;
+ }
+ desktopWallpaperCache = null;
+ return null;
+ } catch (_) {
+ desktopWallpaperCache = null;
+ return null;
+ }
+});
+
// Affiliate install state. Returns the persisted install.json contents so
// the renderer can attach the referral code to authenticated cloud calls
// (Stripe checkout, sign-in events) for downstream attribution.
diff --git a/electron/preload.js b/electron/preload.js
index b34f065a..26c736dc 100644
--- a/electron/preload.js
+++ b/electron/preload.js
@@ -77,6 +77,7 @@ contextBridge.exposeInMainWorld('openswarm', {
capturePage: (rect) => ipcRenderer.invoke('capture-page', rect),
getAppIcon: (name) => ipcRenderer.invoke('get-app-icon', name),
openApplication: (name) => ipcRenderer.invoke('open-application', name),
+ getDesktopWallpaper: () => ipcRenderer.invoke('get-desktop-wallpaper'),
getUpdateStatus: () => ipcRenderer.invoke('get-update-status'),
getCrashRecoveryInfo: () => ipcRenderer.invoke('get-crash-recovery-info'),
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
diff --git a/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx b/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx
index d720d29d..8bc49405 100644
--- a/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx
+++ b/frontend/src/app/pages/Dashboard/desktop/DesktopDock.tsx
@@ -1,13 +1,12 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
-import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
import LanguageIcon from '@mui/icons-material/Language';
+import DashboardGlyph from '../canvas/DashboardGlyph';
import SettingsIcon from '@mui/icons-material/Settings';
import AppsRoundedIcon from '@mui/icons-material/AppsRounded';
import EditNoteIcon from '@mui/icons-material/EditNote';
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
-import CoPresentIcon from '@mui/icons-material/CoPresent';
import { useAppDispatch } from '@/shared/hooks';
import { openSettingsModal } from '@/shared/state/settingsSlice';
import { getWebview } from '@/shared/browserRegistry';
@@ -57,6 +56,21 @@ interface DesktopDockProps {
const TILE = 30;
const PREVIEW_W = 190;
+// Frames show a colorful per-card dock, not uniform tiles; hues rotate by name so two agents rarely match.
+const AGENT_TILE_HUES = [
+ 'linear-gradient(135deg, #4a7dd6, #2b4fa8)',
+ 'linear-gradient(135deg, #8a5bd6, #5b34a8)',
+ 'linear-gradient(135deg, #3aa88f, #1f7a64)',
+ 'linear-gradient(135deg, #d6754a, #a8492b)',
+ 'linear-gradient(135deg, #c94a7d, #96305c)',
+];
+
+function hueFor(name: string): string {
+ let h = 0;
+ for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0;
+ return AGENT_TILE_HUES[Math.abs(h) % AGENT_TILE_HUES.length];
+}
+
/** Left-edge desktop dock: one tile per open card, hover previews, click focuses the window. */
function DesktopDock({
sessions,
@@ -80,12 +94,13 @@ function DesktopDock({
for (const card of Object.values(cards)) {
const session = sessions[card.session_id];
if (!session) continue;
+ const title = displayChatTitle(session);
list.push({
id: card.session_id,
- label: displayChatTitle(session),
+ label: title,
rect: card,
- tileBg: 'linear-gradient(135deg, #4a7dd6, #2b4fa8)',
- icon: ,
+ tileBg: hueFor(title),
+ icon: ,
snippet: session.turn_label?.label || undefined,
});
}
@@ -103,12 +118,17 @@ function DesktopDock({
}
for (const [cardKey, vc] of Object.entries(viewCards)) {
const output = outputs[vc.output_id];
+ const appName = output?.name || 'App';
list.push({
id: cardKey,
- label: output?.name || 'App',
+ label: appName,
rect: vc,
tileBg: 'linear-gradient(135deg, #ef9552, #d96a2b)',
- icon: ,
+ icon: (
+
+ {appName.charAt(0).toUpperCase()}
+
+ ),
thumbnail: output?.thumbnail,
});
}
diff --git a/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx b/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx
index c9a7d035..ee366058 100644
--- a/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx
+++ b/frontend/src/app/pages/Dashboard/desktop/DesktopWallpaper.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import Box from '@mui/material/Box';
import { useThemeMode } from '@/shared/styles/ThemeContext';
@@ -10,6 +10,26 @@ import { useThemeMode } from '@/shared/styles/ThemeContext';
*/
function DesktopWallpaper(): React.ReactElement {
const { mode } = useThemeMode();
+ // The user's real OS wallpaper when the Electron bridge can supply it (never bundled); SVG scenery otherwise.
+ const [realWallpaper, setRealWallpaper] = useState(null);
+ useEffect(() => {
+ const getWallpaper = (window as unknown as { openswarm?: { getDesktopWallpaper?: () => Promise } })
+ .openswarm?.getDesktopWallpaper;
+ if (!getWallpaper) return;
+ let cancelled = false;
+ getWallpaper().then((dataUrl) => { if (!cancelled && dataUrl) setRealWallpaper(dataUrl); }).catch(() => undefined);
+ return () => { cancelled = true; };
+ }, []);
+
+ if (realWallpaper) {
+ return (
+
+
+ {mode === 'dark' && }
+
+ );
+ }
+
return (