github nklmilojevic/sofka v0.15.0
v0.15.0: Sophie learned to click, notify, and diff GitOps

latest releases: v0.28.3, v0.28.2, v0.28.1...
one month ago

v0.15.0 is a feature-heavy release. Eight new user-facing capabilities land alongside a substantial correctness/perf audit, with the README and in-app help brought back in sync with the code.

Highlights

  • More ways in. Mouse support (wheel scroll, click to select, click a header to sort) and a global fuzzy find (:find <text>) over object names across all namespaces.
  • GitOps gets first-class treatment. :diff now works on Flux- and Helm-managed objects (session fallback when last-applied-configuration is absent), and on a HelmRelease jumps straight into the Helm release history it manages.
  • Sofka watches while you work. :notify attaches a long-lived watch to a single object so you can browse other views and still get a flash + terminal bell + desktop notification on every state change.
  • Honest answers under error and load. A code-health audit surfaced swallowed read errors (a 403 previously diagnosed as an empty pod set) and added panic safety, frame coalescing, sort-key caching, and viewport windowing so event storms stay smooth.

Added

Mouse support (#117)

  • Wheel scrolls every view (table, logs, documents, pickers) by synthesizing the mode's own up/down keys, so mouse navigation can never disagree with keyboard navigation.
  • Click a table row to select it; click a column header to sort by that column (click again to flip direction).
  • Set mouse = false in the config to keep the terminal's native text selection. Mouse capture is released while a suspended command (kubectl exec, $EDITOR) runs, on normal exit, and in the panic hook.

Global fuzzy find (:find <text> / :fd) (#120)

  • Lists a curated set of common kinds — workloads, pods, services, configmaps, secrets, ingresses, jobs, cronjobs, PVCs, nodes, namespaces, Kustomizations, HelmReleases — across all namespaces concurrently, fuzzy-matches names, and opens a ranked picker.
  • on a hit jumps to the object via the existing navigation path (a name-filtered view of its kind), so logs/YAML/events are one keystroke away.
  • Kinds the user can't list (RBAC) make the result flash "find is incomplete — N kind(s) could not be listed" instead of silently pretending the sweep was exhaustive.
  • Bare :find prints usage. A curated kind set is deliberate — listing every CRD per search would hammer the API server for kinds nobody names things in.

Saved port-forwards with autostart (#119)

  • Port-forwards previously lived only as session children — killed on quit, retyped every morning. [[forwards]] saves them: name, target (kubectl syntax), namespace, ports, optional autostart and a contexts allowlist.

    [[forwards]]
    name = "argocd"
    target = "svc/argocd-server"
    namespace = "argocd"
    ports = "8080:443"
    autostart = true              # start when sofka connects
    contexts = ["home"]           # optional: only these contexts
  • :pf now lists running forwards (●) followed by saved-but-stopped entries (○ dimmed); /s starts a stopped one, x/s stops a running one ( on a running forward is deliberately a no-op — a reflexive enter shouldn't kill a tunnel).

  • Already-running entries are never duplicated; forwards from the previous context are left alone since kubectl pinned their --context at spawn.

Watch notifications (:notify / :bell) (#116)

  • Toggle a notification on the selected object — "tell me when this rollout finishes / pod becomes Ready / Kustomization reconciles" and keep working.
  • Each notify is its own bounded single-object watch (fields metadata.name=…), deliberately kept out of the generation-scoped task list so it survives view switches and fires from any view until toggled off or the session ends.
  • Change detection reuses the timeline's pure transitions() (rollout progress, readiness, phase, restarts, waiting reasons, conditions), so :notify and T agree on what counts as a state change. The initial list is suppressed — only changes after you toggled are news. Deletion and (re)creation notify too.
  • Notifications arrive as a generation-free Msg::Notify: 🔔 flash in the status line, terminal bell, and an OSC 9 desktop notification (iTerm2/kitty/WezTerm/foot pop a system notification; other terminals ignore the sequence). Session-local, nothing on disk.

Flux HelmRelease → Helm release history (#115)

  • sofka already had a full native Helm storage inspector (:helm → history → values / manifest / NOTES / rollback), but it was unreachable from the HelmRelease objects a Flux user actually browses.
  • on a helmreleases row now drills straight into the helmhistory view for the Helm release that object manages. All existing history-view keys work from there ( values, y manifest, d NOTES, r rollback), and esc returns to the HelmRelease list.
  • Storage coordinates are resolved exactly the way helm-controller composes them: spec.releaseName (defaulting to [<targetNamespace>-]<name>) and spec.storageNamespace (defaulting to the object's namespace).

Session diff — :diff works on GitOps-managed objects (#113)

  • :diff previously diffed live vs last-applied-configuration only — an annotation that is empty for every Flux/Helm-managed object, so the diff view was useless precisely for sofka's GitOps focus.
  • The watch Applied handler now keeps the previous revision of changed objects (bounded FIFO, 256 entries, keyed by kind + store key; survives view switches) next to the existing timeline observation.
  • When the annotation is absent, :diff shows previous → live from the session watch, titled (session: previous → live). When neither baseline exists, it says so explicitly. resourceVersion is stripped from both sides (pure noise).

Node %CPU / %MEM of allocatable (#114)

  • The nodes view showed absolute CPU/MEM usage only — status.allocatable was never read, so the number a nodes view is actually opened for ("how full is this node") didn't exist.
  • Nodes now get %CPU / %MEM columns: metrics usage as a percentage of allocatable, rendered 57% (or - when allocatable is unknown), colored by the existing configurable utilization thresholds (green / peach ≥ warn / red ≥ critical).
  • Both columns sort by value via the sort picker; they're treated as volatile sorts like CPU/MEM (metrics move without a resourceVersion bump).

Condition-typed columns, by name (#118)

  • Custom [views] columns get a new type = "condition": path is the condition type name (Ready, Available, …), looked up in status.conditions by name — replacing the fragile /status/conditions/0/status array-index idiom, since condition order isn't guaranteed by anything. Renders True/False/Unknown (<none> when absent), sorts as text, and drives row coloring like a status column.
  • CRD additionalPrinterColumns with the canonical condition filter (.status.conditions[?(@.type=="Ready")].status — how most CRDs such as cert-manager, Flux, ESO express their READY column) were silently dropped by the JSONPath→Pointer translation. They now become condition columns automatically, so those CRDs finally get their READY column with zero configuration. Genuinely untranslatable filter/wildcard expressions are still skipped.

Fixed

Surfaces swallowed errors, panic safety, and hot-path performance (#112)

A code-health audit fixed correctness issues that were hiding in the render and read paths, and tightened the hot path so a busy watch stream stays smooth.

  • Read errors no longer render as confident answers: explain / gitops / bundle prepend an "evidence incomplete — …" finding when a list fails (a 403 previously diagnosed from an empty pod set); pulse / xray flash "incomplete" instead of showing healthy-looking zero tiles; fleet rows show the list error instead of "0 unhealthy"; gitops distinguishes a read failure from a genuine 404; metrics poll failures surface in :info and clear on the next success; an unreadable kubeconfig reports as an error instead of an empty context picker.
  • Panic safety: a panic in a spawned tokio task no longer restores the terminal under a still-running TUI. Panics now surface as an in-app internal error flash via Msg::Panic; main-thread panics keep the ratatui restore. The cursor is re-shown on every exit path.
  • Char-safe truncation: four hand-rolled truncate helpers consolidated into text::ellipsize(). The fleet one byte-sliced at index 59 and panicked on multi-byte UTF-8 in connection errors.
  • Rightsize honesty: failed PromQL queries propagate as None and render as with a warning banner, instead of becoming 0.0 inside the recommendation math.
  • Smaller: checked_mul in config duration parsing, a type guard before serde_json's panicking IndexMut on CronJob annotations, guarded g on an empty xray list.
  • Performance: frame coalescing (event storm costs at most ~60 renders/s); sort-key caching per resourceVersion (no re-extracting sort cells or re-gunzipping Helm secrets per event); viewport windowing (no more materializing every filtered row per frame); structured filters extract only the named column per object; log filtering is allocation-free for ASCII patterns; rightsize runs its 8 queries × N containers concurrently over a process-wide hyper client.

Full Changelog: v0.14.1...v0.15.0

Don't miss a new sofka release

NewReleases is sending notifications on new releases.