1515 *
1616 * `POST /projects/{id}/preprocessing-preview` renders one asset through a spec
1717 * on the same kernel path an export takes, so what the cells show is what the
18- * archive would hold. Three sample assets, and the choice is the release's: the
19- * first three train-fold members of the newest release with a split, because
20- * variants are written for the train fold only; without one, the first three
21- * assets of the project. The columns are the stages — the asset as it is, after
22- * the resize step alone, and the first augmented variant — each a request
23- * keyed on the spec it renders, so a keystroke re-renders only what it changed.
18+ * archive would hold. One sample asset, and the choice is the release's: the
19+ * first train-fold member of the newest release with a split, because variants
20+ * are written for the train fold only; without one, the project's first asset.
21+ * The columns are the stages — the asset as it is, after the resize step alone,
22+ * and the last augmentation step chosen, in `showcase` mode: the draws fixed at
23+ * the step's declared strength, so the cell shows what the step does rather
24+ * than one seeded draw of it. Each cell is a request keyed on the spec it
25+ * renders, so a keystroke re-renders only what it changed.
2426 *
2527 * `PreviewCell` hands the rendered image and its placed annotations to the
2628 * static overlay pattern, so a label is drawn where the export would write it.
@@ -62,11 +64,13 @@ import {
6264 type Release ,
6365} from "./queries" ;
6466import {
67+ AUGMENT_OPS ,
6568 canonicalSpec ,
6669 draftFromSpec ,
6770 draftToSpec ,
6871 EMPTY_DRAFT ,
6972 sameSpec ,
73+ type AugmentStepSpec ,
7074 type RecipeDraft ,
7175 type RecipeSpec ,
7276} from "./recipeDraft" ;
@@ -366,21 +370,25 @@ function Editor({
366370 ) ;
367371}
368372
369- /** The asset ids the preview renders: the train fold's first three, or the project's. */
370- function useSampleAssets ( projectId : string , datasetId : string | undefined ) : readonly string [ ] | undefined {
373+ /**
374+ * The asset the preview renders: the train fold's first member, or the
375+ * project's first asset; `null` when the project has none, `undefined` while
376+ * the choice is still being read.
377+ */
378+ function useSampleAsset ( projectId : string , datasetId : string | undefined ) : string | null | undefined {
371379 const releases = useReleases ( datasetId ) ;
372380 const newest = newestRelease ( releases . data ?. items ) ;
373381 const withSplit = newest !== undefined && newest . split !== null && newest . split !== undefined ;
374382 const assignment = useReleaseAssignment ( withSplit ? newest . id : undefined ) ;
375- const project = useProjectAssets ( projectId , 3 ) ;
383+ const project = useProjectAssets ( projectId , 1 ) ;
376384 if ( datasetId !== undefined && releases . data === undefined && ! releases . isError ) return undefined ;
377385 if ( withSplit ) {
378386 if ( assignment . data === undefined && ! assignment . isError ) return undefined ;
379- const train = assignment . data ?. train . slice ( 0 , 3 ) ?? [ ] ;
380- if ( train . length > 0 ) return train ;
387+ const train = assignment . data ?. train [ 0 ] ;
388+ if ( train !== undefined ) return train ;
381389 }
382390 if ( project . data === undefined ) return undefined ;
383- return project . data . items . slice ( 0 , 3 ) . map ( ( asset ) => asset . id ) ;
391+ return project . data . items [ 0 ] ?. id ?? null ;
384392}
385393
386394function newestRelease ( items : readonly Release [ ] | undefined ) : Release | undefined {
@@ -416,17 +424,20 @@ function PreviewGrid({
416424 readonly classes : readonly LabelClass [ ] | undefined ;
417425 readonly onReady : ( ready : boolean ) => void ;
418426} ) : JSX . Element {
419- const samples = useSampleAssets ( projectId , datasetId ) ;
427+ const sample = useSampleAsset ( projectId , datasetId ) ;
420428 const settled = useSettledSpec ( spec ) ;
429+ const resizeSteps = settled === null ? [ ] : settled . steps . filter ( ( step ) => step . kind === "resize" ) ;
421430 const resizeOnly : RecipeSpec | null =
422- settled === null
431+ settled === null ? null : { target : settled . target ?? null , steps : resizeSteps , variants_per_asset : 0 } ;
432+ const hasResize = resizeSteps . length > 0 ;
433+ const lastAugment = settled === null ? undefined : lastAugmentStep ( settled ) ;
434+ const showcase : RecipeSpec | null =
435+ settled === null || lastAugment === undefined
423436 ? null
424- : { target : settled . target ?? null , steps : settled . steps . filter ( ( step ) => step . kind === "resize" ) , variants_per_asset : 0 } ;
425- const hasResize = resizeOnly !== null && resizeOnly . steps . length > 0 ;
426- const hasAugment = settled !== null && settled . variants_per_asset > 0 ;
437+ : { target : settled . target ?? null , steps : [ ...resizeSteps , lastAugment ] , variants_per_asset : 1 } ;
427438
428- if ( samples === undefined ) return < LoadingState rows = { 1 } /> ;
429- if ( samples . length === 0 ) {
439+ if ( sample === undefined ) return < LoadingState rows = { 1 } /> ;
440+ if ( sample === null ) {
430441 return (
431442 < p className = "text-sm text-muted-foreground" data-testid = "preview-empty" >
432443 Nothing to preview yet — the project has no images. The recipe can still be saved.
@@ -439,54 +450,64 @@ function PreviewGrid({
439450 < div className = "grid grid-cols-3 gap-2 text-xs text-muted-foreground" >
440451 < span > Original</ span >
441452 < span > After resize</ span >
442- < span > After augmentation</ span >
453+ < span data-testid = "preview-augment-heading" >
454+ { lastAugment === undefined ? "After augmentation" : `After ${ opLabel ( lastAugment . op ) } ` }
455+ </ span >
443456 </ div >
444- { samples . map ( ( assetId , index ) => (
445- < div
446- key = { assetId }
447- className = "grid grid-cols-3 items-center gap-2"
448- data-testid = { `preview-row-${ index } ` }
449- title = { `Sample ${ assetId . slice ( 0 , 8 ) } ` }
450- >
457+ < div
458+ className = "grid grid-cols-3 items-center gap-2"
459+ data-testid = "preview-row-0"
460+ title = { `Sample ${ sample . slice ( 0 , 8 ) } ` }
461+ >
462+ < PreviewCell
463+ projectId = { projectId }
464+ assetId = { sample }
465+ variant = { 0 }
466+ spec = { NO_TRANSFORM }
467+ classes = { classes }
468+ testId = "preview-0-original"
469+ onReady = { onReady }
470+ />
471+ { hasResize ? (
451472 < PreviewCell
452473 projectId = { projectId }
453- assetId = { assetId }
474+ assetId = { sample }
454475 variant = { 0 }
455- spec = { NO_TRANSFORM }
476+ spec = { resizeOnly }
477+ classes = { classes }
478+ testId = "preview-0-resize"
479+ />
480+ ) : (
481+ < Placeholder text = "No resize step" testId = "preview-0-resize" />
482+ ) }
483+ { showcase !== null ? (
484+ < PreviewCell
485+ projectId = { projectId }
486+ assetId = { sample }
487+ variant = { 1 }
488+ spec = { showcase }
456489 classes = { classes }
457- testId = { ` preview-${ index } -original` }
458- { ... ( index === 0 ? { onReady } : { } ) }
490+ testId = " preview-0-augment"
491+ showcase
459492 />
460- { hasResize ? (
461- < PreviewCell
462- projectId = { projectId }
463- assetId = { assetId }
464- variant = { 0 }
465- spec = { resizeOnly }
466- classes = { classes }
467- testId = { `preview-${ index } -resize` }
468- />
469- ) : (
470- < Placeholder text = "No resize step" testId = { `preview-${ index } -resize` } />
471- ) }
472- { hasAugment ? (
473- < PreviewCell
474- projectId = { projectId }
475- assetId = { assetId }
476- variant = { 1 }
477- spec = { settled }
478- classes = { classes }
479- testId = { `preview-${ index } -augment` }
480- />
481- ) : (
482- < Placeholder text = "No augmentation" testId = { `preview-${ index } -augment` } />
483- ) }
484- </ div >
485- ) ) }
493+ ) : (
494+ < Placeholder text = "No augmentation" testId = "preview-0-augment" />
495+ ) }
496+ </ div >
486497 </ div >
487498 ) ;
488499}
489500
501+ /** The augmentation the preview shows: the last one the draft holds, in the editor's own order. */
502+ function lastAugmentStep ( spec : RecipeSpec ) : AugmentStepSpec | undefined {
503+ const augments = spec . steps . filter ( ( step ) : step is AugmentStepSpec => step . kind === "augment" ) ;
504+ return augments [ augments . length - 1 ] ;
505+ }
506+
507+ function opLabel ( op : AugmentStepSpec [ "op" ] ) : string {
508+ return ( AUGMENT_OPS . find ( ( one ) => one . op === op ) ?. label ?? op ) . toLowerCase ( ) ;
509+ }
510+
490511/**
491512 * One asset through one spec. The rendered image with its labels drawn where
492513 * the export would write them, or a placeholder while it has not arrived, or
@@ -507,6 +528,7 @@ function PreviewCell({
507528 classes,
508529 testId,
509530 onReady,
531+ showcase = false ,
510532} : {
511533 readonly projectId : string ;
512534 readonly assetId : string ;
@@ -515,13 +537,15 @@ function PreviewCell({
515537 readonly classes : readonly LabelClass [ ] | undefined ;
516538 readonly testId : string ;
517539 readonly onReady ?: ( ready : boolean ) => void ;
540+ readonly showcase ?: boolean ;
518541} ) : JSX . Element {
519542 const preview = usePreprocessingPreview (
520543 projectId ,
521544 assetId ,
522545 variant ,
523546 spec ,
524547 spec === null ? "" : canonicalSpec ( spec ) ,
548+ showcase ,
525549 ) ;
526550 const ready = preview . data !== undefined ;
527551 useEffect ( ( ) => {
0 commit comments