From cd265a632ea171f35130659672459fcf30fa785e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl-Einar=20Bergstr=C3=B8m?= Date: Tue, 13 Oct 2026 12:22:23 +0200 Subject: [PATCH] feat: share popups draw repos and files like everywhere else 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) --- internal/ui/app/jump.go | 6 +++ internal/ui/app/sharefiles.go | 59 +++++++++++++++++++----------- internal/ui/app/sharefiles_test.go | 2 +- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/internal/ui/app/jump.go b/internal/ui/app/jump.go index dbaa15b..86a7b94 100644 --- a/internal/ui/app/jump.go +++ b/internal/ui/app/jump.go @@ -138,6 +138,12 @@ func (m Model) pathLabel(kind provider.Kind, path []string, account string) stri 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 // links to. func sourceMatches(r ref.Ref, src provider.Source) bool { diff --git a/internal/ui/app/sharefiles.go b/internal/ui/app/sharefiles.go index 23d7089..13d3f3b 100644 --- a/internal/ui/app/sharefiles.go +++ b/internal/ui/app/sharefiles.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path" "path/filepath" "strings" @@ -109,14 +110,27 @@ func (m *Model) handleShareFiles(msg shareFilesMsg) tea.Cmd { 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. func (m *Model) confirmShare(s sharing) { - lines := []string{m.st.Muted.Render(plural(len(s.files), "env file") + " from " + s.repo.Name + ":")} - for _, f := range s.files { - 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{ + lines := append(m.sharedFiles(s), "", m.st.Muted.Render("encrypted with ")+m.refLabel(s.key)) + m.showMenu(shareTitle(s), lines, []option{ {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) }}, }) @@ -130,16 +144,10 @@ func (m *Model) pickShareKey(s sharing) tea.Cmd { m.confirmShare(s) return nil } - m.search.pickTitle = "Encrypt with which secret?" - what := s.files[0].Path - if len(s.files) > 1 { - what = plural(len(s.files), "env file") - } - 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.search.pickTitle = shareTitle(s) + m.search.pickLines = append(m.sharedFiles(s), "", + "Pick the secret to encrypt them 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) return tea.Batch(cmd, m.searchLoads()) } @@ -213,10 +221,10 @@ func (m *Model) offerShare(r ref.Ref) tea.Cmd { } from := "" if s.Repo != "" { - from = " from " + s.Repo + from = m.st.Muted.Render(" from ") + m.repoLabel(s.Repo) } 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), }, []option{ {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 exist := 0 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 { 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 { line += m.st.Error.Render(" · not ignored by git") @@ -339,7 +348,7 @@ func (m *Model) offerWrite(files []share.File, dir string) { default: opts = append(opts, 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) @@ -354,3 +363,11 @@ func expandHome(dir string) string { } 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) +} diff --git a/internal/ui/app/sharefiles_test.go b/internal/ui/app/sharefiles_test.go index 213c03c..fae4ef8 100644 --- a/internal/ui/app/sharefiles_test.go +++ b/internal/ui/app/sharefiles_test.go @@ -74,7 +74,7 @@ func shareFrom(t *testing.T, root string, paste bool) string { 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()) } d.keys("enter")