K9s parity ledger
This is the implementation ledger for K9k, a native macOS application whose behavioural reference is K9s. K9k replaces the TUI with native SwiftUI while keeping Kubernetes semantics in the bundled Go helper.
Reference pin
| Item | Value |
|---|---|
| Upstream | https://github.com/derailed/k9s |
| Reference revision | b5d728c068b3584eb8530fb1f6cc51b13a67b39c |
| Revision date | 2026-08-02T09:08:42-06:00 |
| Revision subject | chore(deps): bump golang from 1.26.4-alpine3.24 to 1.26.5-alpine3.24 (#4115) |
| Upstream default branch at fetch | master at the reference revision |
| Current release tag available in fetched repository | v0.51.0 |
| Source location | .vendor/k9s |
| K9s module Go directive | go 1.25.8 (.vendor/k9s/go.mod) |
| K9s stated minimum | Go 1.23.x or later (.vendor/k9s/README.md) |
| Kubernetes modules | k8s.io/{api,apimachinery,client-go,metrics} v0.35.3; cli-runtime and kubectl v0.35.1; apiextensions-apiserver v0.35.3 |
The fetched pin, rather than a README or release-era blog post, is the authority for every row below. K9k's backend uses the same api, apimachinery, and client-go v0.35.3 family; it is not linked to or dependent on the K9s executable.
Status vocabulary and audit boundary
Complete means the user-visible workflow is available through the native app and its bundled backend, with an automated test. Partial means a meaningful code path exists but is not the complete K9s workflow. Blocked means the required backend or UI operation does not yet exist. N/A means a K9s terminal-only presentation choice intentionally has no direct GUI feature.
Statuses reflect the current early K9k baseline: Backend/internal/api.Server dispatches versioned NDJSON requests for context, namespace, discovery, generic resource, scale, events, logs, cancellation, and watch operations; the SwiftUI app supplies a native context/namespace/resource browser, resource inspector, and log viewer. It is intentionally not treated as parity-complete: substantial K9s workflows remain absent. Test entries name required validation; — means none exists in the baseline.
Capability matrix
| Capability | K9s source | K9s behavior | K9k implementation | Status | Tests | Difference |
|---|---|---|---|---|---|---|
| Kubeconfig, auth, and current context | internal/client/config.go (Config, RESTConfig, SwitchContext); client.go (APIClient.SwitchContext) | Resolves kubeconfig and active context, creates clients, and resets state when context changes. | Backend/internal/kube/cluster.go (Cluster.New, reload, SelectContext) uses clientcmd and creates typed, dynamic, discovery, and metrics clients locally before one atomic successful commit. The helper serializes queued selections and generation-fences every watch/log/exec/attach/forward from the old context; Swift invalidates matching local ownership before switching. Native Settings graphically lists context references, clusters, users, active state, and default namespaces; its redacted relationship visualizer validates named Cluster/AuthInfo links without transmitting endpoints or credential fields. Confirmed namespace/duplicate/rename/inactive-delete workflows remain separate. | Partial | Atomic failure preservation, queued-switch stream barrier, late-event suppression, context-mutation, and redacted relationship-inspection tests. | Swift never reads kubeconfig or credentials. |
| Context listing and management | view/context.go (Context, useContext); client/config.go (Contexts, DelContext, RenameContext) | Lists, selects, renames, and deletes contexts. | context.list, context.select, context.rename, and context.delete are dispatched. The graphical context manager confirms renames and inactive-context removal, while active contexts must first be switched away from. | Partial | List/select and context-mutation IPC tests. | GUI uses a context picker/manager rather than ctx. |
| Namespace selection | view/ns.go; config/config.go (SetActiveNamespace); client/client.go (ValidNamespaceNames) | Lists namespaces, supports all namespaces/favourites, and persists active namespace. | namespace.list feeds a toolbar picker and All Namespaces maps to an empty namespace; selected namespace and locally persisted favourites are available in the toolbar and Settings. | Partial | Namespace list/selection/favourite persistence. | Native picker replaces numeric namespace shortcuts. |
| Discovery and GVR/GVK resolution | client/gvr.go, gvrs.go; dao/registry.go (Meta.LoadResources) | Discovers API resources, resolves aliases/GVRs, and handles built-in plus dynamic resources. | discovery.list returns group, version, resource, kind, scope, and short names to the native catalog; parseGVR preserves identity. | Partial | Fake discovery, including partial failure. | GUI groups discovered resources instead of command aliases. |
| Generic resource list | dao, watch, model, view/browser.go (Browser) | Continuously lists and displays typed and dynamic resources with K9s column renderers. | resource.listPage dynamically reads any discovered GVR in bounded continuation pages. The native Table selects a resource-native operational layout when no K9s view overrides it: Pod placement/IP, Service type/address, Node runtime identity, controller replica progress, Job/CronJob state, ConfigMap/Secret type/immutability, and RBAC binding targets. Unknown/dynamic resources use only discovery identity (name, scope, API version, age) and a schema-free condition-derived status; no CRD spec is guessed and no full object is placed on list/watch events. Every non-metadata cell is a scalar bounded list/watch projection; array and sensitive detail stays on the selected-object inspector. Native list/watch starts are generation-scoped, so a delayed response from a previous sidebar selection cannot replace the currently selected resource table. | Partial | Fake dynamic-client list, condition-summary tests, bounded-page protocol tests, table UI, and live rapid-navigation smoke. | Native Table replaces terminal table. |
| Resource watch | internal/watch; model; view/browser.go | Maintains list/watch views and refreshes as objects change. | The browser pages resource lists in bounded 250-object, lean-projection snapshots and passes the snapshot resourceVersion into resource.watch, closing the list-to-watch lost-event gap. Pages append only their new rows/index entries instead of repeatedly rebuilding the growing filtered snapshot; updates use indexed replacement and full raw objects are fetched only for a selected inspector. An unexpected close performs bounded delayed relist/reconnect attempts, then surfaces an explicit Refresh recovery path. | Partial | Add/modify/delete stream lifecycle, bounded-page protocol tests, compact-watch revision tests, and live native Kind smoke. | Watches stream to Swift, never from terminal output. |
| Details, YAML, raw fidelity | view/browser.go (viewCmd); view/yaml.go; model/yaml.go | Opens structured YAML/raw object views without losing metadata. | The inspector renders sorted, syntax-colored, selectable raw object JSON with Copy; selected Pods expose placement/network/container readiness/restarts, Services expose selector/ports/load-balancer ingress, Nodes expose scheduling/taints/addresses/system identity, ConfigMaps show key/count metadata, Secrets show only key/count metadata in Overview, RBAC bindings expose their role and subjects, and controller inspectors include rollout accounting. Unknown/dynamic resources get a schema-free identity/status summary without assuming a CRD schema. Selection hydration starts immediately and is cancellation/identity-bound; prepared overview content survives repeat reveals, while only an unprepared hidden selection defers its large Form until the native transition settles (and Reduce Motion skips that defer). manifest.get opens a canonical YAML editor with server-managed fields/status removed and preserves the selected UID for later validation/apply. | Partial | Get/YAML round-trip; generic-condition summary tests; manifest canonicalization, native editor validation, and repeated Computer Use inspector-tab/reveal checks. | Native inspector/editor replaces K9s pages. |
| Filter, label selector, search, sorting | view/table.go; browser.go (SetFilter, SetLabelSelector); view/cmd | Supports text filtering, labels, column sorting, marks, and history. | Native text search, table sort/multiselection, and a Filter Resources sheet for Kubernetes label and field selectors exist. Selectors apply equally to the initial list and its live watch, are scoped to their current GVR, and can be cleared inline. Operators can name and save bounded local queries per Kubernetes context/GVR, then restore their namespace and selectors through the same generation-gated list lifecycle; K9s's specialised column renderer set remains incomplete. | Partial | Selector propagation, selector-scoping, saved-query persistence/restore and UI query tests. | Native search/multiselection replace command/filter modes. |
| Generic mutation: patch/edit/delete | view/browser.go (editCmd, deleteCmd); live_view.go; ui/dialog | Opens editor, confirms destructive work, updates/deletes selection. | resource.patch and resource.delete are dispatched; delete is confirmation/read-only guarded and preflighted by SelfSubjectAccessReview. The native manifest editor validates canonical YAML through mandatory server-side dry run; Apply is separately confirmed, UID-pinned, and access-reviewed before its non-forced SSA write. | Partial | Patch/delete integration, permission/confirmation UI, manifest canonicalization/dry-run/identity IPC tests, and Kind editor validation. | Native sheets/alerts, not $EDITOR. |
| Workload views | view/{workload,dp,sts,ds,rs}.go; registrar.go | Specialises Deployments, StatefulSets, DaemonSets, ReplicaSets, and aggregate workloads. | Generic discovery/list represents these GVRs with controller-aware table summaries (Ready, Progressing, Scaled to 0, Succeeded, Failed) and native rollout details/actions. | Partial | Workload summary unit tests and fixture/table tests. | Native grouped navigation replaces commands. |
| Pods and containers | view/{pod,container}.go; registrar.go | Provides pod/container details, logs, shell, attach, transfer, image, ownership, and port-forward actions. | Generic dynamic pod access, native follow-mode logs, a cancellable loopback-only Pod port-forward sheet, permission-checked terminal/attach, and a native Pod File Transfer sheet. Transfer runs tar directly through client-go pods/exec: 48 KiB binary-safe base64 frames, explicit stdin EOF, RBAC review, read-only blocking, container choice, and cancellation. K9k produces/accepts only bounded (1 GiB) USTAR archives containing regular files/directories; both remote paths and returned archive paths reject traversal, symlinks, links, and special files, and downloads never overwrite a local destination. The inspector shows container image references. | Partial | Exec stdin-EOF/binary protocol test, Pod summary/container extraction, log-stream fixture, live loopback tunnel, and interactive Kind shell fixture. | Native save/import panels replace terminal transfer prompts; a container must provide tar. |
| Logs | view/log.go (Log, bindKeys); logs_extender.go; model/log.go; dao/log.go | Streams logs with container choice, previous/follow/filter/timestamp/wrap/autoscroll/save/copy controls. | logs.open streams logs.data/logs.closed through client-go; the native log sheet selects regular, init, or ephemeral containers with clear type labels, and toggles previous (regular containers only), timestamps, and follow before reload. It chooses a bounded 100–10,000-line history and optional since window, filters locally, wraps or horizontally scrolls lines, copies visible lines to the macOS pasteboard, and saves visible output through a native Save panel. The helper caps direct callers at 10,000 requested lines / 16 MiB and the app retains only the newest 10,000 streamed lines with an explicit dropped-line notice. | Partial | Bounded log-parameter protocol tests, stream/cancellation tests, and Kind completed-Job log check. | Native log viewer replaces terminal controls. |
| Exec, shell, attach, debug | view/exec.go; pod.go (shellCmd, attachCmd); container.go; xray.go | Uses Kubernetes exec/attach and K9s shell/debug workflows. | exec.open provides direct client-go SPDY Pod exec with ordered binary stdin, base64 output events, resize, cancellation, and exit results. attach.open uses the same protected VT transport against pods/attach, with a separate pods/attach RBAC review and multi-container picker. A confirmed Debug Container sheet calls the native ephemeralcontainers subresource with an image and target-container choice; Pod Terminal includes ephemeral containers after refresh. The native terminal uses the pinned SwiftTerm VT/xterm AppKit component for ANSI colors, Unicode, scrollback, selection/copy-paste, keyboard input, and terminal resize. | Partial | Exec/attach/debug lifecycle and validation tests; live Kind one-shot and interactive shell test; native terminal smoke. | Native terminal surface; no TUI suspension. |
| Port forwarding | view/pf_extender.go; pf.go; internal/port | Starts/stops forwards, tracks active ports/URLs, supports benchmark handoff. | portforward.open is a cancellable direct client-go SPDY tunnel; native Pod and selector-backed Service actions bind loopback only, display their endpoint, copy the HTTP endpoint, and open it in the default browser. Service forwarding resolves one matching Pod and numeric/named target port before using the same Pod tunnel; selector-less or unresolved services fail clearly. Multiple forwards remain active after their setup sheet closes and a native manager lists each endpoint with Copy/Open/Benchmark/Stop controls. Unexpected closure gets a visible bounded three-attempt backoff; a Service retry resolves its current backing Pod again, while Stop and context changes cancel both the live stream and pending retry so a listener never crosses contexts. Benchmark runs bounded, cancellable native HTTP GET requests solely through a selected K9k-owned loopback tunnel and reports completion, failures, rate, mean latency, and total time. The manager retains only the most recent 32 local summaries in memory, offers a native rate chart and aggregate summary, and exports those summary-only records only through an explicit JSON Save panel. | Partial | Ready/error/cancel protocol tests, stream-cap tests, Kind HTTP-through-tunnel check, and native Kind Service-forward benchmark smoke (200/200 successful). | Native forward list/inspector and basic GET benchmark; not the complete K9s benchmark configuration surface. |
| Metrics and pulse observation | client/metrics.go (MetricsServer); view/{pulse,cluster_info}.go; model/pulse.go | Reads metrics-server node/pod/container data and renders cluster load. | metrics.list reads direct client-go metrics.k8s.io/v1beta1 pod, container, and node usage with canonical quantities; the native inspector shows usage, retries, or explicit unavailable state and can open a selected Pod/Node drilldown in Pulse. Pulse issues no more than one concurrent node/pod request pair, samples at a five-second cadence while open, retains only 60 in-memory samples, preserves usable partial results with source diagnostics, and exports a user-initiated JSON/CSV history or real current-window PNG. | Partial | Pod/node conversion tests; partial/unavailable diagnostics; bounded-history JSON/CSV encoder checks; Metrics Server available/unavailable integration; native Pulse export presentation. | Native charts replace terminal gauges; no persisted background history, alerts, or K9s layout parity yet. |
| Events and diagnostics | view/event.go; client/gvrs.go (EvGVR); watch | Shows Kubernetes events and ties diagnostics to resources. | resource.events retrieves related events through the typed client and the inspector displays a native timeline. While an inspected resource remains selected, K9k refreshes that bounded related-event snapshot every five seconds; no event watch connection is held open. | Partial | Server event tests and Kind fixture diagnostics check. | Native inspector timeline with bounded polling, rather than terminal event rows. |
| Scale, restart, rollout | view/scale_extender.go; restart_extender.go; workload viewers | Scales supported workloads and restarts with K9s semantics. | Native Scale sheet supports selected Deployments, StatefulSets, ReplicaSets, and ReplicationControllers; guarded Restart updates the template timestamp on selected Deployments, StatefulSets, or DaemonSets. A confirmed CronJob Trigger action checks jobs.create, then builds an owned Job from the live CronJob template through typed client-go, following K9s manual-job metadata semantics. Workload inspectors summarize rollout progress, replica counts, revisions, and conditions. rollout.history UID-verifies the selected apps/v1 workload and returns a capped metadata-only native revision browser: Deployment/ReplicaSet history lists owned ReplicaSets, while StatefulSet/DaemonSet history lists owned ControllerRevisions read-only. Only an inactive Deployment-owned ReplicaSet is rollback-eligible; the revision sheet confirms intent, rechecks deployments.patch, then calls the existing helper rollback which revalidates the ReplicaSet UID, owner, and inactive state. Historical PodTemplates are never returned. | Partial | Scale validation/error tests, rollout-history protocol/typed-client tests, CronJob server/template tests, native rollout-summary smoke, and Kind Deployment scale/restore/restart/ReplicaSet rollback smoke. | Native form/RBAC confirmation replaces terminal prompts. |
| Node operations | view/node.go (drainCmd, toggleCordonCmd, sshCmd, yamlCmd) | Views node pods/YAML and manages cordon, drain, and shell/SSH. | Native node actions cordon/uncordon through a patch access review. The Drain sheet requires a cordoned node plus explicit confirmation, lists outcome categories, calls only the Kubernetes policy/v1 eviction API, retains DaemonSet/mirror Pods, honors PDBs, and never force-deletes. Node Shell uses a deliberately configured, pre-existing trusted DaemonSet namespace/name/container; the direct client verifies controller UID, exact selected-node placement, Running state, and a unique Pod before SSAR-gated SPDY exec. It never creates a privileged shell/debug Pod or guesses an image/selector. Host SSH is an explicit /usr/bin/ssh -tt handoff: the operator supplies one config alias or user@host, K9k uses their existing ~/.ssh/config and agent with BatchMode, stores no credential, passes no Kubernetes/cloud environment, and never interpolates a local shell command. | Partial | Drain validation/confirmation IPC test; Node Shell target-resolution protocol and typed-client ownership/ambiguity tests; native Host SSH input validation/build; Kind cordon + non-destructive blocked-drain smoke. | Native progress, Pod terminal, and explicit host SSH handoff. |
| Ownership and reverse relationships | view/owner_extender.go; reference.go; resource-specific refCmd; internal/xray | Traverses owners, used-by references, and topology. | relationships.get returns a bounded direct-client graph for owner refs, workload dependents, label selectors, Service routing, and declared ConfigMap/Secret/PVC references. The native Relationships sheet makes direction explicit, reports partial-RBAC warnings, and opens a resolved node in its exact discovered GVR via a focused metadata.name list; the destination retains the normal browser action surface and Back/Forward history. | Partial | Deterministic graph unit tests and Kind topology smoke; manual native owner-jump acceptance. | Native relationship list/inspector replaces tree navigation. |
| XRay topology | internal/xray/{generic,pod,dp,sts,ds,rs,svc,ns,sa,container}.go; view/xray.go | Builds live supported-resource topology and node actions. | Native XRay recursively resolves owner/reference and workload-routing relationships up to three hops, including explicit Service backends, HPA scale targets, generic ObjectReferences, and RBAC Role/ServiceAccount declarations. It renders every directed edge in a native navigable topology and opens either endpoint in its exact discovered browser GVR. A visible-sheet-only opt-in 20-second refresh keeps operational topology current without an unbounded background watch; snapshot replacement is generation-gated. Kubernetes reads are bounded to 160 objects per supported candidate kind, 640 inventory objects, 64 direct declarations/object, 48 direct resolutions, 96 nodes, and 240 edges; every cap is shown as a truncated snapshot warning. It does not yet stream topology deltas or infer arbitrary CRD relations. | Partial | Deterministic recursive/boundary/reference-extraction graph unit tests and Kind topology smoke. | Native relationship list/inspector replaces tree navigation. |
| Registered core/apps/batch/RBAC/CRD/Helm routes | client/gvrs.go; view/registrar.go | Registers specialised resource viewers and K9s virtual views. | Discovery/generic access cover server-advertised resources; specialised registration is absent. | Partial | Discovery fixtures for each API family. | Dynamic browser remains essential for CRDs. |
| RBAC inspection and access checks | client/client.go (CanI); view/{rbac,policy,user,group,sa}.go | Evaluates access and shows roles, bindings, rules, subjects. | rbac.check performs direct SelfSubjectAccessReview for the active kubeconfig identity. Native inspectors summarize Role/ClusterRole rules, RoleBinding/ClusterRoleBinding role references and subjects, plus ServiceAccount token/image-pull settings. A graphical “Check Access” sheet is the native kubectl auth can-i equivalent: it can prefill a selected resource or test any discovered GVR, verb, namespace, name, and subresource without changing resources. Native Effective RBAC inspects an explicit ServiceAccount, User, or Group through bounded direct RoleBinding/ClusterRoleBinding traversal and resolves readable Role/ClusterRole rules with partial-RBAC and truncation warnings. It never impersonates, claims an authorization decision, or infers membership. | Partial | Allowed, denied, malformed-request, and API-error review tests; deterministic effective-binding/rule/partial-read tests; native RBAC inspector and live Kind access-check smoke. | Surface results before mutation; retain explicit forbidden errors. |
| Helm releases/history | view/{helm_chart,helm_history}.go; internal/helm | Uses Helm v3 for chart/release browsing and history. | Native Helm Releases navigation opens standard Helm v3 Secret storage (owner=helm) through the direct dynamic client. The revision timeline is bounded and newest-first. Selecting a valid revision loads a read-only projection of the stored release: Chart.yaml metadata by default, then rendered manifest, NOTES.txt, and configured values only after a visible acknowledgement. Native sheets expose an explicitly confirmed SDK rollback to a pinned earlier revision; typed-name-confirmed, history-preserving SDK uninstall; and a sensitive, server-dry-run local packaged-chart upgrade. The helper receives only bounded user-selected .tgz bytes, rejects unsafe archive entries, requires a digest of the reviewed exact chart/values/current revision before confirm, rechecks release identity, access-reviews required Secret-driver verbs, blocks stale/pending states and read-only mode, and never invokes the Helm CLI or hand-edits Helm storage. Hooks remain enabled; actions bound individual API calls but do not wait for workload readiness. Remote/OCI sources and non-Secret storage remain absent. | Partial | Metadata/history, lifecycle confirmation/stale/RBAC protocol fixtures, bounded chart/digest upgrade protocol test, storage-envelope, identity-gate, sensitive-confirmation unit tests. | Native inspection sheet with explicit sensitive disclosure, local chart plan/review, and safe rollback/uninstall/upgrade controls. |
| Image scan/vulnerabilities | view/{img_scan,vul_extender}.go; internal/vul; config/scans.go | Runs configured scanners and annotates workload/image views. | A native opt-in adapter extracts unique container, init-container, and ephemeral-container image references from selected Pods and workload templates. The operator supplies an absolute local scanner executable plus one no-shell argument per line, including {{image}}; K9k neither bundles/downloads/guesses a scanner nor auto-runs it. Each scan is separately confirmed, runs with a minimal environment that omits inherited Kubernetes/cloud credential variables, supports cancellation, limits execution to 120 seconds per image and output to 512 KiB, and redacts common credential-shaped output before display. Inline credentials in the stored template are rejected so scanners must use their own Keychain/configuration. Operators can opt into one documented normalized format—Grype -o json—which renders only CVE/advisory ID, severity, package/version, and available fixed versions beside the retained raw output; invalid JSON fails closed with a clear diagnostic. K9s imageScans enable/exclusion settings are intentionally not treated as a permission to execute a local tool. | Partial | Configuration validation/parser typecheck and native build; scanner process execution is intentionally not automated in fixtures. | No embedded Grype database, background scanning, annotations, or CVE detail browser. |
| Manifest directories | view/dir.go; dao/dir.go | Browses manifests and applies, deletes, edits, views, and follows resources. | The native Manifest Workspace recursively presents a local YAML hierarchy, previews a selected file or subtree, and applies only that selected scope through mixed-GVR live discovery. Every bounded batch is strict-parsed and entirely dry-run before non-forced server-side apply. A single selected file can compare against its current live object: the helper resolves its GVR, fetches the live UID itself, then presents the same UID-pinned SSA diff as the editor. After a confirmed apply, each exact Kubernetes result can be followed in the normal live browser or preflighted for UID-preconditioned deletion; delete RBAC, existence, kind, full scope, and UID all pass for the complete batch before the first delete. Apply/delete are visibly non-transactional. General filesystem editing/watch remains absent. | Partial | Manifest validation/identity/diff/delete/follow tests, mixed-GVR discovery/dry-run/duplicate tests, workspace-diff protocol fixture, native directory hierarchy and recursive import smoke. | Native workspace/editor/import/diff/follow/removal; no file watcher. |
| Aliases, hotkeys, custom views/jumps, plugins | config/{alias,hotkey,views,jumps,plugin}.go; view/actions.go; custom_jump.go | Loads per-context config, aliases, shortcuts, columns/views/jumps, and scoped plugins. | config.summary parses declarations and diagnostics. Settings opens a validated, confirmed, SHA-guarded editor for K9s customization files including jumps.yaml; aliases resolve in the catalog. Native resource tables now honour a matching GVR/resource and namespace-regex custom view as actual columns: standard K9s symbols (NAME, NAMESPACE, STATUS, AGE, KIND, UID, RV, LABELS, and supported Pod/Service/workload symbols), escaped metadata.labels keys, and safe `HEADER:.simple.dot.path | flags` projections use the bounded list/watch projection. Hidden or unsupported renderer-only/array/filter definitions are omitted rather than shown as empty placeholder columns. Configured custom jumps navigate to discovered target GVRs with source namespace/default/all handling, supported metadata label/annotation templates, and native Kubernetes label/field selectors applied to list and watch calls. Scoped K9s plugins appear only for a selected matching resource, expand K9s placeholders in both command and args, quote separate args safely, show the complete host command, require confirmation, and stream stdout/stderr to a native output sheet. | Partial | Config parser compatibility tests, custom-column model/build validation, and native settings/plugin-runner smoke. |
| Read-only/dangerous-action safety | config/k9s.go (IsReadOnly); view/actions.go; ui/dialog | Hides/blocks dangerous actions and confirms mutations. | A locally persisted native toggle owns one app-wide desired policy generation. Every primary or secondary CoreClient synchronizes it before an ordinary request, including a fresh/relaunched helper; the process-local helper then rejects config/context writes, Kubernetes mutations, and exec/attach/port-forward opens before calling Kubernetes. UI confirmation remains visible. | Partial | Backend policy/classification tests and deterministic fake-helper replay tests cover fresh primary, fresh secondary, toggle, and relaunch ordering. | Visible risk state, not hidden hotkeys. |
| Exports and clipboard | view/{screen_dump,table_helper,clipboard}.go; table.go | Saves tables/logs/screens and copies selected data. | The native resource browser copies visible rows as TSV and saves its filtered table as JSON, CSV, or TSV through macOS Save panels; log copy/save and raw JSON copy are also available. Save Current Window as PNG captures the actual K9k WindowServer surface, including the current sidebar, browser, inspector, selection, system appearance, and native window chrome; it is not a fabricated off-screen SwiftUI rendering. | Partial | Export-format and pasteboard tests; app build verifies the native WindowServer capture path. | Native save panels/pasteboard. |
| Benchmarks | view/{pf,benchmark}.go; internal/perf; config/benchmark.go | Runs/cancels port-forward benchmarks and shows results. | Active K9k loopback forwards expose a native HTTP Benchmark sheet: configurable path, 200-request/concurrency-1 defaults matching K9s, bounded request count, cancellation, and completed/successful/failed/rate/mean-latency/elapsed results. It uses an ephemeral, cache-bypassing URLSession directly rather than requiring hey, validates loopback before every run, and intentionally cannot target a non-K9k listener. The current app session retains 32 summary-only local runs with an in-sheet Swift Chart and aggregate rate/latency summary; the operator can explicitly export the in-memory JSON history, while K9k persists neither benchmark results nor any response content. | Partial | Native loopback-forward benchmark smoke; cancellation/result model validation; bounded-history/export model build validation. | Native per-forward form/results instead of terminal output/files. |
| Help, command navigation, history | view/{app,command,help,page_stack,picker}.go; view/cmd | Routes views, maintains history, and shows key hints/help. | The Command Palette searches discovered resources, K9s aliases, recent native destinations, and Back/Forward/help commands. K9k persists a bounded local resource-list history including only GVR identity and list scope; it is filtered to the active context, never switches contexts automatically, and restores namespace/selectors with a native ⌘[/⌘] toolbar workflow. A native Navigation Help sheet documents the available macOS commands and safety semantics. | Partial | Native build and manual Back/Forward/recent navigation smoke; dedicated navigation-state UI tests remain absent. | Standard macOS navigation/menus/search supersede terminal prompts. |
Resource registration trace
internal/view/registrar.go is the terminal-view registration authority: namespaces, events, pods, services, nodes, secrets, priority classes, config maps, service accounts, PVCs, workloads, contexts, containers, scans, port forwards, screen dumps, benchmarks, aliases, references, pulse, users/groups, CronJobs/jobs, CRDs, and Helm. internal/client/gvrs.go additionally declares PV, endpoints, endpoint slices, HPA, network policies, storage classes, PDB, ingresses, metrics, XRay, policy, and RBAC bindings. K9k must retain discovery for extensions/CRs rather than limiting itself to this fixed list.
Parity acceptance rule
A row moves to Complete only when it has: (1) a versioned helper operation with structured errors and cancellation where streamed; (2) direct Kubernetes libraries, not a required external kubectl; (3) an accessible native SwiftUI workflow; (4) read-only/RBAC/confirmation-aware mutation; and (5) automated success/error/cancellation validation.