feat(tui): show session workspace (cwd) in the status bar

SessionWorkspaceBoundEvent already recorded the bound workspace root but
it was never surfaced. Map it to a new session.workspace_bound frame
(sealed ServerMessage variant, auto-registered; re-emitted on reconnect
via the snapshot replay), carry it on Session.WorkspaceRoot, and show it
home-abbreviated in the status bar (⌂ ~/Programs/correx). Golden test
pins the decode.
This commit is contained in:
2026-06-03 01:56:46 +04:00
parent 00b08660bd
commit 5721ed21bb
8 changed files with 82 additions and 33 deletions
+1
View File
@@ -73,6 +73,7 @@ func PreviewFrame(kind string, w, h int) string {
m.sessionEntered = true
if s := m.session("04a546aa"); s != nil {
s.CurrentStage = "write_script"
s.WorkspaceRoot = "/home/kami/Programs/correx"
s.Events = sampleEvents()
s.Pending = &Approval{
RequestID: "req-1", SessionID: "04a546aa", Tier: "T3", Risk: "MEDIUM",
+14 -13
View File
@@ -98,19 +98,20 @@ type Approval struct {
// Session is the UI's view of one server session.
type Session struct {
ID string
Status string
WorkflowID string
Name string
LastEventAt int64
CurrentStage string
LastOutput string
LastResponse string
Tools []ToolRecord
ToolsByStage map[string][]ManifestTool
Events []EventEntry
Pending *Approval
Active bool // an inference/tool is in flight (drives the spinner)
ID string
Status string
WorkflowID string
Name string
LastEventAt int64
CurrentStage string
WorkspaceRoot string // bound cwd, from session.workspace_bound
LastOutput string
LastResponse string
Tools []ToolRecord
ToolsByStage map[string][]ManifestTool
Events []EventEntry
Pending *Approval
Active bool // an inference/tool is in flight (drives the spinner)
}
// Workflow is a launchable workflow advertised by the server.
+4
View File
@@ -175,6 +175,10 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
if s := m.session(msg.SessionID); s != nil {
s.addEvent(nowMillis(), "ArtifactCreated", msg.ArtifactID)
}
case protocol.TypeWorkspaceBound:
if s := m.session(msg.SessionID); s != nil {
s.WorkspaceRoot = msg.WorkspaceRoot
}
case protocol.TypeApprovalRequired:
m.onApprovalRequired(msg)
case protocol.TypeApprovalResolved:
+12
View File
@@ -1,6 +1,7 @@
package app
import (
"os"
"strings"
"github.com/charmbracelet/lipgloss"
@@ -74,6 +75,9 @@ func (m Model) renderStatus() string {
if s.Status != "" {
parts = append(parts, lipgloss.NewStyle().Foreground(statusColor(t, s.Status)).Background(bg).Render(statusLabel(s.Status)))
}
if s.WorkspaceRoot != "" {
parts = append(parts, span("⌂ "+shortPath(s.WorkspaceRoot), t.P.Faint))
}
}
model := m.currentModel
@@ -391,6 +395,14 @@ func (m Model) eventRows(w, h int) []string {
// --- width helpers ---
// shortPath abbreviates the user's home dir to ~ for a compact cwd in the status bar.
func shortPath(p string) string {
if home := os.Getenv("HOME"); home != "" && strings.HasPrefix(p, home) {
return "~" + p[len(home):]
}
return p
}
// fill left-justifies a styled line and pads with bg to width w.
func (m Model) fill(s string, w int, bg lipgloss.Color) string {
return " " + padTo(s, w-1, bg)