Skip to main content

Saved Posts & Shelves

Overview

The Saved Posts system allows users to bookmark posts for later reference, organizing them into named shelves. This provides a personal, private collection mechanism that respects the platform's philosophy: saving is a quiet, individual action with no public signal (no "X people saved this" counter, no trending bookmarks).

Each user has at least one shelf (a "Default" shelf created automatically on first save). Users can create additional shelves for organizing saved posts by topic, mood, or any personal taxonomy. Posts can be saved with an optional personal note, and shelves can be renamed or deleted (with posts moved to the default shelf on deletion).

The save interaction has two paths: a quick save that instantly saves to the default shelf, and a detailed save that opens a popover for shelf selection and note addition. On the Saved page, users can browse all saved posts or filter by shelf, add/edit/delete notes, and unsave posts.

Relevant Invariants

  • Invariant #6: "No Public Comparative Metrics" -- Save counts are never shown to anyone. Saving is entirely private.
  • Invariant #3: "Silence Is a Valid Outcome" -- Having no saved posts is presented as a calm empty state, not a deficiency.
  • Invariant #14: "Privacy Is Infrastructure" -- Saved posts and notes are per-user and protected by RLS policies.

User Experience

Saving a Post

  1. On any post card, the bookmark icon in the action bar provides two interaction zones:
    • Click the bookmark: Quick save to default shelf (or unsave if already saved).
    • Click the dropdown arrow: Opens the SavePopover for detailed save options.
  2. The SavePopover (rendered as a portal) shows:
    • Shelf selector dropdown with all user shelves and a "New shelf" inline creation option.
    • Expandable note input for adding a personal note.
    • Save and Cancel buttons.
  3. Saved posts show a filled bookmark icon (BookmarkCheck).

Browsing Saved Posts

  1. Navigate to the Saved page (accessible from navigation).
  2. Shelf tabs at the top allow filtering: "All" or any specific shelf.
  3. Posts render in a masonry grid with the same post card component used in the feed, plus any attached note below the card.
  4. Notes can be added, edited, or deleted inline.
  5. A "Manage Shelves" button opens a modal for renaming, deleting, and creating shelves.

Managing Shelves

  1. Click the settings icon next to the shelf tabs to open the shelf manager modal.
  2. Each shelf shows its name with rename and delete buttons.
  3. The default shelf cannot be deleted (marked with an asterisk).
  4. Deleting a shelf moves its saved posts to the default shelf.
  5. New shelves can be created with a name input at the bottom of the modal.

Technical Implementation

Key Files

FilePurpose
src/components/SavedPage.jsxMain saved posts page (524 lines). Handles shelf tabs, shelf management modal, saved post listing with pagination, and note editing
src/components/post-parts/SavePopover.jsxPortal-based popover for detailed save options: shelf selection, inline shelf creation, note input (175 lines)
src/components/Post.jsxContains save/unsave logic, popover positioning, shelf management, and note editing within post cards

Save Flow (in Post.jsx)

Quick Save (handleQuickSave):

  1. If already saved: delete from saved_posts, update local state.
  2. If not saved: ensure a default shelf exists (ensureDefaultShelf), insert into saved_posts with default shelf and no note.

Detailed Save (handleShowSaveOptions + handleConfirmSave):

  1. Opens the SavePopover positioned relative to the save button.
  2. User selects a shelf and optionally adds a note.
  3. On confirm: inserts into saved_posts with selected shelf_id and note.

SavePopover positioning:

  • Calculated based on save button's bounding rect.
  • Checks space below vs. above viewport to decide popover direction.
  • Rendered via createPortal to document.body to escape overflow constraints.

SavedPage Architecture

The SavedPage fetches saved posts via a Supabase query that joins saved_posts with posts, profiles, and post_circles. It maintains:

  • savedPosts -- Array of post objects
  • savedPostsData -- Map of postId -> { note, shelf_id, saved_at } for the saved metadata
  • userShelves -- Array of shelf objects
  • authorIntentStyles -- Per-author intent styles for rendering post cards

Pagination uses PREVIOUS_POSTS_PAGE_SIZE (20 per page) with a "Load more" button.

Note Editing (in Post.jsx)

On the saved posts page, posts render with note management:

  • Add note: Button appears when hideSaveButton is true and no note exists.
  • Edit note: Inline input with Enter to save, Escape to cancel.
  • Delete note: Confirmation prompt before clearing.

Database Tables

TablePurpose
saved_postsSaves: user_id, post_id, shelf_id, note (text), saved_at (timestamp)
shelvesUser shelves: user_id, name, emoji, position, is_default (boolean)

Shelf Defaults

When a user first saves a post, ensureDefaultShelf checks for existing shelves. If none exist, it creates a "Default" shelf with emoji "pushpin", position 0, and is_default: true.

Edge Cases

ScenarioBehavior
First-ever saveDefault shelf auto-created with name "Default" and is_default flag
Save same post twiceDatabase unique constraint prevents duplicates
Delete a shelfPosts moved to default shelf before deletion; if viewing deleted shelf, switches to "All"
Delete default shelfNot allowed; delete button hidden for default shelves
Shelf name too longCapped at 30 characters (MAX_SHELF_NAME_LENGTH) in both shelf manager and inline creation
Empty saved pageShows "Nothing saved yet. Tap the bookmark on any post to save it here." with bookmark icon
Empty shelfShows "No posts in this shelf yet. Save posts to [shelf name] to see them here."
Escape key pressed in save popoverPopover closes, state resets
Click outside save popoverPopover closes via overlay click handler
  • Posts -- Save button is part of the post action bar
  • Feed -- Saved state is tracked per-post in the feed for bookmark icon rendering

Last updated: 2026-02-07