Incomplete-characters page, dashboard home redesign, and a manual spoiler audit of entity descriptions
New /contribute/incomplete/characters page - dedicated landing page for contributors looking for somewhere to help. Lists every published character whose page is missing one or more of the six completion dimensions (description, physical attributes, abilities, occupations, relationships, appearances), ordered emptiest-first with appearance count as a tiebreaker. Search input, universe filter, and per-page selector (10/20/50) all wired through URL search params. Each row shows portrait, name, primary series and universe (with entity dots), an X/6 completeness pill, and a "Help complete this character" link. Page uses the same EntityLayout, HeroBanner, and ServerPagination chrome as /characters for visual consistency.
Characters with no appearances surface correctly - earlier work moved sorting and pagination into the SQL query, so characters with zero appearances (the emptiest pages, often the most in need of help) are no longer buried by an over-weighted impact score or silently dropped by a 500-row candidate cap. The query now sorts by filled ascending, with appearance_count only as a tiebreaker.
Dashboard home redesign - the active-user dashboard at /dashboard is restructured around what a contributor can actually do next. The three stat cards (Contributions, Requests, Votes Cast) and the Recent Activity notifications feed are removed. In their place:
- Your content - compact two-row summary panel listing Requests and Contributions with a "5 total" subtitle and a "0 pending" badge in warning colour when relevant. "View all →" on each row links to the dedicated dashboard page.
- Quick actions - three-link panel (Submit a Request, Find incomplete pages, View All Requests) sitting beside the summary panel on
lg+viewports (3:2 column split) and stacking below it on smaller screens. Heights match between the two panels. - Characters you can complete - new personalised widget below. Pulls up to five incomplete characters from series the user has marked as read or is currently reading. Falls back to the global top five if the user has no usable reading-profile data, with a subtle "Set up your reading profile" prompt below. Empty state ("You've completed all the characters from your series") when the personalised filter yields no matches.
- Community content requests - the curator-only summary card is restructured: heading sits above the card, "Manage all →" link sits below it (matching the arrow-link style used elsewhere on the page), and the Sparkle icon is dropped. Now visible on every viewport size for curators (previously hidden below
md). Sits above the suggested-characters widget so curators see what needs their attention first.
Header dashboard icon - the user menu's "Dashboard" entry now uses the House icon, matching the sidebar nav.
Per-user incomplete-characters query - new getIncompleteCharactersForUser(userId, limit) helper restricts the result set to series the user has read or is reading via a series_id IN (...) filter on the existing CTE. Wrapped in react.cache only (per-request dedup) - deliberately not in unstable_cache, since per-user seriesIds would explode the cache key space and the data is cheap to recompute. The COUNT(*) query is skipped for this code path because the dashboard widget never displays a total - saving one duplicate CTE evaluation per dashboard load.
Spoiler audit of entity descriptions - manual sweep through every always-visible freeform description field in the database. Flagged and rewrote 33 entries that contained plot spoilers visible to all visitors regardless of reading profile:
- 16 character descriptions across the Mistborn, Wheel of Time, Dune, Lord of the Rings, His Dark Materials, ASOIAF, Sherlock Holmes, Harry Potter, Heroes of Olympus, Realm of the Elderlings, and First Law universes - including major culprits like Vin's "ultimately kills the Lord Ruler", Jack Stapleton's explicit "central solution spoiler" callout, Pettigrew's rat disguise, Hazel's resurrection backstory, and Bayaz's meta-spoiled "trilogy's central revelation" framing.
- 8 group descriptions (Hunters of Artemis, Church of the Survivor, Skaa Rebellion, The Set, Diagram, Ghostbloods, Fremen, White Council, Kelsier's Crew) and 1 lighter trim on the Diagram.
- 2 series descriptions (Mistborn Era 2, King of Scars).
- 12 book descriptions for late-arc volumes (Alloy of Law, Hero of Ages, Knife of Dreams, Towers of Midnight, Gathering Storm, Memory of Light, New Spring, Shepherd's Crown, Goblet of Fire, Dark Tower, Amber Spyglass, Republic of Thieves, Words of Radiance, Rhythm of War, Wind and Truth, Rule of Wolves).
- 1 reading order description (WoT Recommended Reading Order).
The intent throughout is to keep descriptions premise-level - what a reader knows when they first encounter the character or open the book - without naming arc resolutions, deaths, true identities, or "central reveal" meta-pointers. Skipped entries that already met this bar (Fitz, Chivalry, Chade, Age of Madness series description, Harry Potter series description, Farseer trilogy series description). For the structurally-spoilery entities whose existence is itself a reveal (Era 2 institutions, Diagram, Church of the Survivor), softened the worst of the extras and added a backlog item for proper UI gating.
Description-field spoiler filtering on the backlog - new entry under "Spoiler System" in docs/features.md proposing tagging each description with a "safe from" segment or book id, so the full description sits behind a SpoilerGuard for users who haven't progressed past the safe-from point. The structural class of issue this would address: book descriptions for later books in a series unavoidably reference the endings of prior books; group/series descriptions for entities that exist only after a major plot event reveal those events by their existence. Today's manual sweep handled the worst cases; UI gating is the long-term fix.
Stats API tweaks - /api/dashboard/stats now exposes requests.pending derived from the pending_review status, used by the new summary panel. Notifications fetch removed from the dashboard home (the dedicated /dashboard/notifications page is unchanged).