# Render throughput as 'host → bytes @ rate │ bytes @ rate → host'

`fab0029` → [main](/content/gh/entireio/git-sync/commits/main/index.html) ·  Soph · 2mo ago · 6 files · +185 added/-33 removed

Two visual upgrades to the per-side throughput output, applied to both the live --progress ticker and the end-of-run --stats line:

- Replace the bare "source"/"target" labels with the actual URL hostnames, captured once during newConn via setSideDisplay. The internal label is preserved for ordering and arrow placement.

- Bracket each rate with a directional arrow and join the two halves with a vertical bar. Source flows left-to-right with the hostname on the left of its rate; target flows left-to-right with the hostname on the right of its rate. The result reads as a pipeline diagram: "github.com → ... │ ... → cloudflare.net".

Long hostnames (>30 chars) are truncated apex-preserving so the TLD stays visible — e.g. "8b04592ed74a5cce30d355b07276caf3.artifacts.cloudflare.net" renders as "8b04592ed74a5cc…cloudflare.net". Falls back to plain right-truncation when the apex alone doesn't fit.

## Sessions

aa7fc6807802 View transcript

[?\ can you rebase soph/progress-indicators onto soph/smart-subdivision Claude Code · Opus 4.7 [1m] · 1 step](/content/gh/entireio/git-sync/session/3ee1ca7a-a436-44c1-906a-a912c6d33f96#timeline-aa7fc6807802/index.html)

## Changes

6

- internal/syncer

- Mprogress.go +52/-3

- Mprogress_test.go +59/-14

- Mstats.go +31/-4

- Msyncer.go +15/-3

- Mthroughput_test.go +20/-6

- internal/bridge

- Mmodel.go +8/-3

```go
var b strings.Builder
for i, side := range sides {
    if i > 0 {
        b.WriteString(" · ")
        b.WriteString(sideSeparator)
    }
    fmt.Fprintf(&b, "%s: %s @ %s",
        side.Label, formatBytes(side.Bytes), formatRate(side.Bytes, elapsed))
    b.WriteString(formatSide(side, elapsed))
}
line := b.String()
p.lastLen = len(line)
}

const (
    sideSeparator    = "  │  "
    flowArrow        = " → "
    maxHostnameWidth = 30
)

// formatSide renders a single side as host + bytes + rate, with a flow
// arrow positioned to indicate direction: source on the left of its
// counter, target on the right of its counter. Sides with neither label
// fall back to "name: bytes @ rate".
func formatSide(side SideBytes, dur time.Duration) string {
    name := side.Display
    if name == "" {
        name = side.Label
    }
    name = truncateHost(name, maxHostnameWidth)
    rate := formatBytes(side.Bytes) + " @ " + formatRate(side.Bytes, dur)
    switch side.Label {
    case "source":
        return name + flowArrow + rate
    case "target":
        return rate + flowArrow + name
    default:
        return name + ": " + rate
    }
}

// truncateHost shortens long hostnames while keeping the apex domain
// visible. Returns the original string if it already fits within width,
// otherwise preserves the trailing two dotted labels (e.g. "cloudflare.net")
// and spends the remaining budget on a prefix from the leading subdomain.
// Falls back to right-truncation when the apex alone doesn't fit.
func truncateHost(host string, width int) string {
    if width <= 0 || len(host) <= width {
        return host
    }
    labels := strings.Split(host, ".")
    if len(labels) >= 2 {
        apex := labels[len(labels)-2] + "." + labels[len(labels)-1]
        if len(apex)+2 <= width { // room for at least one prefix char + ellipsis
            prefixBudget := width - 1 - len(apex)
            return host[:prefixBudget] + "…" + apex
        }
    }
    if width <= 1 {
        return "…"
    }
    return host[:width-1] + "…"
}

// stderrIsTTY reports whether stderr is attached to a terminal. The
// progress ticker is suppressed otherwise because '\r' updates only make
// sense on a TTY and would otherwise spam log files.
```
