Skip to content

Support NIP-51 bookmark list and set management #2282

Description

@SnowCait

Background

nostter currently handles bookmarks using the deprecated NIP-51 format:

  • kind: 30001
  • d: "bookmark"

The current NIP-51 specification defines:

  • kind: 10003 as the standard uncategorized/global bookmark list
  • kind: 30003 as user-defined categorized bookmark sets
  • kind: 30001 / d: "bookmark" as a deprecated bookmark list format

Automatically migrating the legacy list to kind: 10003 would make those bookmarks unavailable to clients that still read only the legacy format.

Instead, nostter should support the legacy and current formats side by side and let the user migrate bookmarks explicitly through a bookmark list management UI.

Goal

Introduce bookmark list management that can handle:

  • the standard kind: 10003 bookmark list
  • the legacy kind: 30001 / d: "bookmark" bookmark list
  • multiple kind: 30003 bookmark sets

Bookmarks should be movable between these lists. Moving bookmarks out of the legacy list serves as the explicit migration mechanism rather than performing an automatic migration.

This also supersedes #1285, which tracks categorized bookmarks.

Bookmark lists

Treat the following as separate lists and do not merge their contents automatically.

Bookmarks

The standard global bookmark list:

  • kind: 10003
  • one replaceable event per user

New bookmarks created through the normal bookmark action should use this list.

Legacy bookmarks

The deprecated bookmark list:

  • kind: 30001
  • d: "bookmark"

Continue reading and displaying this list so bookmarks remain accessible while users still use clients based on the old format.

Do not automatically copy or migrate its contents to kind: 10003.

nostter should not add new bookmarks to this list except when updating it as the source of an explicit move operation.

Bookmark sets

User-defined categorized bookmark lists:

  • kind: 30003
  • one addressable event per d identifier

Support multiple bookmark sets.

Use NIP-51 set metadata where available:

  • d
  • title
  • image
  • description

The d value identifies the set and should not be derived from a mutable display title.

List management UI

Update the bookmarks UI to expose the different lists separately.

For the logged-in user, provide management operations such as:

  • view the standard bookmark list
  • view the legacy bookmark list when present
  • view existing bookmark sets
  • create a bookmark set
  • edit bookmark set metadata
  • move bookmarks between lists

The legacy list should be clearly identified as a deprecated format.

Other users' bookmark pages should remain read-only and display only information that can be read publicly.

Moving bookmarks

Implement moving as a common list operation rather than as a special legacy migration path.

The same operation should support moves such as:

  • legacy 30001 → standard 10003
  • legacy 30001 → bookmark set 30003
  • standard 10003 → bookmark set 30003
  • bookmark set 30003 → standard 10003
  • bookmark set 30003 → another bookmark set 30003

A bulk move from the legacy list to the standard bookmark list may be provided as an explicit migration action.

Do not automatically union lists when the same bookmark appears in multiple lists.

Publish order

A move requires updating two replaceable/addressable events and cannot be atomic across relays.

To avoid losing a bookmark:

  1. Add the bookmark to the destination list and publish it.
  2. Confirm that the destination update succeeded.
  3. Remove the bookmark from the source list and publish it.

If the source update fails after the destination succeeds, temporary duplication is preferable to losing the bookmark.

If the destination already contains the bookmark, skip adding a duplicate and proceed with removing it from the source.

Public and private bookmarks

Preserve whether a bookmark is public or private when moving it.

NIP-51 stores:

  • public items in event tags
  • private items as encrypted tags in content

Moving a private bookmark therefore requires decrypting the source list and updating/re-encrypting the destination list rather than copying the encrypted content as an opaque value.

Continue supporting existing NIP-04-encrypted list content when reading legacy data, while using the current NIP-44 format for newly created current-format list content.

Do not discard unsupported or unrelated tags when rewriting a list.

Data model

The current bookmark implementation assumes a single bookmark event.

Refactor it so nostter can keep the following states simultaneously:

  • optional kind: 10003
  • optional legacy kind: 30001 / d: "bookmark"
  • zero or more kind: 30003 bookmark sets

The effective state of one list must not overwrite another list simply because an event arrives later from a relay.

The bookmark indicator on a note should consider membership across the supported bookmark lists rather than only the current single bookmark event.

Fetching and caching

Add kind: 10003 to normal replaceable-event handling.

Continue fetching the legacy kind: 30001 / d: "bookmark" event for compatibility.

Fetch all kind: 30003 bookmark sets belonging to the relevant user. Since their d identifiers are not known in advance, do not model them only through the existing fixed-identifier author event configuration.

Ensure cached state can represent multiple bookmark sets without losing sets whose identifiers were not known at application startup.

Concurrency

Before modifying a list, validate that the local source and destination events have not become stale compared with relay state.

For a move involving two lists, avoid overwriting a newer destination or removing an item from an outdated source snapshot.

Reuse the existing bookmark update serialization where appropriate, but extend it to account for operations involving multiple lists.

Out of scope

This issue does not require making every NIP-51-compatible event type bookmarkable from the nostter UI.

Existing bookmarkable content support may remain unchanged, but list updates must preserve NIP-51-compatible tags that nostter does not currently render or edit.

Acceptance criteria

  • kind: 10003 bookmarks are read and displayed.
  • Legacy kind: 30001 / d: "bookmark" bookmarks continue to be read and displayed separately.
  • Legacy bookmarks are not automatically migrated.
  • Existing kind: 30003 bookmark sets are discovered and displayed.
  • Users can create and manage bookmark sets.
  • New bookmarks use kind: 10003 by default.
  • Users can explicitly move bookmarks between supported lists.
  • Moving a bookmark from the legacy list provides a manual migration path.
  • Destination updates are published before source removals.
  • A failed move cannot silently lose a bookmark.
  • Public/private status is preserved when moving bookmarks.
  • Unsupported list tags are not discarded when nostter rewrites an event.
  • Receiving or updating one bookmark list does not overwrite the state of another.
  • Existing clients that still depend on the legacy bookmark event remain usable until the user explicitly moves bookmarks away from that list.

Related issues

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions