Compare commits

..

No commits in common. "fresh-release" and "main" have entirely different histories.

4 changed files with 124 additions and 184 deletions

View file

@ -8,8 +8,7 @@ A modern, high-performance system optimizer for macOS, built with **Electron**,
- **Flash Clean**: Instantly remove system caches, logs, Xcode cache, Homebrew cache, and manage Trash with a detailed inspection view. - **Flash Clean**: Instantly remove system caches, logs, Xcode cache, Homebrew cache, and manage Trash with a detailed inspection view.
- **App Uninstaller**: View installed applications, their sizes, and thoroughly remove them along with their associated preference files and caches. - **App Uninstaller**: View installed applications, their sizes, and thoroughly remove them along with their associated preference files and caches.
- **Deep Clean**: Scan for large files and heavy folders. - **Deep Clean**: Scan for large files and heavy folders.
- **Real-time Monitoring**: Track disk usage and category sizes with accurate categorical percentages. - **Real-time Monitoring**: Track disk usage and category sizes.
- **Web App Support**: Access the fully functional dashboard remotely or locally via any standard web browser at `http://localhost:36969` (or your NAS host IP).
- **Native Menubar Integration**: Includes a responsive, monochrome template icon that adapts to macOS light/dark modes perfectly. - **Native Menubar Integration**: Includes a responsive, monochrome template icon that adapts to macOS light/dark modes perfectly.
- **Cross-Platform**: Runs natively with compiled Go backends on Apple Silicon (M1/M2/M3), Intel Macs, and Windows. - **Cross-Platform**: Runs natively with compiled Go backends on Apple Silicon (M1/M2/M3), Intel Macs, and Windows.
@ -40,10 +39,10 @@ To create distributable release binaries (Universal `.dmg` for macOS, Portable `
### 1. Build the App ### 1. Build the App
```bash ```bash
# macOS Universal DMG # macOS Universal DMG
pnpm run build:mac pnpm run build && pnpm run electron:build && npx electron-builder --mac --universal
# Windows Portable EXE # Windows Portable EXE
pnpm run build:win pnpm run build && pnpm run electron:build && npx electron-builder --win portable --x64
``` ```
### 2. Locate the Installer ### 2. Locate the Installer

View file

@ -47,24 +47,8 @@ func main() {
http.HandleFunc("/api/apps/action", handleAppAction) http.HandleFunc("/api/apps/action", handleAppAction)
http.HandleFunc("/api/apps/uninstall", handleAppUninstall) http.HandleFunc("/api/apps/uninstall", handleAppUninstall)
// Serve Static Files for Web Mode // Static File Serving is handled directly by Electron.
// This serves the built Vite frontend from the `dist` folder. // Backend only needs to provide API routes.
distPath := filepath.Join("..", "dist")
if _, err := os.Stat("dist"); err == nil {
distPath = "dist" // If running directly from the project root
}
fs := http.FileServer(http.Dir(distPath))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// If the requested file exists, serve it
path := filepath.Join(distPath, r.URL.Path)
if _, err := os.Stat(path); err == nil {
fs.ServeHTTP(w, r)
return
}
// Otherwise, fallback to index.html for SPA routing
http.ServeFile(w, r, filepath.Join(distPath, "index.html"))
})
fmt.Printf("🚀 Antigravity Backend running on http://localhost%s\n", Port) fmt.Printf("🚀 Antigravity Backend running on http://localhost%s\n", Port)

View file

@ -414,14 +414,13 @@ export function Dashboard() {
{isSystemDrive && segments.length > 0 ? ( {isSystemDrive && segments.length > 0 ? (
segments.map((seg, i) => { segments.map((seg, i) => {
const width = total > 0 ? (seg.size / total) * 100 : 0; const width = total > 0 ? (seg.size / total) * 100 : 0;
const segUsedPercent = used > 0 ? (seg.size / used) * 100 : 0;
if (width < 0.5) return null; // Hide tiny segments if (width < 0.5) return null; // Hide tiny segments
return ( return (
<div <div
key={i} key={i}
className={`h-full bg-gradient-to-b ${seg.color} border-r border-white/20 last:border-0`} className={`h-full bg-gradient-to-b ${seg.color} border-r border-white/20 last:border-0`}
style={{ width: `${width}%` }} style={{ width: `${width}%` }}
title={`${seg.label}: ${seg.size.toFixed(2)} GB (${width.toFixed(1)}% of total, ${segUsedPercent.toFixed(1)}% of used)`} title={`${seg.label}: ${seg.size.toFixed(2)} GB`}
/> />
); );
}) })
@ -437,18 +436,12 @@ export function Dashboard() {
{/* Legend for System Drive */} {/* Legend for System Drive */}
{isSystemDrive && segments.length > 0 && ( {isSystemDrive && segments.length > 0 && (
<div className="flex flex-wrap gap-x-4 gap-y-2 mt-3"> <div className="flex flex-wrap gap-x-4 gap-y-2 mt-3">
{segments.map((seg, i) => { {segments.map((seg, i) => seg.size > 0.1 && (
if (seg.size <= 0.1) return null; <div key={i} className="flex items-center gap-1.5">
const width = total > 0 ? (seg.size / total) * 100 : 0; <div className={`w-2.5 h-2.5 rounded-full ${seg.legendColor} shadow-sm`} />
return ( <span className="text-[11px] font-medium text-gray-600">{seg.label}</span>
<div key={i} className="flex items-center gap-1.5"> </div>
<div className={`w-2.5 h-2.5 rounded-full ${seg.legendColor} shadow-sm`} /> ))}
<span className="text-[11px] font-medium text-gray-600">
{seg.label} {width > 0 && `(${width.toFixed(1)}%)`}
</span>
</div>
);
})}
</div> </div>
)} )}
</div> </div>
@ -488,146 +481,118 @@ export function Dashboard() {
<div className="liquid-glass-heavy rounded-[var(--radius-lg)] overflow-hidden flex flex-col min-h-0 relative border border-white/20 dark:border-white/10 shadow-lg max-h-full"> <div className="liquid-glass-heavy rounded-[var(--radius-lg)] overflow-hidden flex flex-col min-h-0 relative border border-white/20 dark:border-white/10 shadow-lg max-h-full">
<div className="overflow-y-auto flex-1 stagger-children py-3 px-3"> <div className="overflow-y-auto flex-1 stagger-children py-3 px-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{categorySizes.apps !== 0 && ( <CategoryRow
<CategoryRow icon={<Icons.Apps />}
icon={<Icons.Apps />} label="Applications"
label="Applications" size={categorySizes.apps >= 0 ? formatBytes(categorySizes.apps) : "Not Scanned"}
size={categorySizes.apps >= 0 ? formatBytes(categorySizes.apps) : "Not Scanned"} onClick={() => runScan('apps', 'Applications')}
onClick={() => runScan('apps', 'Applications')} actionIcon
actionIcon description="Installed apps & support files"
description="Installed apps & support files" />
/> <CategoryRow
)} icon={<Icons.Documents />}
{categorySizes.documents !== 0 && ( label="Documents"
<CategoryRow size={categorySizes.documents >= 0 ? formatBytes(categorySizes.documents) : "Not Scanned"}
icon={<Icons.Documents />} onClick={() => runScan('docs', 'Documents')}
label="Documents" actionIcon
size={categorySizes.documents >= 0 ? formatBytes(categorySizes.documents) : "Not Scanned"} description="Personal documents folder"
onClick={() => runScan('docs', 'Documents')} />
actionIcon <CategoryRow
description="Personal documents folder" icon={<Icons.Archives />}
/> label="Archives"
)} size={categorySizes.archives !== undefined && categorySizes.archives >= 0 ? formatBytes(categorySizes.archives) : "Not Scanned"}
{categorySizes.archives !== 0 && ( onClick={() => runScan('archives', 'Archives')}
<CategoryRow actionIcon
icon={<Icons.Archives />} description="Compressed files (.zip, .rar, .7z)"
label="Archives" />
size={categorySizes.archives !== undefined && categorySizes.archives >= 0 ? formatBytes(categorySizes.archives) : "Not Scanned"} <CategoryRow
onClick={() => runScan('archives', 'Archives')} icon={<Icons.Downloads />}
actionIcon label="Downloads"
description="Compressed files (.zip, .rar, .7z)" size={categorySizes.downloads >= 0 ? formatBytes(categorySizes.downloads) : "Not Scanned"}
/> onClick={() => runScan('downloads', 'Downloads')}
)} actionIcon
{categorySizes.downloads !== 0 && ( description="Downloads folder items"
<CategoryRow />
icon={<Icons.Downloads />} <CategoryRow
label="Downloads" icon={<Icons.Desktop />}
size={categorySizes.downloads >= 0 ? formatBytes(categorySizes.downloads) : "Not Scanned"} label="Desktop"
onClick={() => runScan('downloads', 'Downloads')} size={categorySizes.desktop >= 0 ? formatBytes(categorySizes.desktop) : "Not Scanned"}
actionIcon onClick={() => runScan('desktop', 'Desktop Items')}
description="Downloads folder items" actionIcon
/> description="Files on your desktop"
)} />
{categorySizes.desktop !== 0 && ( <CategoryRow
<CategoryRow icon={<Icons.Music />}
icon={<Icons.Desktop />} label="Music"
label="Desktop" size={categorySizes.music >= 0 ? formatBytes(categorySizes.music) : "Not Scanned"}
size={categorySizes.desktop >= 0 ? formatBytes(categorySizes.desktop) : "Not Scanned"} onClick={() => runScan('music', 'Music Library')}
onClick={() => runScan('desktop', 'Desktop Items')} actionIcon
actionIcon description="iTunes/Music library & files"
description="Files on your desktop" />
/> <CategoryRow
)} icon={<Icons.Movies />}
{categorySizes.music !== 0 && ( label="Movies"
<CategoryRow size={categorySizes.movies >= 0 ? formatBytes(categorySizes.movies) : "Not Scanned"}
icon={<Icons.Music />} onClick={() => runScan('movies', 'Movies & Videos')}
label="Music" actionIcon
size={categorySizes.music >= 0 ? formatBytes(categorySizes.music) : "Not Scanned"} description="Movies folder & video files"
onClick={() => runScan('music', 'Music Library')} />
actionIcon <CategoryRow
description="iTunes/Music library & files" icon={<Icons.VirtualMachines />}
/> label="Disk Images"
)} size={categorySizes.virtual_machines !== undefined && categorySizes.virtual_machines >= 0 ? formatBytes(categorySizes.virtual_machines) : "Not Scanned"}
{categorySizes.movies !== 0 && ( onClick={() => runScan('vms', 'Disk Images')}
<CategoryRow actionIcon
icon={<Icons.Movies />} description="ISOs, VM disks, & installers"
label="Movies" />
size={categorySizes.movies >= 0 ? formatBytes(categorySizes.movies) : "Not Scanned"} <CategoryRow
onClick={() => runScan('movies', 'Movies & Videos')} icon={<Icons.Games />}
actionIcon label="Games"
description="Movies folder & video files" size={categorySizes.games !== undefined && categorySizes.games >= 0 ? formatBytes(categorySizes.games) : "Not Scanned"}
/> onClick={() => runScan('games', 'Games Libraries')}
)} actionIcon
{categorySizes.virtual_machines !== 0 && ( description="Steam, Epic, EA, Ubisoft"
<CategoryRow />
icon={<Icons.VirtualMachines />} <CategoryRow
label="Disk Images" icon={<Icons.AI />}
size={categorySizes.virtual_machines !== undefined && categorySizes.virtual_machines >= 0 ? formatBytes(categorySizes.virtual_machines) : "Not Scanned"} label="AI Models"
onClick={() => runScan('vms', 'Disk Images')} size={categorySizes.ai !== undefined && categorySizes.ai >= 0 ? formatBytes(categorySizes.ai) : "Not Scanned"}
actionIcon onClick={() => runScan('ai', 'AI Tools')}
description="ISOs, VM disks, & installers" actionIcon
/> description="ComfyUI, WebUI, Checkpoints"
)} />
{categorySizes.games !== 0 && ( <CategoryRow
<CategoryRow icon={<Icons.Docker />}
icon={<Icons.Games />} label="Docker"
label="Games" size={categorySizes.docker !== undefined && categorySizes.docker >= 0 ? formatBytes(categorySizes.docker) : "Not Scanned"}
size={categorySizes.games !== undefined && categorySizes.games >= 0 ? formatBytes(categorySizes.games) : "Not Scanned"} onClick={() => runScan('docker', 'Docker Data')}
onClick={() => runScan('games', 'Games Libraries')} actionIcon
actionIcon description="Docker Desktop disk usage"
description="Steam, Epic, EA, Ubisoft" />
/> <CategoryRow
)} icon={<Icons.Cache />}
{categorySizes.ai !== 0 && ( label="System Cache"
<CategoryRow size={categorySizes.cache !== undefined && categorySizes.cache >= 0 ? formatBytes(categorySizes.cache) : "Not Scanned"}
icon={<Icons.AI />} onClick={() => runScan('cache', 'Cache & Temp')}
label="AI Models" actionIcon
size={categorySizes.ai !== undefined && categorySizes.ai >= 0 ? formatBytes(categorySizes.ai) : "Not Scanned"} description="Browser & System temporary files"
onClick={() => runScan('ai', 'AI Tools')} />
actionIcon <CategoryRow
description="ComfyUI, WebUI, Checkpoints" icon={<Icons.CloudDrive />}
/> label="iCloud Drive"
)} size={categorySizes.icloud >= 0 ? formatBytes(categorySizes.icloud) : "Not Scanned"}
{categorySizes.docker !== 0 && ( onClick={() => runScan('icloud', 'iCloud Drive')}
<CategoryRow actionIcon
icon={<Icons.Docker />} description="Local iCloud file copies"
label="Docker" />
size={categorySizes.docker !== undefined && categorySizes.docker >= 0 ? formatBytes(categorySizes.docker) : "Not Scanned"} <CategoryRow
onClick={() => runScan('docker', 'Docker Data')} icon={<Icons.Photos />}
actionIcon label="Photos"
description="Docker Desktop disk usage" size={categorySizes.photos >= 0 ? formatBytes(categorySizes.photos) : "Not Scanned"}
/> onClick={() => runScan('photos', 'Photos')}
)} actionIcon
{categorySizes.cache !== 0 && ( description="Photos library caches"
<CategoryRow />
icon={<Icons.Cache />}
label="System Cache"
size={categorySizes.cache !== undefined && categorySizes.cache >= 0 ? formatBytes(categorySizes.cache) : "Not Scanned"}
onClick={() => runScan('cache', 'Cache & Temp')}
actionIcon
description="Browser & System temporary files"
/>
)}
{categorySizes.icloud !== 0 && (
<CategoryRow
icon={<Icons.CloudDrive />}
label="iCloud Drive"
size={categorySizes.icloud >= 0 ? formatBytes(categorySizes.icloud) : "Not Scanned"}
onClick={() => runScan('icloud', 'iCloud Drive')}
actionIcon
description="Local iCloud file copies"
/>
)}
{categorySizes.photos !== 0 && (
<CategoryRow
icon={<Icons.Photos />}
label="Photos"
size={categorySizes.photos >= 0 ? formatBytes(categorySizes.photos) : "Not Scanned"}
onClick={() => runScan('photos', 'Photos')}
actionIcon
description="Photos library caches"
/>
)}
<CategoryRow <CategoryRow
icon={<Icons.Trash />} icon={<Icons.Trash />}
label="Trash" label="Trash"

View file

@ -3,14 +3,6 @@ import { createRoot } from 'react-dom/client'
import './index.css' import './index.css'
import App from './App.tsx' import App from './App.tsx'
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then((registrations) => {
for (let registration of registrations) {
registration.unregister();
}
});
}
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<App /> <App />