v1.1.0 — port-forward + logs UX
Two themed improvements on top of v1.0.0 — plus the polish commit that
made the port-forward feature actually usable on first try.
Port-forward with persisted intents
You can now expose any Service, Pod or Deployment on a local port from
inside k4s — no need to drop to a shell for kubectl port-forward.
- Press
fon a row in services / pods / deployments. - A centred popup asks for
local:remote. The local default is
smart: ports < 1024 get bumped by 8000 (80 → 8080, 443 → 8443) so
non-root users can bind them without "permission denied". Editable
— change either side before Enter. - The forward starts immediately and you land on the new forwards
view to confirm it is running.
The forwards view
Open it any time with :pf (or :forwards).
| Key | Action |
|---|---|
Enter |
start a stopped forward |
s |
stop a running forward |
r |
restart |
d |
delete from state |
Persistence — the "intent" model
The state file at `$XDG_STATE_HOME/k4s/portforwards.json`
(default `~/.local/state/k4s/`) remembers every forward you have ever
declared. Quitting k4s ends the live tunnels (they are SPDY connections
inside this process), but on the next launch k4s shows a centred popup:
Found N saved forwards from a previous session.
y / Enter revive all · n / Esc skip · :pf to manage later
Atomic write (temp + rename), XDG_STATE_HOME honoured, missing file
treated as empty state.
Robust error handling
- A local-port pre-check (`net.Listen` + immediate close) runs before
anything is persisted. Privileged or busy ports surface as friendly
messages right in the command-bar — "permission denied binding local
port 80 (ports < 1024 need root — try 8080)", "local port 8080
already in use — pick another". No half-dead entries pile up in the
forwards list. - kubectl's stderr output (`Unable to listen on port X: bind: …`) is
captured into a buffer instead of leaking to the real terminal and
shredding the TUI render. Stdout (info chatter like `Forwarding from
127.0.0.1:8080 -> 80`) is captured separately and not treated as
an error — the supervisor only flips status to red on real stderr
diagnostics. - Manager.supervise drains pending errors after Done so the
fw.ForwardPorts race (it both closes Done and writes to Err) never
loses the real cause.
Architecture (for the curious)
- `internal/k8s/portforward.go` — `Client.StartPodPortForward` builds
an SPDY dialer from the retained `rest.Config` and drives a
`portforward.PortForwarder` in a goroutine. Service and Deployment
targets resolve to a backing pod via the label selector +
first-ready pod, matching kubectl's behaviour. Session lifetime is
bound to StopCh only, not the caller's ctx. - `internal/forwards/state.go` — JSON wire format with `Validate()`,
pure `Upsert`/`Remove` returning new States. - `internal/forwards/manager.go` — owns the state and active map,
exposes a one-shot `Changes()` channel for the UI to subscribe to,
and supervises sessions so status flips to error or stopped
without manual refreshes. `List()` returns a stable sort
`(namespace, kind, name, localport)` so the UI cursor doesn't jump. - `internal/tui/views/forwards/` — new list view subscribed to
`Manager.Changes`.
Logs UX
Three quality-of-life fixes for the logs view, motivated by
`--tail=5000` making the UI feel "stuck" while it scrolled to the
bottom.
- Drain batching. The old loop scheduled one Update tick per log
line, each of which re-formatted every line with lipgloss
(O(N²) on the initial backlog). Now one tick reads everything the
channel has ready (cap 1000) and rebuilds the viewport once. - Incremental render cache. Formatted lines are kept in a parallel
slice so appends are O(1). Mode toggles that need a full reformat
(`t`, `w`) rebuild explicitly. - Soft wrap + horizontal scroll. Long lines used to be silently
clipped. Now:- `w` toggles wrap mode (soft-wrap to viewport width).
- `←` / `→` pan horizontally in no-wrap mode, 10 columns at a
time. Status row shows `[→ N cols]` while panned. - The keys go inert in wrap mode and drop from the help footer.
Quality
- `go vet` ✓
- `golangci-lint` ✓ (0 issues)
- race detector clean
- new test packages: `internal/forwards` (~75% cov), expanded coverage
in `internal/tui/views/logs` and `internal/k8s`.
Install
```bash
go install github.com/LywwKkA-aD/k4s/cmd/k4s@v1.1.0
```
Or via source: `git clone https://github.com/LywwKkA-aD/k4s && cd k4s && make demo && make run`.
Website: https://k4scli.io