From 5cf69591c57c1b72d7270f2963f0e912fba40f05 Mon Sep 17 00:00:00 2001
From: Milos Petrovic
Date: Tue, 14 Jul 2026 12:46:46 +0200
Subject: [PATCH 1/2] Clips: Stop re-prompting users to download the desktop
app
---
.../app/components/library/library-layout.tsx | 2 +-
.../clips/app/hooks/use-desktop-promo.ts | 18 +++++----------
.../app/lib/capture-install-options.test.ts | 22 ++++++++++++++++++-
.../clips/app/lib/capture-install-options.ts | 21 ++++++++++++++++++
.../clips/app/routes/_app.meetings._index.tsx | 15 -------------
templates/clips/app/routes/download.tsx | 5 +++--
...tay-hidden-after-you-download-the-insta.md | 6 +++++
7 files changed, 58 insertions(+), 31 deletions(-)
create mode 100644 templates/clips/changelog/2026-07-14-desktop-app-prompts-stay-hidden-after-you-download-the-insta.md
diff --git a/templates/clips/app/components/library/library-layout.tsx b/templates/clips/app/components/library/library-layout.tsx
index d4afc1f062..62384c6cf5 100644
--- a/templates/clips/app/components/library/library-layout.tsx
+++ b/templates/clips/app/components/library/library-layout.tsx
@@ -524,7 +524,7 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
<>
{shouldShowSidebarLink && (
-
+
{t("navigation.desktopCta")}
diff --git a/templates/clips/app/hooks/use-desktop-promo.ts b/templates/clips/app/hooks/use-desktop-promo.ts
index 82ad06b317..45fb608278 100644
--- a/templates/clips/app/hooks/use-desktop-promo.ts
+++ b/templates/clips/app/hooks/use-desktop-promo.ts
@@ -1,8 +1,10 @@
import { useCallback, useEffect, useState } from "react";
import { useIsMobile } from "@/hooks/use-mobile";
-
-const DISMISSED_KEY = "clips.desktop-promo.dismissed";
+import {
+ hasDownloadedDesktopApp,
+ markDesktopAppDownloaded,
+} from "@/lib/capture-install-options";
function detectDesktopApp(): boolean {
if (typeof navigator === "undefined") return false;
@@ -25,20 +27,12 @@ export function useDesktopPromo() {
useEffect(() => {
setIsDesktopApp(detectDesktopApp());
- setDismissed(
- typeof window !== "undefined" &&
- window.localStorage?.getItem(DISMISSED_KEY) === "1",
- );
+ setDismissed(hasDownloadedDesktopApp());
}, []);
const dismiss = useCallback(() => {
setDismissed(true);
- try {
- window.localStorage?.setItem(DISMISSED_KEY, "1");
- } catch {
- // localStorage can throw in private browsing — ignore, dismissal
- // still holds for the session via React state.
- }
+ markDesktopAppDownloaded();
}, []);
return {
diff --git a/templates/clips/app/lib/capture-install-options.test.ts b/templates/clips/app/lib/capture-install-options.test.ts
index 989d309c67..e964dd65c8 100644
--- a/templates/clips/app/lib/capture-install-options.test.ts
+++ b/templates/clips/app/lib/capture-install-options.test.ts
@@ -1,11 +1,31 @@
-import { describe, expect, it } from "vitest";
+import { afterEach, describe, expect, it, vi } from "vitest";
import {
+ hasDownloadedDesktopApp,
+ markDesktopAppDownloaded,
resolveClipsChromeExtensionEnabled,
supportsPublishedClipsChromeExtensionHost,
} from "./capture-install-options";
describe("capture install options", () => {
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
+ it("remembers when the desktop installer is downloaded", () => {
+ const values = new Map();
+ vi.stubGlobal("window", {
+ localStorage: {
+ getItem: (key: string) => values.get(key) ?? null,
+ setItem: (key: string, value: string) => values.set(key, value),
+ },
+ });
+
+ expect(hasDownloadedDesktopApp()).toBe(false);
+ markDesktopAppDownloaded();
+ expect(hasDownloadedDesktopApp()).toBe(true);
+ });
+
it("enables the published Chrome extension on supported first-party/local hosts", () => {
expect(
supportsPublishedClipsChromeExtensionHost("clips.agent-native.com"),
diff --git a/templates/clips/app/lib/capture-install-options.ts b/templates/clips/app/lib/capture-install-options.ts
index 01870d5e81..a8315afa8d 100644
--- a/templates/clips/app/lib/capture-install-options.ts
+++ b/templates/clips/app/lib/capture-install-options.ts
@@ -1,3 +1,24 @@
+const DESKTOP_DOWNLOAD_STORAGE_KEY = "clips.desktop-promo.dismissed";
+
+export function hasDownloadedDesktopApp(): boolean {
+ try {
+ return (
+ typeof window !== "undefined" &&
+ window.localStorage?.getItem(DESKTOP_DOWNLOAD_STORAGE_KEY) === "1"
+ );
+ } catch {
+ return false;
+ }
+}
+
+export function markDesktopAppDownloaded(): void {
+ try {
+ window.localStorage?.setItem(DESKTOP_DOWNLOAD_STORAGE_KEY, "1");
+ } catch {
+ // Download tracking is best-effort and must not block the installer.
+ }
+}
+
function isFalsy(value: unknown): boolean {
if (typeof value !== "string") return false;
return ["0", "false", "no", "off"].includes(value.trim().toLowerCase());
diff --git a/templates/clips/app/routes/_app.meetings._index.tsx b/templates/clips/app/routes/_app.meetings._index.tsx
index 15c35af7bd..97bfd63e04 100644
--- a/templates/clips/app/routes/_app.meetings._index.tsx
+++ b/templates/clips/app/routes/_app.meetings._index.tsx
@@ -748,21 +748,6 @@ function MeetingsHeader({
)}
- {showDesktopCta && (
-
-
-
- {t("meetingsRoute.getDesktopApp")}
-
-
- {t("meetingsRoute.requiredForReminders")}
-
-
- )}
>
);
diff --git a/templates/clips/app/routes/download.tsx b/templates/clips/app/routes/download.tsx
index df1dec45b2..fee1e8430a 100644
--- a/templates/clips/app/routes/download.tsx
+++ b/templates/clips/app/routes/download.tsx
@@ -14,6 +14,7 @@ import enMessages from "@/i18n/en-US";
import {
clipsChromeExtensionEnabled,
clipsChromeExtensionUrl,
+ markDesktopAppDownloaded,
} from "@/lib/capture-install-options";
export function meta() {
@@ -118,7 +119,7 @@ function primaryDownloadButton(
if (asset) {
return (
-
-
- {t("dictateRoute.downloadDesktopApp")}
-
);
}
diff --git a/templates/clips/app/routes/_app.meetings.$meetingId.tsx b/templates/clips/app/routes/_app.meetings.$meetingId.tsx
index 4605caa386..a636e8f6c9 100644
--- a/templates/clips/app/routes/_app.meetings.$meetingId.tsx
+++ b/templates/clips/app/routes/_app.meetings.$meetingId.tsx
@@ -797,19 +797,11 @@ export default function MeetingDetailRoute() {
{showDesktopRecordHint && (
-
-
-
{t("meetingDetail.desktopHint")}
- {!isDesktopApp && (
-
-
- {t("meetingDetail.getDesktopApp")}
-
- )}
+
+
+
+ {t("meetingDetail.desktopHint")}
+
)}
diff --git a/templates/clips/app/routes/_app.meetings._index.tsx b/templates/clips/app/routes/_app.meetings._index.tsx
index 97bfd63e04..00af183a49 100644
--- a/templates/clips/app/routes/_app.meetings._index.tsx
+++ b/templates/clips/app/routes/_app.meetings._index.tsx
@@ -5,7 +5,6 @@ import {
} from "@agent-native/core/client";
import {
IconAlertTriangle,
- IconAppWindow,
IconBellRinging,
IconCalendar,
IconCheck,
@@ -24,7 +23,6 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { NavLink, useSearchParams } from "react-router";
import { toast } from "sonner";
-import { CaptureInstallButton } from "@/components/capture-install-options";
import { PageHeader } from "@/components/library/page-header";
import type { AttendeeStackParticipant } from "@/components/meetings/attendee-stack";
import { DayHeader, formatDayLabel } from "@/components/meetings/day-header";
@@ -52,7 +50,6 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
-import { useDesktopPromo } from "@/hooks/use-desktop-promo";
import enMessages from "@/i18n/en-US";
export function meta() {
@@ -465,7 +462,7 @@ function MeetingNotesSteps() {
);
}
-function MeetingNotesGuide({ showDesktopCta }: { showDesktopCta: boolean }) {
+function MeetingNotesGuide() {
const t = useT();
return (
@@ -478,16 +475,6 @@ function MeetingNotesGuide({ showDesktopCta }: { showDesktopCta: boolean }) {
{t("meetingsRoute.howToTriggerDescription")}
- {showDesktopCta && (
-
-
- {t("meetingsRoute.getDesktopApp")}
-
- )}
@@ -696,14 +683,12 @@ function CalendarAccountMenu({
function MeetingsHeader({
query,
onQueryChange,
- showDesktopCta,
calendarAccounts,
onConnected,
onDisconnected,
}: {
query: string;
onQueryChange: (next: string) => void;
- showDesktopCta: boolean;
calendarAccounts: CalendarAccount[];
onConnected?: () => void | Promise;
onDisconnected?: () => void;
@@ -792,7 +777,6 @@ export default function MeetingsIndexRoute() {
}, [query]);
const queryClient = useQueryClient();
- const { shouldShowSidebarLink: showDesktopCta } = useDesktopPromo();
const accounts = useActionQuery<{ accounts: CalendarAccount[] } | undefined>(
"list-calendar-accounts",
@@ -996,7 +980,6 @@ export default function MeetingsIndexRoute() {
)}
- {hasCalendar && meetings.length === 0 && (
-
- )}
+ {hasCalendar && meetings.length === 0 && }
{nothingAtAll ? (