diff --git a/apps/x/apps/renderer/src/components/apps/app-detail.tsx b/apps/x/apps/renderer/src/components/apps/app-detail.tsx index 39d294f0..6e8aa554 100644 --- a/apps/x/apps/renderer/src/components/apps/app-detail.tsx +++ b/apps/x/apps/renderer/src/components/apps/app-detail.tsx @@ -272,6 +272,7 @@ export function AppDetail({ folder, onClose }: { folder: string; onClose: () => setShowPublish(false)} onPublished={() => setReloadNonce((n) => n + 1)} /> diff --git a/apps/x/apps/renderer/src/components/apps/publish-dialog.tsx b/apps/x/apps/renderer/src/components/apps/publish-dialog.tsx index f14dd7e0..ed798a8e 100644 --- a/apps/x/apps/renderer/src/components/apps/publish-dialog.tsx +++ b/apps/x/apps/renderer/src/components/apps/publish-dialog.tsx @@ -3,13 +3,18 @@ import { CheckCircle2, Github, Loader2, XCircle } from 'lucide-react' // Publish dialog (spec §14): device-flow sign-in → confirmation → step // progress (§11.2 step names) → success links / typed error (name_taken gets -// an inline hint to rename and retry). +// an inline hint to rename and retry). With `published` the dialog runs the +// UPDATE path instead (§11.3: version bump + new release, no registry) — +// re-running first-publish on a published app always fails with name_taken. type Phase = 'auth' | 'device' | 'confirm' | 'publishing' | 'done' | 'error' +type Increment = 'patch' | 'minor' | 'major' -export function PublishDialog({ folder, appName, onClose, onPublished }: { +export function PublishDialog({ folder, appName, published, onClose, onPublished }: { folder: string appName: string + /** App already has a publish record — run publish-update instead. */ + published?: boolean onClose: () => void onPublished: () => void }) { @@ -18,7 +23,8 @@ export function PublishDialog({ folder, appName, onClose, onPublished }: { const [userCode, setUserCode] = useState('') const [steps, setSteps] = useState([]) const [error, setError] = useState(null) - const [result, setResult] = useState<{ repoUrl: string; releaseUrl: string; prUrl?: string; status: string } | null>(null) + const [increment, setIncrement] = useState('patch') + const [result, setResult] = useState<{ repoUrl?: string; releaseUrl: string; prUrl?: string; status: string; version?: string } | null>(null) const pollTimer = useRef(null) useEffect(() => { @@ -75,8 +81,13 @@ export function PublishDialog({ folder, appName, onClose, onPublished }: { setError(null) setSteps([]) try { - const r = await window.ipc.invoke('apps:publish', { folder }) - setResult(r) + if (published) { + const r = await window.ipc.invoke('apps:publishUpdate', { folder, increment }) + setResult({ releaseUrl: r.releaseUrl, status: 'published', version: r.version }) + } else { + const r = await window.ipc.invoke('apps:publish', { folder }) + setResult(r) + } setPhase('done') onPublished() } catch (e) { @@ -128,15 +139,47 @@ export function PublishDialog({ folder, appName, onClose, onPublished }: { {phase === 'confirm' && (
-

Signed in as @{login}. This will publish {appName} publicly.

+

+ Signed in as @{login}.{' '} + {published + ? <>This will publish a new version of {appName} (a new release on the existing repo). + : <>This will publish {appName} publicly.} +

+ {published && ( + + )}
)} - {(phase === 'publishing' || phase === 'done' || phase === 'error') && ( + {published && (phase === 'publishing' || phase === 'done' || phase === 'error') && ( +
+ {phase === 'publishing' && ( +

+ Publishing update… +

+ )} + {phase === 'done' && result && ( +
+

Published v{result.version}

+ +
+ )} +
+ )} + + {!published && (phase === 'publishing' || phase === 'done' || phase === 'error') && (
{Object.entries(STEP_LABELS).map(([key, label]) => { const doneStep = steps.includes(key) || phase === 'done'