11import { Command } from "@cliffy/command" ;
2- import { bold , dim , green , red , stripAnsiCode } from "@std/fmt/colors" ;
2+ import {
3+ bold ,
4+ cyan ,
5+ dim ,
6+ green ,
7+ red ,
8+ stripAnsiCode ,
9+ yellow ,
10+ } from "@std/fmt/colors" ;
311import { Sandbox } from "@deno/sandbox" ;
412
513import { formatDuration , renderTemporalTimestamp } from "../util.ts" ;
@@ -108,8 +116,8 @@ function renderScreen(state: DashboardState): string {
108116 const displayList = sortSandboxes ( filtered , state . sortBy , state . sortAsc ) ;
109117
110118 // Header
111- const title = bold ( " Sandbox Dashboard" ) ;
112- const orgLabel = dim ( `Org: ${ state . org } ` ) ;
119+ const title = bold ( cyan ( " Sandbox Dashboard" ) ) ;
120+ const orgLabel = yellow ( `Org: ${ state . org } ` ) ;
113121 const headerPadding = columns - stripAnsiCode ( title ) . length -
114122 stripAnsiCode ( orgLabel ) . length ;
115123 lines . push ( title + " " . repeat ( Math . max ( 1 , headerPadding ) ) + orgLabel ) ;
@@ -138,7 +146,7 @@ function renderScreen(state: DashboardState): string {
138146 stripAnsiCode ( timeStr ) . length ;
139147 lines . push ( summary + " " . repeat ( Math . max ( 1 , summaryPadding ) ) + timeStr ) ;
140148
141- lines . push ( "" ) ;
149+ lines . push ( dim ( "─" . repeat ( columns ) ) ) ;
142150
143151 // How many rows are available for list items (sandboxes or orgs)?
144152 // We subtract: header (3 lines above), table/org header (1 line), footer (2 lines).
@@ -147,20 +155,21 @@ function renderScreen(state: DashboardState): string {
147155
148156 if ( state . mode === "org" ) {
149157 // Org picker — replaces the sandbox table when choosing an org
158+ lines . push ( bold ( cyan ( " Select Organization" ) ) ) ;
150159 lines . push ( dim ( " " + "NAME" . padEnd ( 30 ) + " " + "SLUG" ) ) ;
151160
152161 const { start, end } = getVisibleRange (
153162 state . orgs . length ,
154163 state . orgSelectedIndex ,
155- maxVisibleRows ,
164+ maxVisibleRows - 1 ,
156165 ) ;
157166
158167 for ( let i = start ; i < end ; i ++ ) {
159168 const org = state . orgs [ i ] ;
160169 const isHighlighted = i === state . orgSelectedIndex ;
161170 const isActive = org . slug === state . org ;
162171 const marker = isHighlighted ? ">" : " " ;
163- const activeMarker = isActive ? " *" : "" ;
172+ const activeMarker = isActive ? " " + green ( "●" ) : "" ;
164173
165174 const row = ` ${ marker } ${ org . name . padEnd ( 30 ) } ${
166175 dim ( org . slug )
@@ -174,9 +183,11 @@ function renderScreen(state: DashboardState): string {
174183 }
175184
176185 if ( state . orgs . length > maxVisibleRows ) {
177- lines . push (
178- dim ( ` [${ start + 1 } –${ end } of ${ state . orgs . length } ]` ) ,
179- ) ;
186+ const parts : string [ ] = [ ] ;
187+ if ( start > 0 ) parts . push ( dim ( "▲ more above" ) ) ;
188+ parts . push ( yellow ( `[${ start + 1 } –${ end } of ${ state . orgs . length } ]` ) ) ;
189+ if ( end < state . orgs . length ) parts . push ( dim ( "▼ more below" ) ) ;
190+ lines . push ( " " + parts . join ( " " ) ) ;
180191 }
181192 } else {
182193 // Normal sandbox table
@@ -233,8 +244,8 @@ function renderScreen(state: DashboardState): string {
233244 const marker = isSelected ? ">" : " " ;
234245 const region = sandbox . cluster_hostname . split ( "." ) [ 0 ] ;
235246 const statusText = sandbox . status === "running"
236- ? green ( sandbox . status )
237- : red ( sandbox . status ) ;
247+ ? green ( "● running" )
248+ : red ( "○ stopped" ) ;
238249 const uptime = formatDuration ( duration ) ;
239250 const created = renderTemporalTimestamp (
240251 new Date ( sandbox . created_at ) . toISOString ( ) ,
@@ -260,11 +271,14 @@ function renderScreen(state: DashboardState): string {
260271 }
261272 }
262273
263- // Show a scroll indicator when the list doesn't fit on one screen
274+ // Show scroll indicators when the list doesn't fit on one screen.
275+ // ▲/▼ arrows only appear when there's content in that direction.
264276 if ( displayList . length > maxVisibleRows ) {
265- lines . push (
266- dim ( ` [${ start + 1 } –${ end } of ${ displayList . length } ]` ) ,
267- ) ;
277+ const parts : string [ ] = [ ] ;
278+ if ( start > 0 ) parts . push ( dim ( "▲ more above" ) ) ;
279+ parts . push ( yellow ( `[${ start + 1 } –${ end } of ${ displayList . length } ]` ) ) ;
280+ if ( end < displayList . length ) parts . push ( dim ( "▼ more below" ) ) ;
281+ lines . push ( " " + parts . join ( " " ) ) ;
268282 }
269283 }
270284 }
@@ -288,20 +302,29 @@ function renderScreen(state: DashboardState): string {
288302 bold ( " Select org: " ) + dim ( "↑/↓ Navigate Enter Select Esc Cancel" ) ,
289303 ) ;
290304 } else if ( state . error ) {
291- lines . push ( red ( ` Error: ${ state . error } ` ) ) ;
305+ lines . push ( red ( ` ✗ Error: ${ state . error } ` ) ) ;
292306 } else if ( state . statusMessage ) {
293- lines . push ( green ( ` ${ state . statusMessage } ` ) ) ;
307+ lines . push ( green ( ` ✓ ${ state . statusMessage } ` ) ) ;
294308 } else if ( state . loading ) {
295309 lines . push ( dim ( " Refreshing..." ) ) ;
296310 } else {
297311 lines . push ( "" ) ;
298312 }
299313
300314 const shortcuts = state . mode === "org"
301- ? dim ( " * = active org" )
302- : dim (
303- " ↑/↓ Navigate s SSH k Kill e Extend c Copy ID f Filter o/O Sort t Org r Refresh q Quit" ,
304- ) ;
315+ ? " " + green ( "●" ) + dim ( " = active org" )
316+ : " " + [
317+ bold ( "↑/↓" ) + dim ( " Navigate" ) ,
318+ bold ( "s" ) + dim ( " SSH" ) ,
319+ bold ( "k" ) + dim ( " Kill" ) ,
320+ bold ( "e" ) + dim ( " Extend" ) ,
321+ bold ( "c" ) + dim ( " Copy ID" ) ,
322+ bold ( "f" ) + dim ( " Filter" ) ,
323+ bold ( "o/O" ) + dim ( " Sort" ) ,
324+ bold ( "t" ) + dim ( " Org" ) ,
325+ bold ( "r" ) + dim ( " Refresh" ) ,
326+ bold ( "q" ) + dim ( " Quit" ) ,
327+ ] . join ( " " ) ;
305328 lines . push ( shortcuts ) ;
306329
307330 return CLEAR_SCREEN + CURSOR_HOME + lines . join ( "\n" ) ;
0 commit comments