Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,36 @@ or hex elsewhere. The tone table (`internal/ui/ui.go`) maps each role:
| Error βœ– | `Errorf` (`toneErr`) | red `#f64c4c` | bold glyph |
| Label : value | `Field`, `Stat` (`toneLabel`) | dim neutral | β€” |

**No emoji.** The lime `●` is the online indicator (not 🟒).
**Emoji are welcome** β€” used with intent, for warmth (πŸ‘‹ greeting, πŸ’š sign-off,
πŸš€ sent) and for status (🟒 online, 🟑 starting, πŸ”΄ offline, ⚠ caution). They're a
brand touch, not policed by the guard β€” just don't overuse them.

The engine renders exact 24-bit hex on truecolor terminals, the **deep shade**
(`#01637a` / `#578c2b`) on light backgrounds, the nearest ANSI-16 otherwise, and
nothing when colour is off (`NO_COLOR` / non-TTY / `TERM=dumb` / `--plain`). The
exact brand SGR is pinned by `internal/ui/brand_tones_test.go`, so a drift in the
tone table fails CI. The installer mirrors this in `scripts/lib/common.sh`.

## Guided-prompt spacing

Interactive flows (the `tb data ingest` questionnaire, and any future guided
flow) use one uniform rhythm so every question reads the same:

- **One blank line before each question header** β€” a `Step N of M Β· <question>`
(`PromptStep`) or an unnumbered refinement/confirm header (`Section`). The
header method emits this leading blank itself.
- **One blank line between the header and its supporting text** (the hint /
examples / option list), when there is any.
- **One blank line before the `?` prompt line.** With no supporting text, that
single blank sits directly between the header and the prompt.
- **A result that belongs to an answer attaches to it with no blank** β€” e.g. the
`βœ” Found a CSV table …` sniff echo sits directly under the path answer.

So: `header β†’ blank β†’ [supporting text β†’ blank] β†’ ? prompt`. The prompt line is
answer-only (`? train`); the question lives in the header (the prompter runs
`bare`), never repeated on the `?` line. Keep it uniform β€” don't hand-tune the
spacing of individual questions.

## Terminology

Source of truth: the docs repo `TERMINOLOGY.md`. In user-facing output:
Expand All @@ -58,8 +80,8 @@ word in output text only.
## What's enforced vs reviewed

`scripts/check-style.sh` (CI Lint job, blocking) catches the **mechanical**
violations: hardcoded brand colour outside `internal/ui`, status emoji, and
`workspace` in user-facing text. Run it locally with `make check-style` (also part
violations: hardcoded brand colour outside `internal/ui`, and `workspace` in
user-facing text. Run it locally with `make check-style` (also part
of `make ci`) or directly: `bash scripts/check-style.sh`.

It can't police **judgement** β€” using the right *role* for a token (a command in
Expand Down
595 changes: 595 additions & 0 deletions internal/cli/copy_catalog_test.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/cli/coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestPrintPushPreflight_RendersKeyFacts(t *testing.T) {
// TestPrintClusterSummary_VerboseOnly). The local summary shows regardless.
var buf bytes.Buffer
p := ui.New(&buf, ui.WithColor(false), ui.WithVerbose(true))
printLocalSummary(p, layout, spec)
printLocalSummary(p, layout, spec, true)
printClusterSummary(p, release, pvc)
out := buf.String()

Expand Down
12 changes: 11 additions & 1 deletion internal/cli/data_ingest_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ Exit codes:
interactive := !noInput && !outputJSON && isInteractiveTTY()
var pr prompter
if interactive {
pr = surveyPrompter{}
// bare: the guided flow prints each question as a step header
// (PromptStep), so the prompt line itself stays answer-only.
pr = surveyPrompter{bare: true}
}
// In --output-json mode, human output goes to stderr so
// stdout carries only the JSON result.
Expand Down Expand Up @@ -345,6 +347,14 @@ type runDataIngestArgs struct {
Prompter prompter
TaskSet bool

// ReviewShown records whether the guided flow rendered the pre-confirm
// Review (it only does when it actually prompted for something). It gates
// the duplicate "Ingest settings" block in printLocalSummary: suppress it
// only when the Review already showed those fields. A fully flagged run β€”
// even on a TTY with a Prompter set β€” prompts nothing, shows no Review, so
// it must still print the settings once (like the non-interactive path).
ReviewShown bool

// ChangedFlags records which CLI flags were EXPLICITLY set
// (cmd.Flags().Changed), decoupling "was it passed" from "is its value
// non-zero" β€” the value alone can't tell `--number-of-keypoints 0` (an
Expand Down
33 changes: 22 additions & 11 deletions internal/cli/data_ingest_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,8 @@ func resolveLocalInput(out, errOut io.Writer, a *runDataIngestArgs) (layout *pus
}

a.Printer.Newline()
a.Printer.Para(strings.TrimSpace(`
This ingests a dataset so models can train on it. Your files never leave your
own infrastructure β€” tracebloc copies them into your secure environment's storage,
checks them, and loads them into a table your training runs read from. Other
collaborators can train against that table without ever seeing the raw files.`))
a.Printer.Hintf("Learn more: https://docs.tracebloc.io")
a.Printer.Para("Ingest datasets to your secure environment.")
a.Printer.Hintf("For help: https://docs.tracebloc.io/create-use-case/prepare-dataset")

// 0. Guided mode: prompt for any missing core inputs before
// validation. Flags already provided win; non-TTY / --no-input
Expand Down Expand Up @@ -484,15 +480,26 @@ collaborators can train against that table without ever seeing the raw files.`))
return nil, nil, nil, false, perr
}

printLocalSummary(a.Printer, layout, spec)
// Interactive runs that prompted already echoed name/task/intent/label in
// the pre-confirm Review, so suppress the duplicate "Ingest settings" block
// here. A run that showed no Review (non-interactive, OR a fully flagged
// run on a TTY) still shows it β€” gate on whether the Review rendered, not on
// whether a Prompter exists.
printLocalSummary(a.Printer, layout, spec, !a.ReviewShown)

return layout, spec, specBytes, false, nil
}

// printLocalSummary shows what the CLI found on disk plus the ingest
// settings it assembled β€” the detail under step 1 ("Check your data").
// Mirrors `cluster info`'s section/Field layout.
func printLocalSummary(p *ui.Printer, layout *push.LocalLayout, spec map[string]any) {
// printLocalSummary shows what the CLI found on disk plus (for the flag-only
// path) the ingest settings it assembled β€” the detail under step 1 ("Check your
// data"). Mirrors `cluster info`'s section/Field layout.
//
// showSettings gates the "Ingest settings" block: when the guided flow already
// showed those exact fields in its pre-confirm Review, repeating them here is
// noise β€” the caller passes false in that case. Any run without a Review (a
// non-interactive run, or a fully flagged run on a TTY that prompted nothing)
// passes true, so those users still see the resolved settings once.
func printLocalSummary(p *ui.Printer, layout *push.LocalLayout, spec map[string]any, showSettings bool) {
cat, _ := spec["category"].(string)

p.Section("Local dataset")
Expand Down Expand Up @@ -527,6 +534,10 @@ func printLocalSummary(p *ui.Printer, layout *push.LocalLayout, spec map[string]
}
p.Field("total size", push.HumanBytes(layout.TotalBytes))

if !showSettings {
return
}

p.Section("Ingest settings")
p.Field("name", fmt.Sprintf("%v", spec["table"]))
p.Field("task", fmt.Sprintf("%v", spec["category"]))
Expand Down
2 changes: 1 addition & 1 deletion internal/cli/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ func TestPrintLocalSummary_ShowsDetectedExtension(t *testing.T) {
"table": "t", "category": "image_classification", "intent": "train",
"spec": map[string]any{"file_options": map[string]any{"extension": ".png"}},
}
printLocalSummary(p, layout, spec)
printLocalSummary(p, layout, spec, true)
if !strings.Contains(buf.String(), "1 files (.png)") {
t.Errorf("summary missing detected extension:\n%s", buf.String())
}
Expand Down
Loading
Loading