From 1f9e41040ff4344824217944c67fdd679a4cb11d Mon Sep 17 00:00:00 2001 From: Nicholas Sollazzo Date: Mon, 13 Jul 2026 14:45:23 +0200 Subject: [PATCH] refactor(cli): reuse notFoundWithSuggestion in blogNotFound [POS-248] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit blogNotFound re-implemented the whole "not-found + did-you-mean" algorithm inline, a 4th divergent copy of logic that soulNotFound and listingNotFound already share via notFoundWithSuggestion. It forked only because the helper conflated the message noun ("post") with the list subcommand word ("blog") into a single param. Split that param into (noun, listCmd) so blog can rejoin: noun drives the message and the "server lists this X" hint, listCmd drives "Run: positronick list". soul/listing pass the same value for both; blog passes ("post", "blog"). Output is byte-identical — the golden assertions in blog_test.go still hold. Net -6 lines; the not-found logic now lives in one place. Co-Authored-By: Claude Opus 4.8 --- internal/cli/blog.go | 23 +++++++---------------- internal/cli/listing.go | 2 +- internal/cli/read.go | 19 +++++++++++-------- internal/cli/soul.go | 2 +- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/internal/cli/blog.go b/internal/cli/blog.go index 81772cc..f0c1e1c 100644 --- a/internal/cli/blog.go +++ b/internal/cli/blog.go @@ -139,24 +139,15 @@ func newBlogShowCmd() *cobra.Command { } // blogNotFound builds the exit-3 error for a missing slug, with a did-you-mean -// hint when the gallery has a plausible neighbor. A failing suggestion fetch -// never masks the original not-found. Mirrors soulNotFound. +// hint when the gallery has a plausible neighbor. The entity is a "post" but it +// is listed under the `blog` command. See notFoundWithSuggestion. func blogNotFound(ctx context.Context, client *api.Client, slug string) error { - hint := "Run: positronick blog list" - if posts, err := client.Posts(ctx, ""); err == nil { - slugs := make([]string, len(posts)) - for i, post := range posts { - slugs[i] = post.Slug - } - if match := closestSlug(slug, slugs); match == slug { - // The list knows the slug but the detail fetch 404'd: an older server, - // not a typo — say so rather than suggesting the input back. - hint = "the server lists this post but could not return its details — positronick.com may be running an older API" - } else if match != "" { - hint = fmt.Sprintf("did you mean %q? %s", match, hint) - } + posts, err := client.Posts(ctx, "") + slugs := make([]string, len(posts)) + for i, post := range posts { + slugs[i] = post.Slug } - return output.NotFoundError(fmt.Sprintf("post %q not found", slug), hint) + return notFoundWithSuggestion("post", "blog", slug, slugs, err) } func renderBlogDetail(p *output.Printer, post *api.Post) { diff --git a/internal/cli/listing.go b/internal/cli/listing.go index 2979d6c..d680fd0 100644 --- a/internal/cli/listing.go +++ b/internal/cli/listing.go @@ -202,7 +202,7 @@ func listingNotFound(ctx context.Context, client *api.Client, listingType, slug for i, l := range listings { slugs[i] = l.Slug } - return notFoundWithSuggestion(listingType, slug, slugs, err) + return notFoundWithSuggestion(listingType, listingType, slug, slugs, err) } // renderListingDetail prints the human field view. For loops it also renders diff --git a/internal/cli/read.go b/internal/cli/read.go index 76d1582..c73a821 100644 --- a/internal/cli/read.go +++ b/internal/cli/read.go @@ -117,14 +117,17 @@ func applyLimit[T any](items []T, limit int) []T { } // notFoundWithSuggestion builds the exit-3 error for a slug the detail endpoint -// 404'd on, shared by `soul show` and `listing show`. When the catalog list -// holds a plausible neighbor it appends a did-you-mean hint; when the list -// holds the exact slug, the detail miss means the server is older than this CLI -// (no JSON detail endpoint yet), so it says that instead of suggesting the input -// back. A failing catalog fetch (fetchErr != nil) is ignored so it never masks -// the original not-found — the caller passes the fetch result verbatim. -func notFoundWithSuggestion(noun, slug string, catalog []string, fetchErr error) error { - hint := fmt.Sprintf("Run: positronick %s list", noun) +// 404'd on, shared by `soul show`, `listing show`, and `blog show`. noun names +// the entity in the message ("soul"/"post"); listCmd is the subcommand in the +// "Run: positronick list" hint — usually equal to noun, but `blog show` +// lists "post" entities under the `blog` command. When the catalog list holds a +// plausible neighbor it appends a did-you-mean hint; when the list holds the +// exact slug, the detail miss means the server is older than this CLI (no JSON +// detail endpoint yet), so it says that instead of suggesting the input back. A +// failing catalog fetch (fetchErr != nil) is ignored so it never masks the +// original not-found — the caller passes the fetch result verbatim. +func notFoundWithSuggestion(noun, listCmd, slug string, catalog []string, fetchErr error) error { + hint := fmt.Sprintf("Run: positronick %s list", listCmd) if fetchErr == nil { if match := closestSlug(slug, catalog); match == slug { hint = "the server lists this " + noun + " but could not return its details — positronick.com may be running an older API" diff --git a/internal/cli/soul.go b/internal/cli/soul.go index 1938dbb..a91998e 100644 --- a/internal/cli/soul.go +++ b/internal/cli/soul.go @@ -190,7 +190,7 @@ func soulNotFound(ctx context.Context, client *api.Client, slug string) error { for i, s := range souls { slugs[i] = s.Slug } - return notFoundWithSuggestion("soul", slug, slugs, err) + return notFoundWithSuggestion("soul", "soul", slug, slugs, err) } func renderSoulDetail(p *output.Printer, s *api.Soul) {