15248cae8a
- recovery: two-tier repair ladder — owner then arbiter (recovery stage recast
as intent-holder, holds initial intent, reconciles cross-file contract disputes)
with independent INTENT_ROUTE_BUDGET; RecoveryRoutingTest coverage
- context: remaining-delta checklist pinning (RemainingDelta{Pinning,Entry}Test)
- surface write-only events (session name, quality signals) into stats/browse
- parseToolArguments lenient-Json fallback for bare-key drift
- tools: ChildProcess seam
108 lines
2.9 KiB
Go
108 lines
2.9 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// prefs is the TUI's local, persisted preferences (display-only client state — kept out
|
|
// of the server config, which is shared/replayed). Stored as JSON in the correx config dir.
|
|
type prefs struct {
|
|
StatusbarHidden []string `json:"statusbarHidden"`
|
|
InlineActionsHidden bool `json:"inlineActionsHidden"`
|
|
ThinkingShown bool `json:"thinkingShown"`
|
|
}
|
|
|
|
// statusSegment is one toggleable status-bar segment. The always-on segments (correx label,
|
|
// connection, session name, spinner, background-updates) are not listed — they are load-bearing.
|
|
type statusSegment struct{ id, label string }
|
|
|
|
var statusSegments = []statusSegment{
|
|
{"stage", "current stage (⟐)"},
|
|
{"status", "session status"},
|
|
{"workspace", "workspace path (⌂)"},
|
|
{"model", "model name"},
|
|
{"clock", "last-event clock"},
|
|
{"gauge", "resource gauge"},
|
|
}
|
|
|
|
func prefsPath() string {
|
|
dir := os.Getenv("CORREX_CONFIG_HOME")
|
|
if dir == "" {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
dir = filepath.Join(home, ".config", "correx")
|
|
}
|
|
return filepath.Join(dir, "tui-prefs.json")
|
|
}
|
|
|
|
// loadPrefs reads the whole prefs file (best-effort: a missing or corrupt file yields the
|
|
// zero value, i.e. everything shown / actions visible).
|
|
func loadPrefs() prefs {
|
|
var p prefs
|
|
path := prefsPath()
|
|
if path == "" {
|
|
return p
|
|
}
|
|
if data, err := os.ReadFile(path); err == nil {
|
|
_ = json.Unmarshal(data, &p)
|
|
}
|
|
return p
|
|
}
|
|
|
|
// savePrefs writes the whole prefs file (best-effort; ignores IO errors so a read-only home
|
|
// never crashes the UI). Callers load, mutate one field, then save so nothing is clobbered.
|
|
func savePrefs(p prefs) {
|
|
path := prefsPath()
|
|
if path == "" {
|
|
return
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return
|
|
}
|
|
if data, err := json.MarshalIndent(p, "", " "); err == nil {
|
|
_ = os.WriteFile(path, data, 0o644)
|
|
}
|
|
}
|
|
|
|
// loadStatusbarHidden reads the persisted hidden-segment set (best-effort: a missing or
|
|
// corrupt file yields an empty set, i.e. everything shown).
|
|
func loadStatusbarHidden() map[string]bool {
|
|
out := map[string]bool{}
|
|
for _, id := range loadPrefs().StatusbarHidden {
|
|
out[id] = true
|
|
}
|
|
return out
|
|
}
|
|
|
|
// saveStatusbarHidden writes the hidden set back to disk, preserving the other prefs fields.
|
|
// Order follows statusSegments for a stable file.
|
|
func saveStatusbarHidden(hidden map[string]bool) {
|
|
var ids []string
|
|
for _, seg := range statusSegments {
|
|
if hidden[seg.id] {
|
|
ids = append(ids, seg.id)
|
|
}
|
|
}
|
|
p := loadPrefs()
|
|
p.StatusbarHidden = ids
|
|
savePrefs(p)
|
|
}
|
|
|
|
// saveInlineActionsHidden persists the inline-action-rows toggle, preserving other prefs fields.
|
|
func saveInlineActionsHidden(hidden bool) {
|
|
p := loadPrefs()
|
|
p.InlineActionsHidden = hidden
|
|
savePrefs(p)
|
|
}
|
|
|
|
// saveThinkingShown persists the reasoning-blocks toggle, preserving other prefs fields.
|
|
func saveThinkingShown(shown bool) {
|
|
p := loadPrefs()
|
|
p.ThinkingShown = shown
|
|
savePrefs(p)
|
|
}
|