diff --git a/internal/ui/app/app.go b/internal/ui/app/app.go index 8063a18..cd4b8d4 100644 --- a/internal/ui/app/app.go +++ b/internal/ui/app/app.go @@ -56,6 +56,7 @@ const ( slotDescribe // extra metadata for the selected object slotCopy // fetch a value straight to the clipboard slotVersions // count the selected object's versions + slotDetails // a vault's, scope's or file's details slotCount ) @@ -591,7 +592,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.groups[msg.provider] = groupsState{names: msg.names, err: msg.err} case detailsMsg: - m.details[msg.key] = detailsState{attrs: msg.attrs, err: msg.err} + if msg.req == m.reqID[slotDetails] { + m.done(slotDetails) + } + if errors.Is(msg.err, context.Canceled) || errors.Is(msg.err, context.DeadlineExceeded) { + delete(m.details, msg.key) // tried again when shown again + } else { + m.details[msg.key] = detailsState{attrs: msg.attrs, err: msg.err} + } case crawlMsg: cmds = append(cmds, m.handleCrawl(msg)) diff --git a/internal/ui/app/fields.go b/internal/ui/app/fields.go index 12f119d..2e51e3a 100644 --- a/internal/ui/app/fields.go +++ b/internal/ui/app/fields.go @@ -30,7 +30,8 @@ type groupsMsg struct { } // A vault's, scope's or file's details beyond its listing load once a -// session, the first time it's shown, and again with refresh. +// session, when the cursor rests on it, and again with refresh. They're +// extra: moving on cancels the load, and search never waits for them. type detailsState struct { attrs []provider.Attr err error @@ -38,6 +39,7 @@ type detailsState struct { } type detailsMsg struct { + req int key string attrs []provider.Attr err error @@ -211,14 +213,22 @@ func (m *Model) loadDetails(c provider.Container) tea.Cmd { if _, seen := m.details[key]; !ok || seen { return nil } - m.details[key] = detailsState{loading: true} - timeout := m.timeout - return func() tea.Msg { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - attrs, err := d.Details(ctx, c) - return detailsMsg{key: key, attrs: attrs, err: err} + // One at a time: the previous one, if it's still loading, is + // cancelled and forgotten, to load when it's shown again. + for k, st := range m.details { + if st.loading { + delete(m.details, k) + } } + m.details[key] = detailsState{loading: true} + ctx, req := m.begin(slotDetails) + return m.settled(ctx, func() tea.Msg { + attrs, err := d.Details(ctx, c) + if ctx.Err() != nil { + err = ctx.Err() + } + return detailsMsg{req: req, key: key, attrs: attrs, err: err} + }) } // problemFields show why an account has nothing to show, wrapped to the