fix(cli): state the artwork reprocess estimate as an estimate, not a bound

Neither bound is true. A ceiling is false because plugin agents are invisible to
a CLI that never starts the plugin manager, and a floor is false because a local
hit ends the walk before any agent is asked and a hit on the first agent skips
the rest. "at least N" traded one wrong claim for another.

The line now names its blind spots instead:

  External lookups: ~340 estimated (plugin agents not counted; local hits may
  need fewer).

The same line is reused in the confirmation prompt, and the zero case still
reads "External lookups: none." with the prompt dropping the clause entirely.
The count itself is unchanged.
This commit is contained in:
Deluan 2026-08-14 16:49:50 -04:00
commit 0e681f27c8
2 changed files with 21 additions and 15 deletions

View file

@ -323,13 +323,17 @@ func reprocessConfirm(yes bool, in io.Reader) confirmFunc {
return promptConfirm(in)
}
// externalEstimate is a floor, not a ceiling: plugin-provided agents are unregistered in a CLI that
// never starts the plugin manager, so the calls they would add are invisible here.
// externalEstimate claims no bound: a local hit ends the walk before any agent is asked, and plugin
// agents are unregistered in a CLI that never starts the plugin manager.
func externalEstimate(n int64) string {
if n == 0 {
return "none"
}
return fmt.Sprintf("at least %d", n)
return fmt.Sprintf("~%d estimated (plugin agents not counted; local hits may need fewer)", n)
}
func externalLookupLine(n int64) string {
return fmt.Sprintf("External lookups: %s.", externalEstimate(n))
}
// imageAgentCount counts only the built-in image agents, for the same reason.
@ -342,9 +346,9 @@ func promptConfirm(in io.Reader) confirmFunc {
return func(out io.Writer, total, external int64) bool {
var cost string
if external > 0 {
cost = fmt.Sprintf(", requiring %s external lookups", externalEstimate(external))
cost = fmt.Sprintf(" %s", externalLookupLine(external))
}
fmt.Fprintf(out, "\nThis will re-resolve %d items%s. Continue? [y/N] ", total, cost)
fmt.Fprintf(out, "\nThis will re-resolve %d items.%s Continue? [y/N] ", total, cost)
var answer string
if _, err := fmt.Fscanln(in, &answer); err != nil {
return false
@ -449,7 +453,7 @@ func printReprocessPreview(out io.Writer, kinds []model.Kind, matched []int64, t
fmt.Fprintf(w, "TOTAL\t%d\n", total)
w.Flush()
fmt.Fprintf(out, "\nExternal lookups: %s\n", externalEstimate(external))
fmt.Fprintf(out, "\n%s\n", externalLookupLine(external))
if total == 0 {
fmt.Fprintln(out, "\nNothing matches this selection.")
}

View file

@ -269,10 +269,10 @@ var _ = Describe("promptConfirm", func() {
BeforeEach(func() { out.Reset() })
It("states the external cost as a floor and accepts an explicit yes", func() {
It("states the external cost and accepts an explicit yes", func() {
Expect(promptConfirm(strings.NewReader("y\n"))(&out, 42, 7)).To(BeTrue())
Expect(out.String()).To(ContainSubstring("re-resolve 42 items"))
Expect(out.String()).To(ContainSubstring("at least 7 external lookups"))
Expect(out.String()).To(ContainSubstring("External lookups: ~7 estimated"))
})
It("defaults to no on anything else", func() {
@ -284,7 +284,7 @@ var _ = Describe("promptConfirm", func() {
It("drops the external clause when no lookup will be made", func() {
Expect(promptConfirm(strings.NewReader("y\n"))(&out, 3, 0)).To(BeTrue())
Expect(out.String()).To(ContainSubstring("re-resolve 3 items."))
Expect(out.String()).ToNot(ContainSubstring("external lookups"))
Expect(out.String()).ToNot(ContainSubstring("External lookups"))
})
})
@ -412,7 +412,7 @@ var _ = Describe("reprocessArtwork", func() {
It("shows the external estimate on a dry run, which never reaches the prompt", func() {
Expect(reprocessArtwork(ctx, ds, []model.Kind{model.KindAlbumArtwork}, nil, imageAgents, true, accept, &out)).To(Succeed())
Expect(out.String()).To(ContainSubstring("External lookups: at least 2"))
Expect(out.String()).To(ContainSubstring("External lookups: ~2 estimated"))
})
It("bills every agent per item, not one lookup per item", func() {
@ -423,14 +423,16 @@ var _ = Describe("reprocessArtwork", func() {
Expect(reprocessArtwork(ctx, ds, kinds, nil, imageAgents, false, capture, &out)).To(Succeed())
Expect(external).To(Equal(int64(2*2+2*3)), "2 artists at 2 agents plus 2 albums at 3 agents")
Expect(out.String()).To(ContainSubstring("External lookups: at least 10"))
Expect(out.String()).To(ContainSubstring("External lookups: ~10 estimated"))
})
It("states the estimate as a floor, since plugin agents are invisible to the CLI", func() {
It("names the estimate's blind spots instead of claiming a bound it cannot hold", func() {
Expect(reprocessArtwork(ctx, ds, kinds, nil, imageAgents, true, accept, &out)).To(Succeed())
Expect(out.String()).To(ContainSubstring("External lookups: at least"))
Expect(out.String()).ToNot(ContainSubstring("up to"))
Expect(out.String()).To(ContainSubstring("plugin agents not counted"))
Expect(out.String()).To(ContainSubstring("local hits may need fewer"))
Expect(out.String()).ToNot(ContainSubstring("up to"), "plugin agents make any ceiling false")
Expect(out.String()).ToNot(ContainSubstring("at least"), "a local hit makes any floor false")
})
It("says so when the selection needs no external lookup", func() {
@ -452,7 +454,7 @@ var _ = Describe("reprocessArtwork", func() {
Expect(reprocessArtwork(ctx, ds, []model.Kind{model.KindPlaylistArtwork}, nil, imageAgents, false, capture, &out)).To(Succeed())
Expect(external).To(Equal(int64(1)))
Expect(out.String()).To(ContainSubstring("External lookups: at least 1"))
Expect(out.String()).To(ContainSubstring("External lookups: ~1 estimated"))
})
It("bills a playlist for every album its grid samples, at every agent", func() {