From c7b49b3722bf397e74b363b2b5c2c74972d4173b Mon Sep 17 00:00:00 2001 From: Pratik Bodkhe Date: Mon, 20 Jul 2026 08:27:33 +0900 Subject: [PATCH] Download surface for the macOS companion /download resolves the newest DMG from GitHub Releases at request time (5-minute revalidate) and degrades to an honest coming-soon state while no release exists, so the page never links a 404 or claims a build that is not there. 404 is the one silent degradation; every other resolver failure logs with rate-limit context, and GITHUB_TOKEN is used when set. /download joins the middleware public paths (it previously bounced logged-out visitors to /login), the landing page and the in-app companion install prompt route through it, and it lands in the sitemap and the public-route smoke check. --- CHANGELOG.md | 1 + e2e/smoke.spec.ts | 2 +- src/app/download/page.tsx | 194 ++++++++++++++++++ src/app/page.tsx | 10 + src/app/sitemap.ts | 6 + .../minutia/companion-install-prompt.tsx | 4 +- src/lib/desktop-download.ts | 73 +++++++ src/middleware.ts | 1 + 8 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 src/app/download/page.tsx create mode 100644 src/lib/desktop-download.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4619a..84fecb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Public `/download` page for the Minutia macOS companion. It resolves the latest signed DMG from GitHub Releases at request time (cached hourly) and degrades to a "watch for the release" state until the first build ships, so it never links to a 404. Includes system requirements, an open-source link, and how-it-works steps. The landing page and the in-app companion prompt both point to it. - Minutia Retro: a free, anonymous, multiplayer retrospective board at `/retro`. Guided 7-phase ritual (Lobby, Reflect, Reveal, Theme, Vote, Discuss, Commit) with realtime presence, a synchronized card-flip Reveal, and a closure bloom. Boards are ephemeral (30-day auto-expiry) and rate-limited. - Anonymous board operations run through `SECURITY DEFINER` Postgres RPCs (default-deny `retro_*` tables); liveness uses Supabase Realtime broadcast and presence with a snapshot reconcile. - AI theme-clustering suggestions during the Theme phase (OpenRouter; degrades silently when unconfigured). diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index c5e82a6..59c4e3b 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -34,7 +34,7 @@ test.describe("Smoke Tests", () => { }); test("all public routes return valid responses", async ({ request }) => { - const routes = ["/login"]; + const routes = ["/login", "/download"]; for (const route of routes) { const response = await request.get(route); diff --git a/src/app/download/page.tsx b/src/app/download/page.tsx new file mode 100644 index 0000000..62075a7 --- /dev/null +++ b/src/app/download/page.tsx @@ -0,0 +1,194 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { ArrowRight, Bell, Download, ExternalLink } from "lucide-react"; +import { + DESKTOP_RELEASES_URL, + DESKTOP_REPO_URL, + DESKTOP_REQUIREMENTS, + getDesktopRelease, + type DesktopRelease, +} from "@/lib/desktop-download"; + +export const metadata: Metadata = { + title: "Minutia for Mac", + description: + "Record your meetings from the menu bar. No bot joins the call. Recaps land in Minutia seconds after you hit stop.", +}; + +export const revalidate = 300; + +const steps = [ + { + title: "Install and sign in", + description: + "Download the app and sign in with your Minutia account. It lives in the menu bar, out of your way.", + }, + { + title: "Record from the menu bar", + description: + "Start a recording yourself, or let meeting detection prompt you the moment a call begins.", + }, + { + title: "Stop and get the recap", + description: + "Hit stop and the recap opens in Minutia seconds later, complete with action items.", + }, +]; + +function DownloadCta({ + release, + variant, +}: { + release: DesktopRelease; + variant: "ink" | "accent"; +}) { + const base = + "inline-flex items-center gap-2 rounded-md px-6 py-3 text-sm font-medium text-paper transition-colors"; + const solid = + variant === "ink" + ? "bg-ink hover:bg-ink-2" + : "bg-accent hover:bg-accent-hover"; + + if (release.available) { + return ( + + + Download for macOS + + ); + } + + return ( + + + Watch for the release + + ); +} + +export default async function DownloadPage() { + const release = await getDesktopRelease(); + const requirements = release.available + ? `${release.version} · ${DESKTOP_REQUIREMENTS}` + : DESKTOP_REQUIREMENTS; + + return ( +
+ {/* ─── Hero ─── */} +
+

+ Minutia for Mac +

+

+ Record your meetings from the menu bar. +

+

+ No bot joins the call. Minutia captures your mic and system audio + right from the menu bar, and the recap lands in your workspace seconds + after you hit stop. +

+
+ +
+

{requirements}

+ {!release.available && ( +

+ Not shipped yet. Watch the repo and GitHub tells you the moment the + first signed build lands. +

+ )} +

+ Open source.{" "} + + View it on GitHub + + +

+
+ + {/* ─── How it works ─── */} +
+

+ How it works +

+
+ {steps.map((step, i) => ( +
+
+ {i + 1} +
+
+

+ {step.title} +

+

{step.description}

+
+
+ ))} +
+
+ + {/* ─── Final CTA ─── */} +
+
+ {release.available ? ( + <> +

+ Bring your meetings in automatically. +

+

+ Sign in with your Minutia account and start capturing in + minutes. +

+
+ +
+

{requirements}

+ + ) : ( + <> +

+ Start capturing on the web today. +

+

+ The Mac companion is on the way. Minutia runs in your browser + right now, and the desktop app slots in the moment it ships. +

+
+ + Get started free + + +
+ + )} +
+
+ + {/* ─── Footer ─── */} +
+
+ + Minutia + +

+ The open-source Outstanding Issues Log. Run it yourself. +

+
+
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 854483b..97a44b2 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -59,6 +59,16 @@ export default function LandingPage() {

No credit card required. Open source.

+

+ On a Mac?{" "} + + Get the macOS companion + + +

{/* ─── Features ─── */} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index e08b400..1f6140a 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -10,5 +10,11 @@ export default function sitemap(): MetadataRoute.Sitemap { changeFrequency: "weekly", priority: 1, }, + { + url: `${siteUrl}/download`, + lastModified: new Date(), + changeFrequency: "weekly", + priority: 0.8, + }, ]; } diff --git a/src/components/minutia/companion-install-prompt.tsx b/src/components/minutia/companion-install-prompt.tsx index 06f5e59..6c42fbd 100644 --- a/src/components/minutia/companion-install-prompt.tsx +++ b/src/components/minutia/companion-install-prompt.tsx @@ -4,8 +4,6 @@ import * as React from "react"; import { X, Download } from "lucide-react"; const DISMISS_KEY = "minutia.companion-prompt-dismissed"; -const DOWNLOAD_URL = - "https://github.com/shiprite-dev/minutia-desktop/releases/latest"; // Inline nudge shown near the recorder when the signed-in user has no companion // app checked in yet (companion_last_seen_at is null) and has not dismissed it. @@ -37,7 +35,7 @@ export function CompanionInstallPrompt({ For the best experience, download and install the companion app.

{ + try { + const token = process.env.GITHUB_TOKEN; + const res = await fetch( + `https://api.github.com/repos/${DESKTOP_REPO}/releases/latest`, + { + headers: { + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + next: { revalidate: 300 }, + }, + ); + if (res.status === 404) return { available: false }; + if (!res.ok) { + console.error( + `[desktop-download] GitHub releases/latest ${res.status} (ratelimit-remaining=${res.headers.get("x-ratelimit-remaining")})`, + ); + return { available: false }; + } + + const data = (await res.json()) as GithubRelease; + if (data.draft) return { available: false }; + + const dmg = data.assets?.find((a) => + a.name?.toLowerCase().endsWith(".dmg"), + ); + if (!dmg?.browser_download_url) return { available: false }; + + const version = data.tag_name ?? data.name; + if (!version) { + console.error("[desktop-download] release has a DMG but no tag or name"); + return { available: false }; + } + + return { + available: true, + version, + downloadUrl: dmg.browser_download_url, + releaseUrl: data.html_url ?? DESKTOP_RELEASES_URL, + }; + } catch (err) { + console.error("[desktop-download] resolver threw", err); + return { available: false }; + } +} diff --git a/src/middleware.ts b/src/middleware.ts index 27b2875..7f2a675 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -194,6 +194,7 @@ export async function middleware(request: NextRequest) { "/", "/login", "/signup", + "/download", "/accept-invite", "/auth/callback", "/reset-password",