refactor(frontend): migrate diff dialogs to Modal chrome (D-5) (#909)

* refactor(frontend): extend Modal size system with wide variant

Adds 'wide' (max-w-5xl w-[95vw]) to ModalSize for dialogs that render
Monaco DiffEditor side-by-side, which need ~1024px to display both panels
clearly. The existing 'xl' cap at max-w-xl (576px) is too narrow for that
use case.

* refactor(frontend): migrate ComposeDiffPreviewDialog to Modal chrome (D-5)

Replaces raw Dialog/DialogHeader/DialogFooter with Modal size="wide",
ModalHeader, and ModalFooter. Kicker carries the stack name and file type
context; title is the file name (accessible dialog name for tests). Footer
hint reuses the existing "ON DISK → UNSAVED" label via the hint prop.

* refactor(frontend): migrate GitSourceDiffDialog to Modal chrome (D-5)

Replaces Dialog + nested AlertDialog with Modal size="wide", ModalHeader,
ModalFooter, and ConfirmModal. Kicker is "GIT · PULL PREVIEW"; title is
the stack name. Short SHA moves to the sr-only description. The "Deploy
after apply" checkbox in the footer hint slot uses normal-case and
tracking-normal on the Label to prevent KICKER_CLASS uppercase/tracking
from being inherited. The overwrite confirmation uses ConfirmModal with
variant="destructive" (rose rail) since it replaces local file content.
This commit is contained in:
Anso
2026-05-04 11:53:35 -04:00
committed by GitHub
parent 945259f048
commit 46b3d53274
3 changed files with 167 additions and 188 deletions
@@ -1,8 +1,7 @@
import { useState, Suspense } from 'react';
import { DiffEditor } from '@/lib/monacoLoader';
import { AlertTriangle, GitBranch, Loader2 } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
import { AlertTriangle, Loader2 } from 'lucide-react';
import { Modal, ModalHeader, ModalFooter, ConfirmModal } from '@/components/ui/modal';
import { Tabs, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
@@ -72,79 +71,73 @@ export function GitSourceDiffDialog({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-5xl w-[95vw] p-0 gap-0">
<DialogHeader className="px-6 pt-6 pb-4 border-b border-glass-border">
<DialogTitle className="flex items-center gap-2">
<GitBranch className="w-4 h-4" strokeWidth={1.5} />
<span>Review update for</span>
<span className="font-mono tabular-nums">{stackName}</span>
<span className="font-mono tabular-nums text-xs text-stat-subtitle">@{shortSha}</span>
</DialogTitle>
<DialogDescription className="sr-only">
Review the diff between the current on-disk stack files and the incoming Git commit.
</DialogDescription>
</DialogHeader>
<Modal size="wide" open={open} onOpenChange={onOpenChange}>
<ModalHeader
kicker="GIT · PULL PREVIEW"
title={stackName}
description={`Incoming commit ${shortSha}. Review the diff between the current on-disk stack files and the incoming Git commit.`}
/>
<div className="px-6 pt-4 space-y-3">
{!pull.validation.ok && (
<div className="flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
<div>
<p className="font-medium">Incoming compose failed validation</p>
<pre className="font-mono text-[11px] whitespace-pre-wrap mt-1">{pull.validation.error}</pre>
</div>
<div className="px-6 pt-4 space-y-3">
{!pull.validation.ok && (
<div className="flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
<div>
<p className="font-medium">Incoming compose failed validation</p>
<pre className="font-mono text-[11px] whitespace-pre-wrap mt-1">{pull.validation.error}</pre>
</div>
)}
{pull.hasLocalChanges && (
<div className="flex items-start gap-2 rounded-md border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning">
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
<div>
<p className="font-medium">Local edits detected on disk</p>
<p className="mt-0.5">Applying will overwrite changes that differ from the last applied commit.</p>
</div>
</div>
)}
{envAvailable && (
<Tabs value={diffTab} onValueChange={(v) => setDiffTab(v as 'compose' | 'env')}>
<TabsList>
<TabsHighlight className="rounded-md bg-glass-highlight" transition={springs.snappy}>
<TabsHighlightItem value="compose">
<TabsTrigger value="compose">compose.yaml</TabsTrigger>
</TabsHighlightItem>
<TabsHighlightItem value="env">
<TabsTrigger value="env">.env</TabsTrigger>
</TabsHighlightItem>
</TabsHighlight>
</TabsList>
</Tabs>
)}
</div>
<div className="px-6 pb-4 pt-3">
<div className="h-[55vh] border border-glass-border rounded-md overflow-hidden">
<Suspense fallback={<div className="w-full h-full" aria-busy="true" />}>
<DiffEditor
height="100%"
language={effectiveTab === 'compose' ? 'yaml' : 'ini'}
theme={isDarkMode ? 'vs-dark' : 'vs'}
original={currentValue}
modified={incomingValue}
options={{
readOnly: true,
renderSideBySide: true,
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontFamily: "'Geist Mono', monospace",
fontSize: 12,
}}
/>
</Suspense>
</div>
</div>
)}
{pull.hasLocalChanges && (
<div className="flex items-start gap-2 rounded-md border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning">
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
<div>
<p className="font-medium">Local edits detected on disk</p>
<p className="mt-0.5">Applying will overwrite changes that differ from the last applied commit.</p>
</div>
</div>
)}
<DialogFooter className="px-6 py-4 border-t border-glass-border flex flex-row items-center justify-between sm:justify-between gap-4">
{envAvailable && (
<Tabs value={diffTab} onValueChange={(v) => setDiffTab(v as 'compose' | 'env')}>
<TabsList>
<TabsHighlight className="rounded-md bg-glass-highlight" transition={springs.snappy}>
<TabsHighlightItem value="compose">
<TabsTrigger value="compose">compose.yaml</TabsTrigger>
</TabsHighlightItem>
<TabsHighlightItem value="env">
<TabsTrigger value="env">.env</TabsTrigger>
</TabsHighlightItem>
</TabsHighlight>
</TabsList>
</Tabs>
)}
</div>
<div className="px-6 pb-4 pt-3">
<div className="h-[55vh] border border-glass-border rounded-md overflow-hidden">
<Suspense fallback={<div className="w-full h-full" aria-busy="true" />}>
<DiffEditor
height="100%"
language={effectiveTab === 'compose' ? 'yaml' : 'ini'}
theme={isDarkMode ? 'vs-dark' : 'vs'}
original={currentValue}
modified={incomingValue}
options={{
readOnly: true,
renderSideBySide: true,
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontFamily: "'Geist Mono', monospace",
fontSize: 12,
}}
/>
</Suspense>
</div>
</div>
<ModalFooter
hint={
<div className="flex items-center gap-2">
<Checkbox
id="git-source-deploy-after"
@@ -152,60 +145,57 @@ export function GitSourceDiffDialog({
onCheckedChange={(checked) => setDeployAfter(checked === true)}
disabled={applying || !pull.validation.ok}
/>
<Label htmlFor="git-source-deploy-after" className="text-xs cursor-pointer">
<Label
htmlFor="git-source-deploy-after"
className="text-xs normal-case tracking-normal cursor-pointer"
>
Deploy after apply
</Label>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => onDismiss()}
disabled={applying}
>
Dismiss
</Button>
<Button
size="sm"
onClick={handleApplyClick}
disabled={applying || !pull.validation.ok}
>
{applying ? (
<>
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" strokeWidth={1.5} />
Applying...
</>
) : (
'Apply'
)}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
<AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Overwrite local edits?</AlertDialogTitle>
<AlertDialogDescription>
The on-disk stack files differ from the last applied commit. Applying this pull will replace them with the incoming content.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={applying}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
setConfirmOpen(false);
await apply();
}}
}
secondary={
<Button
variant="outline"
size="sm"
onClick={() => onDismiss()}
disabled={applying}
>
Overwrite and apply
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Dismiss
</Button>
}
primary={
<Button
size="sm"
onClick={handleApplyClick}
disabled={applying || !pull.validation.ok}
>
{applying ? (
<>
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" strokeWidth={1.5} />
Applying...
</>
) : (
'Apply'
)}
</Button>
}
/>
</Modal>
<ConfirmModal
open={confirmOpen}
onOpenChange={setConfirmOpen}
variant="destructive"
kicker="GIT · LOCAL CHANGES"
title="Overwrite local edits?"
description="The on-disk stack files differ from the last applied commit. Applying this pull will replace them with the incoming content."
confirmLabel="Overwrite and apply"
confirming={applying}
onConfirm={async () => {
setConfirmOpen(false);
await apply();
}}
/>
</>
);
}