diff --git a/main.go b/main.go index 31c08e4..94e3d9a 100644 --- a/main.go +++ b/main.go @@ -650,6 +650,13 @@ func (u *ui) filter() { } } +func (u *ui) visibleEntrySnapshot() ([]entry, []widget.Clickable) { + visible := append([]entry(nil), u.visible...) + clicks := make([]widget.Clickable, len(visible)) + copy(clicks, u.entryClicks) + return visible, clicks +} + func defaultStatePaths(stateDir string) statePaths { baseDir := strings.TrimSpace(stateDir) if baseDir == "" { @@ -4145,7 +4152,8 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions { u.ensureNavClickables() if u.mode == "phone" { return panel(gtx, func(gtx layout.Context) layout.Dimensions { - rows := make([]layout.Widget, 0, 16+len(u.visible)) + visibleEntries, entryClicks := u.visibleEntrySnapshot() + rows := make([]layout.Widget, 0, 16+len(visibleEntries)) rows = append(rows, func(gtx layout.Context) layout.Dimensions { gtx.Constraints.Min.X = gtx.Constraints.Max.X return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions { @@ -4209,15 +4217,15 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions { rows = append(rows, u.apiTokenListPanel) case u.state.Section == appstate.SectionAPIAudit: rows = append(rows, u.apiAuditListPanel) - case len(u.visible) == 0: + case len(visibleEntries) == 0: rows = append(rows, func(gtx layout.Context) layout.Dimensions { return emptyStatePanel(gtx, u.theme, u.listEmptyState()) }) default: - for i := range u.visible { + for i := range visibleEntries { idx := i rows = append(rows, func(gtx layout.Context) layout.Dimensions { - return u.entryRow(gtx, &u.entryClicks[idx], idx, u.visible[idx]) + return u.entryRow(gtx, &entryClicks[idx], idx, visibleEntries[idx]) }) } } diff --git a/main_test.go b/main_test.go index ef5876a..748c393 100644 --- a/main_test.go +++ b/main_test.go @@ -821,6 +821,31 @@ func TestUIPhoneListPanelWithExpandedGroupControlsAndEntriesDoesNotPanic(t *test _ = u.listPanel(gtx) } +func TestUIVisibleEntrySnapshotIsStableAfterVisibleMutation(t *testing.T) { + t.Parallel() + + u := newUIWithModel("phone", vault.Model{ + Entries: []vault.Entry{ + {ID: "1", Title: "Alpha", Path: []string{"Joe", "Internet"}}, + {ID: "2", Title: "Beta", Path: []string{"Joe", "Internet"}}, + }, + }) + u.state.NavigateToPath([]string{"Joe", "Internet"}) + u.filter() + + visible, clicks := u.visibleEntrySnapshot() + if len(visible) != 2 || len(clicks) != 2 { + t.Fatalf("snapshot lengths = (%d, %d), want (2, 2)", len(visible), len(clicks)) + } + + u.visible = u.visible[:1] + u.entryClicks = u.entryClicks[:1] + + if got := visible[1].Title; got != "Beta" { + t.Fatalf("visible snapshot second title = %q, want Beta", got) + } +} + func TestUIPhoneBackReturnsFromSubscreenToEntries(t *testing.T) { t.Parallel()