Skip to content

Padding background color not applied #456

@maxplumley

Description

@maxplumley

Describe the bug

Hi folks, really loving building out a TUI with your libraries, thanks for providing them <3. I've just noticed a difference in how padding background is rendered when serving a TUI locally as opposed to serving from a docker container. I'm pretty new to TUIs, Go and Wish/Bubble Tea/Lip Gloss so could be an issue on my end, looking for guidance either way.

Setup

Please complete the following information along with version numbers, if applicable.

  • OS [e.g. Ubuntu, macOS] - macOS (apple silicon)
  • Shell [e.g. zsh, fish] - zsh
  • Terminal Emulator [e.g. kitty, iterm] - iterm
  • Terminal Multiplexer [e.g. tmux] - NA
  • Go version: 1.24.2
>> locale
LANG="en_AU.UTF-8"
LC_COLLATE="en_AU.UTF-8"
LC_CTYPE="en_AU.UTF-8"
LC_MESSAGES="en_AU.UTF-8"
LC_MONETARY="en_AU.UTF-8"
LC_NUMERIC="en_AU.UTF-8"
LC_TIME="en_AU.UTF-8"
LC_ALL=

To Reproduce

Steps to reproduce the behavior:

  1. local

run application

go run .

connect to TUI

ssh localhost -p 8080

notice that the background color is set for padding as expected, see screenshots below

  1. docker

build and run container

docker build -t wish-padding-test:0.0.1 .
docker run -p "8080:8080" -e "PORT=8080" -e "HOST=0.0.0.0" wish-padding-test:0.0.1

connect to TUI

ssh localhost -p 8080

notice that the background color is not applied to padding, see screenshots below

Source Code

main.go

package main

import (
	"context"
	"errors"
	"net"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	tea "github.com/charmbracelet/bubbletea"
	gloss "github.com/charmbracelet/lipgloss"
	"github.com/charmbracelet/log"
	"github.com/charmbracelet/ssh"
	"github.com/charmbracelet/wish"
	"github.com/charmbracelet/wish/activeterm"
	"github.com/charmbracelet/wish/bubbletea"
	"github.com/charmbracelet/wish/logging"
	"github.com/muesli/termenv"
)

var (
	blue = gloss.Color("#6ea3ff")
	pink = gloss.Color("#ff83fc")
)

type style struct {
	background gloss.Style
	base       gloss.Style
}

type model struct {
	width  int
	height int
	style  style
	term   ssh.Pty
}

func initModel(theme theme, renderer *gloss.Renderer, term ssh.Pty) model {
	background := renderer.NewStyle().
		MarginBackground(blue).
		Background(blue)

	base := renderer.NewStyle().
		Inherit(background).
		Foreground(pink)

	return model{
		width:  0,
		height: 0,
		style: style{
			background: background,
			base:       base,
		},
		term: term,
	}
}

func (m model) Init() tea.Cmd {
	return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "q":
			return m, tea.Quit
		}
	case tea.WindowSizeMsg:
		m.height = msg.Height
		m.width = msg.Width
	}
	return m, nil
}

func (m model) View() string {
	document := strings.Builder{}
	document.WriteString(gloss.PlaceHorizontal(20, gloss.Center, m.style.base.Render("hello"), gloss.WithWhitespaceBackground(blue), gloss.WithWhitespaceChars("/")))
	return m.style.background.Render(gloss.Place(m.width, m.height, gloss.Center, gloss.Top, document.String(), gloss.WithWhitespaceBackground(blue), gloss.WithWhitespaceChars(".")))
}

type theme int

const (
	Dark theme = iota
	Light
)

func teaHandler(session ssh.Session) (tea.Model, []tea.ProgramOption) {
	// should never fail as we are using the activeTerm middleware
	pty, _, _ := session.Pty()

	renderer := bubbletea.MakeRenderer(session)
	// set the TrueColor profile so that we get some pretty colors
	renderer.SetColorProfile(termenv.TrueColor)

	theme := Light
	if renderer.HasDarkBackground() {
		theme = Dark
	}

	return initModel(theme, renderer, pty), []tea.ProgramOption{
		tea.WithAltScreen(),
		tea.WithInput(pty.Slave),
		tea.WithOutput(pty.Slave)}
}

func main() {
	host := os.Getenv("HOST")
	if host == "" {
		host = "localhost"
	}

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	s, err := wish.NewServer(
		wish.WithAddress(net.JoinHostPort(host, port)),
		wish.WithHostKeyPath(".ssh/id_ed25519"),
		wish.WithMiddleware(
			bubbletea.Middleware(teaHandler),
			activeterm.Middleware(), // Bubble Tea apps usually require a PTY.
			logging.Middleware(),
		),
	)
	if err != nil {
		log.Error("Could not start server", "error", err)
	}

	done := make(chan os.Signal, 1)
	signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
	log.Info("Starting SSH server", "host", host, "port", port)
	go func() {
		if err = s.ListenAndServe(); err != nil && !errors.Is(err, ssh.ErrServerClosed) {
			log.Error("Could not start server", "error", err)
			done <- nil
		}
	}()

	<-done
	log.Info("Stopping SSH server")
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer func() { cancel() }()
	if err := s.Shutdown(ctx); err != nil && !errors.Is(err, ssh.ErrServerClosed) {
		log.Error("Could not stop server", "error", err)
	}
}

Dockerfile

FROM golang:1.24.2

WORKDIR /app

COPY ./go.mod ./go.sum ./

RUN go mod download

COPY ./*.go ./

RUN CGO_ENABLED=0 GOOS=linux go build -o /under-test

ENV PORT=8080
ENV HOST=0.0.0.0
EXPOSE ${PORT}

CMD ["/under-test"]

minimal reproducible project

Expected behavior

Padding background should be consistent between local and docker deployments

Screenshots

local

Image

docker (padding background not applied)

Image

Additional context

NA

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions