@@ -454,55 +454,67 @@ export function AppConfigDialog({
454454 { generatedFields . map ( ( field ) => < Row key = { field . id } label = { splitFieldLabel ( field . label ) . label } >
455455 { factValue ( field , storedFor ( field . id ) ) }
456456 </ Row > ) }
457- { rows . map ( ( row , index ) => < div className = "mos-row" key = { row . key } >
458- < div className = "mos-row-main" >
459- < input
460- aria-label = "Variable name"
461- autoComplete = "off"
462- className = "mos-row-input suite-env-name"
463- disabled = { busy }
464- // A name that reads like a credential starts masked, until the
465- // owner says otherwise with the eye control beside it.
466- onChange = { ( event ) => update ( index , {
467- name : event . currentTarget . value ,
468- ...( row . secretTouched ? { } : { secret : SECRET_LOOKING_NAME . test ( event . currentTarget . value ) } ) ,
469- } ) }
470- placeholder = "EXAMPLE_API_KEY"
471- spellCheck = { false }
472- value = { row . name }
473- />
474- < RowTrailing >
475- { row . secret && row . stored && ! row . value ? < >
476- < RowValue mask />
477- < RowValue code > { fingerprintTail ( row . stored ) } </ RowValue >
478- < RowAction disabled = { busy } icon = "refresh" label = { `Replace ${ row . name || 'this value' } ` } onClick = { ( ) => update ( index , { stored : null } ) } />
479- </ > : < RowInput
480- aria-label = "Variable value"
457+ { rows . map ( ( row , index ) => {
458+ // MOS never gets a stored secret back from the server, so this row
459+ // has nothing to reveal and no box to put it in: it states what it
460+ // holds, and Replace is the only way to change it.
461+ const held = row . secret && row . stored && ! row . value ;
462+ return < div className = "mos-row suite-env-row" key = { row . key } >
463+ < div className = "mos-row-main" >
464+ < RowInput
465+ aria-label = "Variable name"
481466 autoComplete = "off"
482467 code
483468 disabled = { busy }
484- onChange = { ( event ) => update ( index , { value : event . currentTarget . value } ) }
469+ // A name that reads like a credential starts masked, until the
470+ // owner says otherwise with the eye control beside it.
471+ onChange = { ( event ) => update ( index , {
472+ name : event . currentTarget . value ,
473+ ...( row . secretTouched ? { } : { secret : SECRET_LOOKING_NAME . test ( event . currentTarget . value ) } ) ,
474+ } ) }
475+ placeholder = "EXAMPLE_API_KEY"
485476 spellCheck = { false }
486- type = { row . secret ? 'password' : 'text' }
487- value = { row . value }
488- /> }
489- < RowAction
490- disabled = { busy }
491- icon = { row . secret ? 'eye-off' : 'eye' }
492- label = { row . secret ? `Stop hiding ${ row . name || 'this value' } ` : `Hide ${ row . name || 'this value' } ` }
493- onClick = { ( ) => update ( index , { secret : ! row . secret , secretTouched : true , stored : null , value : row . stored ? '' : row . value } ) }
494- />
495- < RowAction
496- danger
497- disabled = { busy }
498- icon = "x"
499- label = { `Remove ${ row . name || 'this variable' } ` }
500- onClick = { ( ) => setRows ( ( current ) => current . filter ( ( _item , position ) => position !== index ) ) }
477+ value = { row . name }
501478 />
502- </ RowTrailing >
503- </ div >
504- { rowErrors [ index ] ? < p className = "mos-row-help mos-row-invalid" role = "alert" > { rowErrors [ index ] } </ p > : null }
505- </ div > ) }
479+ < RowTrailing >
480+ { held ? < >
481+ { /* Said the way the generated rows above say it, and for the
482+ same reason: bullets followed by characters read as the
483+ tail of the value itself, and this is a fingerprint —
484+ enough to tell two stored secrets apart, and no part of
485+ either one. */ }
486+ < RowValue > { `Hidden value · ${ fingerprintTail ( row . stored ) } ` } </ RowValue >
487+ < RowAction disabled = { busy } icon = "refresh" label = { `Replace ${ row . name || 'this value' } ` } onClick = { ( ) => update ( index , { stored : null } ) } />
488+ </ > : < >
489+ < RowInput
490+ aria-label = "Variable value"
491+ autoComplete = "off"
492+ code
493+ disabled = { busy }
494+ onChange = { ( event ) => update ( index , { value : event . currentTarget . value } ) }
495+ spellCheck = { false }
496+ type = { row . secret ? 'password' : 'text' }
497+ value = { row . value }
498+ />
499+ < RowAction
500+ disabled = { busy }
501+ icon = { row . secret ? 'eye-off' : 'eye' }
502+ label = { row . secret ? `Stop hiding ${ row . name || 'this value' } ` : `Hide ${ row . name || 'this value' } ` }
503+ onClick = { ( ) => update ( index , { secret : ! row . secret , secretTouched : true } ) }
504+ />
505+ </ > }
506+ < RowAction
507+ danger
508+ disabled = { busy }
509+ icon = "x"
510+ label = { `Remove ${ row . name || 'this variable' } ` }
511+ onClick = { ( ) => setRows ( ( current ) => current . filter ( ( _item , position ) => position !== index ) ) }
512+ />
513+ </ RowTrailing >
514+ </ div >
515+ { rowErrors [ index ] ? < p className = "mos-row-help mos-row-invalid" role = "alert" > { rowErrors [ index ] } </ p > : null }
516+ </ div > ;
517+ } ) }
506518 </ Rows > : null }
507519
508520 < p className = "mos-row-help" >
0 commit comments