3939
4040 keyStyle = lipgloss .NewStyle ().
4141 Foreground (lipgloss .Color ("#7D56F4" )).Bold (true )
42+
43+ // fieldStyle for subtle labels inside boxes (calm, readable).
44+ fieldStyle = lipgloss .NewStyle ().
45+ Foreground (lipgloss .Color ("#AAAAAA" ))
46+
47+ // mutedStyle for secondary / placeholder text.
48+ mutedStyle = lipgloss .NewStyle ().
49+ Foreground (lipgloss .Color ("#666666" ))
4250)
4351
52+ // renderBox renders the given inner content inside a copy of the provided
53+ // box style (which may have Width set for consistent sizing). When termWidth
54+ // > 0 the resulting block is horizontally centered using PlaceHorizontal.
55+ // Three lightweight strategies exist in View:
56+ // - header: capped Width + Align, then optional Place for full-term centering
57+ // - boxes: adaptive safe boxW (accounting for border+pad) + Place via this helper
58+ // - footer: raw Place
59+ //
60+ // This keeps the helper tiny while avoiding overflow on narrow terminals and
61+ // m.width==0 (pre-WindowSizeMsg) initial renders.
62+ func renderBox (inner string , termWidth int , st lipgloss.Style ) string {
63+ s := st .Render (inner )
64+ if termWidth > 0 {
65+ s = lipgloss .PlaceHorizontal (termWidth , lipgloss .Center , s )
66+ }
67+ return s
68+ }
69+
4470// tickMsg is sent every second to refresh the dashboard.
4571type tickMsg time.Time
4672
@@ -123,73 +149,141 @@ func (m Model) View() string {
123149
124150 var b strings.Builder
125151
126- // Title
127- b .WriteString (titleStyle .Render (" pastelocal dashboard " ))
152+ // Full-width colored header banner (professional anchor, uses captured width).
153+ // Capped at 100 for very wide terminals; always PlaceHorizontal-centered when
154+ // m.width > 0 so it matches the geometry of the content cards and footer.
155+ title := "pastelocal dashboard"
156+ titleSt := titleStyle .Copy ()
157+ termW := m .width
158+ if termW > 0 {
159+ if termW > 100 {
160+ termW = 100
161+ }
162+ titleSt = titleSt .Width (termW ).Align (lipgloss .Center )
163+ }
164+ titleRendered := titleSt .Render (title )
165+ if m .width > 0 {
166+ titleRendered = lipgloss .PlaceHorizontal (m .width , lipgloss .Center , titleRendered )
167+ }
168+ b .WriteString (titleRendered )
128169 b .WriteString ("\n \n " )
129170
130- // Daemon status
131- statusStr := "stopped"
132- statusStyle := statusErrStyle
171+ // Consistent box width + centering for visual weight and breathing room.
172+ // targetContentWidth (72) chosen for comfortable reading on typical terminals
173+ // while degrading gracefully. The calculation ensures final rendered outer
174+ // width (content + 4 pad + 2 border) never exceeds m.width, preventing
175+ // overflow/clipping on narrow terminals or the initial m.width==0 render.
176+ boxW := 60 // safe default when m.width==0 (before first WindowSizeMsg)
177+ if m .width > 0 {
178+ // lipgloss bordered+padded box outer width ≈ boxW + 6
179+ maxOuter := m .width
180+ desired := 72
181+ boxW = desired
182+ if boxW > maxOuter - 6 {
183+ boxW = maxOuter - 6
184+ }
185+ if boxW < 20 {
186+ boxW = 20
187+ }
188+ }
189+ boxSt := boxStyle .Copy ().Width (boxW )
190+
191+ // Daemon status box (status icon + color for instant scannability).
192+ var statusStyled string
133193 if m .running {
134194 if m .healthy {
135- statusStr = "running"
136- statusStyle = statusOkStyle
195+ statusStyled = statusOkStyle .Render ("● running" )
137196 } else {
138- statusStr = "degraded"
139- statusStyle = statusWarnStyle
197+ statusStyled = statusWarnStyle .Render ("◐ degraded" )
140198 }
199+ } else {
200+ statusStyled = statusErrStyle .Render ("○ stopped" )
141201 }
142202
143- daemonBox := fmt .Sprintf (" Daemon: %s\n Port: %d (loopback)\n PID: %d\n Uptime: %s" ,
144- statusStyle .Render (statusStr ), m .port , m .pid , m .uptime )
145- b .WriteString (boxStyle .Render (daemonBox ))
203+ daemonLines := []string {
204+ fmt .Sprintf ("%s %s" , fieldStyle .Render ("Status:" ), statusStyled ),
205+ fmt .Sprintf ("%s %d (loopback)" , fieldStyle .Render ("Port:" ), m .port ),
206+ }
207+ if m .pid > 0 {
208+ daemonLines = append (daemonLines , fmt .Sprintf ("%s %d" , fieldStyle .Render ("PID:" ), m .pid ))
209+ }
210+ if m .uptime != "" {
211+ daemonLines = append (daemonLines , fmt .Sprintf ("%s %s" , fieldStyle .Render ("Uptime:" ), m .uptime ))
212+ }
213+ daemonInner := strings .Join (daemonLines , "\n " )
214+ b .WriteString (renderBox (daemonInner , m .width , boxSt ))
146215 b .WriteString ("\n " )
147216
148- // Last read
217+ // Last Read box (graceful placeholders for currently unpopulated fields).
149218 lastReadStr := "(never)"
150219 if m .lastRead != "" {
151220 lastReadStr = m .lastRead
152221 }
153- lastReadBox := fmt .Sprintf (" Last Read: %s\n Format: %s" ,
154- lastReadStr , m .lastFmt )
155- b .WriteString (boxStyle .Render (lastReadBox ))
222+ lastReadVal := lastReadStr
223+ if lastReadStr == "(never)" {
224+ lastReadVal = mutedStyle .Render ("(never)" )
225+ }
226+ fmtVal := m .lastFmt
227+ if fmtVal == "" {
228+ fmtVal = mutedStyle .Render ("—" )
229+ }
230+ lastReadInner := fmt .Sprintf ("%s %s\n %s %s" ,
231+ fieldStyle .Render ("Last Read:" ), lastReadVal ,
232+ fieldStyle .Render ("Format:" ), fmtVal )
233+ b .WriteString (renderBox (lastReadInner , m .width , boxSt ))
156234 b .WriteString ("\n " )
157235
158- // Clipboard Watch status (critical for visibility success criterion)
159- watchStr := "disabled (opt-in via [watch] enabled = true in config)"
236+ // Clipboard Watch box (two-line when change timestamp present for density;
237+ // icons + colors make enabled/disabled state pop at a glance).
238+ var watchInner string
239+ watchLabel := fieldStyle .Render ("Clipboard Watch:" )
160240 if m .watchEnabled {
161- watchStr = "enabled (detecting OS changes)"
162241 if m .lastClipboardChange != "" {
163- watchStr = "enabled (last change: " + m .lastClipboardChange + ")"
242+ watchInner = fmt .Sprintf ("%s %s\n %s %s" ,
243+ watchLabel , statusOkStyle .Render ("enabled" ), fieldStyle .Render ("Last change:" ), m .lastClipboardChange )
244+ } else {
245+ watchInner = fmt .Sprintf ("%s %s (detecting OS clipboard changes)" ,
246+ watchLabel , statusOkStyle .Render ("enabled" ))
164247 }
248+ } else {
249+ hint := mutedStyle .Render ("Hint: (set [watch] enabled = true in config)" )
250+ watchInner = fmt .Sprintf ("%s %s\n %s" ,
251+ watchLabel , statusWarnStyle .Render ("disabled" ), hint )
165252 }
166- b .WriteString (boxStyle . Render ( " Clipboard Watch: " + watchStr ))
253+ b .WriteString (renderBox ( watchInner , m . width , boxSt ))
167254 b .WriteString ("\n " )
168255
169- // Hosts
256+ // Hosts box (symbols for quick ok/unreachable scan; termius noted subtly).
170257 if len (m .hosts ) > 0 {
171258 var hostLines []string
172- hostLines = append (hostLines , " Hosts:" )
259+ hostLines = append (hostLines , fieldStyle . Render ( " Hosts:") )
173260 for _ , h := range m .hosts {
174- status := statusOkStyle .Render ("ok" )
261+ statusDisp := statusOkStyle .Render ("✓ ok" )
175262 if h .Status != "ok" {
176- status = statusErrStyle .Render (h .Status )
263+ statusDisp = statusErrStyle .Render ("✗ " + h .Status )
177264 }
178265 suffix := ""
179266 if h .Termius {
180- suffix = " (termius)"
267+ suffix = mutedStyle . Render ( " (termius)" )
181268 }
182- hostLines = append (hostLines , fmt .Sprintf (" %s %s%s" , h .Alias , status , suffix ))
269+ hostLines = append (hostLines , fmt .Sprintf (" %s %s%s" , h .Alias , statusDisp , suffix ))
183270 }
184- b .WriteString (boxStyle . Render (strings .Join (hostLines , "\n " )))
271+ b .WriteString (renderBox (strings .Join (hostLines , "\n " ), m . width , boxSt ))
185272 b .WriteString ("\n " )
186273 } else {
187- b .WriteString (boxStyle .Render (" Hosts: (none configured)" ))
274+ hostsInner := fmt .Sprintf ("%s %s" ,
275+ fieldStyle .Render ("Hosts:" ), mutedStyle .Render ("(none configured)" ))
276+ b .WriteString (renderBox (hostsInner , m .width , boxSt ))
188277 b .WriteString ("\n " )
189278 }
190279
191- // Keyboard shortcuts
192- b .WriteString (dimStyle .Render (" [q] quit " ))
280+ // Centered footer with highlighted key (calm, scannable).
281+ footer := dimStyle .Render ("[" ) + keyStyle .Render ("q" ) + dimStyle .Render ("] quit" )
282+ if m .width > 0 {
283+ footer = lipgloss .PlaceHorizontal (m .width , lipgloss .Center , footer )
284+ }
285+ b .WriteString ("\n " )
286+ b .WriteString (footer )
193287 b .WriteString ("\n " )
194288
195289 return b .String ()
@@ -206,6 +300,17 @@ func (m *Model) refresh() {
206300 m .healthy = false
207301 m .watchEnabled = false
208302 m .lastClipboardChange = ""
303+ // Rebuild hosts from config so the polished Hosts box shows trustworthy
304+ // "✗ unreachable" instead of stale prior "ok" entries (pre-existing gap
305+ // now visible due to always-rendered substantial cards).
306+ m .hosts = m .hosts [:0 ]
307+ for alias , h := range m .cfg .Hosts {
308+ m .hosts = append (m .hosts , hostStatus {
309+ Alias : alias ,
310+ Status : "unreachable" ,
311+ Termius : h .Termius ,
312+ })
313+ }
209314 return
210315 }
211316 defer resp .Body .Close ()
@@ -245,5 +350,6 @@ func (m *Model) refresh() {
245350 }
246351
247352 // Try to read the last clipboard state from the daemon.
248- // We could add a /stats endpoint for this, but for now we leave it as-is.
353+ // LastRead data is intentionally left unpopulated (no /stats endpoint yet);
354+ // View renders graceful placeholders per polish requirements and non-goals.
249355}
0 commit comments