Clarify lifecycle loading and target selection

This commit is contained in:
Joe Julian
2026-04-01 17:00:24 -07:00
parent 7de55d1041
commit a1cbec85da
3 changed files with 305 additions and 18 deletions
+129 -8
View File
@@ -61,8 +61,10 @@ const (
)
type uiBanner struct {
Kind bannerKind
Message string
Kind bannerKind
Message string
Detail string
Dismissable bool
}
type uiSurface struct {
@@ -200,6 +202,9 @@ type ui struct {
pickVaultPath widget.Clickable
pickKeyFile widget.Clickable
pickSyncLocalPath widget.Clickable
clearVaultSelection widget.Clickable
clearRemoteSelection widget.Clickable
dismissBanner widget.Clickable
addEntry widget.Clickable
saveEntry widget.Clickable
duplicateEntry widget.Clickable
@@ -1688,21 +1693,60 @@ func (u *ui) describeActionError(label string, err error) string {
func (u *ui) bannerSurface() uiBanner {
switch {
case strings.TrimSpace(u.loadingMessage) != "":
return uiBanner{Kind: bannerLoading, Message: strings.TrimSpace(u.loadingMessage)}
return uiBanner{
Kind: bannerLoading,
Message: strings.TrimSpace(u.loadingMessage),
Detail: u.loadingDetailMessage(),
}
case strings.TrimSpace(u.state.ErrorMessage) != "":
return uiBanner{Kind: bannerError, Message: strings.TrimSpace(u.state.ErrorMessage)}
return uiBanner{
Kind: bannerError,
Message: strings.TrimSpace(u.state.ErrorMessage),
Dismissable: true,
}
case strings.TrimSpace(u.state.StatusMessage) != "":
if !u.statusExpiresAt.IsZero() && !u.now().Before(u.statusExpiresAt) {
u.state.StatusMessage = ""
u.statusExpiresAt = time.Time{}
return uiBanner{}
}
return uiBanner{Kind: bannerStatus, Message: strings.TrimSpace(u.state.StatusMessage)}
return uiBanner{
Kind: bannerStatus,
Message: strings.TrimSpace(u.state.StatusMessage),
Dismissable: true,
}
default:
return uiBanner{}
}
}
func (u *ui) loadingDetailMessage() string {
if !u.shouldShowLifecycleSetup() {
return ""
}
if u.lifecycleMode == "remote" {
baseURL := strings.TrimSpace(u.remoteBaseURL.Text())
path := strings.TrimSpace(u.remotePath.Text())
switch {
case baseURL != "" && path != "":
return fmt.Sprintf(
"Target: %s (%s)",
friendlyRecentRemoteLabel(recentRemoteRecord{BaseURL: baseURL, Path: path}),
path,
)
case baseURL != "":
return "Target: " + baseURL
default:
return "Preparing remote vault access"
}
}
path := strings.TrimSpace(u.vaultPath.Text())
if path == "" {
return "Preparing local vault access"
}
return "Target: " + path
}
func (u *ui) sessionSurface() uiSurface {
if u.state.Session == nil {
return uiSurface{}
@@ -1739,6 +1783,10 @@ func (u *ui) shouldShowLifecycleSetup() bool {
return !u.hasOpenVault()
}
func (u *ui) lifecycleBusy() bool {
return u.shouldShowLifecycleSetup() && strings.TrimSpace(u.loadingMessage) != ""
}
func (u *ui) shouldUseLockedSinglePane() bool {
return u.isVaultLocked() && !u.shouldShowLifecycleSetup()
}
@@ -1958,12 +2006,21 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
u.showAPIAuditSection()
}
for u.showLocalLifecycle.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.lifecycleMode = "local"
}
for u.showRemoteLifecycle.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.lifecycleMode = "remote"
}
for u.toggleLifecycleAdvanced.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.lifecycleAdvancedHidden = !u.lifecycleAdvancedHidden
u.saveUIPreferences()
}
@@ -2059,9 +2116,15 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
u.loadSelectedEntryIntoEditor()
}
for u.pickVaultPath.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.runAction("choose vault path", func() error { return u.chooseExistingFileAction(&u.vaultPath) })
}
for u.pickKeyFile.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.runAction("choose key file", func() error { return u.chooseExistingFileAction(&u.keyFilePath) })
}
for u.pickSyncLocalPath.Clicked(gtx) {
@@ -2069,18 +2132,50 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
}
for i := range u.recentVaultClicks {
for u.recentVaultClicks[i].Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
if i < len(u.recentVaults) {
u.lifecycleMode = "local"
u.vaultPath.SetText(u.recentVaults[i])
}
}
}
for i := range u.recentRemoteClicks {
for u.recentRemoteClicks[i].Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
if i < len(u.recentRemotes) {
u.lifecycleMode = "remote"
u.applyRecentRemoteRecord(u.recentRemotes[i])
}
}
}
for u.clearVaultSelection.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.vaultPath.SetText("")
u.state.ErrorMessage = ""
u.state.StatusMessage = ""
}
for u.clearRemoteSelection.Clicked(gtx) {
if u.lifecycleBusy() {
continue
}
u.remoteBaseURL.SetText("")
u.remotePath.SetText("")
u.remoteUsername.SetText("")
u.remotePassword.SetText("")
u.state.ErrorMessage = ""
u.state.StatusMessage = ""
}
for u.dismissBanner.Clicked(gtx) {
u.state.ErrorMessage = ""
u.state.StatusMessage = ""
u.statusExpiresAt = time.Time{}
}
for u.addEntry.Clicked(gtx) {
u.state.BeginNewEntry()
u.loadSelectedEntryIntoEditor()
@@ -3304,9 +3399,35 @@ func (u *ui) banner(gtx layout.Context) layout.Dimensions {
return layout.Background{}.Layout(gtx, fill(bg), func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(12)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(14), banner.Message)
lbl.Color = fg
return lbl.Layout(gtx)
return layout.Flex{Alignment: layout.Middle}.Layout(gtx,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(14), banner.Message)
lbl.Color = fg
return lbl.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if strings.TrimSpace(banner.Detail) == "" {
return layout.Dimensions{}
}
return layout.Inset{Top: unit.Dp(2)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
lbl := material.Label(u.theme, unit.Sp(12), banner.Detail)
lbl.Color = fg
return lbl.Layout(gtx)
})
}),
)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if !banner.Dismissable {
return layout.Dimensions{}
}
return layout.Inset{Left: unit.Dp(10)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return tonedButton(gtx, u.theme, &u.dismissBanner, "Dismiss")
})
}),
)
})
})
}