Skip to content

Commit 8a164f1

Browse files
committed
Replace native update dialog with in-app banner, bump to 1.5.0
Show changelog notes in a bottom-right banner when an update is downloaded, with "Restart to update" and "Later" options. Removes the native Electron dialog. Main process parses CHANGELOG.md for release notes and sends them via IPC. Bumps version to 1.5.0.
1 parent 4c05919 commit 8a164f1

7 files changed

Lines changed: 103 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Changelog
22

3-
## 1.4.2
3+
## 1.5.0
44

55
- Fix resubmission after App Review rejection – handle UNRESOLVED_ISSUES submissions to avoid ITEM_PART_OF_ANOTHER_SUBMISSION errors
66
- Rename "Resubmit for review" button to "Update review" to match App Store Connect terminology
77
- Revamp review insights prompt with three categories (strengths, weaknesses, potential) and stricter rules
88
- Show version statuses in portfolio app cards – non-live versions display platform, version, and state like App Store Connect
99
- Add keyboard shortcuts: ⌘P portfolio, ⌘1–9 switch apps, ⌘O overview, ⌘L store listing, ⌘R reviews, ⌘A analytics, ⌘B builds
10+
- Replace native update dialog with in-app banner showing changelog and "Restart to update" button
1011

1112
## 1.4.1
1213

electron/main.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -326,20 +326,38 @@ function setupAutoUpdater(): void {
326326
});
327327

328328
updater.on("update-downloaded", () => {
329-
mainWindow?.webContents.send("update-status", { state: "downloaded" });
330-
dialog.showMessageBox({
331-
message: "Update downloaded",
332-
detail: "The update will be installed when you restart.",
333-
buttons: ["Restart now", "Later"],
334-
}).then(({ response }) => {
335-
if (response === 0) updater.quitAndInstall();
336-
});
329+
const notes = getLatestChangelog();
330+
mainWindow?.webContents.send("update-status", { state: "downloaded", notes });
337331
});
338332

339333
const settings = loadSettings();
340334
if (settings.autoCheckUpdates) startUpdateInterval();
341335
}
342336

337+
/** Read the top section from CHANGELOG.md for the update banner. */
338+
function getLatestChangelog(): string[] {
339+
try {
340+
const changelogPath = path.join(app.getAppPath(), "CHANGELOG.md");
341+
const content = fs.readFileSync(changelogPath, "utf-8");
342+
const lines = content.split("\n");
343+
const notes: string[] = [];
344+
let inSection = false;
345+
for (const line of lines) {
346+
if (line.startsWith("## ") && !inSection) {
347+
inSection = true;
348+
continue;
349+
}
350+
if (line.startsWith("## ") && inSection) break;
351+
if (inSection && line.startsWith("- ")) {
352+
notes.push(line.slice(2).trim());
353+
}
354+
}
355+
return notes;
356+
} catch {
357+
return [];
358+
}
359+
}
360+
343361
function startUpdateInterval(): void {
344362
if (updateInterval || !autoUpdater) return;
345363
autoUpdater.checkForUpdates();
@@ -604,6 +622,10 @@ if (!gotLock) {
604622
return loadSettings().autoCheckUpdates;
605623
});
606624

625+
ipcMain.on("install-update", () => {
626+
autoUpdater?.quitAndInstall();
627+
});
628+
607629
ipcMain.on("set-auto-check-updates", (_, enabled: boolean) => {
608630
const settings = loadSettings();
609631
settings.autoCheckUpdates = enabled;

electron/preload.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ contextBridge.exposeInMainWorld("electron", {
1515
},
1616
updates: {
1717
checkNow: () => ipcRenderer.send("check-for-updates"),
18-
onStatus: (cb: (status: { state: string; message?: string }) => void) => {
19-
const handler = (_: unknown, status: { state: string; message?: string }) => cb(status);
18+
installNow: () => ipcRenderer.send("install-update"),
19+
onStatus: (cb: (status: { state: string; message?: string; notes?: string[] }) => void) => {
20+
const handler = (_: unknown, status: { state: string; message?: string; notes?: string[] }) => cb(status);
2021
ipcRenderer.on("update-status", handler);
2122
return () => { ipcRenderer.removeListener("update-status", handler); };
2223
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "itsyconnect-macos",
3-
"version": "1.4.2",
3+
"version": "1.5.0",
44
"private": true,
55
"license": "AGPL-3.0-only",
66
"repository": {

src/app/dashboard/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { RefreshProvider } from "@/lib/refresh-context";
4040
import { FooterPortalProvider } from "@/lib/footer-portal-context";
4141
import { ConnectionBanner } from "@/components/layout/connection-banner";
4242
import { DemoBanner } from "@/components/layout/demo-banner";
43+
import { UpdateBanner } from "@/components/layout/update-banner";
4344
import { BreadcrumbProvider } from "@/lib/breadcrumb-context";
4445
import { ErrorReportProvider } from "@/lib/error-report-context";
4546
import { InsightsPanelProvider, useInsightsPanel } from "@/lib/insights-panel-context";
@@ -54,7 +55,8 @@ declare global {
5455
onNavigate: (cb: (path: string) => void) => () => void;
5556
updates: {
5657
checkNow: () => void;
57-
onStatus: (cb: (status: { state: string; message?: string }) => void) => () => void;
58+
installNow: () => void;
59+
onStatus: (cb: (status: { state: string; message?: string; notes?: string[] }) => void) => () => void;
5860
getAutoCheck: () => Promise<boolean>;
5961
setAutoCheck: (enabled: boolean) => void;
6062
};
@@ -181,6 +183,7 @@ export default function DashboardLayout({
181183
<Suspense>
182184
<BuildActionFooter />
183185
</Suspense>
186+
<UpdateBanner />
184187
</InsightsPanelProvider>
185188
</SidebarInset>
186189
</SidebarProvider>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { ArrowClockwise, X } from "@phosphor-icons/react";
5+
import { Button } from "@/components/ui/button";
6+
7+
export function UpdateBanner() {
8+
const [notes, setNotes] = useState<string[]>([]);
9+
const [dismissed, setDismissed] = useState(false);
10+
11+
useEffect(() => {
12+
const unsub = window.electron?.updates.onStatus((status) => {
13+
if (status.state === "downloaded") {
14+
setNotes(status.notes ?? []);
15+
setDismissed(false);
16+
}
17+
});
18+
return () => { unsub?.(); };
19+
}, []);
20+
21+
if (dismissed || notes.length === 0) return null;
22+
23+
return (
24+
<div className="fixed right-4 bottom-4 z-50 w-80 rounded-lg border bg-popover p-4 shadow-lg">
25+
<div className="mb-3 flex items-start justify-between gap-2">
26+
<h4 className="text-sm font-medium">Update available</h4>
27+
<button
28+
onClick={() => setDismissed(true)}
29+
className="rounded-sm p-0.5 text-muted-foreground hover:text-foreground"
30+
>
31+
<X size={14} />
32+
</button>
33+
</div>
34+
<ul className="mb-4 space-y-1 text-xs text-muted-foreground">
35+
{notes.map((note, i) => (
36+
<li key={i} className="flex gap-1.5">
37+
<span className="mt-1.5 size-1 shrink-0 rounded-full bg-muted-foreground/50" />
38+
<span>{note}</span>
39+
</li>
40+
))}
41+
</ul>
42+
<div className="flex gap-2">
43+
<Button
44+
size="sm"
45+
className="flex-1"
46+
onClick={() => window.electron?.updates.installNow()}
47+
>
48+
<ArrowClockwise size={14} className="mr-1.5" />
49+
Restart to update
50+
</Button>
51+
<Button
52+
variant="outline"
53+
size="sm"
54+
onClick={() => setDismissed(true)}
55+
>
56+
Later
57+
</Button>
58+
</div>
59+
</div>
60+
);
61+
}

src/lib/version.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export const APP_VERSION = "1.4.2";
2-
export const BUILD_NUMBER = "142";
1+
export const APP_VERSION = "1.5.0";
2+
export const BUILD_NUMBER = "150";

0 commit comments

Comments
 (0)