Restore entries tab state and preload recent vault

This commit is contained in:
Joe Julian
2026-03-30 14:31:06 -07:00
parent 88151dc780
commit 27fdd77aa1
2 changed files with 207 additions and 9 deletions
+101 -9
View File
@@ -90,6 +90,7 @@ type statePaths struct {
type recentVaultRecord struct {
Path string `json:"path"`
LastGroup []string `json:"lastGroup,omitempty"`
UsedAt string `json:"usedAt,omitempty"`
}
type recentRemoteRecord struct {
@@ -98,12 +99,20 @@ type recentRemoteRecord struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
LastGroup []string `json:"lastGroup,omitempty"`
UsedAt string `json:"usedAt,omitempty"`
}
type uiPreferences struct {
GroupControlsHidden bool `json:"groupControlsHidden"`
}
type entriesSectionState struct {
Path []string
SearchQuery string
SelectedEntryID string
Editing bool
}
type syncSourceMode string
const (
@@ -286,6 +295,8 @@ type ui struct {
recentVaults []string
recentRemotes []recentRemoteRecord
recentVaultGroups map[string][]string
recentVaultUsedAt map[string]time.Time
entriesState entriesSectionState
deleteGroupPath []string
apiPolicyGroupScope bool
apiTokenSecret string
@@ -394,6 +405,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
uiPreferencesPath: paths.UIPreferencesPath,
recentRemotesPath: paths.RecentRemotesPath,
recentVaultGroups: map[string][]string{},
recentVaultUsedAt: map[string]time.Time{},
now: time.Now,
syncSourceMode: syncSourceLocal,
syncDirection: syncDirectionPull,
@@ -416,6 +428,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
u.setCustomFieldRows(nil)
u.loadRecentVaults()
u.loadRecentRemotes()
u.restoreStartupLifecycleTarget()
u.loadUIPreferences()
u.filter()
return u
@@ -512,26 +525,28 @@ func (u *ui) selectedAttachmentNames() []string {
func (u *ui) showEntriesSection() {
u.resetPasswordPeek()
preservedPath := append([]string(nil), u.currentPath...)
u.state.ShowSection(appstate.SectionEntries)
u.restoreEntriesPath(preservedPath)
u.restoreEntriesSectionState()
u.filter()
}
func (u *ui) showTemplatesSection() {
u.resetPasswordPeek()
u.rememberEntriesSectionState()
u.state.ShowSection(appstate.SectionTemplates)
u.filter()
}
func (u *ui) showRecycleBinSection() {
u.resetPasswordPeek()
u.rememberEntriesSectionState()
u.state.ShowSection(appstate.SectionRecycleBin)
u.filter()
}
func (u *ui) showAPITokensSection() {
u.resetPasswordPeek()
u.rememberEntriesSectionState()
u.state.ShowSection(appstate.SectionAPITokens)
u.loadSelectedAPITokenIntoEditor()
u.filter()
@@ -539,6 +554,7 @@ func (u *ui) showAPITokensSection() {
func (u *ui) showAPIAuditSection() {
u.resetPasswordPeek()
u.rememberEntriesSectionState()
u.state.ShowSection(appstate.SectionAPIAudit)
u.selectedAuditIndex = -1
u.filter()
@@ -897,11 +913,15 @@ func (u *ui) noteRecentVault(path string) {
if u.recentVaultGroups == nil {
u.recentVaultGroups = map[string][]string{}
}
if u.recentVaultUsedAt == nil {
u.recentVaultUsedAt = map[string]time.Time{}
}
if len(u.currentPath) > 0 {
u.recentVaultGroups[path] = append([]string(nil), u.currentPath...)
} else if _, ok := u.recentVaultGroups[path]; !ok {
u.recentVaultGroups[path] = nil
}
u.recentVaultUsedAt[path] = u.now()
next := []string{path}
for _, existing := range u.recentVaults {
if existing == path {
@@ -929,6 +949,7 @@ func (u *ui) loadRecentVaults() {
}
u.recentVaults = nil
u.recentVaultGroups = map[string][]string{}
u.recentVaultUsedAt = map[string]time.Time{}
var records []recentVaultRecord
switch {
case json.Unmarshal(content, &records) == nil:
@@ -960,7 +981,13 @@ func (u *ui) applyRecentVaultRecords(records []recentVaultRecord) {
if u.recentVaultGroups == nil {
u.recentVaultGroups = map[string][]string{}
}
if u.recentVaultUsedAt == nil {
u.recentVaultUsedAt = map[string]time.Time{}
}
u.recentVaultGroups[path] = append([]string(nil), record.LastGroup...)
if usedAt, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(record.UsedAt)); err == nil {
u.recentVaultUsedAt[path] = usedAt
}
if len(filtered) == 6 {
break
}
@@ -1020,6 +1047,7 @@ func (u *ui) saveRecentVaults() {
records = append(records, recentVaultRecord{
Path: path,
LastGroup: append([]string(nil), u.recentVaultGroups[path]...),
UsedAt: u.recentVaultUsedAt[path].Format(time.RFC3339Nano),
})
}
content, err := json.MarshalIndent(records, "", " ")
@@ -1084,6 +1112,7 @@ func (u *ui) noteRecentRemote(baseURL, path, username, password string, remember
BaseURL: baseURL,
Path: path,
LastGroup: append([]string(nil), u.currentPath...),
UsedAt: u.now().Format(time.RFC3339Nano),
}
if len(record.LastGroup) == 0 {
record.LastGroup = u.recentRemoteGroup(baseURL, path)
@@ -1120,6 +1149,53 @@ func (u *ui) recentRemoteGroup(baseURL, path string) []string {
return nil
}
func (u *ui) restoreStartupLifecycleTarget() {
localPath, localUsedAt := u.latestRecentVault()
remoteRecord, hasRemote, remoteUsedAt := u.latestRecentRemote()
switch {
case hasRemote && (localPath == "" || remoteUsedAt.After(localUsedAt)):
u.lifecycleMode = "remote"
u.applyRecentRemoteRecord(remoteRecord)
case localPath != "":
u.lifecycleMode = "local"
u.vaultPath.SetText(localPath)
}
}
func (u *ui) latestRecentVault() (string, time.Time) {
for _, path := range u.recentVaults {
if strings.TrimSpace(path) == "" {
continue
}
return path, u.recentVaultUsedAt[path]
}
return "", time.Time{}
}
func (u *ui) latestRecentRemote() (recentRemoteRecord, bool, time.Time) {
for _, record := range u.recentRemotes {
if strings.TrimSpace(record.BaseURL) == "" || strings.TrimSpace(record.Path) == "" {
continue
}
usedAt, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(record.UsedAt))
if err != nil {
usedAt = time.Time{}
}
return record, true, usedAt
}
return recentRemoteRecord{}, false, time.Time{}
}
func (u *ui) applyRecentRemoteRecord(record recentRemoteRecord) {
u.remoteBaseURL.SetText(record.BaseURL)
u.remotePath.SetText(record.Path)
u.remoteUsername.SetText(record.Username)
u.remotePassword.SetText(record.Password)
u.remotePassword.Mask = '•'
u.rememberRemoteAuth.Value = strings.TrimSpace(record.Username) != "" || record.Password != ""
}
func (u *ui) noteCurrentRemotePath() {
status, ok := u.state.Session.(sessionStatus)
if !ok || !status.IsRemote() || status.IsLocked() {
@@ -1241,6 +1317,28 @@ func (u *ui) restoreEntriesPath(path []string) {
u.enterHiddenVaultRoot()
}
func (u *ui) rememberEntriesSectionState() {
if u.state.Section != appstate.SectionEntries {
return
}
u.entriesState = entriesSectionState{
Path: append([]string(nil), u.currentPath...),
SearchQuery: u.search.Text(),
SelectedEntryID: u.state.SelectedEntryID,
Editing: u.editingEntry,
}
}
func (u *ui) restoreEntriesSectionState() {
u.search.SetText(u.entriesState.SearchQuery)
u.restoreEntriesPath(u.entriesState.Path)
u.state.SelectedEntryID = u.entriesState.SelectedEntryID
u.editingEntry = u.entriesState.Editing && strings.TrimSpace(u.entriesState.SelectedEntryID) != ""
if u.editingEntry || strings.TrimSpace(u.state.SelectedEntryID) != "" {
u.loadSelectedEntryIntoEditor()
}
}
func (u *ui) displayPath() []string {
path := append([]string(nil), u.currentPath...)
root := u.hiddenVaultRoot()
@@ -1743,13 +1841,7 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
for i := range u.recentRemoteClicks {
for u.recentRemoteClicks[i].Clicked(gtx) {
if i < len(u.recentRemotes) {
record := u.recentRemotes[i]
u.remoteBaseURL.SetText(record.BaseURL)
u.remotePath.SetText(record.Path)
u.remoteUsername.SetText(record.Username)
u.remotePassword.SetText(record.Password)
u.remotePassword.Mask = '•'
u.rememberRemoteAuth.Value = strings.TrimSpace(record.Username) != "" || record.Password != ""
u.applyRecentRemoteRecord(u.recentRemotes[i])
}
}
}