feat: share popups draw repos and files like everywhere else
CI / Checks (pull_request) Successful in 38s
PR title / Conventional title (pull_request) Successful in 2s

repoLabel draws a repo, or a file in it, with pathLabel: the repo icon
and colour. The share popups use it for what's shared, where it's
from and where it's saved, and are titled for what's shared
("Share .env", "Share 2 env files"), the key picker too.

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 12:22:23 +02:00
1 parent 0bfa2a3141
commit cd265a632e
3 files changed
+45 -22

No files matched your search

+6
View File
@@ -138,6 +138,12 @@ func (m Model) pathLabel(kind provider.Kind, path []string, account string) stri
return label return label
} }
// repoLabel draws a repo, or a file in it, as everywhere else: the repo
// icon and colour, "app / apps/api/.env".
func (m Model) repoLabel(repo string, file ...string) string {
return m.pathLabel(provider.KindRepo, append([]string{repo}, file...), "")
}
// sourceMatches reports whether src is the subscription or workspace r // sourceMatches reports whether src is the subscription or workspace r
// links to. // links to.
func sourceMatches(r ref.Ref, src provider.Source) bool { func sourceMatches(r ref.Ref, src provider.Source) bool {
+38 -21
View File
@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -109,14 +110,27 @@ func (m *Model) handleShareFiles(msg shareFilesMsg) tea.Cmd {
return nil return nil
} }
// shareTitle names the action: "Share .env", "Share 2 env files".
func shareTitle(s sharing) string {
if len(s.files) == 1 {
return "Share " + path.Base(s.files[0].Path)
}
return "Share " + plural(len(s.files), "env file")
}
// sharedFiles lists the files being shared, under what's happening.
func (m *Model) sharedFiles(s sharing) []string {
lines := []string{"Sharing " + plural(len(s.files), "env file") + " as an encrypted link:"}
for _, f := range s.files {
lines = append(lines, " "+m.repoLabel(s.repo.Name, f.Path))
}
return lines
}
// confirmShare says what the link will hold before making it. // confirmShare says what the link will hold before making it.
func (m *Model) confirmShare(s sharing) { func (m *Model) confirmShare(s sharing) {
lines := []string{m.st.Muted.Render(plural(len(s.files), "env file") + " from " + s.repo.Name + ":")} lines := append(m.sharedFiles(s), "", m.st.Muted.Render("encrypted with ")+m.refLabel(s.key))
for _, f := range s.files { m.showMenu(shareTitle(s), lines, []option{
lines = append(lines, " "+f.Path)
}
lines = append(lines, "", m.st.Muted.Render("encrypted with ")+m.refLabel(s.key))
m.showMenu("Share as an encrypted link", lines, []option{
{key: "c", label: "Copy the link", run: func(m *Model) tea.Cmd { return m.seal(s) }}, {key: "c", label: "Copy the link", run: func(m *Model) tea.Cmd { return m.seal(s) }},
{key: "k", label: "Encrypt with another secret…", run: func(m *Model) tea.Cmd { return m.pickShareKey(s) }}, {key: "k", label: "Encrypt with another secret…", run: func(m *Model) tea.Cmd { return m.pickShareKey(s) }},
}) })
@@ -130,16 +144,10 @@ func (m *Model) pickShareKey(s sharing) tea.Cmd {
m.confirmShare(s) m.confirmShare(s)
return nil return nil
} }
m.search.pickTitle = "Encrypt with which secret?" m.search.pickTitle = shareTitle(s)
what := s.files[0].Path m.search.pickLines = append(m.sharedFiles(s), "",
if len(s.files) > 1 { "Pick the secret to encrypt them with: whoever can read it can open the link.",
what = plural(len(s.files), "env file") m.st.Muted.Render("Its value isn't shown or put in the link. share.key sets a default."))
}
m.search.pickLines = []string{
"Sharing " + what + " from " + s.repo.Name + " as an encrypted link.",
"Pick the secret to encrypt with: whoever can read it can open the link.",
m.st.Muted.Render("Its value isn't shown or put in the link. share.key sets a default."),
}
m.fillSearch(false) m.fillSearch(false)
return tea.Batch(cmd, m.searchLoads()) return tea.Batch(cmd, m.searchLoads())
} }
@@ -213,10 +221,10 @@ func (m *Model) offerShare(r ref.Ref) tea.Cmd {
} }
from := "" from := ""
if s.Repo != "" { if s.Repo != "" {
from = " from " + s.Repo from = m.st.Muted.Render(" from ") + m.repoLabel(s.Repo)
} }
m.showMenu("Shared env files", []string{ m.showMenu("Shared env files", []string{
m.st.Muted.Render(plural(s.Files, "env file") + from), m.st.Muted.Render(plural(s.Files, "env file")) + from,
m.st.Muted.Render("encrypted with ") + m.refLabel(s.Key), m.st.Muted.Render("encrypted with ") + m.refLabel(s.Key),
}, []option{ }, []option{
{key: "o", label: "Decrypt them…", tip: "reads the secret, then asks before saving", run: func(m *Model) tea.Cmd { return m.openShare(s) }}, {key: "o", label: "Decrypt them…", tip: "reads the secret, then asks before saving", run: func(m *Model) tea.Cmd { return m.openShare(s) }},
@@ -302,10 +310,11 @@ func (m *Model) offerWrite(files []share.File, dir string) {
var lines []string var lines []string
exist := 0 exist := 0
for _, t := range targets { for _, t := range targets {
line := " " + t.Path + m.st.Muted.Render(" · new") file := " " + m.repoLabel(filepath.Base(dir), t.Path)
line := file + m.st.Muted.Render(" · new")
if t.Exists { if t.Exists {
exist++ exist++
line = " " + t.Path + m.st.Muted.Render(" · replaces the one there") line = file + m.st.Muted.Render(" · replaces the one there")
} }
if t.Exposed { if t.Exposed {
line += m.st.Error.Render(" · not ignored by git") line += m.st.Error.Render(" · not ignored by git")
@@ -339,7 +348,7 @@ func (m *Model) offerWrite(files []share.File, dir string) {
default: default:
opts = append(opts, opts = append(opts,
option{key: "w", label: fmt.Sprintf("Save all, replacing %d", exist), run: write(false)}, option{key: "w", label: fmt.Sprintf("Save all, replacing %d", exist), run: write(false)},
option{key: "n", label: "Save only the new " + plural(len(targets)-exist, "file"), run: write(true)}, option{key: "n", label: onlyNew(len(targets) - exist), run: write(true)},
) )
} }
m.showMenu("Save the shared files", lines, opts) m.showMenu("Save the shared files", lines, opts)
@@ -354,3 +363,11 @@ func expandHome(dir string) string {
} }
return dir return dir
} }
// onlyNew labels saving only the n files not there yet.
func onlyNew(n int) string {
if n == 1 {
return "Save only the new one"
}
return fmt.Sprintf("Save only the %d new ones", n)
}
+1 -1
View File
@@ -74,7 +74,7 @@ func shareFrom(t *testing.T, root string, paste bool) string {
t.Errorf("not a secret: %s", o.label) t.Errorf("not a secret: %s", o.label)
} }
} }
if !strings.Contains(d.screen(), "Encrypt with which secret?") { if !strings.Contains(d.screen(), "Share 2 env files") && !strings.Contains(d.screen(), "Share .env") {
t.Errorf("not titled as a picker:\n%s", d.screen()) t.Errorf("not titled as a picker:\n%s", d.screen())
} }
d.keys("enter") d.keys("enter")