feat(chat): polish UI text; pre-connect to show welcome; add server logs

Client/main:
- Colorize display: hide raw IDs in rendered messages
  - Chat lines now “Name: message” (ID omitted)
  - [join]/[leave] now “[join] Name” / “[leave] Name”
  - [rename] now “[rename] OldName -> NewName”
  - Welcome now “Welcome Name”
- Init: if already connected, start reading immediately (textarea.Blink + readLineCmd)
- Pre-connect before TUI starts:
  - Dial with 3s timeout, read up to two initial lines with 1s deadline
  - Seed model with pre-read messages and existing conn

Server:
- IDs now lowercase hex; default username uses ID as-is
- Use strings.CutPrefix for “/name ” parsing
- Add structured logs for join/rename/leave with user, id, and remote addr

UI:
- Simplify chat line format for readability; keep color-coding based on ID-derived color
This commit is contained in:
Syahdan 2025-10-15 11:19:20 +07:00
parent 52d4602e33
commit 4332c2e3a5
2 changed files with 36 additions and 5 deletions

38
main.go
View File

@ -8,6 +8,7 @@ import (
"io"
"net"
"regexp"
"strings"
"time"
@ -25,7 +26,7 @@ func colorizeLine(s string) string {
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
name := nameStyle.Render(m[1])
rest := strings.TrimSpace(m[3])
return fmt.Sprintf("%s (%s): %s", name, id, rest)
return fmt.Sprintf("%s: %s", name, rest)
}
reJoinLeave := regexp.MustCompile(`^\[(join|leave)\] (.+?) \(([0-9a-fA-F]{6})\)$`)
@ -33,7 +34,7 @@ func colorizeLine(s string) string {
id := strings.ToLower(m[3])
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
uname := nameStyle.Render(m[2])
return fmt.Sprintf("[%s] %s (%s)", m[1], uname, id)
return fmt.Sprintf("[%s] %s", m[1], uname)
}
reRename := regexp.MustCompile(`^\[rename\] (.+?) \(([0-9a-fA-F]{6})\) -> (.+)$`)
@ -42,7 +43,7 @@ func colorizeLine(s string) string {
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
oldN := nameStyle.Render(m[1])
newN := nameStyle.Render(m[3])
return fmt.Sprintf("[rename] %s (%s) -> %s", oldN, id, newN)
return fmt.Sprintf("[rename] %s -> %s", oldN, newN)
}
reWelcome := regexp.MustCompile(`^Welcome (.+?) \(([0-9a-fA-F]{6})\)$`)
@ -50,7 +51,7 @@ func colorizeLine(s string) string {
id := strings.ToLower(m[2])
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
uname := nameStyle.Render(m[1])
return fmt.Sprintf("Welcome %s (%s)", uname, id)
return fmt.Sprintf("Welcome %s", uname)
}
return s
@ -93,6 +94,9 @@ func initialModel(serverAddr string) model {
}
func (m model) Init() tea.Cmd {
if m.conn != nil {
return tea.Batch(textarea.Blink, readLineCmd(m.conn))
}
return tea.Batch(textarea.Blink, connectCmd(m.server))
}
@ -248,7 +252,31 @@ func main() {
time.Sleep(200 * time.Millisecond)
p := tea.NewProgram(initialModel(host), tea.WithAltScreen(), tea.WithMouseCellMotion())
// Pre-connect and read initial welcome/instruction before starting UI
var preConn net.Conn
var preMsgs []string
if conn, err := net.DialTimeout("tcp", host, 3*time.Second); err == nil {
preConn = conn
// Read up to two initial lines with a short deadline
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
r := bufio.NewReader(conn)
for i := 0; i < 2; i++ {
line, err := r.ReadString('\n')
if err != nil {
break
}
preMsgs = append(preMsgs, strings.TrimRight(line, "\r\n"))
}
_ = conn.SetReadDeadline(time.Time{})
}
m := initialModel(host)
if preConn != nil {
m.conn = preConn
m.messages = append(m.messages, preMsgs...)
}
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
if _, err := p.Run(); err != nil {
fmt.Println("Error:", err)
}

View File

@ -116,6 +116,7 @@ func handleConn(h *Hub, c net.Conn) {
fmt.Fprintf(c, "Welcome %s (%s)\n", username, id)
fmt.Fprintln(c, "Use /name <username> to set your username. Allowed: [A-Za-z0-9_.-] (spaces become _)")
// Announce join to others, exclude self
log.Printf("join: user=%s id=%s remote=%s", username, id, c.RemoteAddr())
h.msgCh <- broadcast{text: fmt.Sprintf("[join] %s (%s)", username, id), exclude: c}
scanner := bufio.NewScanner(c)
@ -146,6 +147,7 @@ func handleConn(h *Hub, c net.Conn) {
old := username
username = newName
// Broadcast rename to everyone (including the renamer)
log.Printf("rename: user=%s id=%s remote=%s", username, id, c.RemoteAddr())
h.msgCh <- broadcast{text: fmt.Sprintf("[rename] %s (%s) -> %s", old, id, username)}
continue
}
@ -158,6 +160,7 @@ func handleConn(h *Hub, c net.Conn) {
}
// Single, consistent leave announcement
log.Printf("leave: user=%s id=%s remote=%s", username, id, c.RemoteAddr())
h.msgCh <- broadcast{text: fmt.Sprintf("[leave] %s (%s)", username, id)}
}