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:
parent
52d4602e33
commit
4332c2e3a5
38
main.go
38
main.go
@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ func colorizeLine(s string) string {
|
|||||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||||
name := nameStyle.Render(m[1])
|
name := nameStyle.Render(m[1])
|
||||||
rest := strings.TrimSpace(m[3])
|
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})\)$`)
|
reJoinLeave := regexp.MustCompile(`^\[(join|leave)\] (.+?) \(([0-9a-fA-F]{6})\)$`)
|
||||||
@ -33,7 +34,7 @@ func colorizeLine(s string) string {
|
|||||||
id := strings.ToLower(m[3])
|
id := strings.ToLower(m[3])
|
||||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||||
uname := nameStyle.Render(m[2])
|
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})\) -> (.+)$`)
|
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)
|
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||||
oldN := nameStyle.Render(m[1])
|
oldN := nameStyle.Render(m[1])
|
||||||
newN := nameStyle.Render(m[3])
|
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})\)$`)
|
reWelcome := regexp.MustCompile(`^Welcome (.+?) \(([0-9a-fA-F]{6})\)$`)
|
||||||
@ -50,7 +51,7 @@ func colorizeLine(s string) string {
|
|||||||
id := strings.ToLower(m[2])
|
id := strings.ToLower(m[2])
|
||||||
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#" + id)).Bold(true)
|
||||||
uname := nameStyle.Render(m[1])
|
uname := nameStyle.Render(m[1])
|
||||||
return fmt.Sprintf("Welcome %s (%s)", uname, id)
|
return fmt.Sprintf("Welcome %s", uname)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@ -93,6 +94,9 @@ func initialModel(serverAddr string) model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
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))
|
return tea.Batch(textarea.Blink, connectCmd(m.server))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,7 +252,31 @@ func main() {
|
|||||||
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
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 {
|
if _, err := p.Run(); err != nil {
|
||||||
fmt.Println("Error:", err)
|
fmt.Println("Error:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,6 +116,7 @@ func handleConn(h *Hub, c net.Conn) {
|
|||||||
fmt.Fprintf(c, "Welcome %s (%s)\n", username, id)
|
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 _)")
|
fmt.Fprintln(c, "Use /name <username> to set your username. Allowed: [A-Za-z0-9_.-] (spaces become _)")
|
||||||
// Announce join to others, exclude self
|
// 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}
|
h.msgCh <- broadcast{text: fmt.Sprintf("[join] %s (%s)", username, id), exclude: c}
|
||||||
|
|
||||||
scanner := bufio.NewScanner(c)
|
scanner := bufio.NewScanner(c)
|
||||||
@ -146,6 +147,7 @@ func handleConn(h *Hub, c net.Conn) {
|
|||||||
old := username
|
old := username
|
||||||
username = newName
|
username = newName
|
||||||
// Broadcast rename to everyone (including the renamer)
|
// 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)}
|
h.msgCh <- broadcast{text: fmt.Sprintf("[rename] %s (%s) -> %s", old, id, username)}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -158,6 +160,7 @@ func handleConn(h *Hub, c net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Single, consistent leave announcement
|
// 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)}
|
h.msgCh <- broadcast{text: fmt.Sprintf("[leave] %s (%s)", username, id)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user