From 5e0fc059e7f291fc2a79f9ab738ceb78ab43d398 Mon Sep 17 00:00:00 2001 From: Gagan Date: Mon, 6 Jul 2026 20:12:25 +0530 Subject: [PATCH] feat(apps): publish button in app toolbar + published links in detail - toolbar (local apps): 'Publish' button; turns into a green 'Published' badge-check once published (click opens the detail panel) - detail Source section: clickable repo + release links for published apps --- .../src/components/apps/app-detail.tsx | 29 ++++++++++++++-- .../src/components/apps/app-frame.tsx | 33 ++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) 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 1068149e..39d294f0 100644 --- a/apps/x/apps/renderer/src/components/apps/app-detail.tsx +++ b/apps/x/apps/renderer/src/components/apps/app-detail.tsx @@ -180,8 +180,20 @@ export function AppDetail({ folder, onClose }: { folder: string; onClose: () => {app.install.updatedAt && } + ) : app.publish ? ( + <> +

Local app — published to the Rowboat catalog.

+ + {app.publish.lastPublishedVersion && ( + + )} + ) : ( -

Local app — created on this machine{app.publish ? `, published as ${app.publish.repo}` : ''}.

+

Local app — created on this machine.

)}
{app.kind === 'installed' && app.install?.repo && ( @@ -268,11 +280,22 @@ export function AppDetail({ folder, onClose }: { folder: string; onClose: () => ) } -function InfoRow({ k, v, mono }: { k: string; v: string; mono?: boolean }) { +function InfoRow({ k, v, mono, link }: { k: string; v: string; mono?: boolean; link?: string }) { return (
{k} - {v} + {link ? ( + + {v} + + ) : ( + {v} + )}
) } diff --git a/apps/x/apps/renderer/src/components/apps/app-frame.tsx b/apps/x/apps/renderer/src/components/apps/app-frame.tsx index c406b903..ae7dcc5c 100644 --- a/apps/x/apps/renderer/src/components/apps/app-frame.tsx +++ b/apps/x/apps/renderer/src/components/apps/app-frame.tsx @@ -1,8 +1,9 @@ import { useEffect, useState } from 'react' -import { ArrowLeft, ExternalLink, Info, RotateCw } from 'lucide-react' +import { ArrowLeft, BadgeCheck, ExternalLink, Info, RotateCw, UploadCloud } from 'lucide-react' import type { rowboatApp } from '@x/shared' import { appOpened } from '@/lib/analytics' import { AppDetail } from '@/components/apps/app-detail' +import { PublishDialog } from '@/components/apps/publish-dialog' // Full-height iframe on the app's own origin (spec §6.6). No sandbox attr — // per-app browser origins are the isolation boundary. Toolbar: back, reload, @@ -11,6 +12,7 @@ import { AppDetail } from '@/components/apps/app-detail' export function AppFrame({ app, onBack }: { app: rowboatApp.AppSummary; onBack: () => void }) { const [reloadNonce, setReloadNonce] = useState(0) const [showDetail, setShowDetail] = useState(false) + const [showPublish, setShowPublish] = useState(false) // Load watchdog: if the iframe hasn't fired `load` within the deadline, // surface a visible retry state instead of a silent blank pane. const [loadState, setLoadState] = useState<'loading' | 'ok' | 'stuck'>('loading') @@ -62,6 +64,27 @@ export function AppFrame({ app, onBack }: { app: rowboatApp.AppSummary; onBack: > + {app.kind === 'local' && ( + app.publish ? ( + + ) : ( + + ) + )}
+ {showPublish && ( + setShowPublish(false)} + onPublished={() => setShowDetail(true)} + /> + )} ) }