Skip to content

feat(cli): status-aware tracebloc home screen#244

Open
LukasWodka wants to merge 7 commits into
developfrom
feat/home-screen-status
Open

feat(cli): status-aware tracebloc home screen#244
LukasWodka wants to merge 7 commits into
developfrom
feat/home-screen-status

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

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.

  • Before: a flat "Get started" list, identical for everyone, zero I/O.
  • After: greeting → sign-in linesecure-environment line (status + machine compute) → command buckets → tips → footer.

Design — two axes, never fused

  • Sign-in (you) and secure environment (the machine) are two separate lines. The client heartbeats to tracebloc with its own credential, so the environment can be Online while you're logged out — fusing them would misreport.
  • · Online is printed only when the environment is live locally (cluster reachable + jobs-manager Ready) AND positively confirmed heartbeating (backend EdgeDevice status). 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 (lookupClientStatusGET /edge-device/). There is no login-free path that authenticates as the in-cluster client itself — the chart's CLIENT_ID secret is readable, but nothing presents it to the backend as a credential. Consequence:

  • Signed in → the environment line is fully honest (Online / running / starting / offline).
  • Logged out → the screen is the minimal "sign in first" variant and shows no environment status (faithful to the locked home-screen spec).

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 tracebloc runs 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)

  • No command renames. The buckets list only real, working commandscluster doctor (not a not-yet-existing top-level doctor), and no tb resources until that command lands.
  • The tb prefix + tip show only when a real installer-placed tb alias 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:

  • [should-fix] a provisioned machine on a reachable-but-wrong-context cluster was told "no environment — run the installer" → now the honest "can't reach it from here" (parity with the data commands' binding.explain).
  • [should-fix] a probe blocking past the budget dropped the remembered client name → seeded so it degrades to a named offline.
  • [nit] split the degraded-workload copy ("starting up, not ready yet") from heartbeat-unconfirmed ("hasn't heard from it").
  • [nit] comment accuracy (the budget bounds the render wall-clock, not the goroutines).

Consciously not addressed here (follow-ups): glyphs/emoji under --plain (a CLI-wide --plain policy call), the 3× config.Load / re-GET of jobs-manager (cheap + bounded), and GPU counting only nvidia.com/gpu (documented NVIDIA-only).

Test plan

  • make ci green: build, vet, gofmt -s, go test -race -cover ./..., errcheck / ineffassign / misspell, schema-check.
  • Table-driven tests cover every state + the honesty fallback (unconfirmed → running, never Online) + the timeout/degrade path (a context-ignoring probe still renders within budget). The honesty and no-hang guards are mutation-proven.
  • Live-verified on the built binary (logged-out, offline, and timing behavior).

Checklist

  • Targets develop
  • make ci green
  • Tests added/updated + mutation-checked
  • No command renames / scope creep
  • Human review (Bugbot + reviewer on Monday)

PR 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.

doctor is promoted to a top-level command (home screen and failure hints point here). cluster doctor remains as a hidden alias sharing the same RunE. User-facing copy and discovery errors switch from tracebloc cluster doctor to tracebloc doctor. The resources menu row is gated on whether that command is registered on the root.

New ui helpers (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.

LukasWodka and others added 2 commits July 12, 2026 08:59
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>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

👋 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.)

Comment thread internal/cli/home.go Outdated
Comment thread internal/cli/home.go Outdated
…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>
Comment thread internal/cli/home.go
LukasWodka and others added 3 commits July 12, 2026 11:37
… (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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ 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.

Comment thread internal/cli/doctor.go
kubeconfigPath, contextOverride, nsOverride string,
) error {
p.Banner("tracebloc", "cluster doctor")
p.Banner("tracebloc", "doctor")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1584849. Configure here.

Comment thread internal/cli/home.go
m.fullMenu = true
} else {
m.state = homeNoEnv
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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>
@LukasWodka LukasWodka requested a review from aptracebloc July 12, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant