From b58068351a2edeb90f0d26b9098f04e85db225ee Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Sat, 25 Apr 2026 18:48:56 -0400
Subject: [PATCH] fix: emit [] for empty JSON; correct workouts category prime
note
Two gaps the new 'prime' subcommand surfaced when its examples and the
output-formats contract were exercised end-to-end:
- Every subcommand's --format json was emitting 'null' on empty windows
(the underlying slice was nil, which encoding/json marshals as null).
The prime promises "empty result ... JSON prints '[]'", and 'null'
also breaks the prime's sleep-efficiency jq pipeline. Special-case
nil slices in printJSON via reflect so all five subcommands emit
'[]' on empty.
- The workouts gotcha implied JSON carried both a string 'category' and
a numeric 'category_code', but JSON only carries the integer
'category' (the string mapping happens in markdown/CSV). Rewrite the
gotcha to describe actual behavior and list the common code values
inline so an LLM can filter by 'category == 16' without round-tripping
through markdown.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
cmd/prime.go | 8 +++++---
cmd/shared.go | 7 +++++++
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/cmd/prime.go b/cmd/prime.go
index 9a03462..8ba4da1 100644
--- a/cmd/prime.go
+++ b/cmd/prime.go
@@ -135,9 +135,11 @@ GOTCHAS
(250ms between calls); ad-hoc loops should do the same.
- Sleep score and apnea fields are populated only on supported devices.
'data.sleep_score' is null on unsupported wakeup-light models.
- - 'workouts' category codes are integers; common ones are mapped to
- string names (walk/run/bicycling/...) but unknown codes pass through
- as 'unknown' with the numeric category_code preserved.
+ - 'workouts' category is a Withings integer code in JSON (1=walk,
+ 2=run, 6=bicycling, 16=lift_weights, ...). Markdown and CSV map
+ common codes to string names ('lift_weights', 'walk', ...) and
+ unknown codes render as 'unknown'; CSV also keeps the raw integer
+ in a 'category_code' column.
`
var primeCmd = &cobra.Command{
diff --git a/cmd/shared.go b/cmd/shared.go
index 69f821a..cb29751 100644
--- a/cmd/shared.go
+++ b/cmd/shared.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
+ "reflect"
"strconv"
"strings"
"time"
@@ -108,6 +109,12 @@ func untilDayOrToday(s string) (time.Time, error) {
}
func printJSON(v any) error {
+ // A nil slice marshals as "null"; force "[]" so empty windows match the
+ // prime contract (and don't blow up jq pipelines).
+ if rv := reflect.ValueOf(v); rv.Kind() == reflect.Slice && rv.IsNil() {
+ _, err := os.Stdout.WriteString("[]\n")
+ return err
+ }
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(v); err != nil {