Reduce cognitive load on the Content settings page by collapsing the
two-section toggle layout, group sub-headers, sub-kind rows, kind
badges, and column headers into a single flat list of 14 toggles
ordered by importance: Posts, Replies, Reposts, Articles, Highlights,
Photos, Videos, Voice Messages, Events, Polls, Organizations, Badges,
Reactions, Zaps.
Each row is now a plain label + one-line description + switch. No
content-kind icons, no [1234] kind-number badges, no Media / Social /
Whimsy sub-headers, no Normal/Short video or Badge Definitions /
Profile Badges / Badge Awards sub-rows (the parent toggle now governs
all sub-kinds together).
Combine kind 6 (Reposted Notes) and kind 16 (Reposted Other Content)
into a single "Reposts" toggle via extraFeedKinds: [16]. The old
feedIncludeGenericReposts flag stays in the schema for backwards
compat but no longer surfaces in UI.
Rename "Comments" -> "Replies" — Nostr's NIP-22 threading is most
naturally called replies.
Strip NIP / kind-number references from all curated descriptions
(NIP-22, NIP-52, NIP-58, NIP-68, NIP-71, NIP-72, NIP-84, NIP-A0,
"kind 30009", etc.). Plain English only.
Merge the standalone /settings/content page (mutes + sensitive
content) into /settings/feed as inline sections under the toggle
list, since both are about "what you see in the feed." Delete
ContentPage.tsx and its route; remove the Content entry from the
settings index. Drop the giant ShieldAlert icon from the sensitive
content intro.
Rename "Home Feed Tabs" -> "Saved Feeds" in the page section heading.
The settings UI iterates EXTRA_KINDS and renders a toggle row per kind,
which exposed every Nostr content type the app understands (vines,
treasures, colors, decks, webxdc, birdstar, emoji packs, music,
podcasts, development, etc.) regardless of whether they fit Agora's
activist-utopian framing. The result was a wall of toggles with no
meaningful default.
Add an `agora` boolean to ExtraKindDef and mark only the curated set:
posts, comments, reposts, generic-reposts, reactions, zaps, articles,
highlights, photos, videos (with sub-toggles), voice messages, events,
polls, organizations (NIP-72 communities), and badges. Filter the
"Basic Home Feed Options" and "Show More Content Types" sections to
`def.agora === true`. Move badges from the "Whimsy" section into
"Social" so the Whimsy and Development groups vanish entirely after
filtering.
Enable zaps in the home feed by default (they're core engagement,
not noise) and drop "Disabled by default" from the zaps description.
Other pages (KindFeedPage deep-links, ExternalContentHeader quoted
events, etc.) still see the full EXTRA_KINDS registry, so external
content from non-curated kinds still renders correctly when linked.
Remove the spellbook-themed settings index: drop the "Codex of
Configuration" heading, the gradient ornaments with ✦/◆ dividers, the
sigil that appeared after two minutes of inactivity, and the IntroImage
illustration tiles on every section row and sub-page intro block. The
index is now a flat divider-separated list of labels and one-line
descriptions, with breathing room on both sides.
Delete the Magic settings page, its CursorFireEffect overlay, the
animate-sigil-glow / animate-pulse-slow keyframes, the magicMouse
AppConfig flag (schema, default, test fixture), and the /settings/magic
route. Delete the now-unreferenced IntroImage component and the ten
*-intro.png assets it masked.
Disable content types that don't fit an activist tool by default: vines,
treasures (geocaches + found logs), colors, decks, webxdc, birdstar
(detections / birdex / constellations), emoji packs, custom emojis, user
statuses, music, podcasts, and development. They remain available in
settings — just off out of the box. Highlights is bumped on by default
to pair with Articles. Posts, comments, reposts, articles, highlights,
events, polls, communities, people lists, badges, photos, videos, and
voice messages stay on.
- Add useWorldFeed hook combining infinite-scroll pagination with live
streaming and 'X new posts' buffer/flush pattern
- World feed queries all country-tagged events globally with a diversity
cap (max 4 posts per country per page)
- Live streaming via persistent relay subscription with scroll-aware
buffering and highlight animation on flush
- Rename Ditto tab to World across Feed, ContentSettings, and useFeedTab
- Migrate localStorage key from ditto:showDittoFeed to agora:showWorldFeed
SubHeaderBar: add left/right chevron scroll arrows on desktop when tabs
overflow, with gradient fade. Auto-scroll active tab into view and keep
arc hover/active indicators aligned during horizontal scroll.
ContentSettings: add Interest Tabs section with inline add/remove for
hashtags and geotags. Remove buttons always visible (mobile-friendly),
X icons use strokeWidth 4.
Multiple ExtraKindDef entries share the same feedKey (e.g. posts/comments
both use feedIncludePosts) and multiple subKinds share the same showKey
(e.g. both video sub-kinds use showVideos). Using these as React keys
caused 'duplicate key' warnings.
Use def.id (always unique) for ContentTypeRow keys and sub.feedKey
(unique per sub-kind) for SubKindRow keys.
- Add 'ditto' CoreFeedTab that renders the same hot-sorted curated feed
(useInfiniteHotFeed) previously shown only to logged-out visitors
- Change Global tab default from enabled to disabled
- Update useFeedTab to default to 'ditto' instead of 'global' when logged out
- Add showDittoFeed localStorage toggle in Feed.tsx and ContentSettings.tsx
- Add Ditto Feed toggle row in ContentSettings FeedTabsSection
- avatar.tsx: consolidate to single isEmojiShape boolean, fix AvatarFallback using same logic as Avatar
- avatarShape.ts: accept NostrMetadata directly in getAvatarShape (no cast needed)
- Remove 'as Record<string, unknown>' casts from all ~50 call sites
- Replace 'circle' magic string with empty string in form defaults and parseShape
- Use isValidAvatarShape instead of string comparison in save logic
- ProfileCard: extract IIFE overlay style into useMemo, use isEmojiShape throughout
- New 'shape' property on kind 0 profile metadata with 7 predefined shapes:
circle, triangle, inverted-triangle, hexagon, star, inverted-star, hexagram
- Avatar component updated with clip-path support for non-circle shapes
- Visual shape picker added to both EditProfileForm and ProfileSettings
- Shape applied across all 49 avatar render sites in the app
- New UserAvatar wrapper component for future simplified avatar rendering
- Unknown shape values gracefully fall back to circle (forward compatible)
- Rename 'Feed' to 'Home Feed' in settings hub, page header, and SEO title
- Rename 'Notes' section to 'Basic Home Feed Options'
- Rename 'Other Stuff' to 'Show More Content Types in Home Feed'
- Rename 'Reposts' to 'Reposted Notes', 'Generic Reposts' to 'Reposted Other Content'
- Only show community domain management when community feed toggle is on
Convert 10 collapsible sections to always-visible static sections with
plain subheaders across Feed, Content, Network, Profile, and Lists pages.
The 'Advanced' section on Profile and all sections within AdvancedSettings
remain collapsible.
- Add NIP-51 Follow Sets management (useUserLists, UserListsPage at /lists)
- Add Follow Packs support (useFollowPacks) for curated people lists
- Add FeedEditModal for creating/editing home feed tabs with author scope,
kind picker, and list/pack author population
- Add AddToListDialog accessible from the note more-menu
- Unify home feed tab resolution with profile tabs via useResolveTabFilter,
supporting $follows variable expansion
- Wire People filter in Search and FeedEditModal with list/pack picker
- Extract shared ScopeToggle, ListPackPicker, parseSelectedKinds primitives
into SavedFeedFiltersEditor to eliminate duplication across three call sites
- Cache savedFeeds in AppConfig for instant tab render on load
- Strip orphaned profile-destination entries from encrypted settings on read
The proprietary SavedFeedFilters JSON schema in kind 16769 tab events was
non-interoperable. Replace it with standard NIP-01 filter objects that any
Nostr client can execute, plus a variable system (var tags) for dynamic
values like follow lists.
- Tab filters are now standard NIP-01 filters with optional NIP-50 search
- Variables ($name) in filters are resolved via var tags that extract tag
values from referenced events (e.g. follow list pubkeys from kind 3)
- $me is the only runtime variable (profile owner's pubkey)
- Variables in arrays are expanded in-place (spliced)
- Add useResolveTabFilter hook for variable resolution with caching
- Update all consumers: ProfilePage, Feed, SearchPage, ContentSettings
- Remove SavedFeedFilters type entirely in favor of TabFilter
- New src/components/SavedFeedFiltersEditor.tsx exports:
SavedFeedFiltersEditor (controlled), KindPicker, AuthorChip,
AuthorFilterDropdown, buildKindOptions, useScrollCarets
- SavedFeedFiltersEditor renders all filter controls: query input, author
scope toggle (Anyone/Follows/People with chips), sort toggle
(Recent/Hot/Trending), media/platform/language/kind selects
- ContentSettings SavedFeedRow now uses SavedFeedFiltersEditor in its
edit panel — clicking pencil expands the full filter set, not just a
query text box; Save writes label + all filters atomically
- SearchPage filter popover delegates to SavedFeedFiltersEditor
(showQuery=false); all duplicate local implementations removed
- useSavedFeeds gets updateSavedFeed(id, {label?, filters?}) for atomic
label+filters updates
- Add updateSavedFeed to useSavedFeeds (updates label and/or filters atomically)
- SavedFeedRow now has a collapsible edit panel below the summary row:
two inputs side-by-side (Tab name / Search query), Cancel + Save buttons
- Pencil icon toggles the panel open/closed; X in the panel header closes it
- Enter in the query field saves, Escape cancels
- The summary row (label + filter chips) always stays visible above the panel
- Remove button stays in the header row, hidden until hover
SavedFeedsSection appears inside the Feed Tabs collapsible in ContentSettings
after the Community block (only when feeds exist). Shows two groups:
'Home feed tabs' and 'Profile tabs', each listing saved feeds with:
- Feed label + compact filter summary (query, author scope, sort icon)
- Inline rename: click pencil, edit input, Enter/check to save, Escape to cancel
- Remove button (destructive hover, disabled while saving)
- Actions fade in on row hover to keep the list clean
- Icons: Home/User for group headers, Globe/Flame/TrendingUp/Clock for filter hints
Also removes pre-existing unused arrayMove import in ProfileSettings.tsx.
- Add homePage config field (default: 'feed') that determines which
sidebar item renders at the '/' route
- Any sidebar item can be set as the homepage via Feed settings
- The feed is always accessible at /feed when not configured as home
- Normalize sidebar item IDs: every item follows path === '/' + id
(feed path changed from '/' to '/feed', emoji-packs renamed to emojis)
- Remove BOTTOM_NAV_ITEMS filter from MobileDrawer so all sidebar
items are visible on mobile (feed, notifications, search included)
- Mobile bottom nav dynamically reflects the configured homepage icon
- Homepage setting syncs across devices via encrypted settings
- Add emoji-packs -> emojis migration for existing sidebar orders
Muted threads now display as clickable links (shortened event ID with
an external link icon) that navigate to the thread via its nevent
identifier, instead of showing a raw unclickable hex string.
Closes#50
Muted users in settings/content now show their avatar and display name
instead of raw hex pubkeys. Uses useAuthor to resolve profiles with a
skeleton loading state. Non-pubkey mute types (hashtags, words, threads)
retain the existing code display.
Closes#49
The ContentTypeRow only rendered a Switch for entries with feedKey.
Entries with feedOnly=true and showKey (like Custom Emojis) had no
toggle rendered. Added a fallback branch that renders a Switch using
showKey for these display-only toggles.
Replace the dual-registry system (BUILTIN_SIDEBAR_ITEMS in useFeedSettings
+ EXTRA_KINDS in extraKinds) with a single SIDEBAR_ITEMS array in
sidebarItems.tsx. Every sidebar-capable item now has a consistent shape:
id, label, path, icon, and optional requiresAuth.
- Delete extraKindIcons.ts; icons now live in the SIDEBAR_ITEMS registry
- Remove BUILTIN_SIDEBAR_ITEMS, isBuiltinItem, getBuiltinItem, EXTRA_KIND_IDS
from useFeedSettings.ts; validation uses SIDEBAR_ITEM_IDS
- Remove HiddenSidebarItem.builtin field (was never read by consumers)
- Simplify SidebarNavItem.tsx (no more dual icon map lookup)
- All consumers now import sidebarItemIcon from @/lib/sidebarItems
- ExtraKindDef/EXTRA_KINDS unchanged (still used for feed/content settings)
Remove the local ICONS map from ContentSettings.tsx and replace it with
EXTRA_KIND_ICONS from extraKindIcons.ts. Add feed-kind icons (posts,
comments, reposts, generic-reposts) to the shared registry so all icon
references resolve from one place.
Migrate themes from a standalone card to the standard NoteCard rendering
pattern (like treasures), so theme events appear as interactive posts
with reply, repost, reaction, and zap buttons.
- Create ThemeContent component for inline theme color preview
- Add ThemeHeader for 'shared a theme' / 'updated their theme' labels
- Support both kind 36767 (definitions) and 16767 (active themes) in feeds
- Convert extraKinds themes entry to use subKinds pattern with toggles
- Create ThemesPage wrapper (like TreasuresPage) for sub-kind filtering
- Update ContentSettings to sync both sub-kind feed toggles
Add explicit `id` field to ExtraKindDef and remove the `__` prefix
convention for built-in items. Item IDs are now plain strings that don't
need to match URL paths. Route resolution goes through the definition's
`route` field instead of assuming id === route.
- Advance lastSyncedTimestamp when skipping due to recentlyWritten() so the
same snapshot cannot be re-applied once the write window expires
- Use pendingSettings ref in useEncryptedSettings to accumulate rapid mutations
instead of reading stale cache data
- Fix ContentSettings handleToggle to not merge against stale nostrSettings
Three bugs caused settings toggles to get stuck or reset:
1. useEncryptedSettings: onSuccess was updating the parsedSettings cache
under the old event ID, causing NostrSync to re-apply the stale remote
event when the new one arrived with a different ID. Fixed by using the
signed event's ID for both cache entries.
2. ContentTypeRow/SubKindRow: handleToggle was building the Nostr payload
from the local React feedSettings state, which lags behind on rapid
successive toggles. Fixed by merging against the Nostr-cached settings
(nostrSettings?.feedSettings) as the base instead.
3. NostrSync: lastSyncedTimestamp ref reset to 0 on every page reload, so
the 10-second recentlyWritten() guard was the only protection against
stale relay events overwriting local changes. Fixed by seeding the ref
with the current remote lastSync on first settings load, skipping
application of any event that isn't strictly newer than what was
already in the cache.
Replace Layers icon with custom CardsIcon (stacked cards SVG) across all
navigation and settings UIs. Filter out events tagged t:unlisted for kind
37381 (magic decks) and t:hidden for kind 37516 (geocaches) in NoteCard.
Articles (kind 30023) now have both a showArticles sidebar toggle and
feedIncludeArticles feed toggle, matching the pattern of other content
types like Vines and Streams. Added FileText icon to all icon maps.
- Add 'feed' section to EXTRA_KINDS for Posts (kind 1), Reposts (kind 6),
and Articles (kind 30023) as feed-only toggles
- Add feedIncludePosts, feedIncludeReposts, feedIncludeArticles to FeedSettings
(posts and reposts default to enabled, articles disabled)
- Add 'Notes' collapsible section in ContentSettings above 'Other Stuff'
- Feed kinds are now fully dynamic from settings instead of hardcoded
- Repost unwrapping accepts any event kind, not just kind 1
- Add article (kind 30023) card rendering with title, image, and summary
- Update all feed hooks for consistency (useFeed, useProfileFeed,
useStreamPosts, DomainFeedPage, HashtagPage)
Both FeedSettingsForm and ContentSettings were missing the 'streams'
entry in their ICONS map, causing it to fall back to a Palette icon
instead of the Radio icon used everywhere else.