fix(ui): polish session manager shortcuts, layout, and display
- commit
- 3fb5902a227988b8313f09f8271518479ae78f31
- author
- brice · · 2025-12-25 07:33 UTC
- parent
- 85924a1
- signature
- unsigned
2 files · +80 -36
M internal/ui/component/session_manager.go +61 -28
| @@ -3,6 +3,7 @@ package component | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "strings" | |
| 6 | 7 | "sync" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/dumber/internal/application/port" | |
| @@ -348,7 +349,7 @@ func (sm *SessionManager) initList() error { | |||
| 348 | 349 | } | |
| 349 | 350 | ||
| 350 | 351 | func (sm *SessionManager) initFooter() error { | |
| 351 | - | footerText := "↑↓/jk navigate Tab expand Enter open x delete Esc close" | |
| 352 | + | footerText := "↑↓/jk navigate Enter expand r restore x delete Esc close" | |
| 352 | 353 | sm.footerLabel = gtk.NewLabel(&footerText) | |
| 353 | 354 | if sm.footerLabel == nil { | |
| 354 | 355 | return errNilWidget("sessionFooterLabel") | |
| @@ -387,15 +388,17 @@ func (sm *SessionManager) handleKeyPress(keyval uint, _ gdk.ModifierType) bool { | |||
| 387 | 388 | sm.selectNext() | |
| 388 | 389 | return true | |
| 389 | 390 | ||
| 390 | - | case uint(gdk.KEY_Tab), uint(gdk.KEY_space): | |
| 391 | + | case uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter), uint(gdk.KEY_space): | |
| 392 | + | // Enter/Space expands/collapses to preview session contents | |
| 391 | 393 | sm.toggleExpanded() | |
| 392 | 394 | return true | |
| 393 | 395 | ||
| 394 | - | case uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter): | |
| 396 | + | case uint(gdk.KEY_r), uint(gdk.KEY_o): | |
| 397 | + | // r/o restores the selected session | |
| 395 | 398 | sm.openSelected() | |
| 396 | 399 | return true | |
| 397 | 400 | ||
| 398 | - | case uint(gdk.KEY_x), uint(gdk.KEY_Delete): | |
| 401 | + | case uint(gdk.KEY_x), uint(gdk.KEY_d), uint(gdk.KEY_Delete): | |
| 399 | 402 | sm.deleteSelected() | |
| 400 | 403 | return true | |
| 401 | 404 | } | |
| @@ -539,14 +542,15 @@ func (sm *SessionManager) resizeForContent(sessionRowCount, dividerCount, treeRo | |||
| 539 | 542 | return | |
| 540 | 543 | } | |
| 541 | 544 | ||
| 542 | - | // Calculate max visible height (6 session rows max visible, accounting for divider) | |
| 543 | - | maxVisibleHeight := sessionMaxVisibleRows*sessionRowHeight + sessionDividerRowHeight | |
| 544 | - | ||
| 545 | 545 | // Calculate actual content height | |
| 546 | 546 | contentHeight := sessionRowCount*sessionRowHeight + | |
| 547 | 547 | dividerCount*sessionDividerRowHeight + | |
| 548 | 548 | treeRowCount*sessionTreeRowHeight | |
| 549 | 549 | ||
| 550 | + | // Calculate max visible height - limit to prevent oversized modal | |
| 551 | + | // Allow up to sessionMaxVisibleRows session rows plus one divider | |
| 552 | + | maxVisibleHeight := sessionMaxVisibleRows*sessionRowHeight + sessionDividerRowHeight | |
| 553 | + | ||
| 550 | 554 | // Cap at max visible height - rest will be scrollable | |
| 551 | 555 | if contentHeight > maxVisibleHeight { | |
| 552 | 556 | contentHeight = maxVisibleHeight | |
| @@ -557,10 +561,12 @@ func (sm *SessionManager) resizeForContent(sessionRowCount, dividerCount, treeRo | |||
| 557 | 561 | contentHeight = sessionRowHeight | |
| 558 | 562 | } | |
| 559 | 563 | ||
| 560 | - | // Schedule resize | |
| 564 | + | // Schedule resize on main thread | |
| 565 | + | // Use the same pattern as omnibox: reset min to -1 first to avoid GTK assertion | |
| 561 | 566 | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| 562 | - | sm.scrolledWindow.SetMinContentHeight(contentHeight) | |
| 567 | + | sm.scrolledWindow.SetMinContentHeight(-1) | |
| 563 | 568 | sm.scrolledWindow.SetMaxContentHeight(contentHeight) | |
| 569 | + | sm.scrolledWindow.SetMinContentHeight(contentHeight) | |
| 564 | 570 | sm.outerBox.QueueResize() | |
| 565 | 571 | return false | |
| 566 | 572 | }) | |
| @@ -668,9 +674,9 @@ func (sm *SessionManager) createTreeRow(text, nodeType string, hasSibling bool, | |||
| 668 | 674 | ||
| 669 | 675 | // Build indent and branch prefix | |
| 670 | 676 | var prefix string | |
| 671 | - | indent := " " // Base indent to align with session row content | |
| 677 | + | indent := " " // Base indent (minimal, tree branches provide visual alignment) | |
| 672 | 678 | for i := 0; i < depth; i++ { | |
| 673 | - | indent += " " // 4 spaces per depth level | |
| 679 | + | indent += " " // 3 spaces per depth level | |
| 674 | 680 | } | |
| 675 | 681 | ||
| 676 | 682 | // Branch character | |
| @@ -753,13 +759,13 @@ func (sm *SessionManager) addStatusIndicator(hbox *gtk.Box, info entity.SessionI | |||
| 753 | 759 | var statusText string | |
| 754 | 760 | switch { | |
| 755 | 761 | case info.IsCurrent: | |
| 756 | - | statusText = "●" | |
| 762 | + | statusText = "●" // Filled dot for current | |
| 757 | 763 | hbox.AddCssClass("session-current") | |
| 758 | 764 | case info.IsActive: | |
| 759 | - | statusText = "○" | |
| 765 | + | statusText = "○" // Empty dot for other active instance | |
| 760 | 766 | hbox.AddCssClass("session-active") | |
| 761 | 767 | default: | |
| 762 | - | statusText = " " | |
| 768 | + | statusText = "◼" // Small square for exited (like stop icon) | |
| 763 | 769 | hbox.AddCssClass("session-exited") | |
| 764 | 770 | } | |
| 765 | 771 | statusLabel := gtk.NewLabel(&statusText) | |
| @@ -777,20 +783,47 @@ func (sm *SessionManager) addSessionInfo(hbox *gtk.Box, info entity.SessionInfo) | |||
| 777 | 783 | } | |
| 778 | 784 | infoBox.SetHexpand(true) | |
| 779 | 785 | ||
| 780 | - | // Session ID (short) | |
| 781 | - | idText := string(info.Session.ID) | |
| 782 | - | if len(idText) > sessionIDMaxDisplay { | |
| 783 | - | idText = idText[:sessionIDMaxDisplay] | |
| 784 | - | } | |
| 785 | - | if info.IsCurrent { | |
| 786 | - | idText += " (current)" | |
| 787 | - | } | |
| 788 | - | idLabel := gtk.NewLabel(&idText) | |
| 789 | - | if idLabel != nil { | |
| 790 | - | idLabel.AddCssClass("session-id") | |
| 791 | - | idLabel.SetHalign(gtk.AlignStartValue) | |
| 792 | - | idLabel.SetEllipsize(2) // PANGO_ELLIPSIZE_END | |
| 793 | - | infoBox.Append(&idLabel.Widget) | |
| 786 | + | // Session ID row: shortID (accent) + rest of ID (dimmed) + optional (current) badge | |
| 787 | + | idRowBox := gtk.NewBox(gtk.OrientationHorizontalValue, 0) | |
| 788 | + | if idRowBox != nil { | |
| 789 | + | idRowBox.SetHalign(gtk.AlignStartValue) | |
| 790 | + | ||
| 791 | + | fullID := string(info.Session.ID) | |
| 792 | + | shortID := info.Session.ShortID() // Last 4 chars | |
| 793 | + | ||
| 794 | + | // Short ID with accent color (displayed first) | |
| 795 | + | shortLabel := gtk.NewLabel(&shortID) | |
| 796 | + | if shortLabel != nil { | |
| 797 | + | shortLabel.AddCssClass("session-id-short") | |
| 798 | + | idRowBox.Append(&shortLabel.Widget) | |
| 799 | + | } | |
| 800 | + | ||
| 801 | + | // Rest of the ID (prefix before the short suffix), dimmed | |
| 802 | + | // Format: "20251225_083026_7566" -> shortID="7566", prefix="20251225_083026" | |
| 803 | + | // Remove trailing underscore for cleaner display | |
| 804 | + | if len(fullID) > 4 { | |
| 805 | + | prefix := fullID[:len(fullID)-4] | |
| 806 | + | // Trim trailing underscore if present | |
| 807 | + | prefix = strings.TrimSuffix(prefix, "_") | |
| 808 | + | prefix = " " + prefix | |
| 809 | + | prefixLabel := gtk.NewLabel(&prefix) | |
| 810 | + | if prefixLabel != nil { | |
| 811 | + | prefixLabel.AddCssClass("session-id-prefix") | |
| 812 | + | idRowBox.Append(&prefixLabel.Widget) | |
| 813 | + | } | |
| 814 | + | } | |
| 815 | + | ||
| 816 | + | // Current badge | |
| 817 | + | if info.IsCurrent { | |
| 818 | + | badge := " (current)" | |
| 819 | + | badgeLabel := gtk.NewLabel(&badge) | |
| 820 | + | if badgeLabel != nil { | |
| 821 | + | badgeLabel.AddCssClass("session-id-badge") | |
| 822 | + | idRowBox.Append(&badgeLabel.Widget) | |
| 823 | + | } | |
| 824 | + | } | |
| 825 | + | ||
| 826 | + | infoBox.Append(&idRowBox.Widget) | |
| 794 | 827 | } | |
| 795 | 828 | ||
| 796 | 829 | // Tab/pane count | |
| @@ -3,6 +3,7 @@ package component | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "strings" | |||
| 6 | "sync" | 7 | "sync" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 9 | "github.com/bnema/dumber/internal/application/port" | ||
| @@ -348,7 +349,7 @@ func (sm *SessionManager) initList() error { | |||||
| 348 | } | 349 | } | ||
| 349 | 350 | ||||
| 350 | func (sm *SessionManager) initFooter() error { | 351 | func (sm *SessionManager) initFooter() error { | ||
| 351 | - | footerText := "↑↓/jk navigate Tab expand Enter open x delete Esc close" | 352 | + | footerText := "↑↓/jk navigate Enter expand r restore x delete Esc close" |
| 352 | sm.footerLabel = gtk.NewLabel(&footerText) | 353 | sm.footerLabel = gtk.NewLabel(&footerText) | ||
| 353 | if sm.footerLabel == nil { | 354 | if sm.footerLabel == nil { | ||
| 354 | return errNilWidget("sessionFooterLabel") | 355 | return errNilWidget("sessionFooterLabel") | ||
| @@ -387,15 +388,17 @@ func (sm *SessionManager) handleKeyPress(keyval uint, _ gdk.ModifierType) bool { | |||||
| 387 | sm.selectNext() | 388 | sm.selectNext() | ||
| 388 | return true | 389 | return true | ||
| 389 | 390 | ||||
| 390 | - | case uint(gdk.KEY_Tab), uint(gdk.KEY_space): | 391 | + | case uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter), uint(gdk.KEY_space): |
| 392 | + | // Enter/Space expands/collapses to preview session contents | |||
| 391 | sm.toggleExpanded() | 393 | sm.toggleExpanded() | ||
| 392 | return true | 394 | return true | ||
| 393 | 395 | ||||
| 394 | - | case uint(gdk.KEY_Return), uint(gdk.KEY_KP_Enter): | 396 | + | case uint(gdk.KEY_r), uint(gdk.KEY_o): |
| 397 | + | // r/o restores the selected session | |||
| 395 | sm.openSelected() | 398 | sm.openSelected() | ||
| 396 | return true | 399 | return true | ||
| 397 | 400 | ||||
| 398 | - | case uint(gdk.KEY_x), uint(gdk.KEY_Delete): | 401 | + | case uint(gdk.KEY_x), uint(gdk.KEY_d), uint(gdk.KEY_Delete): |
| 399 | sm.deleteSelected() | 402 | sm.deleteSelected() | ||
| 400 | return true | 403 | return true | ||
| 401 | } | 404 | } | ||
| @@ -539,14 +542,15 @@ func (sm *SessionManager) resizeForContent(sessionRowCount, dividerCount, treeRo | |||||
| 539 | return | 542 | return | ||
| 540 | } | 543 | } | ||
| 541 | 544 | ||||
| 542 | - | // Calculate max visible height (6 session rows max visible, accounting for divider) | |||
| 543 | - | maxVisibleHeight := sessionMaxVisibleRows*sessionRowHeight + sessionDividerRowHeight | |||
| 544 | - | ||||
| 545 | // Calculate actual content height | 545 | // Calculate actual content height | ||
| 546 | contentHeight := sessionRowCount*sessionRowHeight + | 546 | contentHeight := sessionRowCount*sessionRowHeight + | ||
| 547 | dividerCount*sessionDividerRowHeight + | 547 | dividerCount*sessionDividerRowHeight + | ||
| 548 | treeRowCount*sessionTreeRowHeight | 548 | treeRowCount*sessionTreeRowHeight | ||
| 549 | 549 | ||||
| 550 | + | // Calculate max visible height - limit to prevent oversized modal | |||
| 551 | + | // Allow up to sessionMaxVisibleRows session rows plus one divider | |||
| 552 | + | maxVisibleHeight := sessionMaxVisibleRows*sessionRowHeight + sessionDividerRowHeight | |||
| 553 | + | ||||
| 550 | // Cap at max visible height - rest will be scrollable | 554 | // Cap at max visible height - rest will be scrollable | ||
| 551 | if contentHeight > maxVisibleHeight { | 555 | if contentHeight > maxVisibleHeight { | ||
| 552 | contentHeight = maxVisibleHeight | 556 | contentHeight = maxVisibleHeight | ||
| @@ -557,10 +561,12 @@ func (sm *SessionManager) resizeForContent(sessionRowCount, dividerCount, treeRo | |||||
| 557 | contentHeight = sessionRowHeight | 561 | contentHeight = sessionRowHeight | ||
| 558 | } | 562 | } | ||
| 559 | 563 | ||||
| 560 | - | // Schedule resize | 564 | + | // Schedule resize on main thread |
| 565 | + | // Use the same pattern as omnibox: reset min to -1 first to avoid GTK assertion | |||
| 561 | cb := glib.SourceFunc(func(_ uintptr) bool { | 566 | cb := glib.SourceFunc(func(_ uintptr) bool { | ||
| 562 | - | sm.scrolledWindow.SetMinContentHeight(contentHeight) | 567 | + | sm.scrolledWindow.SetMinContentHeight(-1) |
| 563 | sm.scrolledWindow.SetMaxContentHeight(contentHeight) | 568 | sm.scrolledWindow.SetMaxContentHeight(contentHeight) | ||
| 569 | + | sm.scrolledWindow.SetMinContentHeight(contentHeight) | |||
| 564 | sm.outerBox.QueueResize() | 570 | sm.outerBox.QueueResize() | ||
| 565 | return false | 571 | return false | ||
| 566 | }) | 572 | }) | ||
| @@ -668,9 +674,9 @@ func (sm *SessionManager) createTreeRow(text, nodeType string, hasSibling bool, | |||||
| 668 | 674 | ||||
| 669 | // Build indent and branch prefix | 675 | // Build indent and branch prefix | ||
| 670 | var prefix string | 676 | var prefix string | ||
| 671 | - | indent := " " // Base indent to align with session row content | 677 | + | indent := " " // Base indent (minimal, tree branches provide visual alignment) |
| 672 | for i := 0; i < depth; i++ { | 678 | for i := 0; i < depth; i++ { | ||
| 673 | - | indent += " " // 4 spaces per depth level | 679 | + | indent += " " // 3 spaces per depth level |
| 674 | } | 680 | } | ||
| 675 | 681 | ||||
| 676 | // Branch character | 682 | // Branch character | ||
| @@ -753,13 +759,13 @@ func (sm *SessionManager) addStatusIndicator(hbox *gtk.Box, info entity.SessionI | |||||
| 753 | var statusText string | 759 | var statusText string | ||
| 754 | switch { | 760 | switch { | ||
| 755 | case info.IsCurrent: | 761 | case info.IsCurrent: | ||
| 756 | - | statusText = "●" | 762 | + | statusText = "●" // Filled dot for current |
| 757 | hbox.AddCssClass("session-current") | 763 | hbox.AddCssClass("session-current") | ||
| 758 | case info.IsActive: | 764 | case info.IsActive: | ||
| 759 | - | statusText = "○" | 765 | + | statusText = "○" // Empty dot for other active instance |
| 760 | hbox.AddCssClass("session-active") | 766 | hbox.AddCssClass("session-active") | ||
| 761 | default: | 767 | default: | ||
| 762 | - | statusText = " " | 768 | + | statusText = "◼" // Small square for exited (like stop icon) |
| 763 | hbox.AddCssClass("session-exited") | 769 | hbox.AddCssClass("session-exited") | ||
| 764 | } | 770 | } | ||
| 765 | statusLabel := gtk.NewLabel(&statusText) | 771 | statusLabel := gtk.NewLabel(&statusText) | ||
| @@ -777,20 +783,47 @@ func (sm *SessionManager) addSessionInfo(hbox *gtk.Box, info entity.SessionInfo) | |||||
| 777 | } | 783 | } | ||
| 778 | infoBox.SetHexpand(true) | 784 | infoBox.SetHexpand(true) | ||
| 779 | 785 | ||||
| 780 | - | // Session ID (short) | 786 | + | // Session ID row: shortID (accent) + rest of ID (dimmed) + optional (current) badge |
| 781 | - | idText := string(info.Session.ID) | 787 | + | idRowBox := gtk.NewBox(gtk.OrientationHorizontalValue, 0) |
| 782 | - | if len(idText) > sessionIDMaxDisplay { | 788 | + | if idRowBox != nil { |
| 783 | - | idText = idText[:sessionIDMaxDisplay] | 789 | + | idRowBox.SetHalign(gtk.AlignStartValue) |
| 784 | - | } | 790 | + | |
| 785 | - | if info.IsCurrent { | 791 | + | fullID := string(info.Session.ID) |
| 786 | - | idText += " (current)" | 792 | + | shortID := info.Session.ShortID() // Last 4 chars |
| 787 | - | } | 793 | + | |
| 788 | - | idLabel := gtk.NewLabel(&idText) | 794 | + | // Short ID with accent color (displayed first) |
| 789 | - | if idLabel != nil { | 795 | + | shortLabel := gtk.NewLabel(&shortID) |
| 790 | - | idLabel.AddCssClass("session-id") | 796 | + | if shortLabel != nil { |
| 791 | - | idLabel.SetHalign(gtk.AlignStartValue) | 797 | + | shortLabel.AddCssClass("session-id-short") |
| 792 | - | idLabel.SetEllipsize(2) // PANGO_ELLIPSIZE_END | 798 | + | idRowBox.Append(&shortLabel.Widget) |
| 793 | - | infoBox.Append(&idLabel.Widget) | 799 | + | } |
| 800 | + | ||||
| 801 | + | // Rest of the ID (prefix before the short suffix), dimmed | |||
| 802 | + | // Format: "20251225_083026_7566" -> shortID="7566", prefix="20251225_083026" | |||
| 803 | + | // Remove trailing underscore for cleaner display | |||
| 804 | + | if len(fullID) > 4 { | |||
| 805 | + | prefix := fullID[:len(fullID)-4] | |||
| 806 | + | // Trim trailing underscore if present | |||
| 807 | + | prefix = strings.TrimSuffix(prefix, "_") | |||
| 808 | + | prefix = " " + prefix | |||
| 809 | + | prefixLabel := gtk.NewLabel(&prefix) | |||
| 810 | + | if prefixLabel != nil { | |||
| 811 | + | prefixLabel.AddCssClass("session-id-prefix") | |||
| 812 | + | idRowBox.Append(&prefixLabel.Widget) | |||
| 813 | + | } | |||
| 814 | + | } | |||
| 815 | + | ||||
| 816 | + | // Current badge | |||
| 817 | + | if info.IsCurrent { | |||
| 818 | + | badge := " (current)" | |||
| 819 | + | badgeLabel := gtk.NewLabel(&badge) | |||
| 820 | + | if badgeLabel != nil { | |||
| 821 | + | badgeLabel.AddCssClass("session-id-badge") | |||
| 822 | + | idRowBox.Append(&badgeLabel.Widget) | |||
| 823 | + | } | |||
| 824 | + | } | |||
| 825 | + | ||||
| 826 | + | infoBox.Append(&idRowBox.Widget) | |||
| 794 | } | 827 | } | ||
| 795 | 828 | ||||
| 796 | // Tab/pane count | 829 | // Tab/pane count | ||
M internal/ui/theme/css.go +19 -8
| @@ -643,25 +643,36 @@ row:selected .session-manager-row { | |||
| 643 | 643 | color: var(--muted); | |
| 644 | 644 | } | |
| 645 | 645 | ||
| 646 | - | /* Exited session - no dot, dimmed */ | |
| 646 | + | /* Exited session - dimmed appearance with subtle stop icon */ | |
| 647 | 647 | .session-exited { | |
| 648 | - | opacity: 0.7; | |
| 648 | + | opacity: 0.75; | |
| 649 | 649 | } | |
| 650 | 650 | ||
| 651 | 651 | .session-exited .session-status { | |
| 652 | - | color: transparent; | |
| 652 | + | color: var(--error); | |
| 653 | + | opacity: 0.5; | |
| 653 | 654 | } | |
| 654 | 655 | ||
| 655 | - | /* Session ID label */ | |
| 656 | - | .session-id { | |
| 656 | + | /* Session ID labels */ | |
| 657 | + | .session-id-short { | |
| 657 | 658 | font-size: 0.875em; | |
| 658 | - | font-weight: 500; | |
| 659 | - | color: var(--text); | |
| 659 | + | font-weight: 600; | |
| 660 | + | color: var(--accent); | |
| 660 | 661 | font-family: var(--font-mono); | |
| 661 | 662 | } | |
| 662 | 663 | ||
| 663 | - | .session-current .session-id { | |
| 664 | + | .session-id-prefix { | |
| 665 | + | font-size: 0.875em; | |
| 666 | + | font-weight: 400; | |
| 667 | + | color: var(--muted); | |
| 668 | + | font-family: var(--font-mono); | |
| 669 | + | } | |
| 670 | + | ||
| 671 | + | .session-id-badge { | |
| 672 | + | font-size: 0.75em; | |
| 673 | + | font-weight: 500; | |
| 664 | 674 | color: var(--accent); | |
| 675 | + | margin-left: 0.5em; | |
| 665 | 676 | } | |
| 666 | 677 | ||
| 667 | 678 | /* Tab/pane count label */ | |
| @@ -643,25 +643,36 @@ row:selected .session-manager-row { | |||||
| 643 | color: var(--muted); | 643 | color: var(--muted); | ||
| 644 | } | 644 | } | ||
| 645 | 645 | ||||
| 646 | - | /* Exited session - no dot, dimmed */ | 646 | + | /* Exited session - dimmed appearance with subtle stop icon */ |
| 647 | .session-exited { | 647 | .session-exited { | ||
| 648 | - | opacity: 0.7; | 648 | + | opacity: 0.75; |
| 649 | } | 649 | } | ||
| 650 | 650 | ||||
| 651 | .session-exited .session-status { | 651 | .session-exited .session-status { | ||
| 652 | - | color: transparent; | 652 | + | color: var(--error); |
| 653 | + | opacity: 0.5; | |||
| 653 | } | 654 | } | ||
| 654 | 655 | ||||
| 655 | - | /* Session ID label */ | 656 | + | /* Session ID labels */ |
| 656 | - | .session-id { | 657 | + | .session-id-short { |
| 657 | font-size: 0.875em; | 658 | font-size: 0.875em; | ||
| 658 | - | font-weight: 500; | 659 | + | font-weight: 600; |
| 659 | - | color: var(--text); | 660 | + | color: var(--accent); |
| 660 | font-family: var(--font-mono); | 661 | font-family: var(--font-mono); | ||
| 661 | } | 662 | } | ||
| 662 | 663 | ||||
| 663 | - | .session-current .session-id { | 664 | + | .session-id-prefix { |
| 665 | + | font-size: 0.875em; | |||
| 666 | + | font-weight: 400; | |||
| 667 | + | color: var(--muted); | |||
| 668 | + | font-family: var(--font-mono); | |||
| 669 | + | } | |||
| 670 | + | ||||
| 671 | + | .session-id-badge { | |||
| 672 | + | font-size: 0.75em; | |||
| 673 | + | font-weight: 500; | |||
| 664 | color: var(--accent); | 674 | color: var(--accent); | ||
| 675 | + | margin-left: 0.5em; | |||
| 665 | } | 676 | } | ||
| 666 | 677 | ||||
| 667 | /* Tab/pane count label */ | 678 | /* Tab/pane count label */ | ||