@@ -302,16 +302,18 @@ func (m *TuiModel) renderCatalogPanel(w, h int, _ bool) string {
302302
303303 // Table header
304304 headerStyle := tableHeaderStyle
305- colNameW := w * 32 / 100
305+ colNameW := w * 28 / 100
306306 if colNameW < 12 {
307307 colNameW = 12
308308 }
309309 header := " " +
310310 padRight ("NAME" , colNameW ) + " " +
311- padRight ("VERSION" , 11 ) + " " +
312- padRight ("SOURCE" , 10 ) + " " +
313- padRight ("STATUS" , 10 ) + " " +
314- padRight ("DESCRIPTION" , w - colNameW - 38 )
311+ padRight ("VERSION" , 9 ) + " " +
312+ padRight ("SRC" , 5 ) + " " +
313+ padRight ("SYNC" , 14 ) + " " +
314+ padRight ("PUBLISHER" , 12 ) + " " +
315+ padRight ("★" , 6 ) + " " +
316+ "AGE"
315317 lines = append (lines , headerStyle .Render (header ))
316318 lines = append (lines , headerStyle .Render (" " + strings .Repeat ("─" , w - 4 )))
317319
@@ -339,29 +341,26 @@ func (m *TuiModel) renderCatalogPanel(w, h int, _ bool) string {
339341}
340342
341343func (m * TuiModel ) renderCatalogRow (item bridge.CatalogEntry , selected bool , w , nameW int ) string {
344+ // ── Badge (checkbox / radiobox) ─────────────────────────────
342345 var badge string
343346 if item .Kind == "theme" {
344- // Radiobox
345347 active := false
346348 if m .ctx != nil && m .ctx .Config .Theme != nil && * m .ctx .Config .Theme == item .Name {
347349 active = true
348350 }
349351 if m .pendingTheme != nil && * m .pendingTheme == item .Name {
350352 active = true
351353 }
352-
353354 if active {
354355 badge = accentStyle .Render ("◉" )
355356 } else {
356357 badge = subtleStyle .Render ("○" )
357358 }
358359 } else {
359- // Checkbox
360360 active := item .Installed
361361 if val , exists := m .pendingPackages [item .Name ]; exists {
362362 active = val
363363 }
364-
365364 if active {
366365 badge = accentStyle .Render ("☒" )
367366 } else {
@@ -372,11 +371,13 @@ func (m *TuiModel) renderCatalogRow(item bridge.CatalogEntry, selected bool, w,
372371 shortName := item .ShortName
373372 name := badge + " " + shortName
374373
374+ // ── VERSION ─────────────────────────────────────────────────
375375 version := "—"
376376 if item .Version != nil {
377377 version = * item .Version
378378 }
379379
380+ // ── SRC (npm / git / dev) ────────────────────────────────────
380381 source := "npm"
381382 if item .LocalSource {
382383 if m .localGitDirs [item .ShortName ] {
@@ -386,45 +387,53 @@ func (m *TuiModel) renderCatalogRow(item bridge.CatalogEntry, selected bool, w,
386387 }
387388 }
388389
389- var status string
390- pendingVal , hasPending := m .pendingPackages [item .Name ]
391- pendingThemeVal := m .pendingTheme
392-
393- if item .Installed {
394- if hasPending && ! pendingVal {
395- status = warnStyle .Render ("remove*" )
396- } else {
397- status = accentStyle .Render ("installed" )
390+ // ── SYNC column: git changes + update indicators ─────────────
391+ var syncParts []string
392+ if gitStat , ok := m .gitChangesMap [item .ShortName ]; ok {
393+ if gitStat .Added > 0 {
394+ syncParts = append (syncParts , accentStyle .Render (fmt .Sprintf ("+%d" , gitStat .Added )))
398395 }
399- } else {
400- if hasPending && pendingVal {
401- status = cyanStyle .Render ("install*" )
402- } else if item .Kind == "theme" && pendingThemeVal != nil && * pendingThemeVal == item .Name {
403- status = cyanStyle .Render ("active*" )
404- } else {
405- status = subtleStyle .Render ("—" )
396+ if gitStat .Modified > 0 {
397+ syncParts = append (syncParts , warnStyle .Render (fmt .Sprintf ("~%d" , gitStat .Modified )))
398+ }
399+ if gitStat .Deleted > 0 {
400+ syncParts = append (syncParts , errStyle .Render (fmt .Sprintf ("-%d" , gitStat .Deleted )))
406401 }
407402 }
408-
409- // Git behind indicator
410403 if upInfo , ok := m .updatesMap [item .ShortName ]; ok {
411404 if upInfo .LocalGit && upInfo .BehindCount > 0 {
412- status += warnStyle .Render (fmt .Sprintf (" (-%d) " , upInfo .BehindCount ))
405+ syncParts = append ( syncParts , cyanStyle .Render (fmt .Sprintf ("↓%d " , upInfo .BehindCount ) ))
413406 } else if upInfo .Npm {
414- status += warnStyle .Render (" (upd)" )
407+ syncParts = append ( syncParts , accentStyle .Render ("↑" ) )
415408 }
416409 }
410+ sync := subtleStyle .Render ("—" )
411+ if len (syncParts ) > 0 {
412+ sync = strings .Join (syncParts , " " )
413+ }
417414
418- // Local edits indicator (Git dirty status)
419- if gitStat , ok := m .gitChangesMap [item .ShortName ]; ok {
420- total := gitStat .Added + gitStat .Modified + gitStat .Deleted
421- if total > 0 {
422- status += dirtyIndicatorStyle .Render ("*" )
423- }
415+ // ── PUBLISHER ────────────────────────────────────────────────
416+ pub := item .Org
417+ if pub == "" || pub == "workspace" {
418+ pub = "owdproject"
424419 }
420+ publisher := truncate (pub , 12 )
425421
426- desc := item .Description
422+ // ── STARS ────────────────────────────────────────────────────
423+ stars := subtleStyle .Render ("—" )
424+ if item .Stars > 0 {
425+ stars = warnStyle .Render (fmt .Sprintf ("★ %d" , item .Stars ))
426+ }
427+
428+ // ── AGE (last push) ──────────────────────────────────────────
429+ age := subtleStyle .Render ("—" )
430+ if item .PushedAt != nil {
431+ age = mutedStyle .Render (formatCatalogAge (item .PushedAt ))
432+ } else if item .UpdatedAt != nil {
433+ age = mutedStyle .Render (formatCatalogAge (item .UpdatedAt ))
434+ }
427435
436+ // ── Assemble row ─────────────────────────────────────────────
428437 var nameCol string
429438 if selected {
430439 nameCol = selectedRowStyle .Render (padRight (name , nameW ))
@@ -433,10 +442,12 @@ func (m *TuiModel) renderCatalogRow(item bridge.CatalogEntry, selected bool, w,
433442 }
434443
435444 row := " " + nameCol + " " +
436- padRight (version , 11 ) + " " +
437- padRight (source , 10 ) + " " +
438- padRight (status , 18 ) + " " +
439- padRight (desc , w - nameW - 46 )
445+ padRight (version , 9 ) + " " +
446+ padRight (source , 5 ) + " " +
447+ padRight (sync , 14 ) + " " +
448+ padRight (publisher , 12 ) + " " +
449+ padRight (stars , 6 ) + " " +
450+ age
440451
441452 if selected {
442453 return selectedRowBgStyle .Render (row )
0 commit comments