📚 Faster uploads, PDFs, and background processing — drop a folder and get organized.
Skip to main content
back to library

Help

How to do everything in Shelfsort. Last updated 2026-06-24.

Don’t see what you’re looking for? Drop a suggestion → — bugs, tweaks, brand new ideas all welcome.

Chip keyAafandom2crossoverpairingauthor
Try:

What's new

Send us feedback

Got a bug, a feature wish, or a screen that feels confusing? Drop us a note here — every suggestion lands in the team's triage queue. You can attach a screenshot if showing is easier than telling.

  • Device picker (required) — when you submit on the Suggestions board, you pick which device you're on: iPhone, iPad, Android phone/tablet, Mac, Windows PC, Linux, Chromebook, Amazon Fire, Kindle e-reader, or Other. The picker auto-detects from your browser and remembers your last choice. “Other” lets you type in a custom device (Steam Deck, BOOX Note, Surface Duo, etc.) — your entry gets added to the picker so other users with the same device see it too.
  • Why required? A bug that says “the reader is laggy” is unhelpful; “the reader is laggy on Amazon Fire” is actionable. The device chip shows on every suggestion card so vote-givers know whether a report applies to their setup.
  • Pictures only — attachments are limited to images under 10 MB (PNG/JPEG). Other file types should go via our support email.

Tell us what would make Shelfsort better

Bug, feature wish, confusion — anything goes. You can attach a screenshot.

0/2000

Getting started

Shelfsort organizes your EPUB library by fandom, author, pairing, completion status, and reading progress — built for fanfiction readers but works for any ebook collection.

  1. Sign in with email + password (or Google OAuth)
  2. Drop an EPUB onto the upload zone on the dashboard
  3. Watch it get auto-classified onto the right shelf, with status and pairings extracted
  4. Click Browse all books to see the full grid; click any cover to read it in-browser

Everything is auto-detected at upload time — you only step in when you want to override.

First-time tour

The first time you land on a fresh account, a friendly overlay walks you through Shelfsort’s main hubs: Library, Friends & DMs, Reading rooms, Recommendations, Appearance (light/dark + colour scheme), Account & usernames, and Suggestions & feedback. Each step highlights the relevant page so you know where the feature lives, not just what it does.

  • Skip any step — “Skip tour” dismisses it for good, “Next” advances one step at a time, “Back” goes one step back.
  • Replay later — at the top of this Help page, hit the Replay tour → link in the “What’s new” strip. It clears the localStorage flag and re-opens the overlay on your next dashboard visit.
  • Per-browser memory — the “seen” flag lives in localStorage, so the tour reappears if you sign in on a new device or wipe browser data.

Dashboard tour (the welcome page)

The page you land on after signing in is your welcome dashboard at /library. It’s deliberately lean — the full books grid lives on its own All books page. From the dashboard you can:

  • See your library at a glance — total book count, top 4 fandoms with counts, “Since you were last here” activity ribbon.
  • Upload — the big drop zone accepts files OR a whole folder. EPUBs go straight in; PDFs / MOBI / DOCX / TXT get a confirmation prompt then Calibre-convert.
  • Filter a URL list — the spotlighted “Have a list of fanfic URLs?” card lets you paste a list of AO3 / FFnet / RoyalRoad / SpaceBattles links right on the dashboard. It detects the count live and ships you to the full filter page with the list pre-loaded. See Filter URLs you already own.
  • Up next — your reading queue surfaces here (see Reading queue).
  • Pinned smart shelves — appear as quick chips once you’ve pinned any.
  • Help & Suggestions — two cards at the bottom: this guide on the left, a 5-second feedback box on the right.

The persistent ribbons at the top — Backup reminder, Polish your library, Help nudge — only appear when relevant (overdue backup, un-templated EPUBs, etc.). Dismissing one is remembered.

Uploading books

The upload zone accepts files and folders. Drag-and-drop or click Choose files / Pick a folder. You can drop more books from the compact zone at the top of All books too — no need to bounce back to the dashboard.

Supported formats:

  • .epub — added silently, full pipeline (metadata + classification + cover + chapters + Reader)
  • .pdf — kept as the original, read in-app via the native PDF viewer (no Calibre roundtrip needed). Optional one-click Convert to EPUB if you want it on the main library.
  • .mobi .azw .azw3 .kfx .docx .doc .rtf .fb2 .lit .lrf .pdb .html .htm — confirmation prompt, then auto-converted to EPUB via Calibre
  • .txt with prose — confirm + convert
  • .txt with fanfic URLs (≥3 URL lines or ≥40% of lines) — treated as a URL list, dedupe-and-skip flow

Fast and resilient. Uploads run in parallel through a sliding window of 3 to 6 files (slow-start ramp — begins with 3, climbs to 6 after successive successes, throttles back down when the origin looks strained), recover from individual file failures, and the asynchronous pipeline means you can drop dozens of files at once without worrying about edge-server timeouts. Submit returns in seconds; processing keeps running in the background, and your library refreshes automatically when each batch completes.

See exactly what’s happening (new — 2026-08-27). Every file gets its own row with an inline progress bar showing which state it’s in — Waiting, Uploading (real byte-level percentage), Processing (indeterminet shimmer while the server does AV scan + Claude classification + R2 mirror), Done, Skipped (duplicate), or Failed (with a specific reason and one-click Retry). The list is grouped by status with collapsible headers, has a filename search + status filter for big batches, an ETA chip in the header that starts showing once we have enough data to be honest about the estimate, and a Copy stuck rows button that dumps failed filenames + reasons to your clipboard as Markdown — handy for bug reports. A big Clear all is always available. When a batch finishes 100% clean you get a ✨ All N books saved pill; there’s an optional gentle bell that plays with it (opt-in via the tiny speaker toggle in the tray, defaults off).

“Stage before upload” — review before you commit (default ON). New drops park in a staging tray so you can see what you selected, remove individual files with the ✕ button, and click Start uploading N when you’re ready. Drop more files later to append to the queue; the tray persists across page navigations. Turning it OFF (via the toggle above the drop zone) makes drops auto-upload immediately — same progress list either way. The toggle now stays in sync across the Dashboard, All-books, and every other place you can upload — no more “one page says on, the other says off” confusion.

Refresh-safe: auto-resume on re-drop. If you accidentally close the tab or reload while uploads are in flight, don’t worry — the browser can’t remember file contents across a refresh, but Shelfsort remembers everything else. Interrupted rows show up as “Drop again to resume”. Drag the same files back in and Shelfsort auto-matches by filename+size, replaces the interrupted rows with fresh queued rows, and a green toast confirms “Auto-resumed N interrupted files from your previous session.” A subtle amber banner also appears at the top while any uploads are still running, warning you before you accidentally reload.

Reliability fix (2026-08-27). Fixed a long-standing bug in the concurrency loop where roughly 3 files per large batch would silently vanish (never sent to the server, never marked failed, just stuck in Waiting forever). Root cause: the sliding-window concurrency counter was being read at the wrong moment during a slow-start ramp, causing the loop to skip past files. Now snapshots the value at each round start. If you’ve been finding you had to re-drop a handful of files after every big upload — that’s over.

Airdrop mode for big drops (new). Drop more than 20 files at once and Shelfsort kicks into airdrop mode — the bytes land on the server in seconds, and the upload bar hits 100% the moment everything’s safely staged. Metadata extraction, Calibre conversion (for PDFs, Kindle, Word), AI classification, cover saving and R2 mirror all happen after the upload completes. You can close the tab the moment the bar finishes; the server keeps grinding through the queue, and a 5-minute backend cron re-kicks anything that gets stuck (backend restart, deploy mid-upload, etc.). Books appear on the library page as each one finishes processing.

Truthful airdrop summary (2026-07-25). While the bytes are still sorting the toast reads “Airdropped N books — sorting on the server…”. Once every job hits a terminal state (up to 4 minutes of background polling), the toast is replaced with a detailed breakdown: how many landed, how many are flagged as possible duplicates awaiting review, how many were auto-deleted per your duplicate policy, how many failed. If Delete immediately is your policy, any duplicates in the batch will show as “N auto-deleted as duplicates (per your policy)” — no more “uploaded 200, got 197, no reason given” mystery. Close the tab if you like; a bell notification lands independently when the batch completes.

Deferred AI sorting. The Claude classifier no longer blocks the upload progress bar. Books land in the library immediately — title, author, cover, AO3 tags, source URLs, duplicate detection all run at upload time — and the AI-driven category + fandom step happens in a background polish queue.

  • A “✨ N books sorting in the background” banner appears at the top of All books while polish is running. You can close the tab — the work continues on the server, and a 5-minute recovery cron sweeps anything that gets stuck (backend restart, missed schedule).
  • Each pending card shows a solid ✨ Sort now chip — one click runs the classifier against that specific book and flips the badge instantly.
  • Already-classified cards reveal a faint ✨ Re-sort chip on hover — useful when Claude picked the wrong fandom. Same endpoint, same effect, no need to open the detail page.
  • Hover the category badge on any card to see why the AI (or heuristic) made that call — e.g. “Matched 3 keywords for Harry Potter (e.g. ‘hogwarts’) in title/description/sample text.”

Batch size — Shelfsort processes 200 books at a time, up to 1,000 books per drop. Drop up to 1,000 EPUBs at once and we’ll show a friendly prompt (“Whoa, that’s a big library!”), then auto-queue them in up to 5 sequential batches of 200 with a live Batch 3 of 5 progress line — no manual re-dropping. The 200-per-batch ceiling keeps the antivirus scanner responsive for everyone. The 1,000-per-drop cap (new — 2026-07-27) is a safety guard against browser tab-throttling silently dropping files during marathon 20-minute uploads — if you drop more than that, we’ll ask you to split into groups of 1,000. You can wander off after the drop starts; we’ll keep going in the background.

AV-flagged files are never shared. If a book in your library is marked as infected by the antivirus scan, it’s automatically hidden from friends’ library browsers and any attempt to request it via DM is rejected at the server. Cleanup happens on your end via Account safety.

Background uploads bell. The cloud-icon in the navbar (top-right) is your command center for in-flight uploads. Always visible. Drop files or folders directly onto the icon from anywhere in the app, or click it for Choose-files / Pick-a-folder buttons. Hover any row in its panel to see a flyout card with the book’s cover, fandom chip, and an Open it CTA. The library grid pulses a coral ring on freshly-arrived cards. Close the tab any time — uploads keep running and the bell re-attaches when you come back.

What happens during upload: metadata is extracted, the book is classified onto a category (Fanfiction / Original Fiction / Non-fiction / etc.), the fandom is detected from 286 canonical fandoms, relationships/pairings are extracted, completion status is detected (see “Detection & overrides”), and any embedded source URLs are saved for future dedupe + the Linkless filter.

Duplicates are caught three ways: (1) by exact source-URL or shared canonical fanfic URL (so all AO3 / FFN / RoyalRoad mirrors of the same work collapse together), (2) by title + author match (case-insensitive, dots stripped from author so ‘J. K. Rowling’ and ‘JK Rowling’ still pair), and (3) by title alone only when one side has no author on file. Two books with the same generic title (e.g. “Crossroads”) but different authors and different URLs are correctly kept as separate books. When a dupe is flagged you choose: keep both / replace older / skip. Cross-format duplicates (same book uploaded as PDF after the EPUB) are filed under Originals.

Cleaning up a big pile at once. Head to /library/duplicates to review flagged duplicates. Each group has a purple “Keep only the latest” button that resolves the whole group in one click — the row with the newest created_at wins; if it’s a duplicate we promote it (the older keeper is archived to Old stories with reading progress preserved) and the rest go to Trash. Or use the toolbar “Keep the latest in all N groups” button to run that logic across every group at once — it runs in batches of 100 with a live progress bar, so it stays under the Cloudflare 120-second edge timeout even for thousands of groups. If it’s interrupted, clicking again resumes from where it stopped (fully idempotent).

Retry-safe by design (2026-07-25, hardened 2026-07-26). Every upload attempt carries a client-generated fingerprint sent in the X-Client-Upload-Key header. The fingerprint uses the file’s stable session identifier plus its folder path, name, size, and last-modified timestamp — so two DIFFERENT files that happen to share a name/size/mtime triple (e.g. two chapter1.epub in different subfolders exported by the same script) still get unique keys and both land. If a POST reaches the server but its response never makes it back to your browser (Cloudflare 524, dropped Wi-Fi, tab throttled), the retry loop sends the same fingerprint again. The server recognises it, replies with the ORIGINAL job_id, and does not re-queue the book. In DevTools you’ll see replays marked "replayed": true — that’s the guardrail firing, not a bug.

Fingerprint conflict detector (2026-07-26). In the extremely rare case that two different files DO produce the same fingerprint (session bug, browser quirk, exotic filesystem clock behaviour), Shelfsort no longer silently drops the second file. Instead the server compares the incoming filename against what it originally stored for that fingerprint — if they mismatch, the POST is rejected with 409 client_key_collision. The upload zone catches the 409, marks the file as “conflict”, and shows a warning banner listing each affected file next to the earlier upload it collides with. You can click Force-upload to re-submit the file with an X-Force-Upload: 1 header that bypasses idempotency and treats it as a fresh upload, or Skip to leave it out. Either way, no file ever disappears without you being told.

Delete-then-redrop is safe (2026-07-25 v4). The idempotency guard is smart: before it treats a fingerprint as a replay, it checks whether the resulting book still exists in your library. If you deleted or trashed a book and then re-drop the same file, the server invalidates the stale fingerprint on the spot and processes the upload as fresh — so the book actually lands in your library instead of returning a phantom job that goes nowhere. The fingerprint TTL is 15 minutes (shortened from 30 minutes after further prod feedback), so accidental key collisions self-heal quickly within the same session while still covering the full worst-case retry window of a stubborn network.

Feel like duplicates are hiding? The same page has a “Rescan library” button (empty-state and in the toolbar). It sweeps your whole non-trash library, catches any duplicate pairs that upload-time detection missed (e.g. author normalization changed after upload, source_url added later, or a book restored from Old stories), and flags the newer copy into the queue so you can resolve it with the same buttons. Respects any “Not a duplicate” choices you’ve made in the past.

Lonely “Old stories” get un-archived (new — 2026-07-25). When you archive a book as an old version of another, the archive is only useful alongside the newer copy. If you later delete that newer copy, the archived book is left dangling with an “Old story” label that no longer means anything. Every rescan now sweeps for those lonely archives and promotes them back to their previous shelf (falling back to Pending sort if the audit trail is missing). The toast tells you how many were promoted, and the full breakdown lands in your bell so you can review later.

Rescan results ship to your bell (new — 2026-07-25). Every rescan now writes a duplicate_rescan notification (same kind the weekly cron uses) with the full breakdown: scanned count, flagged count, un-orphaned old stories, respected “Not a duplicate” choices, and the reasons any active books were skipped. Click the bell notification to jump straight to /library/duplicates. If you don’t want these, silence the Library upkeep group in your Account notification preferences.

Why the scan count sometimes reads “X of Y” (new — 2026-07-25). The rescan intentionally skips books that are already flagged as duplicates (they’re in the queue awaiting your review, no point re-flagging), books archived to Old stories, and books that have been superseded by a newer promoted copy. So if your library shows 204 and the toast says “Scanned 199 of your 204 active books”, the missing 5 are the ones already accounted for. The categories in the reasons list can overlap (an Old stories book with a replaced_by pointer counts in both “archived” and “superseded”), so we present them as contributing reasons rather than a strict arithmetic sum.

Old stories in the rescan — your choice (new — 2026-07-27). Under the toolbar you’ll find an “Old stories:” dropdown that controls how the archive shelf participates in the rescan. Four modes: Skip (the default — Old stories are ignored, current behavior); Scan · skip archive pair (Old stories join the pool, but the tautological “Old story ↔ the newer copy that replaced it” pair is never re-flagged — useful when you accidentally re-uploaded an archived story and want it caught); Only Old stories (audit the archive shelf against itself); Scan · include archive pair (treat Old stories exactly like any other book — even the archive relationship gets re-flagged). Your choice is remembered across sessions and applies to both the manual button and the Sunday auto-rescan. A one-line hint under the toolbar echoes what the currently-selected mode does so you can double-check before hitting Rescan.

Cross-post detection (new — 2026-07-27). When a pair matches on title + author but the Source URLs point to different sites (e.g. AO3 ↔ FanFiction.net, Royal Road ↔ Wattpad), the “Why we caught this” panel highlights both URLs with an amber border and adds a “Different sites detected” warning. These are almost always the same story cross-posted to another platform — not a duplicate. Click “This isn’t a duplicate” to keep both copies and stop the pair from re-flagging on future rescans.

“This isn’t a duplicate” is permanent (clarification — 2026-07-27). When you dismiss a pair, Shelfsort silences it for good — the same (title / author / URL) will not be re-flagged on future uploads or rescans. If you change your mind, head to Account → Duplicate dismissals and click Undo next to the pair you want to re-arm.

Bulk actions on the Duplicates page (new — 2026-07-27). Next to the “Keep the latest in all N groups” button you’ll now find a More ▾ menu with four bulk options that apply to every quarantined duplicate at once:

  • Trash all duplicates — sends every duplicate to Trash (30-day grace, restorable).
  • Keep every copy — un-flags everything so both/all copies survive in place.
  • Archive all as historical — moves every duplicate under its keeper as an Old story.
  • Mark all as not duplicates — permanently silences every pair (same as clicking “This isn’t a duplicate” on each one).

Each bulk action opens a confirm dialog with an impact preview, then runs in batches of 100 with a live progress card, so it stays under the 120-second edge timeout even for large queues.

Selection mode on every list page (new — 2026-07-27). Trash, Duplicates, and Quarantine now each have a checkbox on every row (plus a “Select all” header). Once you tick anything, a small floating “N selected” bar rises from the bottom with the contextual bulk actions for that page:

  • TrashRestore / Delete permanently.
  • DuplicatesTrash / Keep all as primary, plus a More ▾ menu with Archive as historical, Mark as not duplicate, and Merge under a chosen keeper (opens a picker so you can designate which of the selected books is the keeper — the rest are flagged as duplicates of it).
  • Quarantine (AV / parse failures)Release / Delete.

On the main library (All books / Recently added / any shelf) the existing SelectionBar picked up a new Export ZIP button so you can download exactly the books you’ve selected as a bundle. The other SelectionBar actions (Move to shelf, Bulk edit metadata & tags, Set reading state, Delete) were already there and still work the same way.

Weekly auto-rescan. Every Sunday at 04:00 UTC Shelfsort quietly re-runs the same sweep across your library. If it finds anything new, you’ll get a bell notification (kind duplicate_rescan) linking straight to the Duplicates page. Quiet weeks: no notification. Don't want it? Silence the Library upkeep group from your Account notification settings.

Wrapping up: emptying Trash. After you’ve resolved the pile, drop by Trash to permanently delete. Shelfsort shows a ✨ toast with the book count and how much storage you reclaimed. Both this and the “you finished cleaning duplicates” 🎉 toast are opt-in — admins can turn them off in the Notification preferences card on the admin console.

Importing from Kindle? See the step-by-step guide at /help/kindle-import.

What happens if the server hiccups mid-batch? Shelfsort automatically retries transient failures up to 6 times with a jittered exponential backoff (~2s / 6s / 15s / 45s / 90s — ~2.5 min worst-case per file), so a brief origin blip doesn’t cost you a file. On very large batches (200+ files) Shelfsort also uses a slow-start ramp: it begins with 3 concurrent uploads and cranks up to 4 only after the origin has proven it can handle more. If more than a couple of transient failures land in a row, it throttles back down to 2 concurrent automatically to give the origin breathing room. When a file genuinely can’t make it through, you’ll see a friendly “Server briefly overloaded — please wait a moment and retry” banner (never a raw error code), and the file list is retained so you can re-drop just those files without re-selecting everything. Tuned 2026-07-25 after prod feedback: lower ceiling + longer retry budget = fewer terminal “overloaded” failures on big drops.

Why does the “N new books” count sometimes come up short? Because Shelfsort tells brand-new books apart from fanfic refreshes. When you re-upload an AO3 fic that already lives in your library, Shelfsort spots it by story ID, updates the existing entry with the new chapters, and bumps its last_refreshed_at — no duplicate book, no trash, no manual dedupe. Those refreshed fics don’t count toward “N new books” because they aren’t new; they show up as a small (+N refreshed) chip beside the new-book count and in the Fics updated bell in the navbar. So if you dropped 200 files and see “195 new books,” the other 5 are fics that got fresh chapters added in place — still on their shelves, just with new material.

Shelves & filters

Every book lives on multiple shelves at once. Click any chip on the dashboard or use the URLs below directly.

  • Fandom shelves (auto) — one per detected fandom, with crossover detection
  • Crossovers — fics belonging to 2+ fandoms
  • Finished — books with a definitive ending (default for anything without an explicit ongoing signal)
  • Ongoing — WIPs, in-progress, hiatus, abandoned, or "Chapter X of Y" where X < Y
  • Linkless — books with no embedded source URL (originals, very old uploads, manuscripts). Lets you paste a source URL after the fact via the inline claim flow.
  • Unreadable — files that couldn't be parsed (corrupt EPUBs) or converted (Calibre rejected a PDF/Kindle/DOCX). Original bytes stay on disk so you can download a copy to inspect or delete it.
  • Originals — books you uploaded as PDF/MOBI/AZW/DOCX/etc. and chose to keep as-is (without running Calibre). Each row has three buttons:
    • Read opens the smart in-app viewer at /read-original/<book_id>. PDFs now render natively via pdf.js — selectable text, full keyboard nav (PageUp/Down, J/K, arrows, Space), zoom controls (±10%, 50–250%), a page-jump input, and a scroll-tracked progress so bookmarks land on the actual page you're on. Works on mobile Safari and iOS too. HTML/HTM render via your browser's native viewer; TXT renders in a clean serif layout; DOCX is converted to HTML client-side via mammoth.js; everything else (MOBI, AZW, AZW3, KF8, KFX, FB2, LIT, LRF, PDB, DOC, RTF) gets a one-click Convert to EPUB and read button that runs Calibre and lands you in the regular reader.
      Inside the viewer, PDFs and scroll-based formats (TXT/DOCX) support bookmarks: click Bookmark (or hit Cmd/Ctrl+B) to save the current page (PDF — the viewer reports the page automatically, no prompt) or scroll position (TXT/DOCX). The bookmark count chip opens a panel where you can jump back, type a note, and remove bookmarks. Same backend as EPUB bookmarks, so they show up on the Bookmarks page too.
    • Convert to EPUB runs Calibre and promotes the book into the main library — same flow as the bulk Convert all button at the top.
    • Download streams the raw original file so you can read it in an external app like Apple Books, Kindle, or Adobe Reader.
    An Open in new tab link sits in the Read viewer's header too — it pops the raw file open in a fresh browser tab so you can use the browser's built-in viewer / save dialog.
  • Smart Shelves — saved filter combinations (fandom + tag + status + …); manage them on the Account page

The Dashboard surfaces a count chip for any non-empty special shelf so you can see what needs attention.

Library mode (Fanfic / Original / Mixed)

Shelfsort handles every EPUB you throw at it — fanfic, original novels, non-fiction, manuals, the lot. Library mode tells the app which of those worlds you mostly read, so the layout, default category, and friend-finding chips all match. Pick once, change it any time.

Newer in August 2026 — Preset Marketplace & smarter cleanups

  • Search & sort the marketplace — the Preset Marketplace now has a search box and Most upvoted / Newest / Mine chips, so it stays browsable when dozens of shared presets pile up.
  • Comment on a preset — hit the little message-bubble on any card in the marketplace to leave a tip or ask a question; authors get feedback and installers see real-world context.
  • Preset Marketplace — visit the marketplace to browse, upvote, and one-click install column-layout presets shared by other readers. Right-click any chip on your library → “Publish to marketplace…” to share your own.
  • Age-filter for storage cleanup — the admin R2 purge modal now has age chips (7d / 30d / 90d / 1y) so long-abandoned junk can be wiped without touching recent uploads.

Also new in August 2026 — jump-to-row & preset personalisation

  • Jump to any row — click the “→ #” pill in the list header (or press g anywhere on the page in list view), type a row number, hit Enter and the list scrolls straight there with a purple flash on the target row.
  • Rename or overwrite your presets — right-click any preset chip (Reading queue, Fandom deep-dive, Storage audit) to rename it, snapshot your current column layout into it, or reset it back to the default.
  • Save your own presets — hit the dashed “+ New” button next to the built-in chips to save the current visible columns & order as a new named chip (marked with a purple dot). Right-click your custom chip to rename it or delete it.
  • Share presets across devices — right-click any chip → “Copy JSON to share” grabs it as a small blob. On the other device, hit the “Paste” button next to “+ New”, paste, and Import to bring the layout across.

New in August 2026 — row numbers & layout presets

  • Leftmost # column in list view shows row 1..N in the current sort order. Handy when you’re scanning a thousand-book library and need to say “row 47” to a friend or bookmark a scroll position.
  • Layout presets in the columns menu: one-click chips for Reading queue (Time·Words + Status + Added), Fandom deep-dive (Fandom + Pairings + Status), and Storage audit (Size + Time·Words + Added). Each reshapes both what’s visible AND the left-to-right order.
  • “Just the essentials” toggle below the presets — hides Pairings + Time·Words in one click for a compact laptop view. Click again to bring every column back.

New in July 2026 — customisable list view

  • Click any column header to sort ascending, click again for descending, third click clears. Works in List, Grid, and Compact views.
  • Drag the grip icon on a column header sideways to reorder columns Excel-style. Persists.
  • Drag the thin handle at a column's right edge to resize it so long fandom / pairing values stop truncating.
  • Columns menu (three-column icon at the right end of the header) lets you hide Pairings or Time·Words on smaller screens. Title/Author is pinned.
  • Drag a section header (💜 Fanfic / 📖 Original & Non-fic) up or down to put your favourite world on top.
  • Book rows show a file-size column (KB / MB / GB) so you can spot the huge outliers at a glance.
  • 💜 Fanfic-first — the original Shelfsort flow. Fandom shelves up front, pairings panel visible, AO3 chrome (ratings, warnings, tags) front-and-centre. The library shows only books classified as Fanfiction; original-fic and non-fic stay hidden so the fandom feel isn’t diluted.
  • 📖 Original / Non-fic first — author-first navigation. The fandom chip strip and pairings rail are hidden, the default category chip lands on Original Fiction, and the library drops fanfic-category books out of view. Built for readers whose EPUB collection is mostly novels and non-fiction; fanfic isn’t deleted, just out of sight until you switch modes.
  • 🔀 Mixed (default for new accounts) — both worlds, same page. The library renders two collapsible grids: a 💜 Fanfic grid above an 📖 Original & Non-fic grid, with the categories fully sorted between them. Empty side? Its grid is silently suppressed so you don’t see a sad “0 books” header.

How to change it:

  • Inline pill at the top of /library/all — three buttons (📚 Mixed / 💜 Fanfic / 📖 Original) flip the mode on the fly without leaving the page. Hover any pill for a one-line description.
  • Detailed picker at Account → Library mode — three cards with full descriptions if you’d rather think about it before clicking.

Both surfaces hit the same PATCH /api/auth/library-modeendpoint and refresh your auth context immediately, so the layout and book list update without a page reload.

Where the preference shows up:

  • Library page — book list filtered to the chosen world (plus the mixed-mode split-grid layout above).
  • Landing page — the marketing hero text adapts (“sorted by fandom” vs “sorted by author” vs “both worlds”) so signed-in visitors see the pitch that matches their library.
  • Your public library page (when you’ve opted in) shows a small mode badge under your handle (💜 Fanfic reader / 📖 Original-fic reader) so visitors know what to expect. Original-mode profiles automatically hide the fandom chip strip — original fiction libraries have no fandoms, and an empty strip reads like a bug.
  • Reader directory — a “Reader type” chip row lets you filter the directory by mode (💜 Fanfic / 📖 Original fic / 🔀 Mixed / All). Deep-link via ?mode=fanfic etc. If your own mode is non-Mixed, an extra ✨ Like me shortcut chip one-clicks the filter to your own mode — great for finding readers with similar taste.

Tip: legacy accounts (created before this preference shipped) default to Mixed and remain fully visible in the directory. Nothing breaks if you never touch the setting.

Verdicts (favorite, need to read, never again…)

A book’s verdict is your private take on it — separate from shelves, separate from categories, separate from the reading-progress %. Two independent dimensions, both shown as chip clusters on every book card:

  • Reading state (single-select, one value at a time):📖 Need to read · 👀 Reading · ✅ Read ·🪦 DNF. Different from the existing reading-progress % on the card — state is commitment (do I plan to finish?), progress is position (how far have I gotten?). A book can be 80% read and DNF; that’s a valid combination.
  • Verdicts (multi-select, stack as many as you want): ⭐ Favorite · 💀 Least favorite ·🚫 Never again · ♻️ Reread material ·🫶 Would recommend. Mix freely — a fic can be♻️ Reread material AND 🫶 Would recommend.

You can also add up to 10 custom verdicts of your own (label + emoji of your choice) via the API for now — a UI manager on the Account page is coming.

Three ways to set a verdict:

  • One book at a time — tap any chip on the card (or the + Mark pill if it’s unmarked). A popover opens with all the reading states + verdicts; tap to toggle. Each click round-trips to the server. No save button.
  • Bulk — flip on multi-select mode (the checkbox on each card, or the “Select” button at the top of the library page). When the bottom toolbar appears, clickVerdict to open a dropdown — pick a reading state to set, or a verdict to add, across every selected book in one shot. “Clear reading state” removes the state from all selected.
  • Filter the library by verdict — open the collapsible filter chip stack at the top of /library/all and you’ll see two new rows: 🏷️ Verdict (All / Any verdict / Unmarked / each individual verdict) and 📚 State(All / Unset / each reading state). Stacks with the existing category / length / series / library-mode filters, so you can ask for “Original-fic + Read + Favorite” and the library narrows to exactly that.

Marks are private to you. Your friends can’t see which books you marked “Never again”. Verdicts power your own organisation and the new chip filters, not the public-library or friend-library experiences.

Browsing & discovery

Shelfsort indexes your library by who wrote what and who is shipped with whom.

  • Find-a-fandom search — when your library carries more than 10 fandoms, a search box appears above the Fandom rail on the All books page with one-click Try: chips for your top 5 biggest fandoms. Type “harry” and only the Harry Potter shelf surfaces; clear to bring everything back.
  • Chip-icon shorthand — every chip rail uses a tiny glyph so you can read its type at a glance: Aa for a regular fandom, for a crossover (with the joined-fandom count next to it), for a pairing/ship, 👤 for an author. The compact key at the top of this Help page is the official cheatsheet.
  • Authors directory — every author in your library with book counts, sorted by count. Click a name to see all their books with status badges. Reachable from the dashboard's Authors section via “View all →”.
  • Pairings browser — every ship/relationship across your library with counts and sample titles. Click a pairing to see the books featuring it. Pairings are extracted from EPUB metadata at upload time and canonicalized (alphabetical order, slash delimiter) so identical ships from different sources group correctly. Sorted by book count DESC, with an alphabetical tiebreaker so ties don't shuffle as you upload.
  • Characters browser — every character mentioned in your library's pairings, sorted by book count (alphabetical tiebreak). Click a character to see every book they appear in. Characters are derived at read-time by splitting each book's relationships on AO3's / and & separators, so the list updates instantly as you upload new books — no re-parse required.
  • Smart Shelves — combine filters into a saved view (Drarry-Complete-only, Sterek-WIPs, etc.).
  • Fandom Treemap on the Account page — visual overview of how your library splits by franchise.

AO3-style metadata: ratings, warnings, categories, tags

Shelfsort reads AO3 / FF.net export EPUBs and pulls out the canonical metadata fields fanfic readers care about, then stores each on its own field on the book so you can filter and browse by them.

Rating (one of 5): General Audiences, Teen And Up Audiences, Mature, Explicit, Not Rated. FF.net's K, K+, T, M, MA labels are accepted as aliases and normalized to the AO3 equivalent. Stored as rating.

Archive Warnings (one or more of 6): Graphic Depictions Of Violence, Major Character Death, Rape/Non-Con, Underage, No Archive Warnings Apply, Choose Not To Use Archive Warnings. Stored as warnings: [].

Categories (one or more of 6): F/F, F/M, Gen, M/M, Multi, Other. AO3 short forms like Femslash, Slash, Het are also accepted and canonicalized. Stored as categories: [].

Relationships — any <dc:subject> entry containing / (romantic) or & (platonic) is treated as a ship and canonicalized alphabetically so "Hermione/Harry" and "Harry/Hermione" collapse into one. Browse them on the Pairings page.

Freeform tags — everything else from <dc:subject> (Slow Burn, Coffee Shop AU, etc.) lands in ao3_freeform_tags. Distinct from your personal tags field, which is what you add manually via the bulk-edit and tag panels.

How it works: a single classifier walks every <dc:subject> element in the EPUB's OPF metadata. It matches against canonical alias tables (case-insensitive), assigns the subject to a bucket, and dedupes. Anything new is preserved verbatim. The full taxonomy + alias tables live in backend/utils/ao3_metadata.py.

Heads up: the AO3 metadata fields are read at upload time. Books uploaded before this feature shipped won't have them populated — you can refresh from source (if linked) or re-upload to get the fields filled.

AO3 filter chips & Save-as-shelf

On the All books page, click the AO3 filters chip near the top to open a collapsible panel with four filter dimensions:

  • Rating — G / T / M / E / NR (single-pick).
  • Category — F/F · F/M · Gen · M/M · Multi · Other.
  • Show only Warnings — show only books that carry the chosen Archive Warning.
  • Hide Warnings — content-safety opt-out: hide books that carry the chosen warning (e.g. Major Character Death).

Filters compose (AND) and update the books grid live. A small badge counts how many are active; “clear all AO3 filters” resets the panel.

Save as Smart Shelf — once at least one filter is active, the purple “Save as shelf” button opens a modal where you name the shelf and optionally tick “Pin to dashboard sidebar.” The saved shelf becomes editable on its dedicated page (/library/smart/<id>) using the full smart-shelf builder — you can swap the rating, add a fandom rule, change combinator, etc. See Smart shelves.

Smart shelves

Smart shelves are saved filter combinations stored under your account. Build them from the Shelves page, the AO3 filter chips (see above), or by clicking “Edit rules” on any existing shelf.

Rule types supported:

  • Has all / any / no tags — match against personal tag list.
  • Category / Fandom / Author — exact match.
  • Status — reading / finished / unread.
  • Word count — min and/or max.
  • AO3 rating / category / warning / exclude warning — exact match on the AO3 metadata, with the exclude warning rule using $ne so it works as a content-safety filter.

Rules combine via the top-of-builder AND/OR combinator. A live preview pane shows matching books as you edit so you can see counts before saving. Pin any shelf to surface it as a one-click chip on the welcome dashboard.

Reading queue (Up next)

The reading queue is a personal stack of books you want to read next.

  • Add to queue: hover any book card on All books and click the icon that appears (left of the read toggle). Click again to remove.
  • View / reorder / remove: open the Reading queue page from the dashboard's “Up next” rail. Each row shows index + cover + title/author. Reorder by dragging the ⋮⋮ handle at the left of each row, by the ▲▼ arrows for one-step nudges, or × to remove.
  • Dashboard surfacing: while empty you see “Up next: nothing queued.” Once you add anything, the dashboard rail shows the top 5 with a count + a Manage → link.

The queue is per-user and order is persisted server-side, so it follows you across devices.

Reading goals

Set a target, watch the ring fill, get confetti when you hit it. Reading goals live at /goals and surface as a small ring widget on the Dashboard.

  • Two cadences: Yearly (e.g. “52 books in 2026”) and Monthly (e.g. “5 books this June”). You can keep one of each kind running at the same time.
  • Two metrics: Books finished (counts any book you flip to Finished during the period) or Minutes read (sums actual time in the in-app reader plus any manual log entries).
  • Live SVG progress ring: as you finish books or rack up minutes, the ring fills in real time. The fraction is recomputed server-side so it stays honest if you bulk-mark older books finished.
  • Confetti on hit, anywhere: the moment a goal crosses 100%, a CSS-only burst plays and a “🎉 You hit …” toast fires — even if you're on the dashboard, in a reading room, or on a friend's library. The goal is stamped with a hit_at timestamp so it stays celebrated even if you re-edit the target later, and a localStorage list of celebrated goal-ids stops the same achievement from re-firing on every page load.
  • History: closed-out periods stay listed below the active card with a tiny “✓ hit” or “X / Y” summary so you can see how this month compared to last.
  • Edit or retire: bump the target up or down at any time. If you exceed the new target, the hit-state flips on retroactively.

Goals are personal — no one else sees them. They’re a self-pacing tool, not a leaderboard.

Filter URLs you already own

Got a long list of fanfic links from a recs post, a Discord, or someone's reading log? Shelfsort can tell you which ones you already have in your library so you only fetch the genuinely new ones.

From the dashboard: the “Have a list of fanfic URLs?” card shows a textarea that detects fanfic URLs live (AO3, FFnet, RoyalRoad, SpaceBattles, Sufficient Velocity, Questionable Questing, FictionPress). Hit Filter my list to be taken to the full filter page with the list pre-populated.

What the filter does:

  • Normalizes every URL (strips chapter / collection / mirror cruft) so an AO3 URL with ?view_adult=true matches the same work without it, and FFnet URLs with or without the trailing chapter ID collapse together.
  • Flags owned vs new URLs, and also duplicates within your input list so you don't waste a fetch on the same fic twice.
  • Lets you download just the new URLs as a .txt or .xlsx — ready to paste into a download manager or your reader.

For uploads, dropping a .txt of mostly-URLs onto the regular upload zone triggers the same flow automatically.

Fandoms we sort into

Shelfsort recognizes 286 fandoms out of the box and routes a book to one of them automatically when the title, description, or sample text matches enough of that fandom's keywords. Anything that doesn't match well enough falls into Original Fiction or Non-fiction — and the admin's unknown-fandoms queue surfaces popular suggestions for promotion.

Sorted by community-wide popularity (most-used first) and grouped by franchise so related sub-fandoms (NCIS spin-offs, all Stargate series, Marvel + MCU + Avengers, …) stay together. Counts reflect everyone's libraries on this instance, anonymized.

Show the full fandom list(143 groups, 286 fandoms)expand ▾
Harry Potter1493
  • Harry Potter1493
Star Trek471
  • Star Trek465
  • Star Trek: Voyager4
  • Star Trek: Lower Decks2
  • Star Trek: Alternate Original Series (AOS) - Fandom
  • Star Trek: Deep Space Nine
  • Star Trek: Discovery
  • Star Trek: Enterprise
  • Star Trek: Picard
  • Star Trek: Prodigy
  • Star Trek: Strange New Worlds
  • Star Trek: The Next Generation
  • Star Trek: The Original Series
Supernatural442
  • Supernatural442
Star Wars Universe419
  • Star Wars419
  • Andor (TV)
  • Rogue One: A Star Wars Story
  • Star Wars - All Media Types
  • Star Wars Rebels
  • Star Wars Sequel Trilogy
  • Star Wars Visions
  • Star Wars: Knights of the Old Republic
  • Star Wars: The Bad Batch (Cartoon)
  • Star Wars: The Clone Wars (2008) - All Media Types
  • The Mandalorian (TV)
Stargate412
  • Stargate SG-1386
  • Stargate Atlantis26
  • Stargate (Movies)
  • Stargate Universe
Spy x Family304
  • Spy x Family304
Marvel184
  • Marvel183
  • Daredevil (Comics)1
  • Captain America (Movies)
  • Iron Man (Movies)
  • Marvel Cinematic Universe
  • Spider-Man: Homecoming (2017)
  • The Avengers (Marvel Movies)
  • Thor (Movies)
  • X-Men (Comicverse)
Naruto145
  • Naruto145
Bleach127
  • Bleach127
One Piece116
  • One Piece116
Twilight113
  • Twilight113
Doctor Who82
  • Doctor Who82
Sherlock Holmes73
  • Sherlock Holmes73
  • Sherlock (TV)
Middle-earth53
  • Lord of the Rings53
  • Lord of the Rings (Movies)
  • The Hobbit (Jackson Movies)
DC37
  • DC Comics37
  • Batman - All Media Types
  • DCU (Comics)
  • DCU (Movies)
  • Young Justice (Comics)
My Hero Academia36
  • My Hero Academia36
  • My Hero Academia: Vigilantes
Cosmere (Brandon Sanderson)29
  • Warbreaker29
  • Elantris
  • Mistborn Series - Brandon Sanderson
  • Mistborn: Wax & Wayne (Era 2)
  • The Stormlight Archive - Brandon Sanderson
  • The Sunlit Man
  • Tress of the Emerald Sea
  • Yumi and the Nightmare Painter
NCIS20
  • NCIS11
  • NCIS: Tony & Ziva7
  • NCIS: Origins2
  • NCIS: Hawai'i
  • NCIS: Los Angeles
  • NCIS: New Orleans
  • NCIS: Sydney
Vinland Saga18
  • Vinland Saga18
Buffyverse5
  • Buffy the Vampire Slayer5
  • Angel: the Series
Glee5
  • Glee5
Critical Role / D&D4
  • The Mighty Nein (Critical Role)4
  • Bell's Hells (Critical Role)
  • Critical Role (Web Series)
  • Dungeons & Dragons (Role-Playing Game)
  • The Legend of Vox Machina (Cartoon)
  • Vox Machina (Critical Role)
Kimetsu no Yaiba | Demon Slayer4
  • Kimetsu no Yaiba | Demon Slayer4
The Witcher3
  • The Witcher - Andrzej Sapkowski2
  • The Witcher (TV)1
Hunger Games (Suzanne Collins)2
  • Hunger Games2
  • The Ballad of Songbirds and Snakes
Baldur's Gate 32
  • Baldur's Gate 32
Fullmetal Alchemist - All Media Types2
  • Fullmetal Alchemist - All Media Types2
Avatar (Bryke)1
  • Avatar: The Last Airbender1
  • The Legend of Korra
Dream SMP1
  • Dream SMP1
Hollow Knight (Video Game)1
  • Hollow Knight (Video Game)1
Merlin (TV)1
  • Merlin (TV)1
The Princess Bride (1987)1
  • The Princess Bride (1987)1
Undertale (Video Game)1
  • Undertale (Video Game)1
犬夜叉 | InuYasha1
  • 犬夜叉 | InuYasha1
美少女戦士セーラームーン | Bishoujo Senshi Sailor Moon1
  • 美少女戦士セーラームーン | Bishoujo Senshi Sailor Moon1
Studio Ghibli
  • Castle in the Sky
  • Howl's Moving Castle
  • Kiki's Delivery Service
  • My Neighbor Totoro
  • Nausicaä of the Valley of the Wind
  • Ponyo
  • Princess Mononoke
  • Spirited Away
  • The Tale of the Princess Kaguya
  • The Wind Rises
Arrowverse
  • Arrow (TV)
  • Batwoman (TV)
  • Black Lightning (TV)
  • Legends of Tomorrow (TV)
  • Stargirl (TV)
  • Supergirl (TV 2015)
  • The Flash (TV 2014)
  • Titans (TV)
Riordanverse
  • Daughter of the Deep
  • Heroes of Olympus
  • Magnus Chase and the Gods of Asgard
  • Percy Jackson and the Olympians
  • Percy Jackson and the Olympians (TV)
  • The Kane Chronicles
  • The Sun and the Star
  • Trials of Apollo
Shadowhunters (Cassandra Clare)
  • Shadowhunter Chronicles - Cassandra Clare
  • Shadowhunters (TV)
  • Tales from the Shadowhunter Academy
  • The Dark Artifices
  • The Eldest Curses
  • The Infernal Devices
  • The Last Hours
  • The Mortal Instruments
Discworld - Terry Pratchett
  • Discworld - Terry Pratchett
  • Discworld: City Watch
  • Discworld: Death
  • Discworld: Rincewind
  • Discworld: Tiffany Aching
  • Discworld: Witches
Stephen King multiverse
  • Carrie - Stephen King
  • It - Stephen King
  • Misery - Stephen King
  • Salem's Lot - Stephen King
  • The Dark Tower - Stephen King
  • The Shining - Stephen King
Dragon Ball
  • Dragon Ball GT
  • Dragon Ball Super
  • Dragon Ball Z
  • ドラゴンボール | Dragon Ball - All Media Types
Pokémon
  • Detective Pikachu
  • Pokemon - All Media Types
  • Pokémon Adventures / Pokémon Special (Manga)
  • Pokémon GO
The Locked Tomb
  • Gideon the Ninth
  • Harrow the Ninth
  • Nona the Ninth
  • The Locked Tomb - Tamsyn Muir
ASOIAF / GoT / HotD
  • A Song of Ice and Fire - George R. R. Martin
  • Game of Thrones
  • House of the Dragon (TV)
Dune
  • Dune (2021)
  • Dune - Frank Herbert
  • Dune: Part Two (2024)
Final Fantasy
  • Final Fantasy VII
  • Final Fantasy XIV Online
  • Final Fantasy XV
Grishaverse
  • King of Scars Duology - Leigh Bardugo
  • Shadow and Bone Trilogy - Leigh Bardugo
  • Six of Crows Series - Leigh Bardugo
Holly Black (Faerie books)
  • The Folk of the Air Series - Holly Black
  • The Spiderwick Chronicles
  • The Stolen Heir Duology - Holly Black
Honkai / miHoYo Games
  • Honkai Impact 3rd
  • 原神 | Genshin Impact (Video Game)
  • 崩坏:星穹铁道 | Honkai: Star Rail (Video Game)
Mo Xiang Tong Xiu — The Untamed / MDZS
  • 天官赐福 - 墨香铜臭 | Tiān Guān Cì Fú - Mòxiāng Tóngxiù
  • 陈情令 | The Untamed (TV)
  • 魔道祖师 - 墨香铜臭 | Módào Zǔshī - Mòxiāng Tóngxiù
Sarah J. Maas
  • A Court of Thorns and Roses - Sarah J. Maas
  • Crescent City
  • Throne of Glass - Sarah J. Maas
The Boys
  • Gen V (TV)
  • The Boys (Comics)
  • The Boys (TV)
Bridgerton-verse
  • Bridgerton (TV)
  • Queen Charlotte: A Bridgerton Story
Castlevania
  • Castlevania (Cartoon)
  • Castlevania: Nocturne (Cartoon)
Dragon Age
  • Dragon Age - All Media Types
  • Dragon Age: Inquisition
Fire Emblem
  • Fire Emblem Series - All Media Types
  • Fire Emblem: Three Houses
Foundation
  • Foundation (TV)
  • Foundation - Isaac Asimov
League of Legends
  • Arcane: League of Legends (Cartoon)
  • League of Legends
Persona
  • Persona 5
  • Persona Series
Resident Evil
  • Resident Evil (Movies)
  • Resident Evil (Video Games)
Steven Universe
  • Steven Universe
  • Steven Universe: Future
The Expanse
  • The Expanse (TV)
  • The Expanse - James S. A. Corey
The Legend of Zelda
  • The Legend of Zelda - All Media Types
  • The Legend of Zelda: Breath of the Wild
The Sandman
  • The Sandman (TV)
  • The Sandman - Neil Gaiman
Vivziepop
  • Hazbin Hotel
  • Helluva Boss
Wheel of Time
  • The Wheel of Time (TV)
  • The Wheel of Time - Robert Jordan
5SOS (Band)
  • 5SOS (Band)
9-1-1 (TV)
  • 9-1-1 (TV)
aespa (Band)
  • aespa (Band)
ATEEZ (Band)
  • ATEEZ (Band)
Avatar (Pandora - James Cameron)
  • Avatar (Pandora - James Cameron)
Banana Fish
  • Banana Fish
Be More Chill - Iconis/Tracz
  • Be More Chill - Iconis/Tracz
BLACKPINK (Band)
  • BLACKPINK (Band)
Brooklyn Nine-Nine (TV)
  • Brooklyn Nine-Nine (TV)
BTS
  • BTS
Cyberpunk 2077 (Video Game)
  • Cyberpunk 2077 (Video Game)
Dear Evan Hansen - Pasek & Paul
  • Dear Evan Hansen - Pasek & Paul
Death Note (Anime & Manga)
  • Death Note (Anime & Manga)
Detroit: Become Human (Video Game)
  • Detroit: Become Human (Video Game)
Disco Elysium (Video Game)
  • Disco Elysium (Video Game)
Divergent Trilogy - Veronica Roth
  • Divergent Trilogy - Veronica Roth
Downton Abbey
  • Downton Abbey
Elden Ring (Video Game)
  • Elden Ring (Video Game)
Encanto (2021)
  • Encanto (2021)
ENHYPEN (Band)
  • ENHYPEN (Band)
F1 RPF
  • F1 RPF
Fairy Tail
  • Fairy Tail
Frieren: Beyond Journey's End
  • Frieren: Beyond Journey's End
Good Omens (TV)
  • Good Omens (TV)
Good Omens - Pratchett & Gaiman
  • Good Omens - Pratchett & Gaiman
Gravity Falls
  • Gravity Falls
Hades (Video Game 2018)
  • Hades (Video Game 2018)
Haikyuu!!
  • Haikyuu!!
Hamilton - Miranda
  • Hamilton - Miranda
Hannibal (TV)
  • Hannibal (TV)
Heartstopper (TV)
  • Heartstopper (TV)
His Dark Materials - Philip Pullman
  • His Dark Materials - Philip Pullman
House M.D.
  • House M.D.
Hunter X Hunter
  • Hunter X Hunter
ITZY (Band)
  • ITZY (Band)
IVE (Band)
  • IVE (Band)
Killing Eve (TV)
  • Killing Eve (TV)
LE SSERAFIM (Band)
  • LE SSERAFIM (Band)
Mass Effect Trilogy
  • Mass Effect Trilogy
McElroy Family (Podcasts & Adventures)
  • McElroy Family (Podcasts & Adventures)
Mortal Engines - Philip Reeve
  • Mortal Engines - Philip Reeve
NewJeans (Band)
  • NewJeans (Band)
One Direction
  • One Direction
One Punch Man
  • One Punch Man
Our Flag Means Death (TV)
  • Our Flag Means Death (TV)
Overwatch (Video Game)
  • Overwatch (Video Game)
Pirates of the Caribbean (Movies)
  • Pirates of the Caribbean (Movies)
Red Rising - Pierce Brown
  • Red Rising - Pierce Brown
Schitt's Creek
  • Schitt's Creek
SEVENTEEN (Band)
  • SEVENTEEN (Band)
She-Ra and the Princesses of Power (2018)
  • She-Ra and the Princesses of Power (2018)
Silent Hill
  • Silent Hill
Six: The Musical
  • Six: The Musical
Spy Classroom
  • Spy Classroom
Stranger Things (TV 2016)
  • Stranger Things (TV 2016)
Stray Kids (Band)
  • Stray Kids (Band)
Taylor Swift (Musician)
  • Taylor Swift (Musician)
The Dresden Files - Jim Butcher
  • The Dresden Files - Jim Butcher
The Elder Scrolls V: Skyrim
  • The Elder Scrolls V: Skyrim
The Inheritance Cycle - Christopher Paolini
  • The Inheritance Cycle - Christopher Paolini
The Lunar Chronicles - Marissa Meyer
  • The Lunar Chronicles - Marissa Meyer
The Maze Runner - James Dashner
  • The Maze Runner - James Dashner
The Murderbot Diaries - Martha Wells
  • The Murderbot Diaries - Martha Wells
The Owl House
  • The Owl House
TWICE (Band)
  • TWICE (Band)
TXT (Band)
  • TXT (Band)
Voltron: Legendary Defender
  • Voltron: Legendary Defender
Wednesday (TV 2022)
  • Wednesday (TV 2022)
Wings of Fire - Tui T. Sutherland
  • Wings of Fire - Tui T. Sutherland
Yuri!!! on Ice
  • Yuri!!! on Ice
モブサイコ100 | Mob Psycho 100
  • モブサイコ100 | Mob Psycho 100
呪術廻戦 | Jujutsu Kaisen
  • 呪術廻戦 | Jujutsu Kaisen
文豪ストレイドッグス | Bungou Stray Dogs
  • 文豪ストレイドッグス | Bungou Stray Dogs
東京卍リベンジャーズ | Tokyo Revengers
  • 東京卍リベンジャーズ | Tokyo Revengers
進撃の巨人 | Shingeki no Kyojin | Attack on Titan
  • 進撃の巨人 | Shingeki no Kyojin | Attack on Titan
방탄소년단 | Bangtan Boys | BTS
  • 방탄소년단 | Bangtan Boys | BTS

Don't see your fandom? Drop it on the Suggestions page with a couple of distinctive title/description keywords and we'll get it added.

Sources we recognize

Shelfsort recognizes fanfic URLs from 10 archives. All variants of the same story (mobile/www/mirrors/chapter IDs/query strings/http vs https) collapse to a single canonical URL so URL-list dedupe, source labels, and the Linkless claim-URL flow all work consistently.

  • AO3 — every official mirror (.org / .com / .net / .gay / ao3.org / archive.transformativeworks.org) with www / m / insecure subdomains and /collections/<name>/works/N prefix
  • FanFiction.net — www, bare, and m.fanfiction.net (mobile)
  • FictionPress — www, bare, and m.fictionpress.com (mobile)
  • Royal Road
  • SpaceBattles, SufficientVelocity, QuestionableQuesting (forum threads)
  • Adult-FanFiction.org (all per-fandom subdomains)
  • Potions & Snitches, Twilighted (eFiction archives)

Spotted a URL from a site we don't recognize? Shelfsort flags any story-shaped URL (eFiction-style query, forum thread, /works/N, /s/N, etc.) from a new host and queues it on the Unknown sources page. You can:

  • Mark for adding — flags the host for the next agent session to canonicalize
  • Dismiss — not a real fic archive, drop it from the queue
  • Add manually — paste a URL + optional note even without an EPUB upload triggering it

Toasts on upload + URL paste tell you when new hosts are spotted so you don't need to monitor the queue.

Detection & overrides

Most things auto-detect at upload time. When the auto value isn't right, the override survives re-detection forever.

Completion status is detected via a 4-signal cascade:

  1. Status line in EPUB metadata: Status: Complete, Status: In-Progress, Status: Updated, Status: Hiatus, etc.
  2. Tags: complete, wip, ongoing, in-progress, abandoned, discontinued, hiatus
  3. Chapter X of Y heuristic — X < Y means ongoing
  4. To-be-continued / TBC marker in the description

If none match → defaults to Finished (sensible for original novels). Override on any book's detail page via the status badge dropdown — your override lives in manual_status separate from the auto value.

Category & fandom are auto-classified using EPUB metadata + a 286-fandom canonical list + a Claude AI fallback for ambiguous cases. Confidence is shown on the book detail page; correct it manually any time.

Pairings/relationships are extracted directly from EPUB metadata, canonicalized (alphabetical order, slash delimiter) so Harry/Draco and Draco/Harry group together.

In-place metadata editing — the Edit button on any book's detail page now lets you fix the title, author, and description alongside category and fandom. Edits land in the database immediately, and if the original EPUB is still on disk, the OPF metadata is rewritten inside the file too — so when you re-download the book or send it to a friend, your corrections travel with it. Chapters, covers, and every other byte in the EPUB stay untouched (we surgically replace only the <dc:title> / <dc:creator> / <dc:description> tags). If the rewrite ever fails (some malformed EPUBs can't round-trip), the DB save still goes through and a toast lets you know.

Download your library

Grab your books as a ZIP (raw EPUBs) or a spreadsheet manifest (title / author / source URL) from /library/download. Filters let you scope by fandom, pairing, author, or category before downloading.

  • Full library ZIP — leave all filters set to "Any" and hit Download full library ZIP. Files stream directly to your browser (no server-side temp file), so bytes start arriving within about a second. Filename is dated so you can keep multiple.
  • Auto-split for large libraries — if you have 2,000 or more books, the full-library ZIP automatically splits into per-category zips (one for Fanfiction, one for Fiction, one for Non-fiction, etc.) instead of packing everything into a single 5-10 GB file. This avoids the browser and reverse-proxy timeouts that plague giant single downloads. You still click once — the page just delivers a few smaller files instead of one huge one, and shows a heads-up banner beforehand so you know what to expect. Filenames look like shelfsort_Fanfiction.zip, shelfsort_Fiction.zip, etc.
  • Filtered ZIP — pick any combination of fandoms / pairings / authors / categories → hit Download filtered ZIP. Filtered downloads always land as a single file, regardless of size, so a single-fandom archive stays predictable.
  • Excel manifest — switch to the "Library manifest (.xlsx)" toggle at the top for a spreadsheet listing every book with its metadata + source URL. Handy for de-duping against a Kindle library or auditing what you own outside Shelfsort.
  • Progress + cancel — a live toast shows "Streaming... 42 MB so far · 8s" with a Cancel button. Cancelled downloads stop cleanly (in an auto-batch, the current category's download aborts and the loop halts).
  • Sub-shelves preserved — inside the ZIP, Fanfiction is grouped as Fanfiction/<Fandom>/<Pairing>/ (books with multiple pairings file under their first alphabetically), with pairing-less fics in _No_pairing. Everything else lands in a flat category folder.
  • Books that can't be restored — extremely rare, but if a book's bytes are missing from both storage backends (e.g. a rare hardware event) it's skipped and logged. The final ZIP still ships; you get a warning in the toast if any were skipped.

The auto-batch threshold and per-file naming might change as we tune it; the auto-split behavior is designed to be a hands-off improvement, not a surprise — your existing single-file mental model still holds for small-to-medium libraries.

Library cleanup walk-through

One flow that ties every cleanup tool together. Bookmark /help#cleanup-walkthrough and come back to it whenever your library feels off.

  1. Get the lay of the land. Admins: open Admin → Data & diagnostics → My library diagnostics and hit Copy report. The duplicates.excess line tells you how many books could be removed without losing anything unique.
  2. Rescan if you suspect hidden duplicates. Visit /library/duplicates and click “Rescan library” (empty-state hero if the queue is empty, small button in the toolbar otherwise). It catches pairs upload-time detection missed — author normalization changes, source_url added later, books restored from Old stories, and so on.
  3. Resolve the pile. Two ways:
    • Per group — each duplicate group has a purple “Keep only the latest” button. Good for hand-picking.
    • Bulk — the toolbar has “Keep the latest in all N groups”. Runs in batches with a live progress bar, safe under the 120s edge timeout. Idempotent: if it's interrupted, click again to resume.
  4. Empty Trash to reclaim storage. Head to /library/trash and permanently delete. You'll see a ✨ toast with the book count and reclaimed MB/GB (opt-in nudge, on by default).
  5. Confirm the “before/after”. Re-open the diagnostics card — excess should be near 0 and on-shelves down by the amount you resolved. When excess hits 0 you'll get a 🎉 “squeaky clean” toast.
  6. Set it and forget it. Every Sunday at 04:00 UTC Shelfsort silently reruns the rescan across your library. If it finds anything new, you'll get a bell notification (kind duplicate_rescan) linking straight back to the Duplicates page. Quiet weeks: no bell.

Nothing here permanently deletes on the first click — every “kept-latest” older copy lands in Trash with a 30-day grace, and every promoted duplicate's ex-keeper lands in Old stories with reading progress preserved. You can undo an entire pass by restoring from Trash before it purges.

Backup & restore

The data-safety loop is end-to-end:

  • Download backup (Account page) — every EPUB + a JSON manifest of all books, tags, smart shelves, and prefs in a single ZIP. The filename is dated so you can keep multiple. Restore is manual; keep the ZIP somewhere safe.
  • Reminder banner on the dashboard fires when (a) you have 100+ books and no backup yet, (b) it's been 30+ days since the last backup, or (c) 100+ books have been added since the last backup. X button silences it for 14 days; running a backup auto-clears it.
  • Backup history on the Account page shows every backup you've run with timestamps + book counts. ZIPs themselves aren't stored — only metadata. Capped at 50 entries per user.
  • Restore from backup — upload a backup ZIP, preview what's inside, tick exactly which books and smart shelves to bring back, and apply. Books with IDs already in your library are flagged as collisions and default to OFF; tick the Overwrite-collisions toggle to opt in.

Cloud library mirror (durable storage)

On top of the manual ZIP backup above, Shelfsort continuously mirrors every EPUB + cover you upload to durable cloud object storage so a server redeploy or crash can't wipe your library. You don't have to do anything — the mirror is on by default.

  • Constant background mirror — every 10 minutes a tick walks the file system and uploads anything not already in the cloud. Idempotent: re-uploading the same file is a no-op so the job is cheap to run repeatedly.
  • Auto-backfill on boot — when the server restarts, the very first thing it does (after coming online) is fire a backfill, so every redeploy puts you back in sync within minutes.
  • One-tap re-mirror — visit Account → Cloud library mirror and hit Back up my library now to force an immediate run. Useful right before a big trip or a known maintenance window.
  • Visible reassurance signal — once your library has been backed up in the last 24 h, a tiny green ✓ Shield-Check badge appears on your avatar in the top navbar. Click it for a popover showing the timestamp, file count, and a one-tap “Back up again →” button.
  • Transparent restore — if a file goes missing from the server (post-redeploy, never re-uploaded), the next time anyone opens it the bytes are pulled back from cloud storage automatically. First open is slightly slower (10–60 s for a big book); every subsequent open is instant.

Your bytes are stored by Emergent's managed object storage; Shelfsort never reads them except to serve them back to you. The manual ZIP backup above is the right tool when you want a portable copy you control entirely — the cloud mirror is the safety net that keeps things working even if you forget to make a ZIP.

Antivirus & library safety

Shelfsort runs ClamAV over your library to catch malicious files. To keep uploads fast, the scan happens later — not during the upload itself — so big batches don't wait on the scanner.

  • The flow — books arrive marked “unscanned”. When you next visit Polish my library and apply suggestions, Shelfsort automatically runs ClamAV in the background. The polish page shows a live “X / Y books scanned” counter with a progress bar so you can watch it sweep through. Each sweep covers up to 500 books, picking the ones that need it most first (unscanned books, then oldest-scanned). For libraries larger than 500 books just polish again later — each polish picks the next batch automatically until everything has been covered. You can also run a scan manually from Account → Library safety report.
  • What gets scanned — every book in your library when a scan runs, every backup-ZIP restore, every photo attached to feedback, and every file restored back from cloud storage on download. The signature database is refreshed daily, so files that were clean yesterday can still be caught if a brand-new threat is published.
  • Where to check your own reportAccount → Library safety report. Four live counters: clean, flagged, unscanned, total. If anything has been flagged it's listed by filename + signature, with a timestamp. The Unscanned count tells you how many books are waiting for their first scan.
  • Rescan on demand — the same page has a one-tap Rescan now button that re-runs ClamAV across every book in your library (it pulls cloud-only files back to disk first, so the rescan is complete, not just a cache sweep). Capped at 500 books per run to keep the wait reasonable.
  • 90-day nudge banner — if it's been more than 90 days since your last full library scan (or you have unscanned books with no prior scan ever), a quiet banner appears at the top of every page with a single Run scan button. One click runs the rescan; one click dismisses the nudge for the day. Hidden once the report is fresh again.
  • If a flagged file is found — it stays in your library but is marked infected with the signature name. The download endpoint blocks infected files, and Send-to-Kindle refuses to email them. We don't auto-delete (you might have a legitimate but heuristic-flagged backup), but we make it obvious and easy to remove from Account → Safety.
  • What we never do — ClamAV only sees the file bytes; the result is “clean” or “signature name FOUND”. We don't share files with third-party AV services, we don't store hashes in any external registry, and your scan results are visible only to you (the admin antivirus dashboard sees aggregate quarantine entries, not your library contents).

If the AV scanner is temporarily unavailable (signatures still downloading, daemon restarting), uploads still complete and the books stay flagged as unscanned until the daemon is back — your next polish or manual rescan will sweep them.

Community rules

Shelfsort is a quiet corner of the internet for people who love books. To keep it that way, every account holder agrees to a short, written code of conduct.

  • No spam — no off-platform promotion, no commercial schemes, no link-farming.
  • No politics — keep partisan content and election material off the platform.
  • No hate speech or bullying — targeting users, authors, or communities by identity is not tolerated.
  • No piracy promotion — don't share download links to unauthorized copies. Shelfsort is for organizing books you already own (or freely-shared works like fanfiction).
  • Respect intellectual property — authors keep rights to their work. Don't repost full chapters or AI-derivatives that misrepresent the original.
  • Be kind — “what one reader would say to another.” Curiosity, gentle disagreement, shared favorites — yes. Snark at someone's taste — no.

Read the full text at /rules. Rule breaches lead to a warning, suspension, or ban depending on severity. Appeals can be sent via the feedback box at the top of this page.

Reader & stats

Click any book cover to open the in-browser EPUB Reader. Your reading position is saved per-book; come back to where you left off automatically.

PDFs read natively in-app. Open any PDF from Originals and it renders directly via pdf.js — selectable text, keyboard navigation (PageUp/Down, J/K, arrows, Space), zoom (50–250 %), a page-jump input, and a scroll-tracked progress so bookmarks land on the actual page you're on. Mobile Safari and iOS are fully supported. No Calibre roundtrip required to read a PDF. PDFs also have a Read aloud button — same Web Speech voice picker as EPUBs, auto-advances to the next page when each one finishes.

Bookmarks: while reading, tap the Bookmark button in the reader header to save your current page — or just press Cmd/Ctrl+B. If the current page is already bookmarked, the button flips to a filled Saved chip so you don't accidentally save the same spot twice. Open the Bookmark panel (the icon next to it with the count) to see every saved spot for this book, jump to any of them, type or edit a free-form note (saved on blur), and remove on hover. Each bookmark stores the chapter title, your reading-progress percentage, and the date you saved it. Bookmarks sync to your account so they follow you across devices.

You can also see every bookmark across your whole library on the All bookmarks page. PDF and TXT/DOCX originals support bookmarks too — see the Originals section above for how they work in the smart viewer.

Surprise me: on the Dashboard, the “Surprise me” button picks a random book you haven't opened yet and drops you straight into it — useful when decision fatigue strikes.

Books I haven't read: the dedicated unread shelf lists every book you've never opened, newest upload first.

Up next queue: build a personal reading order with the Up next widget on the Dashboard. Books in the queue persist across devices.

The Reading stats page covers your library shape, most-read fandoms, and pairing distribution. For a more cinematic year-end view — books opened, pages turned, longest streak, top fandoms, top author, bookends — open Year in Books below.

Refresh fanfics: the URL-fetching feature is currently disabled while we tune it. Your existing books and their metadata are unaffected.

Cross-device reading sync

Start a fic on your laptop on the train; finish it on your phone in bed. Shelfsort tracks your reading position per-device in the cloud (CFI-precise, not just chapter-precise) so the "where was I?" bookmark follows you everywhere.

  • Resume Reading dashboard card — when the latest cursor came from a different device than the one you're on, the dashboard surfaces a “Pick up where you left off on iPhone” card with the book cover + chapter title. One tap drops you straight back into the Reader at the right page.
  • Yellow Resume pill on book cards — for books with a fresh cloud cursor from a different device in the last 48 h (and you haven't finished them), a small yellow Resume pill appears in the corner of the cover on the library grid. Passive nudge that doesn't require enabling push.
  • Reader pill (you vs you elsewhere) — when you open a book inside the Reader, a small pill in the top bar shows the cursor age + originating device label so you know whether you're ahead of, behind, or at the same spot as your other devices.
  • Web push handoff — opt-in. When you close a book on one device, your other devices get a soft browser notification asking if you want to continue there. Toggle in Account → Notifications; see Web push notifications below for the setup walk-through.
  • Realtime updates — every device's position is published via Shelfsort's unified SSE channel, so when you save progress on the laptop, your phone's Reader pill updates within ~1 second (no polling).
  • “Finished on your iPhone — want a similar one?” strip — when you're near the end of a book (≥ 90 %) on a different device than the one you're currently on, the BookDetail page surfaces a tiny 3-card rail right under the cross-device hint, pulling related books from your library by fandom/author. Hides silently when no matches exist or you dismiss it. Captures the moment of completion as a moment of discovery without nagging.

Privacy: position data is per-user only. Friends never see where you are in a fic — that data isn't shared even when you're a member of a Book Club room reading the same book.

Reading insights (Re-read · Pace · Cohort)

BookDetail pages now surface up to three live pills that turn raw cursor data into reading rhythm signals — all opt-in and silently hidden when the data isn't there yet.

  • ↻ Re-read pill — Shelfsort tracks “backward jumps” (the cursor dropping below 60 % of the running peak after being near 100 %). Three or more of those in 90 days → you're re-reading. The pill says Re-read, and after the fourth jump in 30 days you get a small in-app nudge (“You've kept coming back to Title — want to add it to a Cosy Comforts shelf?”).
  • ⚡ Pace pill — Shelfsort compares your %/hr on this book to your own median across the last six months of finished books. 30 % faster than usual, your usual pace, or 20 % slower than usual. When you haven't opened a fresh book yet, the pill becomes ~Nh to finish, a projection of total reading hours based on your median pace × the book's remaining percentage.
  • ⌖ Cohort progress bar — the Progress field now renders as a slim bar with your current percent in coral plus a purple tick mark at the community average. Cohort-gated (≥5 opted-in readers of the same canonical title+author) so no individual can be inferred. Surfaces as “You: 45 % · Community: 62 %”.
  • Books most likely to be finished — admins also see a leaderboard (`/api/books/most-finished-leaderboard`) of canonical titles sorted by community completion rate. Useful for picking the next book-club read.

Cohort insights respect your Share reading data setting under Account → Privacy. Switching it off both stops contributing to the cohort and stops receiving the cohort pill on your own books.

Finished a book? Want a similar one

The moment you close a book is also the moment you're most likely to want another. The BookDetail page now ends with a soft strip — “Finished on this device. Want a similar one?” — pulling up to six other titles from your own library that share the seed book's fandom or author.

  • When it appears: only after the book is effectively done. Either your progress hit ≥ 95 %, or you tapped Mark as finished. Otherwise the strip stays out of your way.
  • What it surfaces: library-local matches scored on fandom (×3) + author (×2) + whether it's still unfinished (+1) + recency. Unfinished books rise to the top because you've already chosen to keep them — the strip is meant to re-surface, not recommend strangers.
  • Why library-local: embedding-based community recs already live behind Recommendations. The finished strip is for resurfacing things you've forgotten you own. Stays silent when nothing matches.

Reader DNA & comfort reads

Open Reading stats and scroll past the category bars — there's a new Reader DNA card that summarises your reading make-up in one panel.

  • Top 3 fandoms by book count.
  • Fanfic vs original split bar — the proportion of your library that's fanfic vs original work, rendered as a single coral/purple bar.
  • Average book length in words, computed from each book's indexed word count (or a rough size-based estimate when missing).
  • Comfort reads · last 30 days — books you've finished AND re-opened a session for in the last month. The titles you keep coming back to without thinking about it.

One API round-trip (/api/insights/reader-dna). Silently hidden when your library is empty.

Year in Books (Wrapped recap)

Every reading year gets its own Spotify-Wrapped style recap at Reading stats → Year in Books, or directly at /library/year/<year>. It's a nine-slide scroll-snap experience — each slide is its own full-screen card with bold gradients and animated reveal-on-view.

  • The slides: cover (the year in giant serif) → books opened → pages turned (with a rough hours estimate) → longest streak + active days → best month with a mini monthly bar chart → top fandoms (#1 hero + animated ranking bars 2–5) → most-read author (+ supporting authors) → first & last books of the year (“Bookends”) → outro with achievement chips and the Share / Email buttons.
  • Numbers animate with a CountUp effect when each slide scrolls into view, and bar charts grow horizontally from zero — the recap is meant to be watched, not just read.
  • Navigate: scroll-snap pages forward, the progress dots on the right let you jump between slides, and prev/next-year chips in the top bar hop between years. The X in the top-left closes back to /library/stats.
  • Empty years show a single calm “A quiet year on the shelf” slide with a link back to the library — no data, no fake numbers.

Email it to yourself with the Email me this recap button on the final slide. The email uses the same numbers in a clean print-friendly layout (no scroll-snap there). It's also the email sent in early January if you turn on the Year-in-Books channel in Email preferences.

Download as PNG grabs the whole year as a 1080×1350 Instagram-friendly portrait card — purple gradient, big serif year, your top world & top voice, reading peak. It saves locally as shelfsort-wrapped-<year>.png. On browsers that support image-clipboard (Chrome, Edge, modern Safari) a sister Copy image button puts the same PNG straight on your clipboard so you can paste into Threads, iMessage, or Instagram Stories with no save-then-attach dance. Firefox / older Safari users only see the Download button (graceful fallback).

Share publicly with the Share my year button — Shelfsort generates a token-protected public URL. Anyone with the link sees the same Wrapped experience (with your display name on the cover) — no Shelfsort account required. The public view never exposes your email or internal book IDs. The link is engineered to unfurl as a rich preview when pasted into Twitter, iMessage, Slack, Discord, or LinkedIn: a server-side 1200×630 card with your year, books, streak, and top fandom shows up as the link preview, with full Open Graph + Twitter Card meta tags. Manage the link any time: copy it, open it in a new tab, or Revoke to kill it instantly. View counts and the last-seen date show up in the same dialog so you can see how many friends actually clicked.

Stat source-of-truth: a book counts as "opened" if it appears in your reading_activity for that year, and as "finished" if its progress is ≥99% and the last-opened date falls inside the year. Pages are estimated from word count (250 wpm × ~250 words per page). Fanfics with no word-count yet contribute zero pages until backfill runs.

Public usernames & @handles

A username is your public handle on Shelfsort — what friends type when they want to find you without trading email addresses. Claiming one is optional: if you skip it, friends can still invite you by email or by name.

  • Claim it at Account → Username. 3–20 characters, letters/digits/underscores, must be unique. Capital letters are welcome — “@ImCrazy42” displays exactly as typed.
  • Uniqueness is case-insensitive — “@Bookworm” and “@bookworm” can’t both exist, so nobody can impersonate you with a different casing.
  • Rename safely: change your handle whenever you like. Shelfsort remembers your previous handle for a grace period so “@NewName (was @OldName)” appears in friends’ cards until you clear it from the same panel.
  • @-mention autocomplete: anywhere Shelfsort accepts a handle (friend invites, bookclub invites), start typing @ and a live dropdown surfaces matching users. Empty/short queries stay quiet so the UI doesn’t flicker.
  • Find-by-handle from /friends: type a full or partial handle into the invite box, pick the suggestion, hit send. If you already have an email-style invite open, the same field still accepts name@domain.
  • Privacy still applies: toggling Hide me from user search on the Account page removes you from @ autocomplete results too — your existing friends can still DM you.

Heads up: usernames are public. Don’t pick something you wouldn’t want stamped onto a friend’s screenshot.

Messages & friends

Shelfsort has built-in direct messaging — click Message on any friend at /friends to open an inline chat drawer. Three room types coexist:

  • Admin-curated group rooms — an admin creates the room and picks members. Members chat freely inside.
  • 1-on-1 DMs between friends — once you and another user are friends, either side can open a DM that lives forever in your sidebar.
  • 1-on-1 DMs from anyone — if a user has opened up their privacy to “anyone”, you can DM them without being friends first.

Three kinds of messages: text (Enter to send, Shift+Enter for newline), attached book (search your library, recipient gets a card linking to it), and attached palette token (one-click Apply on the recipient’s side using the share-palette work).

Friends live at /friends: search by @handle, name, or email (min 2 chars) — typing @ opens the autocomplete dropdown for instant picks. Send a request, the other side accepts or declines. If both sides happen to send requests at the same time, they auto-pair into accepted. From the Friends page you can also remove a friend (wipes the DM room), block someone (silent, they vanish from your search and can’t message you), or unblock later. Invite by email sends a Resend invite to anyone not yet on Shelfsort — they click the one-time link, sign up, and become your friend automatically. Pending invites expire after 30 days and can be cancelled from the same panel.

Privacy lives on your Account page:

  • Who can DM meFriends only (default) blocks DMs from strangers; Anyone opens it up. When you have pending requests sitting around, a small “Switch to open DM mode” link appears on the Friends page so you don’t have to dig.
  • Hide me from user search — toggle on to keep your name/email out of other users’ search results. Existing friends still see you.
  • Share my library with friends — opt-in (off by default). When on, your accepted friends see a Library button on your row in their Friends page. They can browse your books (title + author + fandom), see which ones they already own, and click Want this to politely DM you about anything they want. No EPUB files are ever auto-sent — you decide how to share each one.

On the Friends page, each accepted friend’s row also shows a 🤝 mutual count — the number of books that appear in both of your libraries (matched on lowercase title + author, ignoring “the/a/an” prefixes).

Notifications: the chat-bubble icon in the Navbar shows one combined badge — unread messages + pending friend requests added together. Hover for the breakdown. Inside /friends, each friend's row gets a red unread-message dot that auto-refreshes every 20 seconds (and instantly when you tab back to Shelfsort) — no manual reload needed to see new DMs.

Phase 1c (one-click “switch to open” banner) and websockets for instant delivery are parked. Current 15-second poll is plenty for casual chat.

Book-club reading rooms

Private invite-only spaces where you and friends read the same book together. Find them at /bookclubs.

  • Chat-style layout: room list on the left, active conversation in the middle, members + progress + invites in a collapsible right rail. Mobile collapses the room list into a drawer.
  • Chapter threads: every room has a Lobby plus one tab per chapter in the book. Pick a tab to scope your messages to that part of the book — perfect for spoiler-safe discussion.
  • Progress slider: drag to mark your current chapter. When you cross the finish line, every other active member gets a notification (and a row in the Sunday digest if they've opted in).
  • Roles: Owner / Moderator / Member. Owners can edit, invite, promote, demote, remove, transfer ownership, or delete. Moderators can edit + invite + remove. Members read & post.
  • Friends-only invites: pick from your accepted-friends list or type an @handle in the quick-invite field at the top of the members rail to invite anyone on Shelfsort. Use /friends to add people first if you'd rather route through the friends list.
  • Notifications: bookclub invites and per-message pings always fire in the bell. Optional weekly email digest ships every Monday at 08:00 UTC summarising the past week's activity across all your rooms — opt-in at Account → Emails.
  • Buddy-pacing prompts: in 2-person rooms, when both members cross into a new chapter Shelfsort auto-posts a system message (“Both of you have reached Chapter 7. Ready to talk about it?”) and pings both readers in-app. Larger rooms skip the prompt to avoid spam. Idempotent — one nudge per chapter per room.

Sharing your library publicly

Your library is private by default. If you want to share it on the open web you opt in from Account → Privacy.

  • Two independent toggles: Share with friends exposes your library to accepted-friends only; Make my library public on the web opens it to any signed-in Shelfsort member at /u/<your-handle>/library.
  • Anonymous visitors never see books: logged-out visitors hitting a public-library URL are bounced to a sign-in gate (with a preview of the owner's avatar, book count, and top fandom to nudge them to join).
  • AV-flagged books are always excluded: anything that hasn't passed antivirus, or that's currently flagged, is filtered out of the public view even when the rest of your library is shared.
  • Revoke any time: flip the toggle off and the public URL 401s within a minute. Existing RSS subscribers see an empty feed immediately on the next poll.
  • RSS feed: opted-in libraries publish a per-user feed at /feed/library/<handle>.xml?token=<rss_token> so power-readers can subscribe in their RSS client. Rotate the token from Account → Privacy if you ever need to invalidate.

Discovering other readers' libraries

Once you've opted in (or while browsing other opted-in readers), Shelfsort surfaces three discovery rails so you can find readers who share your taste:

  • Reader directory (/users): every member with a public @handle is listed. Opted-in libraries are marked with a 📚 chip — click straight through to their public shelves.
  • Featured Readers strip: the landing page rotates 3-5 random opted-in libraries so new visitors always see a fresh sample of the community.
  • Per-fandom discovery (/explore/fandom/<fandom>): every fandom you read has its own page listing other opted-in readers who own books in that fandom — perfect for finding people deep in your niche.
  • Overlap badges: when you browse someone else's public library, books you also own get a “You have this too” badge so you can quickly find your shared shelf.
  • Heart & subscribe: tap the heart on any book in a public library to react (the owner gets an in-app notification), or subscribe to their RSS feed to keep tabs on new uploads.

Community Covers

Shelfsort can generate AI cover art for any book that ships without a great one — and once you're happy with a generated cover, you can share it back to the community pool so other readers of the same book can adopt it.

  • Generate: on any book's detail page click Regenerate cover. Pick an aesthetic (vintage paperback, minimalist, watercolour, neon, etc.) and Shelfsort renders a new cover via Gemini Nano Banana. You can keep iterating; each render is saved as a variant.
  • Share: the Share to community toggle on a saved variant publishes it to the pool. Public URL: /explore/covers. Each shared cover gets its own /cover/<id> page with Open Graph + Twitter Card meta so the link unfurls as a rich preview in Discord / Twitter / iMessage.
  • Vote & adopt: any logged-in user can up-vote a cover. The top-voted covers per canonical title bubble to the top of the Explore page and into the “Cover of the week” dashboard strip. One-click Use this cover on someone else's variant copies it to your library (with attribution to the original sharer).
  • Lineage & profiles: each cover tracks who shared it, who's adopted it, and which variants remixed from it. Click any sharer's @handle to see their public profile with their shared covers gallery.
  • SEO & discovery: the cover pool ships an RSS feed (/feed/covers.xml) and sitemap so search engines + RSS clients can index new community covers. The Explore page exposes browse-by-aesthetic + browse-by-fandom rails.

Privacy: sharing is opt-in per variant. Your library never auto-shares; only covers you explicitly toggle Share to community on become public. Revoke from the same toggle at any time and the public URL 404s within minutes.

Friend recommendations

Once you've added friends and at least one of them has opted to share their library, Shelfsort surfaces books they've loved that you don't own yet.

  • Ranking: 3 × finishers + 1 × serious_readers + reading_minutes/60, capped. A single very-invested friend can punch above multiple casual finishers.
  • Grouping: when several friends have the same book, the rec collapses into one row showing the combined byline (“Alice, Bob +2 more”).
  • Already-owned filter: anything matching by canonical URL or normalised title+author is hidden.
  • Surfaces: top 3 on the Dashboard via the From your friends card (auto-hides when empty); full list at /library/recommendations with hide / restore controls.
  • Weekly "From friends" digest: an in-app notification lands every Sunday at 18:00 UTC summarising what your sharing friends finished. Opt in at Account → Emails to also receive an email copy.

E-reader sync (OPDS catalog)

OPDS is the standard XML feed format that KOReader, Moon+ Reader, Marvin, Foliate and other standalone e-reader apps use to browse + download books from a server. Shelfsort serves a per-user OPDS catalog so you can point your e-reader at your library and read offline.

  • Setup: AccountE-reader sync card. Click Generate catalog password, save the username (your email) + password (shown once), and toggle the channel on.
  • Catalog URL: ends with /api/opds, with HTTP Basic auth using the catalog password (separate from your primary login password).
  • Feeds: root → All books / Recently added / By fandom / By author. Acquisition links download the EPUB; cover thumbnails included.
  • Privacy: only your own books are served. The toggle gates access independently of the password — switch it off any time without re-rolling the password.

Notifications & mutes

The bell icon in the navbar shows every kind of in-app ping — friend requests, accepted requests, friend uploads in fandoms you collect, bookclub invites + messages + finishers, weekly digests, suggestion status changes.

  • In-app notifications always fire — there's no master "all on/off" toggle. Emails are the only thing you opt into.
  • Per-kind mute matrix: at Account → Emails, scroll past the email channels to the In-app notifications card. Each kind has its own toggle so you can keep bookclub invites loud and silence chatty message pings.
  • Critical kinds (friend requests, bookclub invites) cannot be muted — they're actionable and would silently disappear if turned off.
  • Email channels (all opt-in, default OFF): Weekly digest, Fic updates, Year-in-Books recap, From-friends weekly, Book-club weekly. Each has a "send sample" button so you can preview before subscribing.
  • Account updates · NEW (2026-06-20): at the bottom of Account → Emails, a fresh Account updates card lets you choose which one-off emails you want (You're approved!, Approval declined, Suggestion status updates, Year in Books wrapped, Bookclub invites, Weekly recommendations, Fandom overlap with friends). Turn any off and that email becomes an in-app notification on your next visit. Security-critical emails (password resets) always send regardless.

Web push notifications

For cross-device handoff (“Continue this fic on your phone?”), Shelfsort uses Web Push — silent OS-level notifications that fire even when the tab is closed. Strictly opt-in: you have to explicitly enable it.

  • Enable: Account → Notifications → click Enable browser notifications. Your browser shows a permission prompt; allow it.
  • Per-device subscription: subscribe separately on each device you want to receive handoff pings (phone, work laptop, tablet). The list shows every active subscription with the device label and the date you registered it.
  • What triggers a push: closing a book on one device sends a soft handoff prompt to your other devices (“Continue Title on iPhone?”). Tapping the push opens the Reader at the same CFI. Nothing else uses push (no marketing pings, no chat alerts — those stay in-app + email).
  • Unsubscribe: revoke any device from the same panel, or just block notifications in the browser. Server-side subscriptions are deleted on revoke.

Technical: VAPID keys are auto-rotated server-side; the public key ships down with the subscribe call so you never need to copy/paste anything. iOS Safari requires the site to be added to Home Screen as a Web App before push works — Shelfsort prompts you to do this on first enable from iOS.

Scheduled auto-theme

Three modes at Appearance → Theme:

  • Light — always.
  • Dark — always.
  • Auto — switches based on a strategy you pick:
    • Time of day (default): set a dark window (e.g. 19:00 → 07:00 local). Windows that span midnight work. Shelfsort re-evaluates every 60 seconds so the flip lands at the right minute without a page reload.
    • Follow system: tracks the OS / browser prefers-color-scheme media query. Live-reacts when you change the system theme.

Stored to localStorage only (per-browser).

Keyboard shortcuts

A handful of global shortcuts for power users. All listeners skip when you're typing in a text field so they never clobber your paste.

  • Cmd / Ctrl + Shift + D — toggle light ↔ dark theme instantly. Works on every page.
  • / — focus the navbar quick-search. See Navbar quick-search for what it searches.
  • Esc — close any open modal, popover, or the Welcome tour overlay.

More shortcuts coming. Have a request? Send us feedback.

Word count & reading time

Every uploaded EPUB is indexed for full-text search at upload — the same pass also stamps a word count on the book. From that, Shelfsort computes a reading-time estimate using your personal reading speed (words-per-minute).

  • Per-book: open any book's detail page to see its word count and total reading time. For half-read books the estimate splits into "Xh Ym total · Zm left" based on your current progress.
  • Dashboard tile: a "Reading time" card shows your full library at a glance — minutes left to read, minutes already read, library total. Hides automatically until at least one book has a word count.
  • Reading speed: change yours at Appearance → Reading speed. Default is 250 wpm (average adult fiction); presets cover Slow (180), Average (250), Fast (350), Speed reader (500). Slider and number input go from 80 to 1500.
  • Backfill: books uploaded before this feature shipped get their word count filled in lazily by an admin sweep, or instantly if an admin runs POST /api/admin/wordcount/backfill.

Trash — the 30-day undo shelf

Anything you delete from Shelfsort lands in Trash for 30 days before it’s permanently removed. Books you delete manually or in bulk, duplicates you resolve from the Duplicates review page (Discard action), and losers from a "Keep only the latest" pass all end up here. Nothing is really gone until you empty the trash or the daily sweep runs. (Upload-time duplicate detection no longer routes anything to Trash as of 2026-07-26 — suspected duplicates always land in the Duplicates review area for you to inspect first.)

Every row shows when and why:

  • 🕐 Trashed date — a friendly relative label (“3 days ago”, “yesterday”, “2 weeks ago”) alongside the exact date and time (“Aug 21, 2026 · 4:16 pm”) so you always know when a book left the library.
  • ℹ Reason chip — a small purple pill spelling out exactly why the book was trashed. There are five possible reasons (as of 2026-07-26):
    • Discarded from Quarantine — you (or an admin) picked “Discard” on a single row in the Quarantine review page.
    • Batch-discarded from Quarantine — you used the “Keep only the latest” button on a duplicate group, so every non-winner in the group came here.
    • Chose to discard when resolving a duplicate — you hit “Discard” on the individual duplicate-resolution dialog for one book.
    • Removed via bulk delete — you selected books in the library and deleted them together.
    • Removed from library — the fallback for legacy trash rows written before the audit fields existed (2026-08-24 and earlier).

    What happened to “Duplicate discarded during upload”? As of 2026-07-26, upload-time duplicate detection no longer routes anything to Trash. Suspected duplicates always land in the Duplicates review area where you decide whether to Keep both, promote to a new version, archive as historical, or Discard (which is the ONLY path that now moves a duplicate to Trash). The “Send to Trash” auto-policy has been retired — anyone who had it set is now on “Ask me each time”. Existing Trash rows tagged “Duplicate discarded during upload” from before this date will still expire on their 30-day schedule and won’t be regenerated.

  • was on [shelf name] — an italic hint of the shelf the book came from, so you know exactly where Restore will put it back.
  • ⚠ N days left — the auto-delete countdown. Amber for anything > 3 days out, red for ≤ 3 days or already expired. That’s the same clock the nightly sweep uses, so you always have a visible warning before something disappears for good.

Restoring:

  • Restore one book — hit the “Restore” button on any row. It goes straight back to its previous shelf (shown in the “was on” hint). If the previous shelf no longer exists it lands in Unclassified.
  • Restore everything — the “Restore all” button at the top of the page brings every trashed book back to its previous shelf in one pass. Handy after an accidental bulk-delete.
  • Empty Trash — the red button permanently deletes every book currently in Trash, plus their EPUB/cover/links sidecar files from disk. Requires confirm. Shows a ✨ toast with book count and reclaimed storage (opt-in nudge, on by default).

Legacy rows (from before the WHEN/WHY audit shipped on 2026-08-24) will display “Trashed before 2026-08-24” and “Removed from library”. Any book you trash from that date onward carries the exact timestamp and specific reason.

Daily sweep: every night at 06:00 UTC, the digest cron hard-deletes any book whose 30-day grace has expired. There’s no undo after the sweep — that’s why the “days left” countdown goes red at 3 days.

Account & preferences

Your Account page is the control center:

  • Library stats card + Fandom Treemap for at-a-glance overview
  • Library backup card (download + history + restore link)
  • Smart Shelves manager — create, edit, delete saved filter combinations
  • Fandom aliases — map your custom shorthand to canonical fandom names
  • Format prefs for the Originals shelf
  • Appearance (theme + colour) — the Navbar carries a single Sun/Moon icon that opens an Appearance popover. Inside the popover you can flip light↔dark and pick from seven palette swatches (Peach, Purple, Forest, Ocean, Crimson, Charcoal, and Custom). Hover any swatch to see its name in a live caption. Click a swatch to flip the accent colour site-wide instantly. Tap More appearance options at the bottom of the popover to open the dedicated Appearance page, which adds:
    • Light/Dark cards with descriptions instead of a tiny toggle
    • The full Custom hex picker — four colour inputs for Primary, Primary hover, and two pale tints. Dark-mode variants are auto-derived via HSL math
    • Live preview showing your active palette on a primary button, secondary button, NEW pill, and a pale-tint card with link
    • Curated palettes gallery — six hand-picked named palettes (Cozy Library, Midnight Reader, Sun-bleached Paperback, AO3 Classic, Forest Floor, Vintage Ink) you can apply with one click and then tweak
    • Share palette card — three ways to share your current look:
      • Copy token — a short string (e.g. ss-p-forest for presets, longer ss-c-… base64 for Custom) that someone else can paste to apply your palette in one tap.
      • Copy as Markdown — drops a formatted block with the palette name, theme, hex codes, and import token onto your clipboard. Pastes cleanly into Discord, GitHub, Notion, anywhere Markdown lives.
      • Download PNG — generates an 800×420 screenshot of the four swatches with hex labels and palette name; perfect for sharing on social.
    • Reset to defaults link at the bottom — two-click confirmation, restores the default Purple palette + default Custom hexes
    Saved to this browser only.
  • Danger zone — Delete entire library: wipes every book, EPUB file, reading history entry, smart shelf, and custom category. Your account stays — your login, profile settings, fandom aliases, format prefs, and theme palette are all preserved. Useful for a fresh start without losing your settings. No undo; a ZIP backup is your only recovery.
  • Danger zone — Delete account permanently: schedules your account for deletion in 30 days. You're signed out immediately, but books and files stay intact during the grace window — sign back in any time during those 30 days and a banner appears with a one-click Cancel deletion button. When you type your email into the confirmation field, a backup-first reminder appears so you can grab a ZIP before committing. Requires the typed email to match. After day 30 a daily scheduler hard-purges the user record, password hash, sessions, library, files, and reading history — that's the point of no return; a previously-downloaded ZIP is your only recovery.

Found a bug or want a feature? The agent listens — just ask in chat.

Finding a setting fast (sidebar ToC)

Shelfsort’s settings pages — Account, Email preferences, and Appearance — all share the same navigation pattern so you don’t have to hunt for anything twice.

  • Desktop sidebar: on screens ≥ 1024 px wide, a sticky Table of Contents sits on the left. Each category (Profile, Privacy, Library, Backups…) is a collapsible group, and the tile that’s currently in your viewport is highlighted with a purple pill so you always know where you are.
  • Search box at the top: type any word — “theme”, “sessions”, “backups” — and the list narrows in real time. Matches include category names and hidden keywords (e.g. searching “wpm” jumps you to the Reading-speed card on Appearance).
  • Recent block: the three most-recently-clicked settings float to the top of the sidebar so common tasks (like flipping a preference back after testing) are one tap away. Recent is per-page and remembered between visits.
  • Mobile jump menu: below 1024 px the sidebar collapses into a compact “Jump to section” dropdown that stays pinned to the top as you scroll. The current section’s name is always the selected value.
  • Keyboard-friendly: Tab through the ToC and press Enter to jump. The target card briefly flashes so your eye can find it.

Same pattern will land on Help, Rules, and other long pages in the coming weeks.

Signed-in devices & sessions

Shelfsort remembers each device / browser you sign in from so it can show you a per-device activity list and let you sign out anywhere without touching your password. This lives on the Account page under Active sessions (in the Privacy category).

  • What you see: browser + OS (parsed from the user-agent), where the session was created, when it was last active, and a “This device” badge on the row you’re reading this from.
  • Sign this device out: use the regular Sign out button in the navbar. That kills only the current session; other browsers stay signed in.
  • Sign out one other device: click the “Revoke” button on that row. The device is signed out immediately on its next request — no waiting for a session to expire.
  • Sign out everywhere else: the “Sign out other sessions” button at the bottom of the card revokes every session except the one you’re on right now. Useful if you signed in on a shared computer and forgot to sign out, or if you lost a phone.
  • Session lifetime: sessions last 30 days from last activity by default. Any signed-in request refreshes the timer; sessions that go untouched for a full month expire automatically.
  • Suspicious activity: if you see a session from a device you don’t recognise, revoke it and then change your password. Shelfsort has no way to know it was you.

Operator weekly digest (admins only)

If you're an admin, Shelfsort can email you a Sunday-evening rollup of the past week's site analytics so you can keep a light pulse on engagement without opening the admin console.

  • What's in it: explore-page views, cover-page views, new signups, top 5 covers by view count (with the sharer's @handle), and a top-6 referrer mix (Twitter, Reddit, Discord, direct, etc.).
  • Cadence: Sunday 19:00 UTC. Idempotent per ISO week so you never get two for the same Mon–Sun window.
  • Enable: Account → Email preferencesOperator weekly digest card (admin-only — the card is hidden from non-admins). Toggle it on, hit Send sample email to preview the layout.
  • Where the data comes from: same aggregations the Admin Console analytics widget uses — no separate tracking pipeline.