From 340fbee132ae454fd700be91f09c90a493e925a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 22:53:59 +0000 Subject: [PATCH] refactor(cli): give each `amy status` saved item its own line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A busy account lists six or seven footprint items. Joined with `·` and wrapped at 78 columns they read as one run-on sentence that has to be parsed; a column of short lines scans in one pass: saved: 128 events (newest 2h ago) 3 contacts 2 Marmot groups a published key package Drops the wrap machinery (`appendWrapped`, the fixed WIDTH) for a plain hanging indent. `saved: nothing yet` is unchanged, and so is `--json`. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AtKhnNBSr9CWnZyjTWu7dL --- .../amethyst/cli/commands/StatusText.kt | 60 ++++++------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/StatusText.kt b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/StatusText.kt index 38b46d9920..0ed6fa6e38 100644 --- a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/StatusText.kt +++ b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/StatusText.kt @@ -28,16 +28,23 @@ import java.io.File * The terminal rendering of [StatusCommand] — the one command whose answer * the generic key/value renderer buries. * - * One block per account, four lines at most: + * One block per account — three lines of identity, then one line per + * thing the account has saved: * * ``` * alice (current) * Alice Jones · alice@example.com * npub1hje47kz5qeneyqrxc9nzgmz06ml6l9lguqv0qtsz4rkwqkmf636qvg4sz3 * local key, in the login keychain - * saved: 128 events (newest 2h ago) · 3 contacts · 2 Marmot groups + * saved: 128 events (newest 2h ago) + * 3 contacts + * 2 Marmot groups * ``` * + * The footprint gets a line per item rather than one `·`-joined run: a + * busy account lists six or seven of them, and a column of short lines + * scans in one pass where a wrapped sentence does not. + * * The rule that keeps it short: **absent is silent**. A profile the store * hasn't seen, a footprint an account doesn't have — those lines simply * don't print, instead of printing as `(none)` / `no` / `0`. Only the npub @@ -49,11 +56,8 @@ import java.io.File internal object StatusText { private const val DOT = " · " - /** - * Wrap column for the `saved:` list. Fixed rather than probed: JDK 21 - * offers no terminal width, and 80 is the floor every terminal honours. - */ - private const val WIDTH = 78 + /** Hanging indent for the `saved:` list — the visible width of `" saved: "`. */ + private val SAVED_INDENT = " ".repeat(" saved: ".length) fun render( rootBase: File, @@ -100,46 +104,16 @@ internal object StatusText { out.append(" ").append(signerLine(account.name, account.signer, color)).append('\n') if (account.npub == null) return - // Line 4 — the footprint, or one honest word when there isn't one. - // A busy account can list six things, so it wraps under its label. + // Then the footprint — one line per item, hanging off the label — or + // one honest word when there isn't one. val label = " " + color.dim("saved:") + " " if (account.saved.isEmpty) { out.append(label).append(color.dim("nothing yet")).append('\n') } else { - appendWrapped(out, label, indent = " ", parts = savedParts(account.saved)) - } - } - - /** - * Join [parts] with [DOT] across as many lines as it takes to stay inside - * [WIDTH], continuing under [indent] (which must be as wide as the visible - * [label]). The separator stays at the end of a line, so a wrapped list - * still reads as one run-on item rather than a new heading. - */ - private fun appendWrapped( - out: StringBuilder, - label: String, - indent: String, - parts: List, - ) { - out.append(label) - var used = indent.length - parts.forEachIndexed { i, part -> - val isLast = i == parts.lastIndex - val piece = if (isLast) part else part + DOT.trimEnd() - if (i > 0) { - if (used + 1 + piece.length > WIDTH) { - out.append('\n').append(indent) - used = indent.length - } else { - out.append(' ') - used += 1 - } + savedParts(account.saved).forEachIndexed { i, part -> + out.append(if (i == 0) label else SAVED_INDENT).append(part).append('\n') } - out.append(piece) - used += piece.length } - out.append('\n') } /** @@ -183,8 +157,8 @@ internal object StatusText { } /** - * The footprint, most-interesting first, absent items omitted. Reads as - * a sentence fragment: `128 events (newest 2h ago) · 3 contacts · …`. + * The footprint, most-interesting first, absent items omitted. One + * self-contained phrase per entry, since each gets its own line. */ private fun savedParts(saved: StatusReport.Saved): List { val parts = mutableListOf()