@@ -11,11 +11,15 @@ import {
1111} from '@/components/ui/table'
1212import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from '@/components/ui/tooltip'
1313import { cn } from '@/lib/utils'
14- import { MultiFileDiff } from '@pierre/diffs/react'
1514import { useMutation , useQuery , useQueryClient } from '@tanstack/react-query'
1615import { formatDistanceToNow } from 'date-fns'
1716import { ChevronDown , Loader2 , RotateCcw } from 'lucide-react'
18- import { useEffect , useMemo , useState } from 'react'
17+ import { lazy , Suspense , useEffect , useMemo , useState } from 'react'
18+
19+ const LazyMultiFileDiff = lazy ( async ( ) => {
20+ const mod = await import ( '@pierre/diffs/react' )
21+ return { default : mod . MultiFileDiff }
22+ } )
1923
2024type DeploymentChangeSummary = {
2125 added_files : number
@@ -76,11 +80,6 @@ function formatActor(actor: DeploymentRevision['actor']) {
7680 return actor . user_id ?? actor . actor_type
7781}
7882
79- function buildSummaryLabel ( summary ?: DeploymentChangeSummary | null ) {
80- if ( ! summary ) return '—'
81- return `+${ summary . added_files } ~${ summary . modified_files } -${ summary . removed_files } `
82- }
83-
8483function isDiffableFile ( path : string ) {
8584 const lowerPath = path . toLowerCase ( )
8685 const fileName = lowerPath . split ( '/' ) . at ( - 1 ) ?? lowerPath
@@ -109,6 +108,13 @@ function statusBadgeClass(status: string) {
109108 return 'bg-green-500/15 text-green-700 hover:bg-green-500/25 dark:bg-green-500/10 dark:text-green-300 dark:hover:bg-green-500/20 border-0'
110109}
111110
111+ function formatStatusLabel ( status : string ) {
112+ return status
113+ . split ( '_' )
114+ . map ( ( word ) => ( word ? word [ 0 ] ?. toUpperCase ( ) + word . slice ( 1 ) : word ) )
115+ . join ( ' ' )
116+ }
117+
112118async function requestJson < T > ( path : string , init ?: RequestInit ) {
113119 const response = await fetch ( `${ API_BASE } ${ path } ` , { credentials : 'include' , ...init } )
114120 if ( ! response . ok ) {
@@ -154,19 +160,24 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
154160 }
155161 } , [ historyQuery . data , selectedRevisionId ] )
156162
163+ const shouldLoadDiffs = diffOpen && ! ! selectedRevisionId
164+
157165 const selectedRevisionQuery = useQuery ( {
158166 queryKey : [ 'deployment-revision' , guildId , selectedRevisionId ] ,
159- enabled : ! ! selectedRevisionId ,
167+ enabled : shouldLoadDiffs ,
160168 queryFn : ( ) =>
161169 requestJson < DeploymentRevision > (
162170 `/deployments/${ encodeURIComponent ( guildId ) } /revisions/${ selectedRevisionId } `
163171 )
164172 } )
165173
166- const baseRevisionId = selectedRevisionQuery . data ?. base_revision_id ?? null
174+ const selectedRevision = selectedRevisionQuery . data ?? selectedSummary ?? null
175+ const latestRevisionId = historyQuery . data ?. [ 0 ] ?. id ?? null
176+ const isLatestRevision = selectedRevision ?. id === latestRevisionId
177+ const baseRevisionId = selectedRevision ?. base_revision_id ?? null
167178 const baseRevisionQuery = useQuery ( {
168179 queryKey : [ 'deployment-revision' , guildId , baseRevisionId ] ,
169- enabled : ! ! baseRevisionId ,
180+ enabled : diffOpen && ! ! baseRevisionId ,
170181 queryFn : ( ) =>
171182 requestJson < DeploymentRevision > (
172183 `/deployments/${ encodeURIComponent ( guildId ) } /revisions/${ baseRevisionId } `
@@ -205,8 +216,6 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
205216 . sort ( ( a , b ) => a . path . localeCompare ( b . path ) )
206217 } , [ baseRevisionQuery . data ?. files , selectedRevisionQuery . data ?. files ] )
207218
208- const selectedRevision = selectedRevisionQuery . data ?? selectedSummary ?? null
209-
210219 return (
211220 < div className = 'flex h-full min-h-0 flex-col gap-4' >
212221 < div className = 'rounded-lg border bg-card p-4' >
@@ -216,7 +225,7 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
216225 Select a revision to inspect metadata and source diffs.
217226 </ div >
218227 )
219- : selectedRevisionQuery . isLoading
228+ : shouldLoadDiffs && selectedRevisionQuery . isLoading
220229 ? (
221230 < div className = 'flex items-center gap-2 text-sm text-muted-foreground' >
222231 < Loader2 className = 'size-4 animate-spin' />
@@ -233,33 +242,45 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
233242 < div className = 'space-y-3' >
234243 < div className = 'flex items-center justify-between gap-2' >
235244 < div >
236- < div className = 'text-sm font-medium' > Revision { selectedRevision . id } </ div >
245+ < div className = 'flex items-center gap-2 text-sm font-medium' >
246+ < Badge
247+ variant = 'outline'
248+ className = { cn ( 'border-0' , statusBadgeClass ( selectedRevision . status ) ) }
249+ >
250+ { formatStatusLabel ( selectedRevision . status ) }
251+ </ Badge >
252+ < span > Revision { selectedRevision . id } </ span >
253+ </ div >
237254 < div className = 'text-xs text-muted-foreground' >
238255 { formatDateTime ( selectedRevision . deployed_at ) }
239256 </ div >
240257 </ div >
241- < Button
242- size = 'sm'
243- variant = 'outline'
244- disabled = { rollbackMutation . isPending || selectedRevision . status !== 'success' }
245- onClick = { ( ) => {
246- if ( selectedRevision . id ) rollbackMutation . mutate ( selectedRevision . id )
247- } }
248- >
249- { rollbackMutation . isPending
250- ? (
251- < >
252- < Loader2 className = 'mr-1 size-4 animate-spin' />
253- Rolling back…
254- </ >
255- )
256- : (
257- < >
258- < RotateCcw className = 'mr-1 size-4' />
259- Rollback to this
260- </ >
261- ) }
262- </ Button >
258+ { ! isLatestRevision
259+ ? (
260+ < Button
261+ size = 'sm'
262+ variant = 'outline'
263+ disabled = { rollbackMutation . isPending || selectedRevision . status !== 'success' }
264+ onClick = { ( ) => {
265+ if ( selectedRevision . id ) rollbackMutation . mutate ( selectedRevision . id )
266+ } }
267+ >
268+ { rollbackMutation . isPending
269+ ? (
270+ < >
271+ < Loader2 className = 'mr-1 size-4 animate-spin' />
272+ Rolling back…
273+ </ >
274+ )
275+ : (
276+ < >
277+ < RotateCcw className = 'mr-1 size-4' />
278+ Rollback to this
279+ </ >
280+ ) }
281+ </ Button >
282+ )
283+ : null }
263284 </ div >
264285
265286 { rollbackMutation . isError
@@ -270,16 +291,7 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
270291 )
271292 : null }
272293
273- < div className = 'grid gap-3 md:grid-cols-4' >
274- < div className = 'rounded-md border p-2' >
275- < div className = 'text-[11px] text-muted-foreground' > Status</ div >
276- < Badge
277- variant = 'outline'
278- className = { cn ( 'mt-1 border-0' , statusBadgeClass ( selectedRevision . status ) ) }
279- >
280- { selectedRevision . status }
281- </ Badge >
282- </ div >
294+ < div className = 'grid gap-3 md:grid-cols-2' >
283295 < div className = 'rounded-md border p-2' >
284296 < div className = 'text-[11px] text-muted-foreground' > Source</ div >
285297 < div className = 'mt-1 text-sm font-medium' > { selectedRevision . deploy_source } </ div >
@@ -290,19 +302,9 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
290302 { formatActor ( selectedRevision . actor ) }
291303 </ div >
292304 </ div >
293- < div className = 'rounded-md border p-2' >
294- < div className = 'text-[11px] text-muted-foreground' > Changes</ div >
295- < div className = 'mt-1 font-mono text-sm' >
296- { buildSummaryLabel ( selectedRevision . change_summary ) }
297- </ div >
298- </ div >
299305 </ div >
300306
301- < div className = 'grid grid-cols-2 gap-3 text-xs md:grid-cols-4' >
302- < div className = 'rounded-md border p-2' >
303- < div className = 'text-muted-foreground' > Guild</ div >
304- < div className = 'font-mono' > { selectedRevision . guild_id } </ div >
305- </ div >
307+ < div className = 'grid grid-cols-2 gap-3 text-xs md:grid-cols-3' >
306308 < div className = 'rounded-md border p-2' >
307309 < div className = 'text-muted-foreground' > Entry</ div >
308310 < div className = 'font-mono' > { selectedRevision . entry } </ div >
@@ -341,7 +343,9 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
341343 < ChevronDown className = { cn ( 'size-4 transition-transform' , diffOpen && 'rotate-180' ) } />
342344 </ CollapsibleTrigger >
343345 < CollapsibleContent className = 'border-t p-3' >
344- { baseRevisionId && baseRevisionQuery . isLoading
346+ { shouldLoadDiffs && selectedRevisionQuery . isLoading
347+ ? < div className = 'text-sm text-muted-foreground' > Loading revision files…</ div >
348+ : baseRevisionId && baseRevisionQuery . isLoading
345349 ? < div className = 'text-sm text-muted-foreground' > Loading base revision…</ div >
346350 : baseRevisionQuery . isError
347351 ? (
@@ -353,19 +357,30 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
353357 ? < div className = 'text-sm text-muted-foreground' > No base revision for this entry.</ div >
354358 : ! diffFiles . length
355359 ? < div className = 'text-sm text-muted-foreground' > No diffable source-file changes.</ div >
360+ : ! diffOpen
361+ ? null
356362 : (
357363 < div className = 'max-h-[42dvh] space-y-3 overflow-auto' >
358364 { diffFiles . map ( ( file ) => (
359365 < div key = { file . path } className = 'overflow-hidden rounded-lg border' >
360- < MultiFileDiff
361- oldFile = { { name : file . path , contents : file . oldContents } }
362- newFile = { { name : file . path , contents : file . newContents } }
363- options = { {
364- diffStyle : 'split' ,
365- overflow : 'wrap' ,
366- lineDiffType : 'word'
367- } }
368- />
366+ < Suspense
367+ fallback = {
368+ < div className = 'flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground' >
369+ < Loader2 className = 'size-3 animate-spin' />
370+ Loading diff renderer…
371+ </ div >
372+ }
373+ >
374+ < LazyMultiFileDiff
375+ oldFile = { { name : file . path , contents : file . oldContents } }
376+ newFile = { { name : file . path , contents : file . newContents } }
377+ options = { {
378+ diffStyle : 'split' ,
379+ overflow : 'wrap' ,
380+ lineDiffType : 'word'
381+ } }
382+ />
383+ </ Suspense >
369384 </ div >
370385 ) ) }
371386 </ div >
@@ -400,7 +415,6 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
400415 < TableRow className = 'hover:bg-transparent' >
401416 < TableHead > Id</ TableHead >
402417 < TableHead > Actor</ TableHead >
403- < TableHead > Changes</ TableHead >
404418 < TableHead > Deployed</ TableHead >
405419 < TableHead > Source</ TableHead >
406420 < TableHead > Status</ TableHead >
@@ -424,9 +438,6 @@ export function DeploymentHistory({ guildId }: { guildId: string }) {
424438 >
425439 < TableCell className = 'font-mono text-xs' > { shortId ( row . id ) } </ TableCell >
426440 < TableCell className = 'text-xs' > { formatActor ( row . actor ) } </ TableCell >
427- < TableCell className = 'font-mono text-xs' >
428- { buildSummaryLabel ( row . change_summary ) }
429- </ TableCell >
430441 < TableCell className = 'text-xs whitespace-nowrap' >
431442 { formatTimeAgo ( row . deployed_at ) }
432443 </ TableCell >
0 commit comments