feat(ui): hardcode visual preview tab to primary frontend port 3000

This commit is contained in:
2026-05-15 16:03:06 -07:00
parent 4adf7a7659
commit d464ccd19d
2 changed files with 65 additions and 165 deletions

View File

@@ -25,9 +25,9 @@ export default function PreviewTab() {
const { anatomy, loading } = useAnatomy(projectId, { pollMs: 0 });
const previews = anatomy?.hosting.previews ?? [];
const options = previews.filter((p) => p.url);
// Find the port 3000 preview if it exists, otherwise fall back to null
const primaryPreview = previews.find(p => p.port === 3000);
const [selectedUrl, setSelectedUrl] = useState<string | null>(null);
const [iframeSrc, setIframeSrc] = useState<string | null>(null);
const iframeDomRef = useRef<HTMLIFrameElement | null>(null);
const bridge = usePreviewBridge();
@@ -35,16 +35,9 @@ export default function PreviewTab() {
const deviceMode = usePreviewToolbarStore((s) => s.deviceMode);
// Auto-select first preview on load
useEffect(() => {
if (!selectedUrl && options.length > 0) {
setSelectedUrl(options[0].url);
}
}, [options, selectedUrl]);
useLayoutEffect(() => {
setIframeSrc(selectedUrl ?? null);
}, [selectedUrl]);
setIframeSrc(primaryPreview?.url ?? null);
}, [primaryPreview?.url]);
useEffect(() => {
if (!bridge || !iframeSrc || !iframeDomRef.current) return;
@@ -53,23 +46,6 @@ export default function PreviewTab() {
return (
<div style={canvas}>
<div style={toolbar}>
{options.length > 1 && (
<select
value={selectedUrl ?? ""}
onChange={(e) => setSelectedUrl(e.target.value)}
style={select}
>
{options.map((p) => (
<option key={p.id} value={p.url}>
{p.name} :{p.port} {p.state}
{p.command ? ` (${p.command.slice(0, 60)})` : ""}
</option>
))}
</select>
)}
</div>
<div
style={{
flex: 1,
@@ -101,7 +77,7 @@ export default function PreviewTab() {
title="Preview"
ref={(el) => {
iframeDomRef.current = el;
bridge?.registerPreviewIframe(el, iframeSrc);
if (el) bridge?.registerPreviewIframe(el, iframeSrc);
}}
onLoad={() => bridge?.notifyPreviewIframeLoaded()}
style={{
@@ -114,7 +90,8 @@ export default function PreviewTab() {
/>
) : (
<div style={loaderWrap}>
<p style={emptyText}>No preview available</p>
<p style={emptyText}>Preview not running on port 3000.</p>
<p style={{...emptyText, fontSize: '0.75rem', marginTop: 8}}>Ask the AI to start the dev server.</p>
</div>
)}
@@ -137,25 +114,6 @@ const canvas: React.CSSProperties = {
background: "linear-gradient(165deg, #faf8f5 0%, #f4f0ea 42%, #ebe7df 100%)",
};
const toolbar: React.CSSProperties = {
display: "flex",
gap: 8,
marginBottom: 10,
flexWrap: "wrap",
};
const select: React.CSSProperties = {
flex: 1,
maxWidth: 480,
padding: "6px 10px",
borderRadius: 8,
border: "1px solid rgba(26, 26, 26, 0.12)",
background: "rgba(255,255,255,0.85)",
fontSize: "0.8rem",
fontFamily: "inherit",
color: "#1a1a1a",
};
const desktopFrame: React.CSSProperties = {
flex: 1,
width: "100%",
@@ -176,135 +134,77 @@ const mobileFrame: React.CSSProperties = {
height: 844,
flexShrink: 0,
borderRadius: 56,
padding: 12,
background: "linear-gradient(160deg, #2a2a2c 0%, #1a1a1c 50%, #0e0e10 100%)",
overflow: "hidden",
background: "#000",
border: "14px solid #1a1a1a",
boxShadow:
"0 0 0 1px rgba(255,255,255,0.04) inset, 0 0 0 2px #000 inset, 0 28px 60px -12px rgba(0,0,0,0.45), 0 8px 20px -8px rgba(0,0,0,0.35)",
"0 0 0 1px rgba(255,255,255,0.1) inset, 0 24px 60px rgba(26, 26, 26, 0.15)",
display: "flex",
flexDirection: "column",
overflow: "hidden",
};
const homeIndicator: React.CSSProperties = {
position: "absolute",
left: "50%",
bottom: 20,
transform: "translateX(-50%)",
width: 134,
height: 5,
background: "#1a1916",
borderRadius: 999,
opacity: 0.85,
pointerEvents: "none",
zIndex: 10,
};
function MobileChrome() {
return (
<>
<div
style={{
position: "absolute",
top: 22,
left: "50%",
transform: "translateX(-50%)",
width: 124,
height: 36,
background: "#000",
borderRadius: 999,
zIndex: 15,
}}
/>
<div
style={{
position: "absolute",
top: 12,
left: 12,
right: 12,
height: 47,
padding: "18px 26px 0",
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
fontSize: 15,
fontWeight: 600,
letterSpacing: "-0.01em",
color: "#1a1916",
pointerEvents: "none",
zIndex: 10,
}}
>
<span>9:41</span>
<span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
<svg
viewBox="0 0 17 11"
style={{ width: 17, height: 11, fill: "currentColor" }}
aria-hidden
>
<rect x="0" y="7" width="3" height="4" rx="0.6" />
<rect x="4" y="5" width="3" height="6" rx="0.6" />
<rect x="8" y="3" width="3" height="8" rx="0.6" />
<rect x="12" y="0" width="3" height="11" rx="0.6" />
</svg>
<svg
viewBox="0 0 17 11"
style={{ width: 17, height: 11, fill: "currentColor" }}
aria-hidden
>
<path d="M8.5 1.5C5.5 1.5 2.7 2.6 0.5 4.6L2 6.1C3.8 4.5 6.1 3.6 8.5 3.6c2.4 0 4.7 0.9 6.5 2.5l1.5-1.5c-2.2-2-5-3.1-8-3.1zM3.5 7.6L5 9.1c1-0.9 2.2-1.4 3.5-1.4 1.3 0 2.5 0.5 3.5 1.4l1.5-1.5c-1.4-1.3-3.1-2-5-2-1.9 0-3.6 0.7-5 2zM6.5 10.6l2 2 2-2c-0.5-0.5-1.2-0.8-2-0.8s-1.5 0.3-2 0.8z" />
</svg>
<svg
viewBox="0 0 25 11"
style={{ width: 25, height: 11, fill: "currentColor" }}
aria-hidden
>
<rect
x="0.5"
y="0.5"
width="21"
height="10"
rx="2.5"
fill="none"
stroke="currentColor"
strokeOpacity="0.45"
/>
<rect
x="22"
y="3.5"
width="1.5"
height="4"
rx="0.4"
fill="currentColor"
fillOpacity="0.45"
/>
<rect x="2" y="2" width="18" height="7" rx="1.4" />
</svg>
</span>
</div>
</>
);
}
const iframeStyle: React.CSSProperties = {
flex: 1,
width: "100%",
minHeight: 0,
height: "100%",
border: "none",
background: "#fcfcfb",
display: "block",
background: "#fff",
pointerEvents: "auto",
};
const loaderWrap: React.CSSProperties = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: 200,
background: "#fcfcfb",
background: "#fff",
};
const emptyText: React.CSSProperties = {
margin: 0,
fontSize: "0.85rem",
color: "#a09a90",
fontFamily: '"Outfit", "Inter", ui-sans-serif, sans-serif',
color: "#a1a1aa",
};
function MobileChrome() {
return (
<div style={notchWrap}>
<div style={notch} />
</div>
);
}
const notchWrap: React.CSSProperties = {
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 32,
display: "flex",
justifyContent: "center",
zIndex: 10,
pointerEvents: "none",
};
const notch: React.CSSProperties = {
width: 120,
height: 30,
background: "#1a1a1a",
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
};
const homeIndicator: React.CSSProperties = {
position: "absolute",
bottom: 8,
left: "50%",
transform: "translateX(-50%)",
width: 140,
height: 5,
borderRadius: 10,
background: "rgba(255, 255, 255, 0.4)",
mixBlendMode: "difference",
zIndex: 10,
pointerEvents: "none",
};