Skip to content

add bookmarks - #1494

Open
flyingisawol wants to merge 2 commits into
damus-io:masterfrom
flyingisawol:bookmarks
Open

flyingisawol wants to merge 2 commits into
damus-io:masterfrom
flyingisawol:bookmarks

Conversation

@flyingisawol

@flyingisawol flyingisawol commented Jul 23, 2026

Copy link
Copy Markdown

Adds a "Bookmark Note"/"Remove Bookmark" option to the note 3-dot menu, backed by a NIP-51 kind-10003 bookmark list that mirrors the existing mute-list architecture (AccountBookmarksData, local+remote subscription, publish/retract helpers).

Also adds a standalone Bookmarks column that lists all bookmarked notes sorted most-recently-bookmarked-first. Since the standard Timeline insertion path always sorts by a note's own created_at, bookmarks use a bespoke rebuild step (Timeline::rebuild_bookmarks_view) that assigns a synthetic rank from bookmark order instead, only rebuilding the view when the bookmark list actually changes.

Progresses #226 (bookmark + bookmark column; "clear all bookmarks" confirmation flow not yet implemented).

Changelog-Added: Add a bookmark option to the note menu and a Bookmarks column to view saved notes

Summary by CodeRabbit

  • New Features
    • Added a Bookmarks column that renders saved notes in most-recently-bookmarked order.
    • You can bookmark or remove a note from the note options menu (button adapts to current state).
    • Bookmarks are kept in sync per account and the bookmarks view refreshes automatically; Bookmarks is also added to the column picker.
  • Tests
    • Added coverage for bookmark ordering and ensuring unbookmarking removes items from the bookmarks view.

Adds a "Bookmark Note"/"Remove Bookmark" option to the note 3-dot menu,
backed by a NIP-51 kind-10003 bookmark list that mirrors the existing
mute-list architecture (AccountBookmarksData, local+remote subscription,
publish/retract helpers).

Also adds a standalone Bookmarks column that lists all bookmarked notes
sorted most-recently-bookmarked-first. Since the standard Timeline
insertion path always sorts by a note's own created_at, bookmarks use a
bespoke rebuild step (Timeline::rebuild_bookmarks_view) that assigns a
synthetic rank from bookmark order instead, only rebuilding the view when
the bookmark list actually changes.

Progresses damus-io#226 (bookmark + bookmark column; "clear all
bookmarks" confirmation flow not yet implemented).

Changelog-Added: Add a bookmark option to the note menu and a Bookmarks column to view saved notes
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cb44172-9203-4ff5-bf6e-11089c637fad

📥 Commits

Reviewing files that changed from the base of the PR and between b691d1f and 2d88841.

📒 Files selected for processing (10)
  • crates/notedeck/src/account/accounts.rs
  • crates/notedeck/src/lib.rs
  • crates/notedeck/src/note/context.rs
  • crates/notedeck/src/note/mod.rs
  • crates/notedeck_columns/src/app.rs
  • crates/notedeck_columns/src/timeline/kind.rs
  • crates/notedeck_columns/src/timeline/mod.rs
  • crates/notedeck_columns/src/ui/add_column.rs
  • crates/notedeck_columns/src/ui/column/header.rs
  • crates/notedeck_ui/src/note/mod.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/notedeck_columns/src/ui/column/header.rs
  • crates/notedeck/src/note/context.rs
  • crates/notedeck_columns/src/app.rs
  • crates/notedeck_ui/src/note/mod.rs
  • crates/notedeck/src/account/accounts.rs
  • crates/notedeck_columns/src/timeline/kind.rs
  • crates/notedeck_columns/src/timeline/mod.rs
  • crates/notedeck_columns/src/ui/add_column.rs

📝 Walkthrough

Walkthrough

Changes

The PR adds NIP-51 bookmark parsing and account synchronization, bookmark and unbookmark publishing, note-menu actions, and a dedicated bookmarks timeline with ordered refresh behavior and column-picker integration.

Bookmarks feature

Layer / File(s) Summary
Bookmark data and account synchronization
crates/notedeck/src/bookmarks.rs, crates/notedeck/src/account/*, crates/notedeck/src/lib.rs
Kind-10003 bookmark events are parsed into ordered note IDs and synchronized through selected-account queries, polling, and subscriptions.
Bookmark actions and event publishing
crates/notedeck/src/note/context.rs, crates/notedeck/src/note/publish.rs, crates/notedeck/src/note/mod.rs
Bookmark actions add or remove matching e tags and publish the rebuilt kind-10003 list.
Bookmarks timeline construction and refresh
crates/notedeck_columns/src/timeline/*, crates/notedeck_columns/src/app.rs, crates/notedeck_columns/src/ui/add_column.rs
A bookmarks timeline resolves bookmarked note IDs, preserves bookmark recency order, refreshes on list changes, and can be added through the column picker.
Bookmark state in note menus
crates/notedeck_ui/src/note/*
Note menus display bookmark or remove-bookmark actions using the selected account’s current bookmark state.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NoteView
  participant NoteContextSelection
  participant PublishApi
  participant NostrDB
  participant BookmarksTimeline
  NoteView->>NoteContextSelection: select bookmark action
  NoteContextSelection->>PublishApi: publish updated kind-10003 list
  PublishApi->>NostrDB: store and poll bookmark list
  NostrDB-->>BookmarksTimeline: provide current bookmark IDs
  BookmarksTimeline->>BookmarksTimeline: rebuild ordered note view
Loading

Possibly related PRs

  • damus-io/notedeck#1412: Both changes modify notification-sound control flow in try_process_event based on TimelineKind.

Suggested reviewers: jb55, kernelkind

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly matches the main feature added: bookmark support and management.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
crates/notedeck/src/bookmarks.rs (1)

24-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Rustdoc for the new bookmark APIs.

  • crates/notedeck/src/bookmarks.rs#L24-L26: document membership semantics.
  • crates/notedeck/src/account/bookmarks.rs#L8-L15: document the account bookmark cache and constructor.
  • crates/notedeck/src/account/bookmarks.rs#L28-L28: document initial loading behavior.
  • crates/notedeck/src/account/bookmarks.rs#L46-L50: document harvesting/current-event assumptions.
  • crates/notedeck/src/account/bookmarks.rs#L62-L63: document polling and replacement behavior.
  • crates/notedeck/src/account/accounts.rs#L332-L335: document the ownership/snapshot returned by Accounts::bookmarks().

As per coding guidelines, “Ensure docstring coverage for any code added or modified.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck/src/bookmarks.rs` around lines 24 - 26,
crates/notedeck/src/bookmarks.rs:24-26 — Add Rustdoc to Bookmarks::is_bookmarked
describing note-ID membership semantics.
crates/notedeck/src/account/bookmarks.rs:8-15 — Document the account bookmark
cache and its constructor; 28 — document initial loading behavior; 46-50 —
document harvesting and current-event assumptions; 62-63 — document polling and
replacement behavior. crates/notedeck/src/account/accounts.rs:332-335 — Document
Accounts::bookmarks ownership and the snapshot it returns.

Source: Coding guidelines

crates/notedeck/src/note/publish.rs (1)

211-218: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the modified Rust symbols.

  • crates/notedeck/src/note/publish.rs#L211-L218: add Rustdoc for send_unbookmark_event.
  • crates/notedeck/src/note/publish.rs#L265-L272: add Rustdoc for send_bookmark_event.
  • crates/notedeck/src/note/context.rs#L17-L27: document BookmarkNote.
  • crates/notedeck/src/note/context.rs#L129-L154: document the bookmark toggle behavior in process_selection.
  • crates/notedeck_ui/src/note/context.rs#L65-L73: document menu and its bookmark-state parameter.
  • crates/notedeck_ui/src/note/mod.rs#L661-L674: document the modified show_standard behavior.

As per coding guidelines, “Ensure docstring coverage for any code added or modified.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck/src/note/publish.rs` around lines 211 - 218, Document all
modified Rust symbols: add Rustdoc describing send_unbookmark_event and
send_bookmark_event, document the BookmarkNote type and bookmark-toggle behavior
in process_selection, describe menu and its bookmark-state parameter, and
document the changed show_standard behavior. Apply these updates at
crates/notedeck/src/note/publish.rs lines 211-218 and 265-272,
crates/notedeck/src/note/context.rs lines 17-27 and 129-154,
crates/notedeck_ui/src/note/context.rs lines 65-73, and
crates/notedeck_ui/src/note/mod.rs lines 661-674.

Source: Coding guidelines

crates/notedeck_columns/src/timeline/kind.rs (1)

992-999: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate kind-10003 bookmarks-list filter construction.

bookmarks_note_filter rebuilds the same Filter::new().authors([pk.bytes()]).kinds([10003]).limit(1).build() shape that already exists (per graph evidence) in crates/notedeck/src/account/bookmarks.rs and twice in crates/notedeck/src/note/publish.rs (send_bookmark_event/send_unbookmark_event). Consider hoisting a single shared helper (e.g. alongside bookmarks_from_note in crates/notedeck/src/bookmarks.rs) so all four call sites stay in sync if the shape ever changes (e.g. adding since/until).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck_columns/src/timeline/kind.rs` around lines 992 - 999,
Centralize the kind-10003 bookmarks-list filter construction in a shared helper
alongside bookmarks_from_note, then update bookmarks_note_filter and the
matching constructions in the account/bookmarks and publish bookmark flows to
call it. Preserve the existing authors, kind, and limit behavior so all four
call sites remain synchronized.
crates/notedeck_columns/src/timeline/mod.rs (1)

663-709: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Recomputes the full bookmark→NoteRef mapping every frame, even when nothing changed.

rebuild_bookmarks_view is called every frame while the Bookmarks column is ready (per app.rs), and unconditionally re-queries the kind-10003 note plus does a get_note_by_id lookup per bookmarked id before diffing against the current view. Since NIP-51 kind-10003 is a single replaceable event, its NoteKey only changes when the list is actually republished — caching the last-seen bookmarks-note key and short-circuiting the rest of the function when it's unchanged would avoid the O(n) per-bookmark lookups on every render pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck_columns/src/timeline/mod.rs` around lines 663 - 709, Cache
the last-seen bookmarks kind-10003 NoteKey in the timeline state and update
rebuild_bookmarks_view to compare the queried note’s key before rebuilding the
bookmark-to-NoteRef mapping. Return early when the key is unchanged, while
preserving the existing lookup and view-reset behavior when the bookmark list is
newly published or changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/notedeck_columns/src/timeline/kind.rs`:
- Around line 1006-1027: Update bookmarks_filter_state so it returns
FilterState::Broken(FilterError::EmptyList) when the collected ids vector is
empty. Only construct and return the ready Filter with kinds, ids, and limit
when at least one bookmark ID exists, preserving the existing query-error
behavior.

In `@crates/notedeck_columns/src/timeline/mod.rs`:
- Around line 672-677: Update the bookmarks query flow around list_filter and
ndb.query to handle errors explicitly instead of calling .ok() and silently
defaulting to empty bookmarks. Log the query failure with error! consistently
with bookmarks_filter_state in kind.rs, while preserving the existing
successful-result mapping and default fallback behavior.

In `@crates/notedeck/src/account/bookmarks.rs`:
- Around line 62-72: The poll_for_updates flow should refresh bookmark state
from the canonical current kind-10003 event after detecting a notification,
rather than harvesting the potentially stale nks result directly. Re-query using
the selected account’s existing filter, then pass that current event set to
harvest_nip51_bookmarks before replacing self.bookmarks; preserve the early
return when no notification is received.
- Around line 28-43: Update Bookmarks::query so an error from ndb.query is
logged and returns without replacing self.bookmarks, preserving the existing
snapshot or default initial value; retain the current mapping and harvest flow
for successful queries.

In `@crates/notedeck/src/note/publish.rs`:
- Around line 287-291: The bookmark publishing path around the existing_note
lookup must preserve database errors instead of converting them to an empty
list: explicitly match query and note-read results, log failures, and return
before publishing. Apply the same failure-versus-missing distinction in the
unbookmark path at crates/notedeck/src/note/publish.rs#L235-L239 so errors are
not reported as an absent list; the existing missing-list behavior should remain
unchanged.

---

Nitpick comments:
In `@crates/notedeck_columns/src/timeline/kind.rs`:
- Around line 992-999: Centralize the kind-10003 bookmarks-list filter
construction in a shared helper alongside bookmarks_from_note, then update
bookmarks_note_filter and the matching constructions in the account/bookmarks
and publish bookmark flows to call it. Preserve the existing authors, kind, and
limit behavior so all four call sites remain synchronized.

In `@crates/notedeck_columns/src/timeline/mod.rs`:
- Around line 663-709: Cache the last-seen bookmarks kind-10003 NoteKey in the
timeline state and update rebuild_bookmarks_view to compare the queried note’s
key before rebuilding the bookmark-to-NoteRef mapping. Return early when the key
is unchanged, while preserving the existing lookup and view-reset behavior when
the bookmark list is newly published or changed.

In `@crates/notedeck/src/bookmarks.rs`:
- Around line 24-26: crates/notedeck/src/bookmarks.rs:24-26 — Add Rustdoc to
Bookmarks::is_bookmarked describing note-ID membership semantics.
crates/notedeck/src/account/bookmarks.rs:8-15 — Document the account bookmark
cache and its constructor; 28 — document initial loading behavior; 46-50 —
document harvesting and current-event assumptions; 62-63 — document polling and
replacement behavior. crates/notedeck/src/account/accounts.rs:332-335 — Document
Accounts::bookmarks ownership and the snapshot it returns.

In `@crates/notedeck/src/note/publish.rs`:
- Around line 211-218: Document all modified Rust symbols: add Rustdoc
describing send_unbookmark_event and send_bookmark_event, document the
BookmarkNote type and bookmark-toggle behavior in process_selection, describe
menu and its bookmark-state parameter, and document the changed show_standard
behavior. Apply these updates at crates/notedeck/src/note/publish.rs lines
211-218 and 265-272, crates/notedeck/src/note/context.rs lines 17-27 and
129-154, crates/notedeck_ui/src/note/context.rs lines 65-73, and
crates/notedeck_ui/src/note/mod.rs lines 661-674.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d56b483-1909-4a08-ba40-9792f272d734

📥 Commits

Reviewing files that changed from the base of the PR and between b41ffeb and b691d1f.

📒 Files selected for processing (16)
  • crates/notedeck/src/account/accounts.rs
  • crates/notedeck/src/account/bookmarks.rs
  • crates/notedeck/src/account/mod.rs
  • crates/notedeck/src/bookmarks.rs
  • crates/notedeck/src/lib.rs
  • crates/notedeck/src/note/context.rs
  • crates/notedeck/src/note/mod.rs
  • crates/notedeck/src/note/publish.rs
  • crates/notedeck_columns/src/app.rs
  • crates/notedeck_columns/src/timeline/kind.rs
  • crates/notedeck_columns/src/timeline/mod.rs
  • crates/notedeck_columns/src/timeline/route.rs
  • crates/notedeck_columns/src/ui/add_column.rs
  • crates/notedeck_columns/src/ui/column/header.rs
  • crates/notedeck_ui/src/note/context.rs
  • crates/notedeck_ui/src/note/mod.rs

Comment on lines +1006 to +1027
fn bookmarks_filter_state(txn: &Transaction, ndb: &Ndb, pk: &Pubkey) -> FilterState {
let list_filter = bookmarks_note_filter(pk);

let results = match ndb.query(txn, std::slice::from_ref(&list_filter), 1) {
Ok(results) => results,
Err(err) => {
error!("bookmarks list query failed: {err}");
return FilterState::Broken(FilterError::EmptyList);
}
};

let ids: Vec<[u8; 32]> = results
.first()
.map(|qr| bookmarks_from_note(&qr.note).note_ids.into_iter().collect())
.unwrap_or_default();

FilterState::ready(vec![Filter::new()
.kinds([1])
.ids(ids.iter())
.limit(default_limit())
.build()])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle the empty bookmark list before building the filter.

When bookmarks_filter_state() gets no bookmarks, ids is empty but the code still builds Filter::new().kinds([1]).ids(ids.iter()).... Build the ready filter only when there are bookmark IDs; otherwise return FilterState::Broken(FilterError::EmptyList) to avoid producing a list-filter edge case for the Bookmarks column.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck_columns/src/timeline/kind.rs` around lines 1006 - 1027,
Update bookmarks_filter_state so it returns
FilterState::Broken(FilterError::EmptyList) when the collected ids vector is
empty. Only construct and return the ready Filter with kinds, ids, and limit
when at least one bookmark ID exists, preserving the existing query-error
behavior.

Comment on lines +672 to +677
let list_filter = bookmarks_note_filter(pk);
let bookmarks = ndb
.query(txn, std::slice::from_ref(&list_filter), 1)
.ok()
.and_then(|results| results.first().map(|qr| bookmarks_from_note(&qr.note)))
.unwrap_or_default();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Bookmarks-note query errors are silently swallowed.

.ok() discards any ndb.query error and falls back to an empty Bookmarks, which would blank the bookmarks view on a transient failure with no trace. The sibling bookmarks_filter_state in kind.rs logs via error! on the same kind of failure — consider doing the same here for consistency and debuggability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck_columns/src/timeline/mod.rs` around lines 672 - 677, Update
the bookmarks query flow around list_filter and ndb.query to handle errors
explicitly instead of calling .ok() and silently defaulting to empty bookmarks.
Log the query failure with error! consistently with bookmarks_filter_state in
kind.rs, while preserving the existing successful-result mapping and default
fallback behavior.

Comment on lines +28 to +43
pub(super) fn query(&mut self, ndb: &Ndb, txn: &Transaction) {
// Query the ndb immediately to see if the user's bookmarks list is already there
let lim = self
.filter
.limit()
.unwrap_or(crate::filter::default_limit()) as i32;
let nks = ndb
.query(txn, std::slice::from_ref(&self.filter), lim)
.expect("query user bookmarks results")
.iter()
.map(|qr| qr.note_key)
.collect::<Vec<NoteKey>>();
let bookmarks = Self::harvest_nip51_bookmarks(ndb, txn, &nks);
debug!("initial bookmarks {:?}", bookmarks);

self.bookmarks = Arc::new(bookmarks);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not crash on a bookmark-list query failure.

Line 36 panics during startup or account switching if ndb.query fails. Log the error and retain the existing snapshot (or the default initial value) instead.

Proposed fix
-use tracing::debug;
+use tracing::{debug, error};

-        let nks = ndb
-            .query(txn, std::slice::from_ref(&self.filter), lim)
-            .expect("query user bookmarks results")
+        let nks = match ndb.query(txn, std::slice::from_ref(&self.filter), lim) {
+            Ok(results) => results,
+            Err(err) => {
+                error!("query user bookmarks failed: {err}");
+                return;
+            }
+        }
             .iter()
             .map(|qr| qr.note_key)
             .collect::<Vec<NoteKey>>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub(super) fn query(&mut self, ndb: &Ndb, txn: &Transaction) {
// Query the ndb immediately to see if the user's bookmarks list is already there
let lim = self
.filter
.limit()
.unwrap_or(crate::filter::default_limit()) as i32;
let nks = ndb
.query(txn, std::slice::from_ref(&self.filter), lim)
.expect("query user bookmarks results")
.iter()
.map(|qr| qr.note_key)
.collect::<Vec<NoteKey>>();
let bookmarks = Self::harvest_nip51_bookmarks(ndb, txn, &nks);
debug!("initial bookmarks {:?}", bookmarks);
self.bookmarks = Arc::new(bookmarks);
use tracing::{debug, error};
pub(super) fn query(&mut self, ndb: &Ndb, txn: &Transaction) {
// Query the ndb immediately to see if the user's bookmarks list is already there
let lim = self
.filter
.limit()
.unwrap_or(crate::filter::default_limit()) as i32;
let nks = match ndb.query(txn, std::slice::from_ref(&self.filter), lim) {
Ok(results) => results,
Err(err) => {
error!("query user bookmarks failed: {err}");
return;
}
}
.iter()
.map(|qr| qr.note_key)
.collect::<Vec<NoteKey>>();
let bookmarks = Self::harvest_nip51_bookmarks(ndb, txn, &nks);
debug!("initial bookmarks {:?}", bookmarks);
self.bookmarks = Arc::new(bookmarks);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck/src/account/bookmarks.rs` around lines 28 - 43, Update
Bookmarks::query so an error from ndb.query is logged and returns without
replacing self.bookmarks, preserving the existing snapshot or default initial
value; retain the current mapping and harvest flow for successful queries.

Comment on lines +62 to +72
#[profiling::function]
pub(super) fn poll_for_updates(&mut self, ndb: &Ndb, txn: &Transaction, sub: Subscription) {
let nks = ndb.poll_for_notes(sub, 1);

if nks.is_empty() {
return;
}

let bookmarks = Self::harvest_nip51_bookmarks(ndb, txn, &nks);
debug!("updated bookmarks {:?}", bookmarks);
self.bookmarks = Arc::new(bookmarks);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Refresh from the canonical replaceable event after a notification.

Line 70 replaces state with whichever matching event was polled. A delayed older kind-10003 event can therefore regress the selected account’s bookmark state. After detecting an update, re-query the account filter and derive state from the current replaceable event rather than directly harvesting nks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck/src/account/bookmarks.rs` around lines 62 - 72, The
poll_for_updates flow should refresh bookmark state from the canonical current
kind-10003 event after detecting a notification, rather than harvesting the
potentially stale nks result directly. Re-query using the selected account’s
existing filter, then pass that current event set to harvest_nip51_bookmarks
before replacing self.bookmarks; preserve the early return when no notification
is received.

Comment on lines +287 to +291
let existing_note = ndb
.query(txn, std::slice::from_ref(&filter), lim)
.ok()
.and_then(|results| results.first().map(|qr| qr.note_key))
.and_then(|nk| ndb.get_note_by_key(txn, nk).ok());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not treat database failures as an empty bookmark list.

ok() turns query/read failures into None. In the bookmark path, that enters the fresh-list branch and publishes a replacement kind-10003 event, discarding every existing bookmark. Match and log errors explicitly, then return without publishing.

  • crates/notedeck/src/note/publish.rs#L287-L291: distinguish an empty query from query/read failure before creating a new list.
  • crates/notedeck/src/note/publish.rs#L235-L239: distinguish failure from a missing list so unbookmark errors are not reported as absence.
📍 Affects 1 file
  • crates/notedeck/src/note/publish.rs#L287-L291 (this comment)
  • crates/notedeck/src/note/publish.rs#L235-L239
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/notedeck/src/note/publish.rs` around lines 287 - 291, The bookmark
publishing path around the existing_note lookup must preserve database errors
instead of converting them to an empty list: explicitly match query and
note-read results, log failures, and return before publishing. Apply the same
failure-versus-missing distinction in the unbookmark path at
crates/notedeck/src/note/publish.rs#L235-L239 so errors are not reported as an
absent list; the existing missing-list behavior should remain unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant