@@ -61,6 +61,8 @@ export function BalanceCard({
6161 const supplyPriority = useStore ( doc . store , ( s ) => s . supplyPriority ?? 0 ) ;
6262 const supplyPriorities = useStore ( doc . store , ( s ) => s . supplyPriorities ?? { } ) ;
6363 const spoilables = useSpoilables ( ) ;
64+ const showImports = ! ! res && ( res . displayImports . length > 0 || res . displayExports . length === 0 ) ;
65+ const showExports = ! ! res ?. displayExports . length ;
6466 // One mixed loop candidate: every solid item touching a belt in this block —
6567 // imports, exports, AND internal row-to-row flows all ride the same loop in
6668 // the "everything on one belt" pattern. A good's belt rate is
@@ -302,89 +304,91 @@ export function BalanceCard({
302304 </ div >
303305 ) }
304306 < div
305- className = { `grid gap-4 p-3 ${ res ?. displayExports . length ? "grid-cols-2" : "grid-cols-1" } ` }
307+ className = { `grid gap-4 p-3 ${ showImports && showExports ? "grid-cols-2" : "grid-cols-1" } ` }
306308 >
307- < div >
308- < div className = "mb-1 text-sm font-semibold text-warning" > Imports</ div >
309- < div className = "flex flex-wrap gap-x-3 gap-y-2" >
310- { res ?. imports . length ? (
311- res . imports . map ( ( f ) => (
312- < span key = { f . name } className = "group flex flex-col items-start gap-1.5" >
313- < span className = "inline-flex items-center gap-1.5" >
314- < ItemChip
315- name = { f . name }
316- kind = { f . kind }
317- display = { res . display ?. [ f . name ] }
318- rate = { f . rate }
319- link = "import"
320- craftable = { producible . has ( f . name ) }
321- fuel = { fuelSet . has ( f . name ) }
322- onClick = { ( ) => onMakeFor ( f . name ) }
323- onContext = { ( e ) =>
324- onCtxMenu ( e , { name : f . name , kind : f . kind , link : "import" } )
325- }
326- />
327- { /* Locked-as-block-driver state (set via right-click → "Size block by this
309+ { showImports && (
310+ < div >
311+ < div className = "mb-1 text-sm font-semibold text-warning" > Imports</ div >
312+ < div className = "flex flex-wrap gap-x-3 gap-y-2" >
313+ { res . displayImports . length ? (
314+ res . displayImports . map ( ( f ) => (
315+ < span key = { f . name } className = "group flex flex-col items-start gap-1.5" >
316+ < span className = "inline-flex items-center gap-1.5" >
317+ < ItemChip
318+ name = { f . name }
319+ kind = { f . kind }
320+ display = { res . display ?. [ f . name ] }
321+ rate = { f . rate }
322+ link = "import"
323+ craftable = { producible . has ( f . name ) }
324+ fuel = { fuelSet . has ( f . name ) }
325+ onClick = { ( ) => onMakeFor ( f . name ) }
326+ onContext = { ( e ) =>
327+ onCtxMenu ( e , { name : f . name , kind : f . kind , link : "import" } )
328+ }
329+ />
330+ { /* Locked-as-block-driver state (set via right-click → "Size block by this
328331 input"): edit its rate inline + an unlock control. The toggle itself
329332 lives in the context menu, so non-locked rows stay uncluttered. */ }
330- { /* the block imports this while an in-block recipe
333+ { /* the block imports this while an in-block recipe
331334 produces it — usually the import-instead-of-make
332335 trap (block 27); one click claims it in-block */ }
333- { res . importedProducible ?. includes ( f . name ) && (
334- < button
335- title = "an enabled recipe in this block produces this good, but the plan imports it. Click to mark it made in-block (production must cover consumption)."
336- onClick = { ( ) => {
337- doc . markMade ( f . name ) ;
338- doc . note ( `Mark "${ res . display ?. [ f . name ] ?? f . name } " made in-block` ) ;
339- } }
340- className = "bg-warning/25 px-1.5 py-0.5 text-sm text-warning hover:brightness-110"
341- >
342- made here? · make in-block
343- </ button >
344- ) }
345- { lockedInput === f . name && (
346- < >
347- < Tooltip content = "locked rate — the block is sized to consume this much of this input" >
348- < Input
349- type = "number"
350- value = { lockedRate }
351- step = "0.01"
352- min = "0"
353- autoFocus
354- onChange = { ( e ) => onLockedRateChange ( Number ( e . target . value ) || 0 ) }
355- className = "h-7 w-16 border-info/60 px-1"
356- />
357- </ Tooltip >
358- < Button
359- variant = "ghost"
360- size = "icon-xs"
361- onClick = { onUnlock }
362- title = "unlock — the Goal rate is editable again"
363- className = "text-info"
336+ { res . importedProducible ?. includes ( f . name ) && (
337+ < button
338+ title = "an enabled recipe in this block produces this good, but the plan imports it. Click to mark it made in-block (production must cover consumption)."
339+ onClick = { ( ) => {
340+ doc . markMade ( f . name ) ;
341+ doc . note ( `Mark "${ res . display ?. [ f . name ] ?? f . name } " made in-block` ) ;
342+ } }
343+ className = "bg-warning/25 px-1.5 py-0.5 text-sm text-warning hover:brightness-110"
364344 >
365- < Lock className = "size-3.5" />
366- </ Button >
367- </ >
345+ made here? · make in-block
346+ </ button >
347+ ) }
348+ { lockedInput === f . name && (
349+ < >
350+ < Tooltip content = "locked rate — the block is sized to consume this much of this input" >
351+ < Input
352+ type = "number"
353+ value = { lockedRate }
354+ step = "0.01"
355+ min = "0"
356+ autoFocus
357+ onChange = { ( e ) => onLockedRateChange ( Number ( e . target . value ) || 0 ) }
358+ className = "h-7 w-16 border-info/60 px-1"
359+ />
360+ </ Tooltip >
361+ < Button
362+ variant = "ghost"
363+ size = "icon-xs"
364+ onClick = { onUnlock }
365+ title = "unlock — the Goal rate is editable again"
366+ className = "text-info"
367+ >
368+ < Lock className = "size-3.5" />
369+ </ Button >
370+ </ >
371+ ) }
372+ </ span >
373+ { logi . resolved && f . kind === "item" && (
374+ < LogiTag
375+ resolved = { logi . resolved }
376+ rate = { f . rate }
377+ machineCount = { 0 }
378+ showBelts = { logi . showBelts }
379+ showInserters = { logi . showInserters }
380+ launch = { logi . launchInfo ( f . name , f . rate ) }
381+ />
368382 ) }
369383 </ span >
370- { logi . resolved && f . kind === "item" && (
371- < LogiTag
372- resolved = { logi . resolved }
373- rate = { f . rate }
374- machineCount = { 0 }
375- showBelts = { logi . showBelts }
376- showInserters = { logi . showInserters }
377- launch = { logi . launchInfo ( f . name , f . rate ) }
378- />
379- ) }
380- </ span >
381- ) )
382- ) : (
383- < span className = "text-sm text-muted-foreground" > none</ span >
384- ) }
384+ ) )
385+ ) : (
386+ < span className = "text-sm text-muted-foreground" > none</ span >
387+ ) }
388+ </ div >
385389 </ div >
386- </ div >
387- { ! ! res ?. displayExports . length && (
390+ ) }
391+ { showExports && (
388392 < div >
389393 < div className = "mb-1 text-sm font-semibold text-surplus" > Exports</ div >
390394 < div className = "flex flex-wrap gap-x-3 gap-y-2" >
0 commit comments