feat: a share link that can't be decrypted says why in a popup
CI / Checks (pull_request) Successful in 39s
PR title / Conventional title (pull_request) Successful in 2s

Where its offer was, instead of the footer: the first line of the
reason, r for the whole message (Key Vault's refusals list what to
check), g to go to the key's secret at the link's version, and x to
dismiss.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-13 13:59:43 +02:00
1 parent 61de5e616d
commit 2a3379b1c1
2 files changed
+75 -1

No files matched your search

+34 -1
View File
@@ -12,6 +12,7 @@ import (
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/x/ansi"
"git.kebcloud.no/keb/lazysecrets/internal/provider"
"git.kebcloud.no/keb/lazysecrets/internal/ref"
@@ -288,7 +289,7 @@ func (m *Model) handleShareOpened(msg shareOpenedMsg) tea.Cmd {
switch {
case msg.err != nil:
m.noteErr(msg.err)
m.flash = flash{text: "open: " + msg.err.Error(), isErr: true}
m.openFailed(msg.sealed, msg.err, false)
case msg.dir == "":
return m.askShareDir(msg)
default:
@@ -302,6 +303,38 @@ func (m *Model) handleShareOpened(msg shareOpenedMsg) tea.Cmd {
return nil
}
// openFailed says why a share link couldn't be decrypted, in a popup
// where its offer was: its first line, or with whole all of it
// (Key Vault's refusals say what to check on later lines). From there,
// the key's secret can be gone to: its version, which may be disabled.
func (m *Model) openFailed(s share.Sealed, err error, whole bool) {
width := max(30, min(m.w-12, 76))
text := capitalize(err.Error())
first, rest, _ := strings.Cut(text, "\n")
cut := !whole && (rest != "" || ansi.StringWidth(first) > width-2)
if !whole {
text = ansi.Truncate(first, width-2, "…")
}
var lines []string
for i, l := range strings.Split(ansi.Wordwrap(text, width-2, " "), "\n") {
if i == 0 {
l = "✗ " + l
} else if l != "" {
l = " " + l
}
lines = append(lines, m.st.Error.Render(l))
}
var opts []option
if cut {
opts = append(opts, option{key: "r", label: "Show the whole message", run: func(m *Model) tea.Cmd {
m.openFailed(s, err, true)
return nil
}})
}
opts = append(opts, option{key: "g", label: "Go to " + s.Key.Name, run: func(m *Model) tea.Cmd { return m.openRef(s.Key, actGo) }})
m.showMenu("Can't decrypt the shared files", lines, opts)
}
// askShareDir asks where to save files from a repo that isn't here. It
// stays until they're saved, saying why when they can't be.
func (m *Model) askShareDir(msg shareOpenedMsg) tea.Cmd {