feat(cli): status-aware tracebloc home screen#244
Conversation
Rework the bare-`tracebloc` home screen from a stateless command list into a status-aware landing screen that opens with where you actually stand — signed in? is this machine's secure environment live? — then the commands. Two separate, never-fused axes: sign-in (you) and the secure environment (the machine), because the client heartbeats with its own credential. A green "· Online" prints ONLY when the environment is live locally (chart present + jobs-manager Ready) AND positively confirmed heartbeating to tracebloc; a heartbeat we can't confirm degrades to "· running", never a false green. States: not-signed-in / online / running-not-heard-from / offline / no-environment. Detection is best-effort and bounded so bare `tracebloc` (run constantly, previously zero-I/O) stays snappy: probes run concurrently, each with its own short timeout, all capped by a ~1.5s overall budget; any error/timeout degrades to the softer state and the screen still renders. Logged out does zero cluster/backend I/O. An unreachable cluster caps at the probe timeout (~1.2s) instead of the OS default. Reuses existing seams: config sign-in + cached email, the data commands' namespace binding + release discovery, delete.go's `tb`-alias ownership check, and node allocatable for the compute parenthetical (no `resources` command on develop). `<inv>` echoes the invoked binary name (tb/tracebloc); the doctor path shown is the real `cluster doctor`. HEARTBEAT CREDENTIAL: read via the signed-in user token (ListClients), the only path that exists — there's no login-free in-cluster-client credential path, so the environment line is confirmable only while signed in (documented in realHeartbeat). Adds ui.CheckLine/CrossLine/WarnLine for the locked ✓/✗/⚠ status glyphs. Tests are table-driven and cluster-free: every state, the honesty fallback (can't-confirm → running, not Online), and the timeout/degrade path (a slow probe still renders fast with the softer state). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, starting state Code-review follow-ups on the status-aware home screen: - A provisioned machine is never told "no environment / run the installer". A reachable cluster that doesn't host this release (wrong kube-context, or the client runs on a cluster this kubeconfig doesn't point at) now degrades to a NAMED offline when a client name is cached — the "runs elsewhere" case the sibling data commands explain via binding.explain — instead of the no-environment lie. Only a machine that was never provisioned shows no-env. - The budget-timeout degrade keeps the remembered name. resolveHomeModel now reads the cached client name up front (new rememberedName seam) and fills it whenever the probe surfaced none — including the bctx.Done() path — so a context-ignoring kubeconfig exec-credential plugin (aws eks get-token, etc.) that outlives the render degrades to a named offline, not no-env. - The offline copy is honest for BOTH causes (stopped/unreachable AND reachable-but-release-not-here): "· can't reach it from here — run <inv> cluster doctor", not the bare "offline". - Degraded workload gets its own state + line (homeStarting: "· starting up, not ready yet"), distinct from the live-but-unconfirmed-heartbeat "running, but tracebloc hasn't heard from it" (heartbeat is never consulted when the workload isn't Ready). Both still point at cluster doctor. - Header comment: the budget bounds the RENDER (wall-clock), not the probe goroutines — a context-ignoring probe can outlive it; the buffered channels just keep it from blocking. Invariants held + mutation-proven: no green Online without localLive + beatOnline (honesty), and detection never hangs (collector bails on the budget without probe cooperation). New/changed tests below; make ci green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
👋 Heads-up — Code review queue is at 40 / 30 Above the WIP limit. The team convention is to review existing PRs before opening new work. Open PRs currently in Code review (oldest first):
Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.) |
…copy (Bugbot) Two review findings on the status-aware home screen: - Budget expiry could drop completed probe results: when bctx.Done() and a buffered result are ready in the same select, the pick is random — a probe that finished just as the budget fired could be discarded for the softer default (live release rendering as offline/no-env). The collector (now collectProbes, extracted for a deterministic test) drains both buffers non-blocking on Done, so only probes that truly haven't reported degrade. - The running line said "tracebloc hasn't heard from it" for BOTH heartbeat answers. That claim is only earned when the backend positively reported not-online (offline/pending); a mere couldn't-confirm (backend unreachable, timeout) now says "couldn't confirm it's connected to tracebloc" instead of asserting a backend view we never obtained. homeModel carries confirmedNotOnline so the render stays pure. Both fixes mutation-proven (drain removal and flag collapse each fail their new tests); make ci green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (Bugbot) With no active client cached, realProbeEnv's discovery fell back to the kubeconfig's default namespace and then the cluster-wide scan (binding.allowScan() is true when the binding isn't applied) — so on a shared cluster a colleague's release could render as YOUR live environment, full data menu included. The data commands only run that scan behind a visible retarget note and an explicit user action; the home probe passes p=nil, so even that disclosure was silently dropped, and §7.5's rule (a miss must never silently retarget to some other client) applies doubly to a status screen. Ownership gate: no active-client binding ⇒ report localNoRelease before any cluster I/O — resolveHomeModel renders the honest no-env screen (or a named offline via the remembered-name fallback). Provisioned machines are untouched (binding scopes discovery to the active client's namespace, scan already disabled). Side effect: the common unprovisioned re-entry now does zero cluster I/O. Mutation-proven both ways (gate removed → the unprovisioned probe dials out and lands unreachable; over-gated → the provisioned probe stops short); make ci green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed resources
Design sign-off for the status-aware `tracebloc` home screen (cli#244): fold
the LOCKED reference layout into the shipping renderer — byte-for-byte for the
signed-in/Online screen.
- ui: add Printer.MenuRow (dim · bullet, command padded to width, 4-space gap,
dimmed description) — the locked command-row style.
- home: rework renderHome to the locked layout — two-blank header, greeting by
first name (profile → clean email local-part → omit gracefully), a 30-col dim
rule, the two honest status axes (detection + no-false-Online invariant
unchanged), two command buckets ("Your data" + "Your secure environment",
`delete` folded in, "Manage" dropped), rows via MenuRow, and a dim
`love from tracebloc` sign-off. Not-signed-in + no-env restyled to match.
- doctor: promote `doctor` to a real top-level command; `cluster doctor` stays
as a hidden alias sharing one RunE (single diagnostic code path). The home
screen + env-status lines now read `<inv> doctor`.
- home: gate the `resources` row on the live command tree — absent until #237
wires `resources`, appears automatically once it does (never a hardcode).
- tests: byte-identical lock test against the reference render; name derivation;
resources gating (render + command-tree, both ways); top-level doctor shares
the cluster-doctor path; all existing state/honesty/timeout coverage kept.
make ci green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nts to `doctor` Review follow-ups on the locked home screen (cli#244): - home: greetingName now runs BOTH the profile first name and the email local-part through one clean-token check (letters only, single word, no interior whitespace/newline/control chars) and caps the result at greetingNameMax = 14 runes — otherwise it omits the name. Stops a long local-part from stretching, or a newline-bearing FirstName from SPLITTING, the locked single-line header. The clean short demo name is unaffected, so the byte-identical golden render is unchanged. - doctor: retarget the now-hidden `cluster doctor` remediation hints to the canonical `tracebloc doctor` (the alias still runs, so non-breaking) — client.go x4 (create-fail hint, discovery-fail error, two connect-timeout errors) + cluster/discover.go's ErrNoParentRelease tail. Kept doctor.Run's suffix-strip in lockstep (it trims that exact tail so doctor never tells you to run doctor) and updated the two tests that pinned the old text. - tests: greeting-name bound/sanitize cases (over-long / interior-newline / tab / control-char / multi-word -> omit; cap boundary used); an end-to-end one-line-header guard; a direct hidden-alias assertion (`doctor` visible top-level, `cluster doctor` present but Hidden). Fixed the golden test's stale cmd/hsdemo comment. make ci green; golden render still byte-identical. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1584849. Configure here.
| kubeconfigPath, contextOverride, nsOverride string, | ||
| ) error { | ||
| p.Banner("tracebloc", "cluster doctor") | ||
| p.Banner("tracebloc", "doctor") |
There was a problem hiding this comment.
Doctor ignores active-client binding
Medium Severity
The new home screen resolves environment status with bindActiveClientNamespace and the same discovery rules as data and cluster info, and its status lines tell users to run top-level doctor. runClusterDoctor still loads the kubeconfig without that binding and runs checks only in the context default namespace, so a typical install whose client lives in the cached slug namespace can show one state on the home screen and a conflicting “no client here” diagnosis when the user follows the on-screen doctor hint.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1584849. Configure here.
| m.fullMenu = true | ||
| } else { | ||
| m.state = homeNoEnv | ||
| } |
There was a problem hiding this comment.
Provisioned without cached name misclassified
Medium Severity
Offline versus “no environment” is decided only by whether env.name is non-empty after realRememberedName, which returns ActiveClientName alone. A signed-in profile with active client id and namespace but an empty cached display name is treated as never provisioned on unreachable or no-release probe paths, showing “run the installer” instead of the named offline state the PR guarantees for enrolled machines.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1584849. Configure here.
Lukas spacing tweak (cli#244): the status->first-section gap was 2 blanks (a standalone pre-loop Newline + Section's own leading blank) while the inter- section gaps were only 1. renderBuckets now emits a Newline before each Section, and the standalone pre-loop Newline is dropped — so every section gets exactly 2 blanks above and the status->first-section gap stays 2 (not 3). Mirrors the locked demo's new Newline+Section+Newline-per-bucket render. Golden test updated (+1 blank before "Your secure environment"); make ci green, render still byte-identical to the demo (850 bytes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>


Summary
Reworks the bare
tracebloc(no subcommand) home screen from a stateless command list into a status-aware landing screen: it opens with where you actually stand — are you signed in, and is this machine's secure environment live and connected — then the commands. This is the everyday re-entry point for a returning user, and the screen the installer's upcoming "stop-and-check" gate will hand off to on a healthy re-run.Design — two axes, never fused
· Onlineis printed only when the environment is live locally (cluster reachable + jobs-manager Ready) AND positively confirmed heartbeating (backendEdgeDevicestatus). Anything we can't confirm — heartbeat unknown, backend unreachable, probe timed out — degrades to· running, never a false green. This honesty rule is mutation-tested.States:
not-signed-in·online·running(live, heartbeat unconfirmed) ·starting(workload not Ready) ·offline / can't reach it from here·no-environment.Heartbeat credential — note for the follow-up installer PR
The online status is read via the signed-in user token (
lookupClientStatus→GET /edge-device/). There is no login-free path that authenticates as the in-cluster client itself — the chart'sCLIENT_IDsecret is readable, but nothing presents it to the backend as a credential. Consequence:Fine here — but it's an input for the installer "stop-and-check" gate (which runs before login); we'll decide there how the healthy hand-off behaves when not currently signed in.
Performance / robustness
Bare
traceblocruns constantly; the old screen did zero I/O. Detection here is concurrent and bounded (1.5s overall budget, 1.2s per-probe +RestConfig.Timeout), best-effort, never fatal. Measured: logged-out ~25ms; an unreachable / black-hole cluster caps at ~1.2s and degrades to a named offline rather than hanging. A context-ignoring credential-plugin probe (aws eks get-token/gke-gcloud-auth-plugin/az) can't hang the render — the render bails at the budget while the abandoned goroutine finishes in the background via a cap-1 buffered channel.Scope deliberately deferred (rides the terminology-cleanup wave)
cluster doctor(not a not-yet-existing top-leveldoctor), and notb resourcesuntil that command lands.tbprefix + tip show only when a real installer-placedtbalias exists and you invoked the long name.Code review
Ran an adversarial review before opening. The three critical guarantees — never lies about Online, never hangs, never crashes — all hold and are mutation-proven. Fixed in the second commit:
datacommands'binding.explain).Consciously not addressed here (follow-ups): glyphs/emoji under
--plain(a CLI-wide--plainpolicy call), the 3×config.Load/ re-GETof jobs-manager (cheap + bounded), and GPU counting onlynvidia.com/gpu(documented NVIDIA-only).Test plan
make cigreen: build, vet,gofmt -s,go test -race -cover ./..., errcheck / ineffassign / misspell, schema-check.Checklist
developmake cigreenPR 1 of 2. PR 2 = the installer "stop-and-check" gate (client repo) that recognizes an existing install and hands off to this screen instead of re-provisioning.
Note
Medium Risk
Every bare CLI invocation may run bounded Kubernetes and backend probes, which affects UX timing and status accuracy but not auth or data paths. Doctor path consolidation is low risk; the home screen state machine is the main regression surface.
Overview
Bare
tracebloc(no subcommand) is now a status-aware home screen instead of a static command list: greeting, separate sign-in and secure-environment lines, then command buckets. Detection is concurrent and time-bounded (~1.5s); probes degrade softly and never fail the command.Online only when the client is live locally (jobs-manager Ready) and the backend positively reports online via the signed-in user token; unconfirmed heartbeat stays running, never a false green. An ownership gate skips cluster discovery without an active-client binding so unrelated installs are not shown as yours; remembered client names keep provisioned machines on a named offline path when probes time out or miss the release.
doctoris promoted to a top-level command (home screen and failure hints point here).cluster doctorremains as a hidden alias sharing the sameRunE. User-facing copy and discovery errors switch fromtracebloc cluster doctortotracebloc doctor. Theresourcesmenu row is gated on whether that command is registered on the root.New
uihelpers (MenuRow,CheckLine,CrossLine,WarnLine) support the locked layout; broad table tests cover states, honesty, timeouts, and the doctor alias.Reviewed by Cursor Bugbot for commit 03ad7d3. Bugbot is set up for automated code reviews on this repo. Configure here.