Make close workspace modal responsive for mobile

- Reduce padding and text sizes on small screens (p-4/text-xs -> sm:p-6/sm:text-sm)
- Tighter spacing between sections on mobile (space-y-3 -> sm:space-y-4)
- Sticky footer so action buttons stay visible while scrolling
- Grid layout stays 2-col on all sizes for git status
- Add shrink-0 to loading spinner

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-04-04 09:55:10 -05:00
parent 35f2fc7230
commit 2615450afc

View file

@ -88,27 +88,27 @@ export function ExecutionWorkspaceCloseDialog({
<Dialog open={open} onOpenChange={(nextOpen) => { <Dialog open={open} onOpenChange={(nextOpen) => {
if (!closeWorkspace.isPending) onOpenChange(nextOpen); if (!closeWorkspace.isPending) onOpenChange(nextOpen);
}}> }}>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-2xl"> <DialogContent className="max-h-[85vh] overflow-y-auto p-4 sm:max-w-2xl sm:p-6">
<DialogHeader> <DialogHeader>
<DialogTitle>{actionLabel}</DialogTitle> <DialogTitle>{actionLabel}</DialogTitle>
<DialogDescription className="break-words"> <DialogDescription className="break-words text-xs sm:text-sm">
Archive <span className="font-medium text-foreground">{workspaceName}</span> and clean up any owned workspace Archive <span className="font-medium text-foreground">{workspaceName}</span> and clean up any owned workspace
artifacts. Paperclip keeps the workspace record and issue history, but removes it from active workspace views. artifacts. Paperclip keeps the workspace record and issue history, but removes it from active workspace views.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{readinessQuery.isLoading ? ( {readinessQuery.isLoading ? (
<div className="flex items-center gap-2 rounded-xl border border-border bg-muted/30 px-4 py-3 text-sm text-muted-foreground"> <div className="flex items-center gap-2 rounded-xl border border-border bg-muted/30 px-3 py-2.5 text-xs sm:px-4 sm:py-3 sm:text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin shrink-0" />
Checking whether this workspace is safe to close... Checking whether this workspace is safe to close...
</div> </div>
) : readinessQuery.error ? ( ) : readinessQuery.error ? (
<div className="rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive"> <div className="rounded-xl border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-xs sm:px-4 sm:py-3 sm:text-sm text-destructive">
{readinessQuery.error instanceof Error ? readinessQuery.error.message : "Failed to inspect workspace close readiness."} {readinessQuery.error instanceof Error ? readinessQuery.error.message : "Failed to inspect workspace close readiness."}
</div> </div>
) : readiness ? ( ) : readiness ? (
<div className="space-y-4"> <div className="space-y-3 sm:space-y-4">
<div className={`rounded-xl border px-4 py-3 text-sm ${readinessTone(readiness.state)}`}> <div className={`rounded-xl border px-3 py-2.5 text-xs sm:px-4 sm:py-3 sm:text-sm ${readinessTone(readiness.state)}`}>
<div className="font-medium"> <div className="font-medium">
{readiness.state === "blocked" {readiness.state === "blocked"
? "Close is blocked" ? "Close is blocked"
@ -129,10 +129,10 @@ export function ExecutionWorkspaceCloseDialog({
{blockingIssues.length > 0 ? ( {blockingIssues.length > 0 ? (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-sm font-medium">Blocking issues</h3> <h3 className="text-xs font-medium sm:text-sm">Blocking issues</h3>
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
{blockingIssues.map((issue) => ( {blockingIssues.map((issue) => (
<div key={issue.id} className="rounded-xl border border-destructive/20 bg-destructive/5 px-4 py-3 text-sm"> <div key={issue.id} className="rounded-xl border border-destructive/20 bg-destructive/5 px-3 py-2 text-xs sm:px-4 sm:py-3 sm:text-sm">
<div className="flex min-w-0 flex-wrap items-center justify-between gap-2"> <div className="flex min-w-0 flex-wrap items-center justify-between gap-2">
<Link to={issueUrl(issue)} className="min-w-0 break-words font-medium hover:underline"> <Link to={issueUrl(issue)} className="min-w-0 break-words font-medium hover:underline">
{issue.identifier ?? issue.id} · {issue.title} {issue.identifier ?? issue.id} · {issue.title}
@ -147,10 +147,10 @@ export function ExecutionWorkspaceCloseDialog({
{readiness.blockingReasons.length > 0 ? ( {readiness.blockingReasons.length > 0 ? (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-sm font-medium">Blocking reasons</h3> <h3 className="text-xs font-medium sm:text-sm">Blocking reasons</h3>
<ul className="space-y-2 text-sm text-muted-foreground"> <ul className="space-y-1.5 text-xs sm:space-y-2 sm:text-sm text-muted-foreground">
{readiness.blockingReasons.map((reason, idx) => ( {readiness.blockingReasons.map((reason, idx) => (
<li key={`blocking-${idx}`} className="break-words rounded-lg border border-destructive/20 bg-destructive/5 px-3 py-2 text-destructive"> <li key={`blocking-${idx}`} className="break-words rounded-lg border border-destructive/20 bg-destructive/5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-destructive">
{reason} {reason}
</li> </li>
))} ))}
@ -160,10 +160,10 @@ export function ExecutionWorkspaceCloseDialog({
{readiness.warnings.length > 0 ? ( {readiness.warnings.length > 0 ? (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-sm font-medium">Warnings</h3> <h3 className="text-xs font-medium sm:text-sm">Warnings</h3>
<ul className="space-y-2 text-sm text-muted-foreground"> <ul className="space-y-1.5 text-xs sm:space-y-2 sm:text-sm text-muted-foreground">
{readiness.warnings.map((warning, idx) => ( {readiness.warnings.map((warning, idx) => (
<li key={`warning-${idx}`} className="break-words rounded-lg border border-amber-500/20 bg-amber-500/5 px-3 py-2"> <li key={`warning-${idx}`} className="break-words rounded-lg border border-amber-500/20 bg-amber-500/5 px-2.5 py-1.5 sm:px-3 sm:py-2">
{warning} {warning}
</li> </li>
))} ))}
@ -173,9 +173,9 @@ export function ExecutionWorkspaceCloseDialog({
{readiness.git ? ( {readiness.git ? (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-sm font-medium">Git status</h3> <h3 className="text-xs font-medium sm:text-sm">Git status</h3>
<div className="rounded-xl border border-border bg-muted/20 px-4 py-3 text-sm"> <div className="rounded-xl border border-border bg-muted/20 px-3 py-2.5 text-xs sm:px-4 sm:py-3 sm:text-sm">
<div className="grid gap-2 sm:grid-cols-2"> <div className="grid grid-cols-2 gap-2">
<div> <div>
<div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">Branch</div> <div className="text-xs uppercase tracking-[0.16em] text-muted-foreground">Branch</div>
<div className="font-mono text-xs">{readiness.git.branchName ?? "Unknown"}</div> <div className="font-mono text-xs">{readiness.git.branchName ?? "Unknown"}</div>
@ -209,10 +209,10 @@ export function ExecutionWorkspaceCloseDialog({
{otherLinkedIssues.length > 0 ? ( {otherLinkedIssues.length > 0 ? (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-sm font-medium">Other linked issues</h3> <h3 className="text-xs font-medium sm:text-sm">Other linked issues</h3>
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
{otherLinkedIssues.map((issue) => ( {otherLinkedIssues.map((issue) => (
<div key={issue.id} className="rounded-xl border border-border bg-muted/20 px-4 py-3 text-sm"> <div key={issue.id} className="rounded-xl border border-border bg-muted/20 px-3 py-2 text-xs sm:px-4 sm:py-3 sm:text-sm">
<div className="flex min-w-0 flex-wrap items-center justify-between gap-2"> <div className="flex min-w-0 flex-wrap items-center justify-between gap-2">
<Link to={issueUrl(issue)} className="min-w-0 break-words font-medium hover:underline"> <Link to={issueUrl(issue)} className="min-w-0 break-words font-medium hover:underline">
{issue.identifier ?? issue.id} · {issue.title} {issue.identifier ?? issue.id} · {issue.title}
@ -227,10 +227,10 @@ export function ExecutionWorkspaceCloseDialog({
{readiness.runtimeServices.length > 0 ? ( {readiness.runtimeServices.length > 0 ? (
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-sm font-medium">Attached runtime services</h3> <h3 className="text-xs font-medium sm:text-sm">Attached runtime services</h3>
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
{readiness.runtimeServices.map((service) => ( {readiness.runtimeServices.map((service) => (
<div key={service.id} className="rounded-xl border border-border bg-muted/20 px-4 py-3 text-sm"> <div key={service.id} className="rounded-xl border border-border bg-muted/20 px-3 py-2 text-xs sm:px-4 sm:py-3 sm:text-sm">
<div className="flex min-w-0 flex-wrap items-center justify-between gap-2"> <div className="flex min-w-0 flex-wrap items-center justify-between gap-2">
<span className="font-medium">{service.serviceName}</span> <span className="font-medium">{service.serviceName}</span>
<span className="text-xs text-muted-foreground">{service.status} · {service.lifecycle}</span> <span className="text-xs text-muted-foreground">{service.status} · {service.lifecycle}</span>
@ -245,10 +245,10 @@ export function ExecutionWorkspaceCloseDialog({
) : null} ) : null}
<section className="space-y-2"> <section className="space-y-2">
<h3 className="text-sm font-medium">Cleanup actions</h3> <h3 className="text-xs font-medium sm:text-sm">Cleanup actions</h3>
<div className="space-y-2"> <div className="space-y-1.5 sm:space-y-2">
{readiness.plannedActions.map((action, index) => ( {readiness.plannedActions.map((action, index) => (
<div key={`${action.kind}-${index}`} className="rounded-xl border border-border bg-muted/20 px-4 py-3 text-sm"> <div key={`${action.kind}-${index}`} className="rounded-xl border border-border bg-muted/20 px-3 py-2 text-xs sm:px-4 sm:py-3 sm:text-sm">
<div className="font-medium">{action.label}</div> <div className="font-medium">{action.label}</div>
<div className="mt-1 break-words text-muted-foreground">{action.description}</div> <div className="mt-1 break-words text-muted-foreground">{action.description}</div>
{action.command ? ( {action.command ? (
@ -262,14 +262,14 @@ export function ExecutionWorkspaceCloseDialog({
</section> </section>
{currentStatus === "cleanup_failed" ? ( {currentStatus === "cleanup_failed" ? (
<div className="rounded-xl border border-amber-500/20 bg-amber-500/5 px-4 py-3 text-sm text-muted-foreground"> <div className="rounded-xl border border-amber-500/20 bg-amber-500/5 px-3 py-2.5 text-xs sm:px-4 sm:py-3 sm:text-sm text-muted-foreground">
Cleanup previously failed on this workspace. Retrying close will rerun the cleanup flow and update the Cleanup previously failed on this workspace. Retrying close will rerun the cleanup flow and update the
workspace status if it succeeds. workspace status if it succeeds.
</div> </div>
) : null} ) : null}
{currentStatus === "archived" ? ( {currentStatus === "archived" ? (
<div className="rounded-xl border border-border bg-muted/20 px-4 py-3 text-sm text-muted-foreground"> <div className="rounded-xl border border-border bg-muted/20 px-3 py-2.5 text-xs sm:px-4 sm:py-3 sm:text-sm text-muted-foreground">
This workspace is already archived. This workspace is already archived.
</div> </div>
) : null} ) : null}
@ -291,7 +291,7 @@ export function ExecutionWorkspaceCloseDialog({
</div> </div>
) : null} ) : null}
<DialogFooter> <DialogFooter className="sticky bottom-0 -mx-4 -mb-4 rounded-b-lg border-t border-border bg-background px-4 py-3 sm:-mx-6 sm:-mb-6 sm:px-6 sm:py-4">
<Button <Button
variant="outline" variant="outline"
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}