v1.3.0
The big one. km8 becomes a graph navigator — the Links tab lets you
chase ownership / consumer / ref chains by repeatedly drilling
(Deployment → Pods → ConfigMap → consumer Pods → ...) without ever
leaving panel 3. 25 of 26 resource kinds carry Links data; every drill
respects a cycle pre-check; a breadcrumb popup lets you jump back to
any ancestor level in one step. Alongside that: a persistent embedded
shell (KM8erm), aggregate Deployment logs, a full-screen Y YAML
popup, and a layout refactor that ditched percentage-math heuristics
for absolute stacking.
Added
- Links tab — Lens-style graph navigation. Every detail panel
(except Namespaces, which has no meaningful refs) carries a Links
tab listing the resource's navigable references.Enter/l
drills into a ref — the panel re-renders showing that resource's
Links, building a navigation chain (Deployment → Pod → ConfigMap →
consumer Pods, ...).h/Escpops one level.bopens a
breadcrumb popup listing the full chain so you can jump back to any
ancestor in one step (j/kto pick,Enterto commit).Yon
the cursor-pointed entry opens its YAML popup. The tab label
surfaces depth asLinks ↳Nand the panel border carries a
[b]readcrumbshint at the top-right whenever you're deeper than
the root. Cycle detection (kind+ns+name) blocks revisiting an
ancestor; fetch failures show a peachShowWarntoast and don't
push a frame. Stale-drop guards (source item UID) keep async fetch
results from clobbering the panel when you've moved on to a
different row. - Links coverage for 25 of 26 resource kinds. Pods / Services /
Deployments / StatefulSets / DaemonSets / Jobs / CronJobs / Ingresses
/ HPAs / PVCs each surface their kind-specific refs (owners,
selected pods, scaleTargetRef, claimRef, ...). ConfigMaps / Secrets
/ ServiceAccounts / PVs surface reverse refs (which pods mount me
/ use me as their SA / are bound to me). Nodes /
PodDisruptionBudgets / NetworkPolicies / EndpointSlices / Roles /
RoleBindings / ClusterRoles / ClusterRoleBindings / StorageClasses /
IngressClasses all wired. Namespace hides the Links tab entirely
(no concrete drill target). - Aggregate Logs for Deployments. Selecting a Deployment row
streams logs from every pod in its current ReplicaSet into one Logs
tab (also Deployment's default tab — "which pod is misbehaving
during a rollout" is the question that opens 90% of Deployment
details). Lines are prefixed<pod-hash>│<container>│<text>with
three independent FNV-derived colors from the 8-entry Catppuccin
palette so any pod / container combination stays visually distinct.
Cross-stream timestamp sorting deliberately not attempted (clock
skew + jitter would make any ordering misleading). Falls back to
the Deployment's full selector when the current-ReplicaSet lookup
fails (RBAC denies RS list, etc.). - Persistent KM8erm (
Alt+t). The embedded shell survives
visibility toggling. FirstAlt+tspawns it; subsequent presses
hide / show while cwd, history, env vars, and background jobs all
persist. Status bar carries a chip in thens:row showing state —
greenattachedwhile visible, peachkm8ermwhile hidden. Shell
exits cleanly on km8 quit.Alt+tonly applies to the Shell-kind
PTY;kubectl editandkubectl execpopups treat it as a regular
key (their lifecycle is bound to the subprocess).e/swhile
any PTY is alive refuse with aShowWarntoast instead of
clobbering the in-flight subprocess. YYAML popup. Full-screen popup of the currently-selected
resource's YAML withj/kline scroll,u/dhalf-page,
gg/Gtop / bottom,/search (Entercommits;n/N
step through matches with full-row highlight; search-box border
flips cyan → amber when the filter locks),eto dispatch
kubectl editdirectly from the popup (skips the table-level
confirm), andyto OSC-52-copy the full YAML to your clipboard.
Solves the "YAML wall in narrow Panel 3 is hard to read" friction
without dropping YAML access. On the Links tab,Yfollows the
cursor — opens the YAML of the link entry you're pointing at, so
previewing a drill target's YAML doesn't require drilling into it
first.- App Log
yto copy. Pressyinside the App Log popup (!)
to OSC-52-copy the full log (newest-first, matching display order).
Makes "paste the error into Slack / GitHub issue" one key away. - Toast levels —
Show/ShowWarn. Info-level (Show) stays
1s sky-blue (Copied!, PTY hints); warning-level (ShowWarn) is 2s
peach with a warning glyph () for cycle-blocked / drill-failed
messages. Longer duration means you actually get to read what
blocked.ShowErrorreserved for when the first error caller
appears. - Per-popup distinct icons. Each popup (toast, confirm, help, app
log, context picker, namespace picker, YAML popup, breadcrumb,
PTY view) gets its own Nerd Font glyph in the title. N/Cuppercase aliases for namespace / context pickers.
Lowercase still works but feels too easy to misfire (nis
vim-search-next muscle memory). Lowercase will be deprecated later.- Sidebar category-name search. Typing
/followed by a category
name (cluster) expands matching categories and shows all their
children, not only items whose own label matches. - Detail panel refetch spinner. Panel 3 border shows an animated
braille spinner whilefetchResourceDetailis in flight.
Changed
-
Detail tab order: YAML moved out, Links is the default tab.
YAML lives in theYpopup now. New defaults:- Pod:
Logs/Links/Events - Deployment:
Logs/Links/Events - Events:
Linksalone - everything else:
Links/Events
Existing users who pressed
1/2/3to cycle to a YAML tab — use
Yinstead. - Pod:
-
h/lno longer switches detail tabs from inside Panel 3.
On the Links tab those keys belong to the drill chain (push / pop),
and dual-purposing them was confusing. To switch detail tabs while
reading panel 3, move focus to panel 2 first. From Panel 2h/
lstill cycle tabs as before. -
Tab label format. Drilled-into Links tab shows
Links ↳N(was
Links(N)); the down-arrow reads as "you've gone N levels deep" at
a glance. -
Panel layout uses absolute stacking math. Replaced percentage
heuristics (*N/100) with named constants (panelSidebarWidth = 24,panelDetailHeight = 14, ...) and pure subtraction. Side
benefit: predictable behavior on any terminal width. Panel 1
narrowed 28 → 24. Panel 2 ↔ Panel 3 vertical space dropped to 0
(borders themselves act as the separator). Sidebar ↔ Table
horizontal space also dropped to 0. -
Status line is fixed 1 row. Removed the dynamic two-row mode.
Hints are condensed (?,q, panel-specific keys,Y,M-t) —
no more vim-convention reminders, no overflow. -
YAML popup spans full terminal width. Was sized to a percentage
of the screen; now matches the panel-border alignment. Same for
the help popup. -
Help popup is two-column. Counts wrap rows per group to balance
the columns; padding distributes across inter-section gaps so the
columns terminate at the same height.
Fixed
- Panic on quit when KM8erm was hidden.
Stop()nil'dp.cmd
whilereadLoopwas still doingcmd.Wait(); the loop now
captures local pointer copies before the wait so the nil
reassignment can't race the in-flight wait. - Pod STATUS column lost its color when truncated. A
CrashLoopBackOffclipped toCrashL…no longer matched the color
lookup switch — color logic now reads the pre-truncation value
while the renderer keeps the clipped string. - Pod owner drill resolves past the ReplicaSet layer. A Pod's
OwnerReferences[0]is the auto-created ReplicaSet, but
kindToResourceTypemapped it toDeployments. The Name was the
RS's (<deployment>-<hash>), so drilling into Owner failed with
deployments.apps "..." not found.EnrichLinksnow looks up the
RS to find its owning Deployment and rewritesPodLinks.Ownerin
place. Also fixes cycle detection for the Deployment → Pod → Owner
round trip. - Stale
ResourceDetailMsgdrops by UID. Rapid row switching
used to let a slow fetch overwrite the current row's detail after
the user had moved on.ResourceDetailMsgnow carries the source
item UID; the handler ignores mismatches. - Help popup right border on odd-width terminals. Off-by-one
from integer-truncated column split — fixed by letting the middle
gutter absorb the leftover column. - KM8erm hidden status-bar marker uses peach (
#fab387). The
previous yellow was identical to thens:text; the new color
matches the panel-border palette and is unambiguous. Alt+thint everywhere is lowercase. The keymap is
case-sensitive; help / status line / KM8erm border hints now match
the actual key.- Long Links values wrap consistently for cursor and non-cursor
rows. Cursor row usedlipgloss.Width()(which wraps); non-
cursor rows had no width constraint and gotansi-truncated by
the outer panel — and the drill arrow disappeared from the
truncated rows, hiding the fact that the row was drillable. Both
branches now share an explicitwrapPlainpath; the drill arrow
→is split back off the last wrap chunk so its color stays in
drillStyle. - Breadcrumb cursor row aligns with non-cursor rows. The
cursor's highlight wrapped both the prefix's leading space and
an outer wrap-space, doubling it up;2.was shifted right by one
cell. Now both render with a single leading space inside the same
content frame. - Various popup margin and padding tightening. Top/bottom
padding rows dropped from the YAML popup, breadcrumb popup, and
overview cursor — the borders alone provide enough visual
separation.
Internal
- New
k8s.LinkSection/k8s.LinkRowgeneric Links payload on
ResourceDetail; Pod and Service keep their typedPodLinks/
ServiceLinksfor richer per-kind structure. Per-kind builders
live ininternal/k8s/links.go;EnrichLinks(ctx, cs, rt, item, *detail)is the extension point AppModel calls after the
synchronousDetailerreturns — the place to put API-needing
resolution (RS-skip, selector→pods, reverse refs). k8s.FetchResourceByRef(ctx, cs, ref)fetches any supported kind
by(Type, Name, Namespace), used by both the YAML popup drill
(Y) and the chain drill (Enter/l). Supports 21 kinds.DetailModel.drillStack []drillFramecarries the Links navigation
chain (level 2+); the root is implicit inm.detail.Depth(),
RootRef(),DrillChain(),PushDrillFrame,PopDrillFrame,
JumpToDrillLevel,ResetDrillStack,BorderTopRightHint,
CurrentLevelYAMLgive AppModel + the breadcrumb popup the API
surface they need.- New
LinkPushMsg/linkDrillFetchedMsg/LinkBreadcrumbMsg/
LinkJumpMsgmessages. The fetched message carriessourceUID
for the same stale-drop guardResourceDetailMsguses. - New
BreadcrumbPopupModel(PopupAnimator-based, follows the
ConfirmModel pattern):j/kmove cursor,Enterjumps back
to that level,Esc/q/bclose. Long resource names wrap
with continuation indented under the label start; the cursor
highlight spans every wrapped line as one block. - New
ToastModellevels —toastLevelenum + per-level duration
/ glyph / color helpers. k8s.PodTarget+k8s.PodsForDeployment/
k8s.PodsForWorkload.LogStreamer.StartMulti([]PodTarget)is
the aggregate entry point; the single-podStartis a thin
wrapper.LogLine.Podis populated only in aggregate mode, so
single-pod streams stay free of the<pod-hash>│prefix.YamlPopupModelininternal/ui/yamlpopup.gomirrors
HelpModel/AppLogModelstructure. Captures edit target at
Open()soeknows what to dispatch even after scroll.PtyViewgainshidden bool+kind PtyKind(Shell / Edit /
Exec).IsActive()means "alive AND visible";IsAlive()
reports the subprocess state.Hide()is a no-op for Edit / Exec
(transient by design).Show(w,h)re-syncs PTY size on un-hide.- Panel layout constants in one block at the top of
app.go:
panelSidebarWidth,panelDetailHeight,panelHMargin,
panelHSpace,panelVSpace.panelSizes()is pure subtraction. aggregateLogsReadyMsg/resourceFetchedForDrillMsg/
linkDrillFetchedMsgall carry the source item UID for
stale-drop. AppModel'scurrentItemUID()helper centralizes the
lookup.
Known trade-offs
- Cluster-wide Links enrichers (ClusterRole bindings,
StorageClass PVCs, IngressClass Ingresses) issue cluster-wide List
calls. On large clusters this can push the Links tab populate time
into multiple seconds. OrbStack-scale clusters are unaffected. If
it matters in your environment, file an issue — the simplest fix
is making these specific enrichers opt-in via config. - Bare ReplicaSets (RS without a parent Deployment, rare in
practice) still hitnot foundon Owner drill —
enrichPodOwnerhas no Deployment to resolve to. Would need
ReplicaSet as a first-class km8 resource to fix; not in scope
here.