From 561e8d48698d87825cd9c4d3643ee00c20707201 Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Mon, 20 Jul 2026 17:22:29 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(resources):=20clearer=20two-line=20vie?= =?UTF-8?q?w=20=E2=80=94=20"your=20secure=20environment"=20vs=20per-run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tb resources used the labels "This machine" (capacity) and "tracebloc uses" (per training run), which confused: "This machine" is actually the summed capacity of the environment's Ready nodes (misleading on a remote/multi-node cluster), and "tracebloc uses" reads like total usage when it's the per-RUN ceiling. Reframe as two plain aligned lines: Your secure environment is equipped with: 12 CPU · 23.3 GiB A training run is allocated up to: 2 CPU · 8 GiB Do you want to change the allocation? Run `tb resources set` (guided ...). - New ui.Printer.Stat: aligned label/value for full-sentence labels (Field's 14-col key is too narrow here). - The trailing hint now resolves the launcher like the home screen (invokedName + tbAliasAvailable) so it reads `tb ...` on a real install and `tracebloc ...` otherwise — fixing the tb/tracebloc inconsistency Lukas spotted. - Value strips the leading "up to " (the label carries it now). Part of epic tracebloc/backend#1142 / cli#355 (copy consistency). Co-Authored-By: Claude Opus 4.8 --- internal/cli/resources.go | 31 +++++++++++++++++++------------ internal/cli/resources_test.go | 5 +++-- internal/ui/ui.go | 7 +++++++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/internal/cli/resources.go b/internal/cli/resources.go index e54f758b..31660282 100644 --- a/internal/cli/resources.go +++ b/internal/cli/resources.go @@ -2,6 +2,7 @@ package cli import ( "context" + "strings" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,11 +37,11 @@ func newResourcesCmd() *cobra.Command { Short: "Show how much of this machine tracebloc may use", Long: `Shows, in plain terms, how much of this machine tracebloc may use: - • This machine — the CPU and memory the cluster can schedule - • tracebloc uses — the ceiling a single training run may use right now + • Your secure environment — the CPU and memory it can schedule + • Each training run — the per-run ceiling every run may use (cluster-wide) -No Kubernetes concepts, no YAML — one number for the machine and one for -tracebloc's share of it. +No Kubernetes concepts, no YAML — one number for your environment and one for +each training run's share of it. Raise the share with ` + "`tracebloc resources set`" + `. Run with --verbose for the per-node breakdown and the raw values. @@ -125,16 +126,16 @@ func renderResources(ctx context.Context, p *ui.Printer, target *clusterTarget) train.HasGPU = false } - p.Section("This machine") if nodeErr != nil { - p.Field("capacity", "unavailable") - p.Hintf(" couldn't read node capacity: %v", nodeErr) + p.Stat("Your secure environment is equipped with:", "unavailable") + p.Hintf(" couldn't read capacity: %v", nodeErr) } else { - p.Field("capacity", machineLine(machine)) + p.Stat("Your secure environment is equipped with:", machineLine(machine)) } - - p.Section("tracebloc uses") - p.Field("per training run", trainingLine(train)) + // The per-run ceiling is cluster-wide — jobs-manager stamps it on EVERY + // training run (there is no per-run override today). trainingLine prefixes + // "up to "; the label already says it, so trim to avoid saying it twice. + p.Stat("A training run is allocated up to:", strings.TrimPrefix(trainingLine(train), "up to ")) if p.Verbose() { p.Section("Details") @@ -151,7 +152,13 @@ func renderResources(ctx context.Context, p *ui.Printer, target *clusterTarget) } p.Newline() - p.Hintf("Raise tracebloc's share with `tracebloc resources set` (run it on a terminal for a guided walkthrough).") + // Match the home screen's launcher resolution so the hint reads `tb …` on a + // real install (where the `tb` alias exists) and `tracebloc …` otherwise. + cmd := invokedName() + if tbAliasAvailable() { + cmd = binTB + } + p.Hintf("Do you want to change the allocation? Run `%s resources set` (guided walkthrough on a terminal).", cmd) return nil } diff --git a/internal/cli/resources_test.go b/internal/cli/resources_test.go index a045c324..1dbbfc93 100644 --- a/internal/cli/resources_test.go +++ b/internal/cli/resources_test.go @@ -72,7 +72,7 @@ func TestRenderResources_ShowsMachineAndTrainingCeiling(t *testing.T) { t.Fatalf("renderResources: %v", err) } out := buf.String() - for _, want := range []string{"This machine", "8 CPU · 32 GiB", "tracebloc uses", "up to 4 CPU · 16 GiB"} { + for _, want := range []string{"Your secure environment is equipped with:", "8 CPU · 32 GiB", "A training run is allocated up to:", "4 CPU · 16 GiB"} { if !strings.Contains(out, want) { t.Errorf("missing %q in:\n%s", want, out) } @@ -93,7 +93,8 @@ func TestRenderResources_ChartDefaultWhenEnvUnset(t *testing.T) { if err := renderResources(context.Background(), ui.New(&buf, ui.WithColor(false)), resTarget(cs)); err != nil { t.Fatalf("renderResources: %v", err) } - if !strings.Contains(buf.String(), "up to 2 CPU · 8 GiB") { + if !strings.Contains(buf.String(), "A training run is allocated up to:") || + !strings.Contains(buf.String(), "2 CPU · 8 GiB") { t.Errorf("want chart-default ceiling 2 CPU · 8 GiB:\n%s", buf.String()) } } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 22ddac6d..211311db 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -245,6 +245,13 @@ func (p *Printer) Field(label, value string) { p.out(" %s %s\n", p.paint(fmt.Sprintf("%-14s", label+":"), color.Faint), value) } +// Stat prints an aligned "label value" row with a dimmed, fixed-width label, +// so a short block of them lines up. Unlike Field's compact 14-col key, Stat +// fits full-sentence labels (e.g. the resources view's two lines). +func (p *Printer) Stat(label, value string) { + p.out(" %s %s\n", p.paint(fmt.Sprintf("%-42s", label), color.Faint), value) +} + // Action prints an imperative instruction row — a bold verb label and its value, // with no trailing colon: " Open https://…". Used for the device-flow // sign-in steps (Open the URL / Enter the code), where the label is a thing to From 17738e710423a36b22622a129907c5f7634961e7 Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Mon, 20 Jul 2026 17:28:33 +0200 Subject: [PATCH 2/2] refactor(resources): use perRunSize + drop the now-dead trainingLine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugbot: the Stat value called trainingLine then stripped its 'up to ' prefix — a round-trip that duplicated perRunSize (resources_set.go), which already returns the clean 'CPU · mem' ceiling. Use perRunSize directly and delete trainingLine (its only caller was this stripped one). Co-Authored-By: Claude Opus 4.8 --- internal/cli/resources.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/internal/cli/resources.go b/internal/cli/resources.go index 31660282..8c311938 100644 --- a/internal/cli/resources.go +++ b/internal/cli/resources.go @@ -2,7 +2,6 @@ package cli import ( "context" - "strings" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -133,9 +132,9 @@ func renderResources(ctx context.Context, p *ui.Printer, target *clusterTarget) p.Stat("Your secure environment is equipped with:", machineLine(machine)) } // The per-run ceiling is cluster-wide — jobs-manager stamps it on EVERY - // training run (there is no per-run override today). trainingLine prefixes - // "up to "; the label already says it, so trim to avoid saying it twice. - p.Stat("A training run is allocated up to:", strings.TrimPrefix(trainingLine(train), "up to ")) + // training run (there is no per-run override today). perRunSize is the same + // "CPU · mem" string `resources set` shows; the label carries the "up to". + p.Stat("A training run is allocated up to:", perRunSize(train)) if p.Verbose() { p.Section("Details") @@ -172,15 +171,6 @@ func machineLine(m resources.Machine) string { return line } -// trainingLine renders the per-run ceiling: "up to 2 CPU · 8 GiB" (+ GPU). -func trainingLine(t resources.Training) string { - line := "up to " + resources.FormatCPU(t.CPU) + " · " + resources.FormatMem(t.Mem) - if t.HasGPU { - line += " · " + resources.FormatGPU(t.GPUName, t.GPU) - } - return line -} - // firstNonEmptyEnv returns the first present, non-empty value among keys. func firstNonEmptyEnv(env map[string]string, keys ...string) string { for _, k := range keys {