fix: say a sign-in error once, then in Details for that account
CI / Checks (pull_request) Successful in 24s
PR title / Conventional title (pull_request) Successful in 2s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-11 10:02:45 +02:00
1 parent 87d98d038d
commit b5c89bc6d9
3 files changed
+123 -15

No files matched your search

+65 -15
View File
@@ -721,7 +721,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.waiting = 0
m.askHandlerSetup()
m.keys.Login.SetEnabled(m.auth != nil && len(m.auth.Login) > 0)
m.keys.Login.SetEnabled(m.loginFor() != nil)
m.detail.SetMessage(m.selectedProblem())
if m.anyLoading() && !m.spinning {
m.spinning = true
cmds = append(cmds, m.spin.Tick)
@@ -881,8 +882,8 @@ func (m *Model) handleKey(msg tea.KeyPressMsg) (cmd tea.Cmd, quit bool) {
case key.Matches(msg, k.Edit):
return m.edit(), false
case key.Matches(msg, k.Login):
if m.auth != nil && len(m.auth.Login) > 0 {
c := exec.Command(m.auth.Login[0], m.auth.Login[1:]...)
if auth := m.loginFor(); auth != nil {
c := exec.Command(auth.Login[0], auth.Login[1:]...)
return tea.ExecProcess(c, func(err error) tea.Msg { return loginDoneMsg{err: err} }), false
}
case key.Matches(msg, k.Refresh):
@@ -1261,7 +1262,14 @@ func (m *Model) handleSources(msg sourcesMsg) tea.Cmd {
errs = append(errs, fmt.Sprintf("%s: %v", p.Label(), r.err))
}
}
m.status = strings.Join(errs, " · ")
// Said once, when they change; after that the Details pane says it
// for the selected account.
if status := strings.Join(errs, " · "); status != m.status {
m.status = status
if status != "" {
m.flash = flash{text: m.problemText(msg.provider, status), isErr: true}
}
}
m.rebuildSources()
m.sources.Loading = m.sourcesPending > 0
return m.autoLoad(msg)
@@ -1532,14 +1540,27 @@ func (m *Model) openSelected() tea.Cmd {
return nil
}
// noteErr remembers authentication failures so the UI can offer sign-in.
// noteErr remembers authentication failures so the UI can offer sign-in,
// saying so once for each account.
func (m *Model) noteErr(err error) {
var auth *provider.AuthError
if errors.As(err, &auth) {
if m.auth == nil || m.auth.Who() != auth.Who() {
m.flash = flash{text: m.authText(auth), isErr: true}
}
m.auth = auth
}
}
// authText says which account needs signing in, and how.
func (m Model) authText(auth *provider.AuthError) string {
if len(auth.Login) == 0 {
return fmt.Sprintf("%s: sign-in required: %v", auth.Who(), auth.Err)
}
return fmt.Sprintf("%s: sign-in required (not signed in or session expired)%s",
auth.Who(), withKey(m.keys.Login, " · press %s to run "+strings.Join(auth.Login, " ")))
}
func (m Model) anyLoading() bool {
return m.sources.Loading || m.containers.Loading || m.secrets.Loading ||
m.detail.State() == panes.ValueLoading
@@ -1560,16 +1581,6 @@ func (m Model) footerView() string {
help = m.helpBar(contextHelp{k: m.keys, focus: m.focus, commands: len(m.commandsHere()) > 0}.ShortHelp(), 2)
}
var lines []string
failed := func(text string) { lines = append(lines, m.flashLine(flash{text: text, isErr: true})) }
switch {
case m.auth != nil && len(m.auth.Login) > 0:
failed(fmt.Sprintf("%s: sign-in required (not signed in or session expired)%s",
m.auth.Who(), withKey(m.keys.Login, " · press %s to run "+strings.Join(m.auth.Login, " "))))
case m.auth != nil:
failed(fmt.Sprintf("%s: sign-in required: %v", m.auth.Who(), m.auth.Err))
case m.status != "":
failed(m.status)
}
if !m.clipDeadline.IsZero() {
left := max(0, int(math.Ceil(m.clipDeadline.Sub(m.now()).Seconds())))
lines = append(lines, m.flashLine(done(fmt.Sprintf("copied %s · clipboard clears in %ds", m.clipLabel, left))))
@@ -1624,3 +1635,42 @@ func (m Model) View() tea.View {
v.WindowTitle = "lazysecrets"
return v
}
// selectedProblem is why the selected Sources row's account has nothing
// to show, if it failed: for the Details pane.
func (m Model) selectedProblem() string {
r, ok := m.sources.Selected()
if !ok || (r.kind != rowGroup && r.kind != rowSource) {
return ""
}
res, done := m.sourceResults[r.provider]
if !done || res.err == nil {
return ""
}
return m.problemText(r.provider, res.err.Error())
}
// problemText says what went wrong for a provider: for a sign-in, which
// account and how to sign in again; else err as given.
func (m Model) problemText(providerID, err string) string {
var auth *provider.AuthError
if r, ok := m.sourceResults[providerID]; ok && errors.As(r.err, &auth) {
return m.authText(auth)
}
return err
}
// loginFor is the sign-in L runs: the selected account's, if it needs
// one, else the last one asked for.
func (m Model) loginFor() *provider.AuthError {
if r, ok := m.sources.Selected(); ok {
var auth *provider.AuthError
if res := m.sourceResults[r.provider]; errors.As(res.err, &auth) && len(auth.Login) > 0 {
return auth
}
}
if m.auth != nil && len(m.auth.Login) > 0 {
return m.auth
}
return nil
}