feat(trail): show trail URL in 'trail list' (table column + --json) · Entire
feat(trail): show trail URL in 'trail list' (table column + --json)
fb6ce0f→main·
toothbrush·3w ago·3 files·+51 added/-2 removed
Consume the server-provided url field in the list path: attach it to each trail's metadata (server-preferred, locally constructed fallback for older servers), add a URL column to the table when present, and surface it in --json. Mirrors create/show.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
f2d02933f5adView transcript
[?
Add Web URLs to Trail CommandsClaude Code·Opus 4.8[1m]·4 steps](/content/gh/entireio/cli/session/18a97739-bf62-4c0b-9cf2-9a1673f59583#timeline-f2d02933f5ad/index.html)
Changes
3
cmd/entire/cli
trail
Mtrail.go+1
Mtrail_cmd.go+21/-2
Mtrail_cmd_test.go+29
152 unmodified lines
153
154
155
156
157
158
159
152 unmodified lines
type Metadata struct {
Number int `json:"number,omitempty"`
TrailID ID `json:"trail_id"`
URL string `json:"url,omitempty"`
Branch string `json:"branch"`
Base string `json:"base"`
Title string `json:"title"`
}
Mcmd/entire/cli/trail/trail.go+1
379 unmodified lines
380
381
382
383
383
384
385
386
386
387
388
389
390
391
392
182 unmodified lines
575
576
577
578
579
580
581
5 unmodified lines
587
588
589
590
591
592
593
594
595
15 unmodified lines
611
612
613
614
615
616
617
618
619
8 unmodified lines
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
379 unmodified lines
return fmt.Errorf("failed to decode trail list: %w", err)
}
// Convert to metadata for display
// Convert to metadata for display, attaching the browser URL (server-provided
// when present, locally constructed as a fallback for older servers).
trails := make([]*trail.Metadata, 0, len(listResp.Trails))
for i := range listResp.Trails {
trails = append(trails, listResp.Trails[i].ToMetadata())
m := listResp.Trails[i].ToMetadata()
m.URL = trailDisplayURL(listResp.Trails[i], forge, owner, repo)
trails = append(trails, m)
}
totalMatched := listResp.Total
182 unmodified lines
// branch names or logins don't throw off the table.
tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0)
showPhase := trailListHasPhase(trails)
showURL := trailListHasURL(trails)
columns := []string{"NUM", "BRANCH", "TITLE"}
if showStatus {
columns = append(columns, "STATUS")
}
columns = append(columns, "AUTHOR")
columns = append(columns, "UPDATED")
if showURL {
columns = append(columns, "URL")
}
fmt.Fprintln(tw, " "+strings.Join(columns, "\t"))
for _, t := range trails {
number := "-"
15 unmodified lines
fields = append(fields, t.AuthorLogin())
}
fields = append(fields, timeAgo(t.UpdatedAt))
if showURL {
fields = append(fields, t.URL)
}
fmt.Fprintln(tw, " "+strings.Join(fields, "\t"))
}
_ = tw.Flush()
8 unmodified lines
return false
}
func trailListHasURL(trails []*trail.Metadata) bool {
for _, t := range trails {
if t != nil && strings.TrimSpace(t.URL) != "" {
return true
}
}
return false
}
func trailPhaseDisplay(phase string) string {
phase = strings.TrimSpace(phase)
if phase == "" {
Mcmd/entire/cli/trail_cmd.go+21/-2
892 unmodified lines
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
892 unmodified lines
}
}
func TestPrintTrailListShowsURLColumnWhenPresent(t *testing.T) {
t.Parallel()
alice := trailListTestAuthorAlice
var out bytes.Buffer
printTrailList(&out, []*trail.Metadata{
{Number: 5, Branch: "feat/a", Status: trail.StatusOpen, URL: "https://entire.io/gh/acme/repo/trails/5", Author: &trail.Author{Login: &alice}, UpdatedAt: time.Now()},
}, trailListDisplayOptions{StatusFilters: []trail.Status{trail.StatusOpen}})
text := out.String()
if !strings.Contains(text, "URL") || !strings.Contains(text, "https://entire.io/gh/acme/repo/trails/5") {
t.Fatalf("expected a URL column with the trail url, got:\n%s", text)
}
}
func TestPrintTrailListOmitsURLColumnWhenAbsent(t *testing.T) {
t.Parallel()
alice := trailListTestAuthorAlice
var out bytes.Buffer
printTrailList(&out, []*trail.Metadata{
{Number: 5, Branch: "feat/a", Status: trail.StatusOpen, Author: &trail.Author{Login: &alice}, UpdatedAt: time.Now()},
}, trailListDisplayOptions{StatusFilters: []trail.Status{trail.StatusOpen}})
// The column header must not appear when no trail carries a URL (e.g. an
// older server that omits the field and no local fallback was attached).
if text := out.String(); strings.Contains(text, "URL") {
t.Fatalf("expected URL column omitted when no trail has a url, got:\n%s", text)
}
}
func TestPrintTrailListAnyStatusShowsStatusColumn(t *testing.T) {
t.Parallel()
alice := trailListTestAuthorAlice