Fix TUI startup and test script bugs

- TUI: replace per-call bufio.Scanner with a goroutine+channel reader
  (lineReader) so a single scanner lives for the connection lifetime;
  previous pattern silently dropped messages due to scanner read-ahead
- test-tui.sh / test-network.sh: add `return 0` to wait_port — when the
  port opens and the while condition becomes false, bash returned the
  condition's exit code (1) to the caller, tripping set -e immediately
- .gitignore: use /tui (root-only) instead of tui to avoid ignoring cmd/tui/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-21 18:46:40 +02:00
parent 714ac15816
commit 5a0bcee8c6
4 changed files with 47 additions and 24 deletions

View File

@@ -42,11 +42,45 @@ const sideW = 22 // total width of each sidebar box (inner = sideW-2)
// ── tea messages ──────────────────────────────────────────────────────────────
type connectedMsg struct{ conn net.Conn }
type connectedMsg struct {
conn net.Conn
reader *lineReader
}
type ipcLineMsg struct{ line []byte }
type connectErrMsg struct{ err error }
type readErrMsg struct{ err error }
// lineReader pumps a TCP connection through a channel so a single bufio.Scanner
// is alive for the lifetime of the connection (avoids read-ahead data loss when
// a new scanner is created on each call).
type lineReader struct {
ch chan []byte
}
func newLineReader(conn net.Conn) *lineReader {
lr := &lineReader{ch: make(chan []byte, 64)}
go func() {
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
b := make([]byte, len(scanner.Bytes()))
copy(b, scanner.Bytes())
lr.ch <- b
}
close(lr.ch)
}()
return lr
}
func (lr *lineReader) next() tea.Cmd {
return func() tea.Msg {
b, ok := <-lr.ch
if !ok {
return readErrMsg{fmt.Errorf("daemon closed connection")}
}
return ipcLineMsg{b}
}
}
// ── model ─────────────────────────────────────────────────────────────────────
type entry struct {
@@ -62,8 +96,9 @@ type model struct {
width, height int
conn net.Conn
enc *json.Encoder
conn net.Conn
enc *json.Encoder
reader *lineReader
localID proto.PeerID
localAlias string
@@ -112,22 +147,7 @@ func connectCmd(port int) tea.Cmd {
if err != nil {
return connectErrMsg{err}
}
return connectedMsg{conn}
}
}
func listenCmd(conn net.Conn) tea.Cmd {
return func() tea.Msg {
scanner := bufio.NewScanner(conn)
if scanner.Scan() {
b := make([]byte, len(scanner.Bytes()))
copy(b, scanner.Bytes())
return ipcLineMsg{b}
}
if err := scanner.Err(); err != nil {
return readErrMsg{err}
}
return readErrMsg{fmt.Errorf("daemon closed connection")}
return connectedMsg{conn: conn, reader: newLineReader(conn)}
}
}
@@ -156,11 +176,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case connectedMsg:
m.conn = msg.conn
m.enc = json.NewEncoder(msg.conn)
m.reader = msg.reader
m.status = "joining " + m.networkName + "…"
cmds = append(cmds,
sendIPC(m.enc, proto.IpcMessage{Type: proto.CmdJoinNetwork, NetworkName: m.networkName}),
sendIPC(m.enc, proto.IpcMessage{Type: proto.CmdGetState}),
listenCmd(m.conn),
m.reader.next(),
)
case ipcLineMsg:
@@ -168,7 +189,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if json.Unmarshal(msg.line, &evt) == nil {
m = m.applyEvent(evt)
}
cmds = append(cmds, listenCmd(m.conn))
cmds = append(cmds, m.reader.next())
case readErrMsg:
m.errMsg = "IPC disconnected: " + msg.err.Error()