52ea861750a0

Entry view: notes are no longer in its navigation set, counter, or default-selection path; single nonNoteEntries memo and pickNavigable helper make the invariant hold at every fallback (v4.3.7)

time
2026-07-27 23:26 UTC · 3 months ago
machine
koh2
agent
claude-MiniMax-M3
session
sync false-failure and bug trio
parent
3453e4d02417
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+31 −14
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.3.6",4+  "version": "4.3.7",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.3.6",4+  "version": "4.3.7",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.3.6",3+  "version": "4.3.7",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/App.tsx+31 −14
@@ -283,10 +283,17 @@283283     }284284   });285285 286+  // Notes are not a destination of the Entry view; selecting a note opens the287+  // notes surface instead (handleSelectEntry). Anything that walks or counts288+  // the Entry view's neighbours (counter, prev/next, index) must use this289+  // filtered list, otherwise the user can arrow into a note and is stuck —290+  // the Entry view can't render a note and the prev/next buttons stop moving.291+  const nonNoteEntries = createMemo(() => entries().filter((e) => e.type !== "note"));292+286293   const selectedIndex = createMemo(() => {287294     const currentId = selectedEntry()?.id;288-    if (!currentId) return entries().length > 0 ? 0 : -1;289-    return entries().findIndex((entry) => entry.id === currentId);295+    if (!currentId) return nonNoteEntries().length > 0 ? 0 : -1;296+    return nonNoteEntries().findIndex((entry) => entry.id === currentId);290297   });291298 292299   async function applyAccentColor(name: AccentColorName) {@@ -983,7 +990,7 @@983990     const currentId = selectedEntry()?.id;984991     if (currentId) {985992       setSelectedEntry(986-        loadedEntries.find((entry) => entry.id === currentId) ?? loadedEntries[0] ?? null993+        loadedEntries.find((entry) => entry.id === currentId) ?? pickNavigable(loadedEntries)987994       );988995     }989996 @@ -1027,7 +1034,7 @@10271034         }) ?? null;10281035       }10291036     }1030-    nextSelectedEntry ??= loadedEntries[0] ?? null;1037+    nextSelectedEntry ??= pickNavigable(loadedEntries);10311038 10321039     setSelectedEntry(nextSelectedEntry);10331040     setEditEntry(undefined);@@ -1100,7 +1107,7 @@11001107     if (!entry) return;11011108     await sendMessage({ type: "DELETE_ENTRY", payload: { id: entry.id } });11021109     const loadedEntries = await loadEntries() ?? [];1103-    setSelectedEntry(loadedEntries[0] ?? null);1110+    setSelectedEntry(pickNavigable(loadedEntries));11041111     setView("entry");11051112   }11061113 @@ -1111,7 +1118,7 @@11111118     const loadedEntries = await loadEntries() ?? [];11121119     // If the currently selected entry was among the deleted, move to first11131120     const stillExists = loadedEntries.find(e => e.id === selectedEntry()?.id);1114-    if (!stillExists) setSelectedEntry(loadedEntries[0] ?? null);1121+    if (!stillExists) setSelectedEntry(pickNavigable(loadedEntries));11151122   }11161123 11171124   async function handleSave(savedEntry?: VaultEntry) {@@ -1184,16 +1191,26 @@11841191   }11851192 11861193   function navigate(dir: number) {1187-    const allEntries = entries();1188-    if (allEntries.length === 0) return;1194+    const navigable = nonNoteEntries();1195+    if (navigable.length === 0) return;11891196     const currentIndex = selectedIndex();11901197     const baseIndex = currentIndex >= 0 ? currentIndex : 0;1191-    const nextIndex = (baseIndex + dir + allEntries.length) % allEntries.length;1192-    setSelectedEntry(allEntries[nextIndex]);1198+    const nextIndex = (baseIndex + dir + navigable.length) % navigable.length;1199+    setSelectedEntry(navigable[nextIndex]);11931200     setContentPhase("enter");11941201     queueContentIdle();11951202   }11961203 1204+  /**1205+   * The Entry view's selection invariant: never select a note. Notes are a1206+   * separate surface (handleSelectEntry opens the notes view), so any code path1207+   * that picks a default — vault open, post-delete, refresh — must skip them1208+   * and return null if the only entries are notes.1209+   */1210+  function pickNavigable(list: VaultEntry[]): VaultEntry | null {1211+    return list.find((e) => e.type !== "note") ?? null;1212+  }1213+11971214   function openList() {11981215     setView("list");11991216   }@@ -1572,8 +1589,8 @@15721589                   />15731590                   <div class="peach-search-meta">15741591                     <span class="peach-search-counter">1575-                      {entries().length > 01576-                        ? `${selectedIndex() + 1} / ${entries().length}`1592+                      {nonNoteEntries().length > 01593+                        ? `${selectedIndex() + 1} / ${nonNoteEntries().length}`15771594                         : "0 entries"}15781595                     </span>15791596                     <span class="peach-divider"></span>@@ -1581,7 +1598,7 @@15811598                       class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"15821599                       onClick={() => navigate(-1)}15831600                       aria-label="Previous entry"1584-                      disabled={entries().length === 0}1601+                      disabled={nonNoteEntries().length === 0}15851602                     >15861603                       <svg width="6" height="11" viewBox="0 0 6 11" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">15871604                         <path d="M5 1L1 5.5L5 10" />@@ -1591,7 +1608,7 @@15911608                       class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"15921609                       onClick={() => navigate(1)}15931610                       aria-label="Next entry"1594-                      disabled={entries().length === 0}1611+                      disabled={nonNoteEntries().length === 0}15951612                     >15961613                       <svg width="6" height="11" viewBox="0 0 6 11" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">15971614                         <path d="M1 1L5 5.5L1 10" />

view file