44 "bytes"
55 "fmt"
66 "os"
7+ "path/filepath"
78 "strings"
89 "time"
910
@@ -228,10 +229,14 @@ func (s Service) StartSandbox(cfg StartSandboxConfig) (*StartSandboxResult, erro
228229 finishSpinner = SpinUntilDone ("Starting sandbox..." , s .StdoutIsTTY , s .Stdout )
229230 }
230231
232+ // Construct a descriptive title for the sandbox run
233+ title := SandboxTitle (cwd , branch , cfg .ConfigFile )
234+
231235 runResult , err := s .InitiateRun (InitiateRunConfig {
232236 MintFilePath : cfg .ConfigFile ,
233237 RwxDirectory : cfg .RwxDirectory ,
234238 Json : cfg .Json ,
239+ Title : title ,
235240 })
236241
237242 if err != nil {
@@ -599,14 +604,36 @@ func (s Service) ResetSandbox(cfg ResetSandboxConfig) (*ResetSandboxResult, erro
599604// Helper methods
600605
601606func (s Service ) waitForSandboxReady (runID string , jsonMode bool ) (* api.SandboxConnectionInfo , error ) {
607+ // Check once before showing spinner - sandbox may already be ready
608+ connInfo , err := s .APIClient .GetSandboxConnectionInfo (runID )
609+ if err != nil {
610+ return nil , errors .Wrap (err , "unable to get sandbox connection info" )
611+ }
612+
613+ if connInfo .Sandboxable {
614+ return & connInfo , nil
615+ }
616+
617+ if connInfo .Polling .Completed {
618+ return nil , fmt .Errorf ("Sandbox run '%s' completed before becoming ready" , runID )
619+ }
620+
621+ // Sandbox not ready yet - start spinner and poll
602622 var stopSpinner func ()
603623 if ! jsonMode {
604624 stopSpinner = Spin ("Waiting for sandbox to be ready..." , s .StdoutIsTTY , s .Stdout )
605625 defer stopSpinner ()
606626 }
607627
608628 for {
609- connInfo , err := s .APIClient .GetSandboxConnectionInfo (runID )
629+ // Use backoff from server, or default to 2 seconds
630+ backoffMs := 2000
631+ if connInfo .Polling .BackoffMs != nil {
632+ backoffMs = * connInfo .Polling .BackoffMs
633+ }
634+ time .Sleep (time .Duration (backoffMs ) * time .Millisecond )
635+
636+ connInfo , err = s .APIClient .GetSandboxConnectionInfo (runID )
610637 if err != nil {
611638 return nil , errors .Wrap (err , "unable to get sandbox connection info" )
612639 }
@@ -618,13 +645,6 @@ func (s Service) waitForSandboxReady(runID string, jsonMode bool) (*api.SandboxC
618645 if connInfo .Polling .Completed {
619646 return nil , fmt .Errorf ("Sandbox run '%s' completed before becoming ready" , runID )
620647 }
621-
622- // Use backoff from server, or default to 2 seconds
623- backoffMs := 2000
624- if connInfo .Polling .BackoffMs != nil {
625- backoffMs = * connInfo .Polling .BackoffMs
626- }
627- time .Sleep (time .Duration (backoffMs ) * time .Millisecond )
628648 }
629649}
630650
@@ -657,6 +677,24 @@ func (s Service) connectSSH(connInfo *api.SandboxConnectionInfo) error {
657677 return nil
658678}
659679
680+ // SandboxTitle constructs a descriptive title for sandbox runs using the
681+ // project directory name, branch, and config file (if non-default).
682+ func SandboxTitle (cwd , branch , configFile string ) string {
683+ project := filepath .Base (cwd )
684+ if branch == "" {
685+ branch = "detached"
686+ }
687+
688+ title := fmt .Sprintf ("Sandbox: %s (%s)" , project , branch )
689+
690+ // Include config file if it's not the default
691+ if configFile != "" && configFile != ".rwx/sandbox.yml" {
692+ title = fmt .Sprintf ("%s [%s]" , title , configFile )
693+ }
694+
695+ return title
696+ }
697+
660698func (s Service ) syncChangesToSandbox (jsonMode bool ) error {
661699 patch , lfsFiles , err := s .GitClient .GeneratePatch (nil )
662700 if err != nil {
0 commit comments