Skip to content

Commit 1b36bda

Browse files
feat: public desktop download page with v1.0.2 installers (#98)
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> ## Summary Surfaces the green `desktop-v1.0.2` release installers on the public `/download` page and adds discoverability from the login screen. ## Changes - **`src/pages/Download.jsx`** — Enhanced public download page: - Fetches GitHub releases and filters `desktop-v*` tags (avoids `/releases/latest` returning app `v1.3.0`) - Platform detection with primary "Download for {platform}" CTA - Asset ranking (prefer dmg/deb/installer over zip/portable) - macOS Apple Silicon vs Intel preference on arm64 Macs - Grouped macOS / Windows / Linux sections with file sizes - **`src/pages/Login.jsx`** — "Download DeepSQL Desktop" link in sidebar and footer - **`README.md`** — Documents `/download` and GitHub Releases for installers ## Testing Verified locally against live GitHub API — page shows **Version 1.0.2** with all platform installers: [Download page showing v1.0.2 installers](https://cursor.com/agents/bc-8ce91e70-c67b-48c6-84b3-05bb9d06231a/artifacts?path=%2Fopt%2Fcursor%2Fartifacts%2Fdownload-page-v102.png) Login page discoverability: [Login page with download links](https://cursor.com/agents/bc-8ce91e70-c67b-48c6-84b3-05bb9d06231a/artifacts?path=%2Fopt%2Fcursor%2Fartifacts%2Flogin-download-link.png) ## Deploy note Once this frontend is deployed, users can visit `/download` on any DeepSQL instance to grab macOS, Windows, and Linux installers from the latest `desktop-v*` GitHub release — no backend changes required. <sub>To show artifacts inline, <a href="https://cursor.com/dashboard/cloud-agents#my-pull-requests">enable</a> in settings.</sub> <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-8ce91e70-c67b-48c6-84b3-05bb9d06231a?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-8ce91e70-c67b-48c6-84b3-05bb9d06231a&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 3136574 commit 1b36bda

4 files changed

Lines changed: 172 additions & 32 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,13 @@ your schema — all from one shared brain.
248248
### DeepSQL Desktop (optional)
249249

250250
A thin Electron client for a self-hosted VM — direct TLS or an in-process SSH tunnel —
251-
without bundling a second copy of the web UI. Separate npm project:
251+
without bundling a second copy of the web UI.
252+
253+
**Download installers** (macOS, Windows, Linux) from the public page at `/download` on
254+
your DeepSQL instance, or browse
255+
[GitHub Releases](https://github.com/DeepSQLAI/deepsql/releases) for `desktop-v*` tags.
256+
257+
To build from source (separate npm project):
252258

253259
```bash
254260
cd desktop

desktop/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ of the transport layer knows or cares which one is in use.
5353
| Windows | `DeepSQL Setup <version>.exe` (NSIS), portable `.exe` |
5454
| Linux | `.AppImage`, `.deb`, `.rpm` |
5555

56-
macOS builds are unsigned unless you supply signing credentials, so the first
57-
launch needs **right-click → Open** (or `xattr -dr com.apple.quarantine
58-
/Applications/DeepSQL.app`).
56+
macOS builds are unsigned unless you supply signing credentials, so Gatekeeper
57+
blocks a normal double-click on first launch (&ldquo;DeepSQL&rdquo; Not Opened).
58+
Use one of:
59+
60+
1. **Right-click → Open** on `DeepSQL.app`, then click **Open** in the dialog.
61+
2. **System Settings → Privacy & Security → Open Anyway** after the block.
62+
3. Terminal: `xattr -dr com.apple.quarantine /Applications/DeepSQL.app`
5963

6064
### From source
6165

src/pages/Download.jsx

Lines changed: 142 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { useEffect, useMemo, useState } from 'react'
2+
import { Link } from 'react-router-dom'
23
import {
34
AlertTriangle,
45
Apple,
6+
ArrowLeft,
57
Download as DownloadIcon,
68
Loader2,
79
Monitor,
@@ -42,17 +44,18 @@ function classify(asset) {
4244
? 'Intel / AMD64'
4345
: null
4446

45-
if (name.endsWith('.dmg')) return { platform: 'mac', kind: 'Disk image', arch }
46-
if (name.endsWith('.zip')) return { platform: 'mac', kind: 'Zip archive', arch }
47+
if (name.endsWith('.dmg')) return { platform: 'mac', kind: 'Disk image', arch, rank: 0 }
48+
if (name.endsWith('.zip')) return { platform: 'mac', kind: 'Zip archive', arch, rank: 1 }
4749
if (name.endsWith('.exe'))
4850
return {
4951
platform: 'windows',
5052
kind: name.includes('setup') ? 'Installer' : 'Portable',
5153
arch,
54+
rank: name.includes('setup') ? 0 : 1,
5255
}
53-
if (name.endsWith('.appimage')) return { platform: 'linux', kind: 'AppImage', arch }
54-
if (name.endsWith('.deb')) return { platform: 'linux', kind: 'Debian package', arch }
55-
if (name.endsWith('.rpm')) return { platform: 'linux', kind: 'RPM package', arch }
56+
if (name.endsWith('.appimage')) return { platform: 'linux', kind: 'AppImage', arch, rank: 1 }
57+
if (name.endsWith('.deb')) return { platform: 'linux', kind: 'Debian package', arch, rank: 0 }
58+
if (name.endsWith('.rpm')) return { platform: 'linux', kind: 'RPM package', arch, rank: 2 }
5659
return null
5760
}
5861

@@ -65,12 +68,44 @@ function detectPlatform() {
6568
return null
6669
}
6770

71+
/** Prefer Apple Silicon builds on arm64 Macs when both exist. */
72+
function detectMacArchPreference() {
73+
const ua = navigator.userAgent || ''
74+
if (/arm64|aarch64/i.test(ua)) return 'Apple Silicon'
75+
return 'Intel / AMD64'
76+
}
77+
6878
function formatSize(bytes) {
6979
if (!bytes) return ''
7080
const mb = bytes / (1024 * 1024)
7181
return `${mb.toFixed(1)} MB`
7282
}
7383

84+
function pickPrimaryAsset(grouped, platform) {
85+
const assets = grouped[platform] || []
86+
if (!assets.length) return null
87+
88+
const archPref = platform === 'mac' ? detectMacArchPreference() : null
89+
const sorted = [...assets].sort((a, b) => {
90+
if (archPref) {
91+
const aMatch = a.arch === archPref ? 0 : 1
92+
const bMatch = b.arch === archPref ? 0 : 1
93+
if (aMatch !== bMatch) return aMatch - bMatch
94+
}
95+
return (a.rank ?? 9) - (b.rank ?? 9)
96+
})
97+
return sorted[0]
98+
}
99+
100+
function latestDesktopRelease(releases) {
101+
return releases
102+
.filter((r) => r.tag_name?.startsWith(TAG_PREFIX) && !r.draft)
103+
.sort(
104+
(a, b) =>
105+
new Date(b.published_at).getTime() - new Date(a.published_at).getTime(),
106+
)[0]
107+
}
108+
74109
export default function Download() {
75110
const [state, setState] = useState({ status: 'loading' })
76111
const detected = useMemo(() => detectPlatform(), [])
@@ -87,11 +122,7 @@ export default function Download() {
87122
})
88123
.then((releases) => {
89124
if (cancelled) return
90-
const release = releases.find(
91-
(r) => r.tag_name?.startsWith(TAG_PREFIX) && !r.draft,
92-
)
93-
// No desktop release yet is a *different* answer from "we could not
94-
// check", and the page must not blur the two into one empty state.
125+
const release = latestDesktopRelease(releases)
95126
if (!release) return setState({ status: 'none' })
96127
setState({ status: 'ready', release })
97128
})
@@ -112,12 +143,33 @@ export default function Download() {
112143
const meta = classify(asset)
113144
if (meta) out[meta.platform].push({ ...asset, ...meta })
114145
}
146+
for (const key of Object.keys(out)) {
147+
out[key].sort((a, b) => (a.rank ?? 9) - (b.rank ?? 9))
148+
}
115149
return out
116150
}, [state])
117151

152+
const primary = useMemo(
153+
() => (detected ? pickPrimaryAsset(grouped, detected) : null),
154+
[grouped, detected],
155+
)
156+
157+
const versionLabel =
158+
state.status === 'ready'
159+
? state.release.tag_name.replace(TAG_PREFIX, 'Version ')
160+
: null
161+
118162
return (
119163
<div className="min-h-screen bg-white text-gray-900">
120164
<div className="max-w-3xl mx-auto px-6 py-16">
165+
<Link
166+
to="/login"
167+
className="inline-flex items-center gap-1.5 text-sm text-gray-500 hover:text-gray-800 transition-colors mb-8"
168+
>
169+
<ArrowLeft className="h-4 w-4" />
170+
Back to sign in
171+
</Link>
172+
121173
<header className="mb-12">
122174
<div className="flex items-center gap-3 mb-4">
123175
<div className="h-10 w-10 rounded-xl bg-gray-900 flex items-center justify-center">
@@ -144,7 +196,10 @@ export default function Download() {
144196
tone="error"
145197
title="Could not reach GitHub"
146198
body={`The download list could not be loaded (${state.message}). This is a problem fetching the release list, not a sign that no build exists — you can browse releases directly on GitHub.`}
147-
action={{ href: `https://github.com/${REPO}/releases`, label: 'Open releases on GitHub' }}
199+
action={{
200+
href: `https://github.com/${REPO}/releases`,
201+
label: 'Open releases on GitHub',
202+
}}
148203
/>
149204
)}
150205

@@ -153,21 +208,40 @@ export default function Download() {
153208
tone="info"
154209
title="No desktop build published yet"
155210
body="The release list loaded fine — there is simply no desktop-v* release with attached installers. Builds are produced by the desktop-release workflow when a desktop-v* tag is pushed."
156-
action={{ href: `https://github.com/${REPO}/releases`, label: 'Open releases on GitHub' }}
211+
action={{
212+
href: `https://github.com/${REPO}/releases`,
213+
label: 'Open releases on GitHub',
214+
}}
157215
/>
158216
)}
159217

160218
{state.status === 'ready' && (
161219
<>
162-
<div className="flex items-baseline gap-3 mb-8 pb-4 border-b border-gray-200">
163-
<span className="text-sm font-semibold text-gray-900">
164-
{state.release.tag_name.replace(TAG_PREFIX, 'Version ')}
165-
</span>
220+
<div className="flex flex-wrap items-baseline gap-3 mb-6 pb-4 border-b border-gray-200">
221+
<span className="text-sm font-semibold text-gray-900">{versionLabel}</span>
166222
<span className="text-sm text-gray-400">
167223
released {new Date(state.release.published_at).toLocaleDateString()}
168224
</span>
169225
</div>
170226

227+
{primary && (
228+
<div className="mb-10">
229+
<a
230+
href={primary.browser_download_url}
231+
className="inline-flex w-full sm:w-auto items-center justify-center gap-2 rounded-full bg-gray-900 px-6 py-3.5 text-sm font-semibold text-white shadow-lg transition-all hover:bg-gray-800 active:scale-[0.98]"
232+
>
233+
<DownloadIcon className="h-4 w-4" />
234+
Download for {PLATFORMS[detected].label}
235+
{primary.arch ? ` (${primary.arch})` : ''}
236+
</a>
237+
<p className="mt-2 text-xs text-gray-400">
238+
{primary.kind} · {primary.name} · {formatSize(primary.size)}
239+
</p>
240+
</div>
241+
)}
242+
243+
{detected === 'mac' && <MacGatekeeperNotice />}
244+
171245
{Object.entries(PLATFORMS).map(([key, meta]) => {
172246
const assets = grouped[key] || []
173247
if (!assets.length) return null
@@ -190,6 +264,13 @@ export default function Download() {
190264
>
191265
desktop/README.md
192266
</a>
267+
. All installers for this release are also on{' '}
268+
<a
269+
className="underline hover:text-gray-600 transition-colors"
270+
href={state.release.html_url}
271+
>
272+
GitHub
273+
</a>
193274
.
194275
</p>
195276
</>
@@ -227,7 +308,9 @@ function PlatformSection({ platform, assets, highlight, showMacNote }) {
227308
}`}
228309
>
229310
<span className="flex items-center gap-3 min-w-0">
230-
<Package className={`h-4 w-4 shrink-0 ${highlight ? 'text-gray-300' : 'text-gray-400'}`} />
311+
<Package
312+
className={`h-4 w-4 shrink-0 ${highlight ? 'text-gray-300' : 'text-gray-400'}`}
313+
/>
231314
<span className="min-w-0">
232315
<span className="block text-sm font-medium truncate">
233316
{asset.kind}
@@ -247,18 +330,52 @@ function PlatformSection({ platform, assets, highlight, showMacNote }) {
247330
))}
248331
</div>
249332

250-
{showMacNote && (
251-
<p className="mt-3 text-xs text-gray-400 leading-relaxed">
252-
Builds are unsigned unless signing credentials are configured, so the first
253-
launch needs <span className="text-gray-600">right-click → Open</span> (or{' '}
254-
<code className="text-gray-600">xattr -dr com.apple.quarantine /Applications/DeepSQL.app</code>
255-
).
256-
</p>
257-
)}
333+
{showMacNote && <MacGatekeeperNotice compact />}
258334
</section>
259335
)
260336
}
261337

338+
/** macOS builds ship unsigned unless signing secrets are configured in CI. */
339+
function MacGatekeeperNotice({ compact = false }) {
340+
return (
341+
<div
342+
className={`rounded-xl border border-amber-200 bg-amber-50 text-amber-950 ${
343+
compact ? 'mt-3 px-4 py-3' : 'mb-10 px-5 py-4'
344+
}`}
345+
>
346+
<div className="flex items-start gap-3">
347+
<AlertTriangle className="h-4 w-4 text-amber-700 mt-0.5 shrink-0" />
348+
<div className="text-sm leading-relaxed">
349+
<p className={`font-semibold text-amber-950 ${compact ? 'mb-1' : 'mb-2'}`}>
350+
macOS may block the first launch
351+
</p>
352+
<p className="text-amber-900/90 mb-3">
353+
Installers are not Apple-notarized yet, so double-clicking can show
354+
&ldquo;DeepSQL&rdquo; Not Opened with only <strong>Done</strong> and{' '}
355+
<strong>Move to Trash</strong>. That is expected — use one of these instead:
356+
</p>
357+
<ol className="list-decimal pl-5 space-y-2 text-amber-900/90">
358+
<li>
359+
In Finder, <strong>right-click DeepSQL.app → Open</strong>, then click{' '}
360+
<strong>Open</strong> in the dialog. You only need to do this once.
361+
</li>
362+
<li>
363+
Or open <strong>System Settings → Privacy &amp; Security</strong>, scroll
364+
down after the block, and click <strong>Open Anyway</strong>.
365+
</li>
366+
<li>
367+
Or run in Terminal:{' '}
368+
<code className="text-xs bg-amber-100/80 px-1.5 py-0.5 rounded">
369+
xattr -dr com.apple.quarantine /Applications/DeepSQL.app
370+
</code>
371+
</li>
372+
</ol>
373+
</div>
374+
</div>
375+
</div>
376+
)
377+
}
378+
262379
function Notice({ tone, title, body, action }) {
263380
return (
264381
<div

src/pages/Login.jsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
22
import { Link, useSearchParams } from 'react-router-dom'
33
import { useAuth } from '@/hooks/useAuth'
44
import { authAPI, setupAPI } from '@/lib/api/client'
5-
import { ArrowLeft, Database, KeyRound, Mail, ShieldCheck, Sparkles, Zap, Activity, LineChart } from 'lucide-react'
5+
import { ArrowLeft, Database, Download, KeyRound, Mail, ShieldCheck, Sparkles, Zap, Activity, LineChart } from 'lucide-react'
66

77
const STEP_LOGIN = 'login'
88
const STEP_OTP = 'otp'
@@ -303,6 +303,14 @@ export default function Login() {
303303
</div>
304304
))}
305305
</div>
306+
307+
<Link
308+
to="/download"
309+
className="mt-10 inline-flex items-center gap-2 text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors"
310+
>
311+
<Download className="w-4 h-4" />
312+
Download DeepSQL Desktop
313+
</Link>
306314
</div>
307315
</div>
308316

@@ -352,8 +360,13 @@ export default function Login() {
352360
)}
353361
</div>
354362

355-
<div className="absolute bottom-6 left-0 right-0 text-center text-gray-500 text-sm px-6">
356-
&copy; 2026 DeepSQL. Built for developers who love databases.
363+
<div className="absolute bottom-6 left-0 right-0 text-center text-gray-500 text-sm px-6 space-y-1">
364+
<p>
365+
<Link to="/download" className="text-gray-600 hover:text-gray-900 underline underline-offset-2">
366+
Download DeepSQL Desktop
367+
</Link>
368+
</p>
369+
<p>&copy; 2026 DeepSQL. Built for developers who love databases.</p>
357370
</div>
358371
</div>
359372
</div>

0 commit comments

Comments
 (0)