@@ -7,6 +7,13 @@ import { supportedEfforts, type ReasoningEffort } from "../../provider/reasoning
77import { PROVIDER_TIERS , type ProviderTier , type TierConfig } from "../../config/settings.js" ;
88import { formatTierChain , normalizeTierDefinition } from "../../config/inference-sources.js" ;
99import type { AgentProfile } from "../../agent/profiles.js" ;
10+ import { useTerminalSize } from "../hooks/use-terminal-size.js" ;
11+ import {
12+ STACK_FORM_COLUMNS ,
13+ fitTrailingText ,
14+ formContentWidth ,
15+ wrapHelpSegments ,
16+ } from "./form-reflow.js" ;
1017
1118// Effort display: undefined means "no override" (field omitted); "none" is
1219// OpenAI's explicit disable-reasoning value. Both read as "off".
@@ -249,6 +256,15 @@ export function AgentModal({
249256 unauthedProviders,
250257 onRequestLogin,
251258} : AgentModalProps ) : ReactNode {
259+ const { columns } = useTerminalSize ( ) ;
260+ const stackFields = columns < STACK_FORM_COLUMNS ;
261+ const contentWidth = formContentWidth ( columns , stackFields ) ;
262+ // Label column widths used in row layout; stacked layout uses full content width for values.
263+ const providerLabelWidth = 16 ;
264+ const profileLabelWidth = 14 ;
265+ const valueWidth = stackFields
266+ ? contentWidth
267+ : Math . max ( 8 , contentWidth - Math . max ( providerLabelWidth , profileLabelWidth ) - 1 ) ;
252268 const initialProvider = Math . max (
253269 0 ,
254270 providers . findIndex ( ( p ) => p . name === activeProvider ) ,
@@ -773,13 +789,40 @@ export function AgentModal({
773789 setFormError ( null ) ;
774790 } ) ;
775791
792+ const helpText = ( ( ) : string | null => {
793+ switch ( step ) {
794+ case "provider" :
795+ return "Up/Down navigate · Enter models · a add · e edit · x remove · t tiers · p profiles · Esc close" ;
796+ case "tiers" :
797+ return "Up/Down navigate · Enter add · e edit chain · m mode · c clear · Esc back" ;
798+ case "tier-chain" :
799+ return "Up/Down leg · a/Enter add · x remove · u/d reorder · m mode · Esc back" ;
800+ case "profiles" :
801+ return "Up/Down navigate · a add · e edit · x remove · Esc back" ;
802+ case "profile-form" :
803+ return "Up/Down fields · Left/Right for tier · Enter next/save · Esc cancel" ;
804+ case "profile-delete" :
805+ return "y remove · n cancel · Esc back" ;
806+ case "model" :
807+ return "Up/Down navigate · Enter effort · Esc back" ;
808+ case "effort" :
809+ return "Up/Down navigate · Enter use now · d set as default · Esc back" ;
810+ case "form" :
811+ return "Up/Down fields · Left/Right toggle keyless · Enter next/save · Esc cancel" ;
812+ case "delete" :
813+ return "y remove · n cancel · Esc back" ;
814+ }
815+ } ) ( ) ;
816+ const helpLines = helpText !== null ? wrapHelpSegments ( helpText . split ( " · " ) , contentWidth ) : [ ] ;
817+
776818 return (
777819 < Box
778820 flexDirection = "column"
779- paddingX = { 2 }
821+ paddingX = { stackFields ? 1 : 2 }
780822 paddingY = { 1 }
781823 marginX = { 1 }
782824 marginY = { 1 }
825+ width = { Math . max ( 1 , columns - 2 ) }
783826 >
784827 < Text bold color = { color ( "accent" ) } >
785828 Agent Configuration
@@ -859,16 +902,18 @@ export function AgentModal({
859902 const assignment = tiers [ tier ] ;
860903 const isCursor = i === tierIndex ;
861904 const assignmentLabel = formatTierChain ( assignment ) ;
905+ const rowDir = stackFields ? "column" : "row" ;
862906 return (
863- < Box key = { tier } flexDirection = "row" gap = { 2 } >
864- < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
865- { isCursor ? ">" : " " }
866- </ Text >
867- < Box width = { 10 } flexShrink = { 0 } >
907+ < Box key = { tier } flexDirection = { rowDir } gap = { stackFields ? 0 : 2 } >
908+ < Box flexDirection = "row" gap = { 1 } >
909+ < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
910+ { isCursor ? ">" : " " }
911+ </ Text >
868912 < Text color = { isCursor ? color ( "accent" ) : color ( "text" ) } > { tier } </ Text >
869913 </ Box >
870914 < Text color = { assignment !== undefined ? color ( "text" ) : color ( "muted" ) } >
871- { assignmentLabel }
915+ { stackFields ? " " : "" }
916+ { fitTrailingText ( assignmentLabel , stackFields ? contentWidth - 2 : Math . max ( 8 , contentWidth - 12 ) ) }
872917 </ Text >
873918 </ Box >
874919 ) ;
@@ -886,17 +931,27 @@ export function AgentModal({
886931 { models . map ( ( m , i ) => {
887932 const isActive = selectedProvider ?. name === activeProvider && m === activeModel ;
888933 const isCursor = i === modelIndex ;
934+ const desc = MODEL_DESCRIPTIONS [ m ] ;
935+ const namePart = `${ isActive ? "* " : " " } ${ m } ` ;
936+ const showDescInline = desc !== undefined && ! stackFields && namePart . length + desc . length + 4 < contentWidth ;
889937 return (
890- < Box key = { m } flexDirection = "row" gap = { 1 } >
891- < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
892- { isCursor ? ">" : " " }
893- </ Text >
894- < Text color = { isCursor ? color ( "accent" ) : color ( "text" ) } >
895- { isActive ? "* " : " " }
896- { m }
897- </ Text >
898- { MODEL_DESCRIPTIONS [ m ] !== undefined && (
899- < Text color = { color ( "muted" ) } > — { MODEL_DESCRIPTIONS [ m ] } </ Text >
938+ < Box key = { m } flexDirection = "column" >
939+ < Box flexDirection = "row" gap = { 1 } >
940+ < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
941+ { isCursor ? ">" : " " }
942+ </ Text >
943+ < Text color = { isCursor ? color ( "accent" ) : color ( "text" ) } >
944+ { fitTrailingText ( namePart , contentWidth - 2 ) }
945+ </ Text >
946+ { showDescInline && (
947+ < Text color = { color ( "muted" ) } > — { desc } </ Text >
948+ ) }
949+ </ Box >
950+ { desc !== undefined && ! showDescInline && (
951+ < Text color = { color ( "muted" ) } >
952+ { " " }
953+ { fitTrailingText ( desc , contentWidth - 2 ) }
954+ </ Text >
900955 ) }
901956 </ Box >
902957 ) ;
@@ -915,17 +970,27 @@ export function AgentModal({
915970 { efforts . map ( ( e , i ) => {
916971 const isActive = e === activeEffort ;
917972 const isCursor = i === effortIndex ;
973+ const desc = EFFORT_DESCRIPTIONS [ e ] ;
974+ const namePart = `${ isActive ? "* " : " " } ${ effortLabel ( e ) } ` ;
975+ const showDescInline = desc !== undefined && ! stackFields && namePart . length + desc . length + 4 < contentWidth ;
918976 return (
919- < Box key = { e } flexDirection = "row" gap = { 1 } >
920- < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
921- { isCursor ? ">" : " " }
922- </ Text >
923- < Text color = { isCursor ? color ( "accent" ) : color ( "text" ) } >
924- { isActive ? "* " : " " }
925- { effortLabel ( e ) }
926- </ Text >
927- { EFFORT_DESCRIPTIONS [ e ] !== undefined && (
928- < Text color = { color ( "muted" ) } > — { EFFORT_DESCRIPTIONS [ e ] } </ Text >
977+ < Box key = { e } flexDirection = "column" >
978+ < Box flexDirection = "row" gap = { 1 } >
979+ < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
980+ { isCursor ? ">" : " " }
981+ </ Text >
982+ < Text color = { isCursor ? color ( "accent" ) : color ( "text" ) } >
983+ { fitTrailingText ( namePart , contentWidth - 2 ) }
984+ </ Text >
985+ { showDescInline && (
986+ < Text color = { color ( "muted" ) } > — { desc } </ Text >
987+ ) }
988+ </ Box >
989+ { desc !== undefined && ! showDescInline && (
990+ < Text color = { color ( "muted" ) } >
991+ { " " }
992+ { fitTrailingText ( desc , contentWidth - 2 ) }
993+ </ Text >
929994 ) }
930995 </ Box >
931996 ) ;
@@ -949,45 +1014,67 @@ export function AgentModal({
9491014 const isCursor = i === formIndex ;
9501015 const value = formValues [ field ] ;
9511016 const isKeyless = formValues . keyless === "yes" ;
952- // gap only between label and value — never between value and caret,
1017+ // gap only between label and value — never between value and caret,
9531018 // or the caret sits after a phantom space the user did not type.
9541019 const showCaret =
9551020 isCursor &&
9561021 field !== "keyless" &&
9571022 ! ( field === "apiKey" && isKeyless ) ;
1023+ const rawDisplay =
1024+ field === "keyless"
1025+ ? null
1026+ : field === "apiKey" && isKeyless
1027+ ? "(disabled — keyless provider)"
1028+ : value . length > 0
1029+ ? maskInput ( field , value )
1030+ : field === "apiKey" && editingProvider !== undefined
1031+ ? "leave blank to keep existing"
1032+ : FIELD_HINTS [ field ] ;
1033+ // Reserve one cell for the caret so long values do not push it off-screen.
1034+ const fitted =
1035+ rawDisplay === null
1036+ ? null
1037+ : fitTrailingText ( rawDisplay , showCaret ? Math . max ( 1 , valueWidth - 1 ) : valueWidth ) ;
9581038 return (
959- < Box key = { field } flexDirection = "row" gap = { 1 } >
960- < Box width = { 16 } flexShrink = { 0 } >
1039+ < Box
1040+ key = { field }
1041+ flexDirection = { stackFields ? "column" : "row" }
1042+ gap = { stackFields ? 0 : 1 }
1043+ marginBottom = { stackFields ? 1 : 0 }
1044+ >
1045+ < Box width = { stackFields ? undefined : providerLabelWidth } flexShrink = { 0 } >
9611046 < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
9621047 { FIELD_LABELS [ field ] }
9631048 </ Text >
9641049 </ Box >
965- { field === "keyless" ? (
966- < Text color = { value === "yes" ? color ( "accent" ) : color ( "muted" ) } >
967- { isCursor ? "< " : " " }
968- { value === "yes" ? "yes" : "no" }
969- { isCursor ? " >" : "" }
970- </ Text >
971- ) : field === "apiKey" && isKeyless ? (
972- < Text color = { color ( "muted" ) } > (disabled — keyless provider)</ Text >
973- ) : (
974- < Box >
975- < Text color = { value . length > 0 ? color ( "text" ) : color ( "muted" ) } >
976- { value . length > 0
977- ? maskInput ( field , value )
978- : field === "apiKey" && editingProvider !== undefined
979- ? "leave blank to keep existing"
980- : FIELD_HINTS [ field ] }
1050+ < Box flexDirection = "row" gap = { 0 } >
1051+ { field === "keyless" ? (
1052+ < Text color = { value === "yes" ? color ( "accent" ) : color ( "muted" ) } >
1053+ { isCursor ? "< " : " " }
1054+ { value === "yes" ? "yes" : "no" }
1055+ { isCursor ? " >" : "" }
9811056 </ Text >
982- { showCaret && < Text color = { color ( "accent" ) } > |</ Text > }
983- </ Box >
984- ) }
1057+ ) : (
1058+ < Text
1059+ color = {
1060+ field === "apiKey" && isKeyless
1061+ ? color ( "muted" )
1062+ : value . length > 0
1063+ ? color ( "text" )
1064+ : color ( "muted" )
1065+ }
1066+ >
1067+ { fitted }
1068+ </ Text >
1069+ ) }
1070+ { showCaret && < Text color = { color ( "accent" ) } > |</ Text > }
1071+ </ Box >
9851072 </ Box >
9861073 ) ;
9871074 } ) }
9881075 { formError !== null && (
9891076 < Box marginTop = { 1 } >
990- < Text color = { color ( "danger" ) } > { formError } </ Text >
1077+ < Text color = { color ( "danger" ) } > { fitTrailingText ( formError , contentWidth ) } </ Text >
9911078 </ Box >
9921079 ) }
9931080 </ Box >
@@ -1000,18 +1087,23 @@ export function AgentModal({
10001087 ) }
10011088 { profiles . map ( ( p , i ) => {
10021089 const isCursor = i === profileIndex ;
1090+ const meta = `${ p . tier !== undefined ? `[${ p . tier } ]` : "" } ${ p . description !== undefined ? ` ${ p . description } ` : "" } ` . trim ( ) ;
10031091 return (
1004- < Box key = { p . id } flexDirection = "row" gap = { 2 } >
1005- < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
1006- { isCursor ? ">" : " " }
1007- </ Text >
1008- < Box width = { 20 } flexShrink = { 0 } >
1009- < Text color = { isCursor ? color ( "accent" ) : color ( "text" ) } > { p . id } </ Text >
1092+ < Box key = { p . id } flexDirection = { stackFields ? "column" : "row" } gap = { stackFields ? 0 : 2 } >
1093+ < Box flexDirection = "row" gap = { 1 } >
1094+ < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
1095+ { isCursor ? ">" : " " }
1096+ </ Text >
1097+ < Text color = { isCursor ? color ( "accent" ) : color ( "text" ) } >
1098+ { fitTrailingText ( p . id , stackFields ? contentWidth - 2 : 20 ) }
1099+ </ Text >
10101100 </ Box >
1011- < Text color = { color ( "muted" ) } >
1012- { p . tier !== undefined ? `[${ p . tier } ]` : "" }
1013- { p . description !== undefined ? ` ${ p . description } ` : "" }
1014- </ Text >
1101+ { meta . length > 0 && (
1102+ < Text color = { color ( "muted" ) } >
1103+ { stackFields ? " " : "" }
1104+ { fitTrailingText ( meta , stackFields ? contentWidth - 2 : Math . max ( 8 , contentWidth - 24 ) ) }
1105+ </ Text >
1106+ ) }
10151107 </ Box >
10161108 ) ;
10171109 } ) }
@@ -1032,53 +1124,62 @@ export function AgentModal({
10321124 </ Text >
10331125 { PROFILE_FORM_FIELDS . map ( ( field , i ) => {
10341126 const isCursor = i === profileFormIndex ;
1127+ const showCaret = isCursor && field !== "tier" ;
1128+ const raw =
1129+ field === "tier"
1130+ ? null
1131+ : profileFormValues [ field ] . length > 0
1132+ ? profileFormValues [ field ]
1133+ : PROFILE_FIELD_HINTS [ field ] ;
1134+ const fitted =
1135+ raw === null
1136+ ? null
1137+ : fitTrailingText ( raw , showCaret ? Math . max ( 1 , valueWidth - 1 ) : valueWidth ) ;
10351138 return (
1036- < Box key = { field } flexDirection = "row" gap = { 1 } >
1037- < Box width = { 14 } flexShrink = { 0 } >
1139+ < Box
1140+ key = { field }
1141+ flexDirection = { stackFields ? "column" : "row" }
1142+ gap = { stackFields ? 0 : 1 }
1143+ marginBottom = { stackFields ? 1 : 0 }
1144+ >
1145+ < Box width = { stackFields ? undefined : profileLabelWidth } flexShrink = { 0 } >
10381146 < Text color = { isCursor ? color ( "accent" ) : color ( "muted" ) } bold = { isCursor } >
10391147 { PROFILE_FIELD_LABELS [ field ] }
10401148 </ Text >
10411149 </ Box >
1042- { field === "tier" ? (
1043- < Text color = { profileFormValues . tier . length > 0 ? color ( "text" ) : color ( "muted" ) } >
1044- { isCursor ? "< " : " " }
1045- { profileFormValues . tier . length > 0 ? profileFormValues . tier : "none" }
1046- { isCursor ? " >" : "" }
1047- </ Text >
1048- ) : (
1049- < Box >
1050- < Text color = { profileFormValues [ field ] . length > 0 ? color ( "text" ) : color ( "muted" ) } >
1051- { profileFormValues [ field ] . length > 0 ? profileFormValues [ field ] : PROFILE_FIELD_HINTS [ field ] }
1150+ < Box flexDirection = "row" gap = { 0 } >
1151+ { field === "tier" ? (
1152+ < Text color = { profileFormValues . tier . length > 0 ? color ( "text" ) : color ( "muted" ) } >
1153+ { isCursor ? "< " : " " }
1154+ { profileFormValues . tier . length > 0 ? profileFormValues . tier : "none" }
1155+ { isCursor ? " >" : "" }
10521156 </ Text >
1053- { isCursor && < Text color = { color ( "accent" ) } > |</ Text > }
1054- </ Box >
1055- ) }
1157+ ) : (
1158+ < Text
1159+ color = { profileFormValues [ field ] . length > 0 ? color ( "text" ) : color ( "muted" ) }
1160+ >
1161+ { fitted }
1162+ </ Text >
1163+ ) }
1164+ { showCaret && < Text color = { color ( "accent" ) } > |</ Text > }
1165+ </ Box >
10561166 </ Box >
10571167 ) ;
10581168 } ) }
10591169 { profileFormError !== null && (
10601170 < Box marginTop = { 1 } >
1061- < Text color = { color ( "danger" ) } > { profileFormError } </ Text >
1171+ < Text color = { color ( "danger" ) } > { fitTrailingText ( profileFormError , contentWidth ) } </ Text >
10621172 </ Box >
10631173 ) }
10641174 </ Box >
10651175 ) }
10661176
1067- < Box marginTop = { 1 } >
1068- < Text dimColor >
1069- { step === "provider" && "Up/Down navigate · Enter models · a add · e edit · x remove · t tiers · p profiles · Esc close" }
1070- { step === "tiers" &&
1071- "Up/Down navigate · Enter add · e edit chain · m mode · c clear · Esc back" }
1072- { step === "tier-chain" &&
1073- "Up/Down leg · a/Enter add · x remove · u/d reorder · m mode · Esc back" }
1074- { step === "profiles" && "Up/Down navigate · a add · e edit · x remove · Esc back" }
1075- { step === "profile-form" && "Up/Down fields · Left/Right for tier · Enter next/save · Esc cancel" }
1076- { step === "profile-delete" && "y remove · n cancel · Esc back" }
1077- { step === "model" && "Up/Down navigate · Enter effort · Esc back" }
1078- { step === "effort" && "Up/Down navigate · Enter use now · d set as default · Esc back" }
1079- { step === "form" && "Up/Down fields · Left/Right toggle keyless · Enter next/save · Esc cancel" }
1080- { step === "delete" && "y remove · n cancel · Esc back" }
1081- </ Text >
1177+ < Box marginTop = { 1 } flexDirection = "column" >
1178+ { helpLines . map ( ( line , i ) => (
1179+ < Text key = { i } dimColor >
1180+ { line }
1181+ </ Text >
1182+ ) ) }
10821183 </ Box >
10831184 </ Box >
10841185 ) ;
0 commit comments