Browse Source

chore: remove status service

adamdottv 8 months ago
parent
commit
3c94d26570

+ 20 - 146
packages/tui/cmd/opencode/main.go

@@ -6,13 +6,9 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
-	"sync"
-	"time"
 
 	tea "github.com/charmbracelet/bubbletea/v2"
-	zone "github.com/lrstanley/bubblezone"
 	"github.com/sst/opencode/internal/app"
-	"github.com/sst/opencode/internal/pubsub"
 	"github.com/sst/opencode/internal/tui"
 	"github.com/sst/opencode/pkg/client"
 )
@@ -26,27 +22,6 @@ func main() {
 		slog.Error("Failed to create client", "error", err)
 		os.Exit(1)
 	}
-	paths, err := httpClient.PostPathGetWithResponse(context.Background())
-	if err != nil {
-		panic(err)
-	}
-	logfile := filepath.Join(paths.JSON200.Data, "log", "tui.log")
-
-	if _, err := os.Stat(filepath.Dir(logfile)); os.IsNotExist(err) {
-		err := os.MkdirAll(filepath.Dir(logfile), 0755)
-		if err != nil {
-			slog.Error("Failed to create log directory", "error", err)
-			os.Exit(1)
-		}
-	}
-	file, err := os.Create(logfile)
-	if err != nil {
-		slog.Error("Failed to create log file", "error", err)
-		os.Exit(1)
-	}
-	defer file.Close()
-	logger := slog.New(slog.NewTextHandler(file, &slog.HandlerOptions{Level: slog.LevelDebug}))
-	slog.SetDefault(logger)
 
 	// Create main context for the application
 	ctx, cancel := context.WithCancel(context.Background())
@@ -61,13 +36,11 @@ func main() {
 		panic(err)
 	}
 
-	// Set up the TUI
-	zone.NewGlobal()
 	program := tea.NewProgram(
 		tui.NewModel(app_),
-		// tea.WithMouseCellMotion(),
-		tea.WithKeyboardEnhancements(),
 		tea.WithAltScreen(),
+		tea.WithKeyboardEnhancements(),
+		// tea.WithMouseCellMotion(),
 	)
 
 	eventClient, err := client.NewClient(url)
@@ -88,54 +61,30 @@ func main() {
 		}
 	}()
 
-	// Setup the subscriptions, this will send services events to the TUI
-	ch, cancelSubs := setupSubscriptions(app_, ctx)
-
-	// Create a context for the TUI message handler
-	tuiCtx, tuiCancel := context.WithCancel(ctx)
-	var tuiWg sync.WaitGroup
-	tuiWg.Add(1)
-
-	// Set up message handling for the TUI
-	go func() {
-		defer tuiWg.Done()
-		// defer logging.RecoverPanic("TUI-message-handler", func() {
-		// 	attemptTUIRecovery(program)
-		// })
+	paths, err := httpClient.PostPathGetWithResponse(context.Background())
+	if err != nil {
+		panic(err)
+	}
+	logfile := filepath.Join(paths.JSON200.Data, "log", "tui.log")
 
-		for {
-			select {
-			case <-tuiCtx.Done():
-				slog.Info("TUI message handler shutting down")
-				return
-			case msg, ok := <-ch:
-				if !ok {
-					slog.Info("TUI message channel closed")
-					return
-				}
-				program.Send(msg)
-			}
+	if _, err := os.Stat(filepath.Dir(logfile)); os.IsNotExist(err) {
+		err := os.MkdirAll(filepath.Dir(logfile), 0755)
+		if err != nil {
+			slog.Error("Failed to create log directory", "error", err)
+			os.Exit(1)
 		}
-	}()
-
-	// Cleanup function for when the program exits
-	cleanup := func() {
-		// Cancel subscriptions first
-		cancelSubs()
-
-		// Then cancel TUI message handler
-		tuiCancel()
-
-		// Wait for TUI message handler to finish
-		tuiWg.Wait()
-
-		slog.Info("All goroutines cleaned up")
 	}
+	file, err := os.Create(logfile)
+	if err != nil {
+		slog.Error("Failed to create log file", "error", err)
+		os.Exit(1)
+	}
+	defer file.Close()
+	logger := slog.New(slog.NewTextHandler(file, &slog.HandlerOptions{Level: slog.LevelDebug}))
+	slog.SetDefault(logger)
 
 	// Run the TUI
 	result, err := program.Run()
-	cleanup()
-
 	if err != nil {
 		slog.Error("TUI error", "error", err)
 		// return fmt.Errorf("TUI error: %v", err)
@@ -143,78 +92,3 @@ func main() {
 
 	slog.Info("TUI exited", "result", result)
 }
-
-func setupSubscriber[T any](
-	ctx context.Context,
-	wg *sync.WaitGroup,
-	name string,
-	subscriber func(context.Context) <-chan pubsub.Event[T],
-	outputCh chan<- tea.Msg,
-) {
-	wg.Add(1)
-	go func() {
-		defer wg.Done()
-		// defer logging.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
-
-		subCh := subscriber(ctx)
-		if subCh == nil {
-			slog.Warn("subscription channel is nil", "name", name)
-			return
-		}
-
-		for {
-			select {
-			case event, ok := <-subCh:
-				if !ok {
-					slog.Info("subscription channel closed", "name", name)
-					return
-				}
-
-				var msg tea.Msg = event
-
-				select {
-				case outputCh <- msg:
-				case <-time.After(2 * time.Second):
-					slog.Warn("message dropped due to slow consumer", "name", name)
-				case <-ctx.Done():
-					slog.Info("subscription cancelled", "name", name)
-					return
-				}
-			case <-ctx.Done():
-				slog.Info("subscription cancelled", "name", name)
-				return
-			}
-		}
-	}()
-}
-
-func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg, func()) {
-	ch := make(chan tea.Msg, 100)
-
-	wg := sync.WaitGroup{}
-	ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context
-
-	setupSubscriber(ctx, &wg, "status", app.Status.Subscribe, ch)
-
-	cleanupFunc := func() {
-		slog.Info("Cancelling all subscriptions")
-		cancel() // Signal all goroutines to stop
-
-		waitCh := make(chan struct{})
-		go func() {
-			// defer logging.RecoverPanic("subscription-cleanup", nil)
-			wg.Wait()
-			close(waitCh)
-		}()
-
-		select {
-		case <-waitCh:
-			slog.Info("All subscription goroutines completed successfully")
-			close(ch) // Only close after all writers are confirmed done
-		case <-time.After(5 * time.Second):
-			slog.Warn("Timed out waiting for some subscription goroutines to complete")
-			close(ch)
-		}
-	}
-	return ch, cleanupFunc
-}

+ 0 - 5
packages/tui/go.mod

@@ -5,14 +5,12 @@ go 1.24.0
 require (
 	github.com/BurntSushi/toml v1.5.0
 	github.com/alecthomas/chroma/v2 v2.18.0
-	github.com/bmatcuk/doublestar/v4 v4.8.1
 	github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1
 	github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3
 	github.com/charmbracelet/glamour v0.10.0
 	github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1
 	github.com/charmbracelet/x/ansi v0.8.0
 	github.com/lithammer/fuzzysearch v1.1.8
-	github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231
 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
 	github.com/muesli/reflow v0.3.0
 	github.com/muesli/termenv v0.16.0
@@ -27,7 +25,6 @@ require (
 	dario.cat/mergo v1.0.2 // indirect
 	github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
 	github.com/atombender/go-jsonschema v0.20.0 // indirect
-	github.com/charmbracelet/bubbletea v1.3.4 // indirect
 	github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
 	github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
 	github.com/charmbracelet/x/input v0.3.5-0.20250424101541-abb4d9a9b197 // indirect
@@ -66,13 +63,11 @@ require (
 	github.com/charmbracelet/x/term v0.2.1 // indirect
 	github.com/disintegration/imaging v1.6.2
 	github.com/dlclark/regexp2 v1.11.5 // indirect
-	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
 	github.com/google/go-cmp v0.7.0 // indirect
 	github.com/gorilla/css v1.0.1 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/lucasb-eyer/go-colorful v1.2.0
 	github.com/mattn/go-isatty v0.0.20 // indirect
-	github.com/mattn/go-localereader v0.0.1 // indirect
 	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/microcosm-cc/bluemonday v1.0.27 // indirect
 	github.com/muesli/cancelreader v0.2.2 // indirect

+ 0 - 11
packages/tui/go.sum

@@ -24,12 +24,8 @@ github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/
 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
-github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
-github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 h1:swACzss0FjnyPz1enfX56GKkLiuKg5FlyVmOLIlU2kE=
 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
-github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
-github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3 h1:5A2e3myxXMpCES+kjEWgGsaf9VgZXjZbLi5iMTH7j40=
 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3/go.mod h1:ZFDg5oPjyRYrPAa3iFrtP1DO8xy+LUQxd9JFHEcuwJY=
 github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
@@ -69,8 +65,6 @@ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn
 github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
 github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
 github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
@@ -126,16 +120,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
 github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
-github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 h1:9rjt7AfnrXKNSZhp36A3/4QAZAwGGCGD/p8Bse26zms=
-github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4=
 github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
 github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@@ -267,7 +257,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

+ 8 - 17
packages/tui/internal/app/app.go

@@ -12,7 +12,6 @@ import (
 	"github.com/sst/opencode/internal/commands"
 	"github.com/sst/opencode/internal/config"
 	"github.com/sst/opencode/internal/state"
-	"github.com/sst/opencode/internal/status"
 	"github.com/sst/opencode/internal/theme"
 	"github.com/sst/opencode/internal/util"
 	"github.com/sst/opencode/pkg/client"
@@ -26,7 +25,6 @@ type App struct {
 	Model      *client.ModelInfo
 	Session    *client.SessionInfo
 	Messages   []client.MessageInfo
-	Status     status.Service
 	Commands   commands.Registry
 }
 
@@ -38,12 +36,6 @@ type AppInfo struct {
 var Info AppInfo
 
 func New(ctx context.Context, version string, httpClient *client.ClientWithResponses) (*App, error) {
-	err := status.InitService()
-	if err != nil {
-		slog.Error("Failed to initialize status service", "error", err)
-		return nil, err
-	}
-
 	appInfoResponse, _ := httpClient.PostAppInfoWithResponse(ctx)
 	appInfo := appInfoResponse.JSON200
 	Info = AppInfo{Version: version}
@@ -114,7 +106,6 @@ func New(ctx context.Context, version string, httpClient *client.ClientWithRespo
 		Model:      currentModel,
 		Session:    &client.SessionInfo{},
 		Messages:   []client.MessageInfo{},
-		Status:     status.GetService(),
 		Commands:   commands.NewCommandRegistry(),
 	}
 
@@ -160,7 +151,7 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
 
 	session, err := a.CreateSession(ctx)
 	if err != nil {
-		status.Error(err.Error())
+		// status.Error(err.Error())
 		return nil
 	}
 
@@ -175,10 +166,10 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
 			ModelID:    a.Model.Id,
 		})
 		if err != nil {
-			status.Error(err.Error())
+			// status.Error(err.Error())
 		}
 		if response != nil && response.StatusCode != 200 {
-			status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode))
+			// status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode))
 		}
 	}()
 
@@ -214,7 +205,7 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
 	if a.Session.Id == "" {
 		session, err := a.CreateSession(ctx)
 		if err != nil {
-			status.Error(err.Error())
+			// status.Error(err.Error())
 			return nil
 		}
 		a.Session = session
@@ -243,11 +234,11 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
 		})
 		if err != nil {
 			slog.Error("Failed to send message", "error", err)
-			status.Error(err.Error())
+			// status.Error(err.Error())
 		}
 		if response != nil && response.StatusCode != 200 {
 			slog.Error("Failed to send message", "error", fmt.Sprintf("failed to send message: %d", response.StatusCode))
-			status.Error(fmt.Sprintf("failed to send message: %d", response.StatusCode))
+			// status.Error(fmt.Sprintf("failed to send message: %d", response.StatusCode))
 		}
 	}()
 
@@ -262,12 +253,12 @@ func (a *App) Cancel(ctx context.Context, sessionID string) error {
 	})
 	if err != nil {
 		slog.Error("Failed to cancel session", "error", err)
-		status.Error(err.Error())
+		// status.Error(err.Error())
 		return err
 	}
 	if response != nil && response.StatusCode != 200 {
 		slog.Error("Failed to cancel session", "error", fmt.Sprintf("failed to cancel session: %d", response.StatusCode))
-		status.Error(fmt.Sprintf("failed to cancel session: %d", response.StatusCode))
+		// status.Error(fmt.Sprintf("failed to cancel session: %d", response.StatusCode))
 		return fmt.Errorf("failed to cancel session: %d", response.StatusCode)
 	}
 	return nil

+ 5 - 34
packages/tui/internal/components/chat/editor.go

@@ -17,7 +17,6 @@ import (
 	"github.com/sst/opencode/internal/components/dialog"
 	"github.com/sst/opencode/internal/image"
 	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/status"
 	"github.com/sst/opencode/internal/styles"
 	"github.com/sst/opencode/internal/theme"
 	"github.com/sst/opencode/internal/util"
@@ -157,7 +156,7 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		}
 		if key.Matches(msg, editorMaps.OpenEditor) {
 			if m.app.IsBusy() {
-				status.Warn("Agent is working, please wait...")
+				// status.Warn("Agent is working, please wait...")
 				return m, nil
 			}
 			value := m.textarea.Value()
@@ -323,7 +322,7 @@ func (m *editorComponent) openEditor(value string) tea.Cmd {
 	tmpfile, err := os.CreateTemp("", "msg_*.md")
 	tmpfile.WriteString(value)
 	if err != nil {
-		status.Error(err.Error())
+		// status.Error(err.Error())
 		return nil
 	}
 	tmpfile.Close()
@@ -333,16 +332,16 @@ func (m *editorComponent) openEditor(value string) tea.Cmd {
 	c.Stderr = os.Stderr
 	return tea.ExecProcess(c, func(err error) tea.Msg {
 		if err != nil {
-			status.Error(err.Error())
+			// status.Error(err.Error())
 			return nil
 		}
 		content, err := os.ReadFile(tmpfile.Name())
 		if err != nil {
-			status.Error(err.Error())
+			// status.Error(err.Error())
 			return nil
 		}
 		if len(content) == 0 {
-			status.Warn("Message is empty")
+			// status.Warn("Message is empty")
 			return nil
 		}
 		os.Remove(tmpfile.Name())
@@ -381,7 +380,6 @@ func (m *editorComponent) send() tea.Cmd {
 	// 		return util.CmdHandler(commands.ExecuteCommandMsg{Name: commandName})
 	// 	}
 	// }
-	slog.Info("Send message", "value", value)
 
 	return tea.Batch(
 		util.CmdHandler(SendMsg{
@@ -391,33 +389,6 @@ func (m *editorComponent) send() tea.Cmd {
 	)
 }
 
-func (m *editorComponent) attachmentsContent() string {
-	if len(m.attachments) == 0 {
-		return ""
-	}
-
-	t := theme.CurrentTheme()
-	var styledAttachments []string
-	attachmentStyles := styles.BaseStyle().
-		MarginLeft(1).
-		Background(t.TextMuted()).
-		Foreground(t.Text())
-	for i, attachment := range m.attachments {
-		var filename string
-		if len(attachment.FileName) > 10 {
-			filename = fmt.Sprintf(" %s %s...", styles.DocumentIcon, attachment.FileName[0:7])
-		} else {
-			filename = fmt.Sprintf(" %s %s", styles.DocumentIcon, attachment.FileName)
-		}
-		if m.deleteMode {
-			filename = fmt.Sprintf("%d%s", i, filename)
-		}
-		styledAttachments = append(styledAttachments, attachmentStyles.Render(filename))
-	}
-	content := lipgloss.JoinHorizontal(lipgloss.Left, styledAttachments...)
-	return content
-}
-
 func createTextArea(existing *textarea.Model) textarea.Model {
 	t := theme.CurrentTheme()
 	bgColor := t.BackgroundElement()

+ 4 - 133
packages/tui/internal/components/core/status.go

@@ -3,14 +3,11 @@ package core
 import (
 	"fmt"
 	"strings"
-	"time"
 
 	tea "github.com/charmbracelet/bubbletea/v2"
 	"github.com/charmbracelet/lipgloss/v2"
 	"github.com/sst/opencode/internal/app"
 	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/pubsub"
-	"github.com/sst/opencode/internal/status"
 	"github.com/sst/opencode/internal/styles"
 	"github.com/sst/opencode/internal/theme"
 )
@@ -20,27 +17,12 @@ type StatusComponent interface {
 }
 
 type statusComponent struct {
-	app         *app.App
-	queue       []status.StatusMessage
-	width       int
-	messageTTL  time.Duration
-	activeUntil time.Time
-}
-
-// clearMessageCmd is a command that clears status messages after a timeout
-func (m statusComponent) clearMessageCmd() tea.Cmd {
-	return tea.Tick(time.Second, func(t time.Time) tea.Msg {
-		return statusCleanupMsg{time: t}
-	})
-}
-
-// statusCleanupMsg is a message that triggers cleanup of expired status messages
-type statusCleanupMsg struct {
-	time time.Time
+	app   *app.App
+	width int
 }
 
 func (m statusComponent) Init() tea.Cmd {
-	return m.clearMessageCmd()
+	return nil
 }
 
 func (m statusComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -48,53 +30,6 @@ func (m statusComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case tea.WindowSizeMsg:
 		m.width = msg.Width
 		return m, nil
-	case pubsub.Event[status.StatusMessage]:
-		if msg.Type == status.EventStatusPublished {
-			// If this is a critical message, move it to the front of the queue
-			if msg.Payload.Critical {
-				// Insert at the front of the queue
-				m.queue = append([]status.StatusMessage{msg.Payload}, m.queue...)
-
-				// Reset active time to show critical message immediately
-				m.activeUntil = time.Time{}
-			} else {
-				// Otherwise, just add it to the queue
-				m.queue = append(m.queue, msg.Payload)
-
-				// If this is the first message and nothing is active, activate it immediately
-				if len(m.queue) == 1 && m.activeUntil.IsZero() {
-					now := time.Now()
-					duration := m.messageTTL
-					if msg.Payload.Duration > 0 {
-						duration = msg.Payload.Duration
-					}
-					m.activeUntil = now.Add(duration)
-				}
-			}
-		}
-	case statusCleanupMsg:
-		now := msg.time
-
-		// If the active message has expired, remove it and activate the next one
-		if !m.activeUntil.IsZero() && m.activeUntil.Before(now) {
-			// Current message expired, remove it if we have one
-			if len(m.queue) > 0 {
-				m.queue = m.queue[1:]
-			}
-			m.activeUntil = time.Time{}
-		}
-
-		// If we have messages in queue but none are active, activate the first one
-		if len(m.queue) > 0 && m.activeUntil.IsZero() {
-			// Use custom duration if specified, otherwise use default
-			duration := m.messageTTL
-			if m.queue[0].Duration > 0 {
-				duration = m.queue[0].Duration
-			}
-			m.activeUntil = now.Add(duration)
-		}
-
-		return m, m.clearMessageCmd()
 	}
 	return m, nil
 }
@@ -190,75 +125,11 @@ func (m statusComponent) View() string {
 
 	blank := styles.BaseStyle().Background(t.Background()).Width(m.width).Render("")
 	return blank + "\n" + status
-
-	// Display the first status message if available
-	// var statusMessage string
-	// if len(m.queue) > 0 {
-	// 	sm := m.queue[0]
-	// 	infoStyle := styles.Padded().
-	// 		Foreground(t.Background())
-	//
-	// 	switch sm.Level {
-	// 	case "info":
-	// 		infoStyle = infoStyle.Background(t.Info())
-	// 	case "warn":
-	// 		infoStyle = infoStyle.Background(t.Warning())
-	// 	case "error":
-	// 		infoStyle = infoStyle.Background(t.Error())
-	// 	case "debug":
-	// 		infoStyle = infoStyle.Background(t.TextMuted())
-	// 	}
-	//
-	// 	// Truncate message if it's longer than available width
-	// 	msg := sm.Message
-	// 	availWidth := statusWidth - 10
-	//
-	// 	// If we have enough space, show inline
-	// 	if availWidth >= minInlineWidth {
-	// 		if len(msg) > availWidth && availWidth > 0 {
-	// 			msg = msg[:availWidth] + "..."
-	// 		}
-	// 		status += infoStyle.Width(statusWidth).Render(msg)
-	// 	} else {
-	// 		// Otherwise, prepare a full-width message to show above
-	// 		if len(msg) > m.width-10 && m.width > 10 {
-	// 			msg = msg[:m.width-10] + "..."
-	// 		}
-	// 		statusMessage = infoStyle.Width(m.width).Render(msg)
-	//
-	// 		// Add empty space in the status bar
-	// 		status += styles.Padded().
-	// 			Foreground(t.Text()).
-	// 			Background(t.BackgroundSubtle()).
-	// 			Width(statusWidth).
-	// 			Render("")
-	// 	}
-	// } else {
-	// 	status += styles.Padded().
-	// 		Foreground(t.Text()).
-	// 		Background(t.BackgroundSubtle()).
-	// 		Width(statusWidth).
-	// 		Render("")
-	// }
-
-	// status += diagnostics
-	// status += modelName
-
-	// If we have a separate status message, prepend it
-	// if statusMessage != "" {
-	// 	return statusMessage + "\n" + status
-	// } else {
-	// blank := styles.BaseStyle().Background(t.Background()).Width(m.width).Render("")
-	// return blank + "\n" + status
-	// }
 }
 
 func NewStatusCmp(app *app.App) StatusComponent {
 	statusComponent := &statusComponent{
-		app:         app,
-		queue:       []status.StatusMessage{},
-		messageTTL:  4 * time.Second,
-		activeUntil: time.Time{},
+		app: app,
 	}
 
 	return statusComponent

+ 4 - 5
packages/tui/internal/components/dialog/complete.go

@@ -7,7 +7,6 @@ import (
 	"github.com/charmbracelet/lipgloss/v2"
 	"github.com/sst/opencode/internal/components/list"
 	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/status"
 	"github.com/sst/opencode/internal/styles"
 	"github.com/sst/opencode/internal/theme"
 	"github.com/sst/opencode/internal/util"
@@ -158,7 +157,7 @@ func (c *completionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 				if query != c.query {
 					items, err := c.completionProvider.GetChildEntries(query)
 					if err != nil {
-						status.Error(err.Error())
+						// status.Error(err.Error())
 					}
 
 					c.list.SetItems(items)
@@ -189,7 +188,7 @@ func (c *completionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		} else {
 			items, err := c.completionProvider.GetChildEntries("")
 			if err != nil {
-				status.Error(err.Error())
+				// status.Error(err.Error())
 			}
 
 			c.list.SetItems(items)
@@ -246,7 +245,7 @@ func (c *completionDialogComponent) SetProvider(provider CompletionProvider) {
 		c.completionProvider = provider
 		items, err := provider.GetChildEntries("")
 		if err != nil {
-			status.Error(err.Error())
+			// status.Error(err.Error())
 		}
 		c.list.SetItems(items)
 	}
@@ -257,7 +256,7 @@ func NewCompletionDialogComponent(completionProvider CompletionProvider) Complet
 
 	items, err := completionProvider.GetChildEntries("")
 	if err != nil {
-		status.Error(err.Error())
+		// status.Error(err.Error())
 	}
 
 	li := list.NewListComponent(

+ 1 - 2
packages/tui/internal/components/dialog/theme.go

@@ -5,7 +5,6 @@ import (
 	list "github.com/sst/opencode/internal/components/list"
 	"github.com/sst/opencode/internal/components/modal"
 	"github.com/sst/opencode/internal/layout"
-	"github.com/sst/opencode/internal/status"
 	"github.com/sst/opencode/internal/styles"
 	"github.com/sst/opencode/internal/theme"
 	"github.com/sst/opencode/internal/util"
@@ -71,7 +70,7 @@ func (t *themeDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 					return t, util.CmdHandler(modal.CloseModalMsg{})
 				}
 				if err := theme.SetTheme(selectedTheme); err != nil {
-					status.Error(err.Error())
+					// status.Error(err.Error())
 					return t, nil
 				}
 				return t, tea.Sequence(

+ 0 - 113
packages/tui/internal/pubsub/broker.go

@@ -1,113 +0,0 @@
-package pubsub
-
-import (
-	"context"
-	"fmt"
-	"log/slog"
-	"sync"
-	"time"
-)
-
-const defaultChannelBufferSize = 100
-
-type Broker[T any] struct {
-	subs     map[chan Event[T]]context.CancelFunc
-	mu       sync.RWMutex
-	isClosed bool
-}
-
-func NewBroker[T any]() *Broker[T] {
-	return &Broker[T]{
-		subs: make(map[chan Event[T]]context.CancelFunc),
-	}
-}
-
-func (b *Broker[T]) Shutdown() {
-	b.mu.Lock()
-	if b.isClosed {
-		b.mu.Unlock()
-		return
-	}
-	b.isClosed = true
-
-	for ch, cancel := range b.subs {
-		cancel()
-		close(ch)
-		delete(b.subs, ch)
-	}
-	b.mu.Unlock()
-	slog.Debug("PubSub broker shut down", "type", fmt.Sprintf("%T", *new(T)))
-}
-
-func (b *Broker[T]) Subscribe(ctx context.Context) <-chan Event[T] {
-	b.mu.Lock()
-	defer b.mu.Unlock()
-
-	if b.isClosed {
-		closedCh := make(chan Event[T])
-		close(closedCh)
-		return closedCh
-	}
-
-	subCtx, subCancel := context.WithCancel(ctx)
-	subscriberChannel := make(chan Event[T], defaultChannelBufferSize)
-	b.subs[subscriberChannel] = subCancel
-
-	go func() {
-		<-subCtx.Done()
-		b.mu.Lock()
-		defer b.mu.Unlock()
-		if _, ok := b.subs[subscriberChannel]; ok {
-			close(subscriberChannel)
-			delete(b.subs, subscriberChannel)
-		}
-	}()
-
-	return subscriberChannel
-}
-
-func (b *Broker[T]) Publish(eventType EventType, payload T) {
-	b.mu.RLock()
-	defer b.mu.RUnlock()
-
-	if b.isClosed {
-		slog.Warn("Attempted to publish on a closed pubsub broker", "type", eventType, "payload_type", fmt.Sprintf("%T", payload))
-		return
-	}
-
-	event := Event[T]{Type: eventType, Payload: payload}
-
-	for ch := range b.subs {
-		// Non-blocking send with a fallback to a goroutine to prevent slow subscribers
-		// from blocking the publisher.
-		select {
-		case ch <- event:
-			// Successfully sent
-		default:
-			// Subscriber channel is full or receiver is slow.
-			// Send in a new goroutine to avoid blocking the publisher.
-			// This might lead to out-of-order delivery for this specific slow subscriber.
-			go func(sChan chan Event[T], ev Event[T]) {
-				// Re-check if broker is closed before attempting send in goroutine
-				b.mu.RLock()
-				isBrokerClosed := b.isClosed
-				b.mu.RUnlock()
-				if isBrokerClosed {
-					return
-				}
-
-				select {
-				case sChan <- ev:
-				case <-time.After(2 * time.Second): // Timeout for slow subscriber
-					slog.Warn("PubSub: Dropped event for slow subscriber after timeout", "type", ev.Type)
-				}
-			}(ch, event)
-		}
-	}
-}
-
-func (b *Broker[T]) GetSubscriberCount() int {
-	b.mu.RLock()
-	defer b.mu.RUnlock()
-	return len(b.subs)
-}

+ 0 - 24
packages/tui/internal/pubsub/events.go

@@ -1,24 +0,0 @@
-package pubsub
-
-import "context"
-
-type EventType string
-
-const (
-	EventTypeCreated EventType = "created"
-	EventTypeUpdated EventType = "updated"
-	EventTypeDeleted EventType = "deleted"
-)
-
-type Event[T any] struct {
-	Type    EventType
-	Payload T
-}
-
-type Subscriber[T any] interface {
-	Subscribe(ctx context.Context) <-chan Event[T]
-}
-
-type Publisher[T any] interface {
-	Publish(eventType EventType, payload T)
-}

+ 0 - 142
packages/tui/internal/status/status.go

@@ -1,142 +0,0 @@
-package status
-
-import (
-	"context"
-	"fmt"
-	"log/slog"
-	"sync"
-	"time"
-
-	"github.com/sst/opencode/internal/pubsub"
-)
-
-type Level string
-
-const (
-	LevelInfo  Level = "info"
-	LevelWarn  Level = "warn"
-	LevelError Level = "error"
-	LevelDebug Level = "debug"
-)
-
-type StatusMessage struct {
-	Level     Level         `json:"level"`
-	Message   string        `json:"message"`
-	Timestamp time.Time     `json:"timestamp"`
-	Critical  bool          `json:"critical"`
-	Duration  time.Duration `json:"duration"`
-}
-
-// StatusOption is a function that configures a status message
-type StatusOption func(*StatusMessage)
-
-// WithCritical marks a status message as critical, causing it to be displayed immediately
-func WithCritical(critical bool) StatusOption {
-	return func(msg *StatusMessage) {
-		msg.Critical = critical
-	}
-}
-
-// WithDuration sets a custom display duration for a status message
-func WithDuration(duration time.Duration) StatusOption {
-	return func(msg *StatusMessage) {
-		msg.Duration = duration
-	}
-}
-
-const (
-	EventStatusPublished pubsub.EventType = "status_published"
-)
-
-type Service interface {
-	pubsub.Subscriber[StatusMessage]
-
-	Info(message string, opts ...StatusOption)
-	Warn(message string, opts ...StatusOption)
-	Error(message string, opts ...StatusOption)
-	Debug(message string, opts ...StatusOption)
-}
-
-type service struct {
-	broker *pubsub.Broker[StatusMessage]
-	mu     sync.RWMutex
-}
-
-var globalStatusService *service
-
-func InitService() error {
-	if globalStatusService != nil {
-		return fmt.Errorf("status service already initialized")
-	}
-	broker := pubsub.NewBroker[StatusMessage]()
-	globalStatusService = &service{
-		broker: broker,
-	}
-	return nil
-}
-
-func GetService() Service {
-	if globalStatusService == nil {
-		panic("status service not initialized. Call status.InitService() at application startup.")
-	}
-	return globalStatusService
-}
-
-func (s *service) Info(message string, opts ...StatusOption) {
-	s.publish(LevelInfo, message, opts...)
-	slog.Info(message)
-}
-
-func (s *service) Warn(message string, opts ...StatusOption) {
-	s.publish(LevelWarn, message, opts...)
-	slog.Warn(message)
-}
-
-func (s *service) Error(message string, opts ...StatusOption) {
-	s.publish(LevelError, message, opts...)
-	slog.Error(message)
-}
-
-func (s *service) Debug(message string, opts ...StatusOption) {
-	s.publish(LevelDebug, message, opts...)
-	slog.Debug(message)
-}
-
-func (s *service) publish(level Level, messageText string, opts ...StatusOption) {
-	statusMsg := StatusMessage{
-		Level:     level,
-		Message:   messageText,
-		Timestamp: time.Now(),
-	}
-
-	// Apply all options
-	for _, opt := range opts {
-		opt(&statusMsg)
-	}
-
-	s.broker.Publish(EventStatusPublished, statusMsg)
-}
-
-func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[StatusMessage] {
-	return s.broker.Subscribe(ctx)
-}
-
-func Info(message string, opts ...StatusOption) {
-	GetService().Info(message, opts...)
-}
-
-func Warn(message string, opts ...StatusOption) {
-	GetService().Warn(message, opts...)
-}
-
-func Error(message string, opts ...StatusOption) {
-	GetService().Error(message, opts...)
-}
-
-func Debug(message string, opts ...StatusOption) {
-	GetService().Debug(message, opts...)
-}
-
-func Subscribe(ctx context.Context) <-chan pubsub.Event[StatusMessage] {
-	return GetService().Subscribe(ctx)
-}

+ 0 - 18
packages/tui/internal/tui/tui.go

@@ -224,24 +224,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 		return a, tea.Batch(cmds...)
 
-	// case pubsub.Event[permission.PermissionRequest]:
-	// 	a.showPermissions = true
-	// 	return a, a.permissions.SetPermissions(msg.Payload)
-
-	case dialog.PermissionResponseMsg:
-		// TODO: Permissions service not implemented in API yet
-		// var cmd tea.Cmd
-		// switch msg.Action {
-		// case dialog.PermissionAllow:
-		// 	a.app.Permissions.Grant(context.Background(), msg.Permission)
-		// case dialog.PermissionAllowForSession:
-		// 	a.app.Permissions.GrantPersistant(context.Background(), msg.Permission)
-		// case dialog.PermissionDeny:
-		// 	a.app.Permissions.Deny(context.Background(), msg.Permission)
-		// }
-		// a.showPermissions = false
-		return a, nil
-
 	case page.PageChangeMsg:
 		return a, a.moveToPage(msg.ID)