From 3019387f38ec7bd7a8176b6dc77fa8e88ee9f127 Mon Sep 17 00:00:00 2001
From: Rob Watson <rob@netflux.io>
Date: Thu, 24 Apr 2025 21:06:47 +0200
Subject: [PATCH] refactor(app): add StartDestinationFailedEvent

---
 internal/app/app.go           |  5 ++--
 internal/event/events.go      |  8 ++++++
 internal/terminal/terminal.go | 53 ++++++++++++++++++-----------------
 3 files changed, 38 insertions(+), 28 deletions(-)

diff --git a/internal/app/app.go b/internal/app/app.go
index c74bada..7a04f97 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -181,7 +181,7 @@ func (a *App) Run(ctx context.Context) error {
 				return nil
 			}
 
-			if !a.handleCommand(cmd, state, repl, ui) {
+			if !a.handleCommand(cmd, state, repl) {
 				return nil
 			}
 		case <-uiUpdateT.C:
@@ -209,7 +209,6 @@ func (a *App) handleCommand(
 	cmd domain.Command,
 	state *domain.AppState,
 	repl *replicator.Actor,
-	ui *terminal.UI,
 ) bool {
 	a.logger.Debug("Command received", "cmd", cmd.Name())
 	switch c := cmd.(type) {
@@ -243,7 +242,7 @@ func (a *App) handleCommand(
 		a.eventBus.Send(event.DestinationRemovedEvent{URL: c.URL})
 	case domain.CommandStartDestination:
 		if !state.Source.Live {
-			ui.ShowSourceNotLiveModal()
+			a.eventBus.Send(event.StartDestinationFailedEvent{})
 			break
 		}
 
diff --git a/internal/event/events.go b/internal/event/events.go
index 921b3d1..933a037 100644
--- a/internal/event/events.go
+++ b/internal/event/events.go
@@ -8,6 +8,7 @@ const (
 	EventNameAppStateChanged         Name = "app_state_changed"
 	EventNameDestinationAdded        Name = "destination_added"
 	EventNameAddDestinationFailed    Name = "add_destination_failed"
+	EventNameStartDestinationFailed  Name = "start_destination_failed"
 	EventNameDestinationRemoved      Name = "destination_removed"
 	EventNameRemoveDestinationFailed Name = "remove_destination_failed"
 	EventNameFatalErrorOccurred      Name = "fatal_error_occurred"
@@ -46,6 +47,13 @@ func (e AddDestinationFailedEvent) name() Name {
 	return EventNameAddDestinationFailed
 }
 
+// StartDestinationFailedEvent is emitted when a destination fails to start.
+type StartDestinationFailedEvent struct{}
+
+func (e StartDestinationFailedEvent) name() Name {
+	return EventNameStartDestinationFailed
+}
+
 // DestinationRemovedEvent is emitted when a destination is successfully
 // removed.
 type DestinationRemovedEvent struct {
diff --git a/internal/terminal/terminal.go b/internal/terminal/terminal.go
index 2e38ca9..2929282 100644
--- a/internal/terminal/terminal.go
+++ b/internal/terminal/terminal.go
@@ -282,6 +282,7 @@ func (ui *UI) run(ctx context.Context) {
 	appStateChangedC := ui.eventBus.Register(event.EventNameAppStateChanged)
 	destinationAddedC := ui.eventBus.Register(event.EventNameDestinationAdded)
 	addDestinationFailedC := ui.eventBus.Register(event.EventNameAddDestinationFailed)
+	startDestinationFailedC := ui.eventBus.Register(event.EventNameStartDestinationFailed)
 	destinationRemovedC := ui.eventBus.Register(event.EventNameDestinationRemoved)
 	removeDestinationFailedC := ui.eventBus.Register(event.EventNameRemoveDestinationFailed)
 	mediaServerStartedC := ui.eventBus.Register(event.EventNameMediaServerStarted)
@@ -308,6 +309,10 @@ func (ui *UI) run(ctx context.Context) {
 			ui.app.QueueUpdateDraw(func() {
 				ui.handleDestinationAdded(evt.(event.DestinationAddedEvent))
 			})
+		case evt := <-startDestinationFailedC:
+			ui.app.QueueUpdateDraw(func() {
+				ui.handleStartDestinationFailed(evt.(event.StartDestinationFailedEvent))
+			})
 		case evt := <-addDestinationFailedC:
 			ui.app.QueueUpdateDraw(func() {
 				ui.handleDestinationEventError(evt.(event.AddDestinationFailedEvent).Err)
@@ -413,16 +418,14 @@ func (ui *UI) fkeyHandler(key tcell.Key) {
 	}
 }
 
-func (ui *UI) ShowSourceNotLiveModal() {
-	ui.app.QueueUpdateDraw(func() {
-		ui.showModal(
-			pageNameModalNotLive,
-			"Waiting for stream.\n\nStart streaming to a source URL then try again.",
-			[]string{"Ok"},
-			false,
-			nil,
-		)
-	})
+func (ui *UI) handleStartDestinationFailed(event.StartDestinationFailedEvent) {
+	ui.showModal(
+		pageNameModalStartDestinationFailed,
+		"Waiting for stream.\n\nStart streaming to a source URL then try again.",
+		[]string{"Ok"},
+		false,
+		nil,
+	)
 }
 
 // ShowStartupCheckModal shows a modal dialog to the user, asking if they want
@@ -592,21 +595,21 @@ func (ui *UI) updateProgressModal(container domain.Container) {
 // Modals should generally have a unique name, which allows them to be stacked
 // on top of other modals.
 const (
-	pageNameMain                   = "main"
-	pageNameAddDestination         = "add-destination"
-	pageNameViewURLs               = "view-urls"
-	pageNameConfigUpdateFailed     = "modal-config-update-failed"
-	pageNameNoDestinations         = "no-destinations"
-	pageNameModalAbout             = "modal-about"
-	pageNameModalClipboard         = "modal-clipboard"
-	pageNameModalDestinationError  = "modal-destination-error"
-	pageNameModalFatalError        = "modal-fatal-error"
-	pageNameModalPullProgress      = "modal-pull-progress"
-	pageNameModalQuit              = "modal-quit"
-	pageNameModalRemoveDestination = "modal-remove-destination"
-	pageNameModalSourceError       = "modal-source-error"
-	pageNameModalStartupCheck      = "modal-startup-check"
-	pageNameModalNotLive           = "modal-not-live"
+	pageNameMain                        = "main"
+	pageNameAddDestination              = "add-destination"
+	pageNameViewURLs                    = "view-urls"
+	pageNameConfigUpdateFailed          = "modal-config-update-failed"
+	pageNameNoDestinations              = "no-destinations"
+	pageNameModalAbout                  = "modal-about"
+	pageNameModalClipboard              = "modal-clipboard"
+	pageNameModalDestinationError       = "modal-destination-error"
+	pageNameModalFatalError             = "modal-fatal-error"
+	pageNameModalPullProgress           = "modal-pull-progress"
+	pageNameModalQuit                   = "modal-quit"
+	pageNameModalRemoveDestination      = "modal-remove-destination"
+	pageNameModalSourceError            = "modal-source-error"
+	pageNameModalStartDestinationFailed = "modal-start-destination-failed"
+	pageNameModalStartupCheck           = "modal-startup-check"
 )
 
 // modalVisible returns true if any modal, including the add destination form,