Move build-time ditto.json injection from a Vite define global to
import.meta.env.DITTO_CONFIG (a JSON string parsed and validated at
runtime via DittoConfigSchema). Remove the global type declaration
from vite-env.d.ts.
Drop ThemeSchemaCompat and its legacy "black"/"pink" migration code
from AppProvider and NostrSync — invalid theme values now simply fail
Zod validation.
Fix a latent bug where a partial feedSettings from ditto.json would
replace the full hardcoded defaults; defaultConfig now deep-merges
feedSettings.
The useApplyFonts hook in AppProvider only loaded body fonts
(activeConfig.font) but never loaded title fonts (activeConfig.titleFont).
The loadAndApplyTitleFont function and CSS variable --title-font-family
existed but were never called on startup/theme change, so components
referencing the variable always fell back to inherit.
Blossom servers now work exactly like relays:
- App default servers (APP_BLOSSOM_SERVERS) can be toggled on/off
- User's kind 10063 server list is fetched and synced from Nostr
- NostrSync fetches kind 10063 on login and applies it to config
- useInitialSync fetches kind 10063 in parallel on first login
- BlossomSettings UI mirrors RelayListManager with two sections:
App Blossom Servers (with enable toggle) + Your Blossom Servers
- User changes publish a kind 10063 replaceable event to Nostr
- getEffectiveBlossomServers() merges app + user servers like getEffectiveRelays()
- Migration from old blossomServers[] to blossomServerMetadata handled in AppProvider
Read an optional ditto.json (or CONFIG_FILE env) at build time, validate
it with Zod, and inject the result via Vite's define. At runtime the
build-time values are merged between the hardcoded defaults and user
localStorage, giving precedence: user > build-time > hardcoded.
Extract canonical Zod schemas into config/schema.ts (no path aliases)
so they can be imported from both vite.config.ts and runtime code.
Move AppConfigSchema out of AppProvider into src/lib/schemas.ts to
eliminate duplication.
Every place that previously read builtinThemes directly for light/dark
mode now calls resolveThemeConfig(), which checks config.themes first
and falls back to builtinThemes. This ensures configured themes are
actually used for colors, fonts, and backgrounds in all code paths:
AppProvider, useTheme, ThemeSelector, ThemeBuilderPage, ProfilePage.
Allow self-hosters to override the builtin light/dark themes by setting
a themes field in AppConfig with full ThemeConfig objects (colors, fonts,
backgrounds) for each mode.
Use primitive fontFamily/fontUrl strings as effect dependencies instead
of the font object reference, matching the same fix applied to
background handling.
Use primitive bgUrl/bgMode strings as effect dependencies instead of
the background object reference, ensuring React detects the change
when switching between presets with and without backgrounds.
Upload images via Blossom, store in ThemeConfig.background, and apply
as a fixed CSS background on <body>. Supports cover/tile/contain modes.
Background picker sits in the consolidated customize card between the
font picker and the share toggle.
Reduce font complexity from two-role (title/body) to a single font
that applies globally to all text. Updates NIP.md f tag format from
["f", family, role, url] to ["f", family, url], removes ThemeFonts
wrapper in favor of a single ThemeFont on ThemeConfig.font, and
simplifies FontPicker to a single dropdown.
- Add ThemeConfig wrapper type (colors + fonts + background) replacing bare CoreThemeColors
- Sync theme event parsing/building with NIP.md spec: colors in c tags (hex), fonts in f tags, background in bg tags (imeta-style), empty content field
- Legacy backward compat: parse old JSON-in-content format and bare CoreThemeColors in localStorage/encrypted settings
- Bundle 10 curated fonts via fontsource (Inter, DM Sans, Outfit, Montserrat, Lora, Merriweather, Playfair Display, JetBrains Mono, Comfortaa, Comic Neue) with lazy dynamic imports
- Create font loader (src/lib/fontLoader.ts) for @font-face injection from remote URLs and CSS override application for title/body roles
- Rewrite FontPicker as dual title/body picker using bundled fonts instead of Google Fonts CDN
- Remove Google Fonts integration (useGoogleFont.ts, GoogleFontLoader.tsx)
- Resolve bundled font family names to CDN URLs when publishing to Nostr
- Apply profile theme fonts when visiting other users' profiles
- Update all consumers: AppProvider, useTheme, usePublishTheme, useEncryptedSettings, ThemeSelector, ThemeBuilderPage, ProfilePage, EditProfileForm, InitialSyncGate, NostrSync
Remove the secondary/accent color from user-level configuration. The
Tailwind 'accent' token now mirrors 'primary' directly. CoreThemeColors
is now { background, text, primary } — 3 fields instead of 4.
- deriveTokensFromCore() takes 3 params; accent = primary
- Theme builder shows pickers in order: Primary, Text, Background
- Backward-compatible parsing for old 4-color and 19-token formats
- Color swatches reordered to primary, text, background everywhere
Replace the 19-token ThemeTokens interface with a 4-field CoreThemeColors
for config storage, Nostr events, and encrypted settings. All other
Tailwind CSS tokens (card, muted, border, etc.) are now derived
automatically via deriveTokensFromCore().
- Add CoreThemeColors interface: { background, text, primary, secondary }
- Keep ThemeTokens as internal type for CSS variable injection
- Update builtinThemes and themePresets to store CoreThemeColors
- Add backward-compatible parsing for legacy 19-token format in Nostr
events, encrypted settings, and localStorage
- Simplify ThemeBuilderPage: remove Surface/UI color sections, just 4
color pickers with auto-derivation always on
- Update all consumers: ThemeSelector, ScopedTheme, ThemeUpdateCard,
MobileDrawer, LeftSidebar, ProfilePage, InitialSyncGate, etc.
- Desktop: Remove redundant Home/Search nav items (logo serves as Home, inline
search covers both quick lookup and full search page navigation)
- Add visual section grouping with labeled separators (Explore, You, Settings)
- Replace ugly dashed 'Add' button with subtle 'More...' inline link
- Add edit mode with drag-and-drop reordering (@dnd-kit) and remove buttons
for content-type items in the Explore section
- Hover-to-remove on desktop content-type items outside edit mode
- Mobile: Replace avatar menu trigger with standard hamburger icon
- Mobile: Add search icon to top bar right side
- Mobile: Redesign bottom nav as 4-tab layout (Home, Explore, Notifications, You)
- Mobile: Explore tab opens bottom sheet with content type links (vaul Drawer)
- Mobile drawer: Replace profile header with logo, restructure to match desktop
sections, add 'Manage...' link for settings, remove redundant Profile link
- Add sidebarOrder to AppConfig for persisting custom item order
- Extend useFeedSettings hook with orderedRoutes, addToSidebar, removeFromSidebar
Drop the 'Builtin' and 'Custom' section headers from the ThemeSelector
grid and the LeftSidebar dropdown so the distinction is invisible to
users. Add emoji metadata to theme presets (ThemePreset interface) and
display them in the sidebar dropdown and mobile drawer cycle button
instead of the generic Palette icon.
Restructure the theme architecture so 'light', 'dark', and 'system' are
builtin themes with build-time colors (customizable by self-hosters),
while 'black', 'pink', and future themes become presets that apply to a
single 'custom' theme slot.
Key changes:
- Theme type is now 'light' | 'dark' | 'system' | 'custom'
- builtinThemes (light/dark) and themePresets (black/pink/etc.) are
separate maps in themes.ts
- AppConfig gains optional customTheme (ThemeTokens) field
- useTheme exposes applyCustomTheme(tokens) for applying presets
- Custom theme tokens sync across devices via encrypted settings
- Backward compat: legacy 'black'/'pink' values in localStorage and
encrypted settings auto-migrate to custom + preset tokens
- All UI surfaces updated: ThemeSelector, LeftSidebar dropdown,
MobileDrawer cycle, and onboarding ThemeStep
useEffect fires after paint, causing a one-frame flash of old colors when switching themes. useLayoutEffect fires synchronously before paint, eliminating the flicker. Also keep document.documentElement.className in sync with the active theme.
Defer body inline style removal until CSS variables are injected by
useApplyTheme, preventing a one-frame flash of unstyled background.
Replace preloader logo <img> with CSS mask technique matching the React
DittoLogo component, so the logo uses the theme's primary color instead
of filter:invert() which turned purple into green.
The pubkey was incorrectly hardcoded in the previous commit. Restore it
to AppConfig, the Zod schema, defaultConfig objects, all three
useNip85Stats hooks, and the Stats Source section in AdvancedSettings.
The NIP-85 Only Mode toggle (and its nip85OnlyMode config field) remains
removed — NIP-85 is always used.
Remove nip85StatsPubkey and nip85OnlyMode from AppConfig, AppProvider
schema, and all defaultConfig objects. The stats pubkey is now a
hardcoded constant in useNip85Stats. Remove the Stats Source section
from AdvancedSettings (UI toggle and pubkey input). Simplify
useEventStats to use NIP-85 exclusively — delete the direct-query
fallback, computeStats, extractZapAmount, and parseBolt11Amount.
Simplify useComments to remove the NIP-85-driven limit heuristic and
the nip85Stats return value.
Encrypted settings synced via NIP-78 were parsed with a bare JSON.parse()
and cast with 'as EncryptedSettings', allowing invalid data (e.g. theme as
an object instead of a string) to pass through unchecked and crash the UI
when applied to classList.add(). Now both useEncryptedSettings and
useInitialSync validate decrypted JSON with a shared EncryptedSettingsSchema,
gracefully dropping invalid fields instead of propagating them.
Link previews now fetch from a dedicated link preview service (default:
https://fetch.ditto.pub/link/{url}) that returns OEmbed JSON, instead of
fetching raw HTML through a CORS proxy and parsing OG tags client-side.
- Add linkPreviewUrl config with RFC 6570 URI template support
- Rewrite useLinkPreview to consume OEmbed responses
- Update LinkPreview to render OEmbed fields (thumbnail_url, provider_name, etc.)
- Use ExternalFavicon component for link preview favicons (consistent with rest of app)
- Add linkPreviewUrl setting to Advanced Settings UI
- Generalize faviconUrl.ts into templateUrl() with {url} variable support
The old DomainFavicon component had a multi-step fallback chain (HTML scraping,
direct URL guessing, then the configured provider) which meant the configurable
setting was almost never reached. The new ExternalFavicon component uses the
configured favicon URL template directly via RFC 6570 URI templates.
- Rename config field faviconProvider -> faviconUrl
- Add faviconUrl() utility with uri-templates for RFC 6570 support
- Remove local Favicon component from ProfileRightSidebar
- Update all consumers to use ExternalFavicon
Support kind 37381 addressable events for MTG deck sharing per NIP-MG spec.
Includes MagicDeckContent component with card list rendering, format/archetype
badges, commander/companion display, sideboard, foil indicators, and
expand/collapse for large decks. Adds /decks route and sidebar navigation.
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)
- Cache follow list and author profiles in localStorage for instant feed on return visits
- Prefetch profile images via <link rel=preload> for instant avatar rendering
- Replace Radix Avatar with lightweight component that renders <img> immediately
- Lazy-load route components (React.lazy) reducing main bundle from 1.7MB to 920KB
- Dynamic import hls.js (1.3MB) only when visiting stream pages
- Remove unused Buffer polyfill
- Add pre-React loading spinner in index.html outside #root for seamless handoff
- Fix 3-5s feed rerender caused by NostrSync relay/settings sync
- Fix Zod 4 strict mode rejecting feedSettings with extra/missing keys from localStorage
- Fix useLocalStorage skip update when value reference unchanged
- Reduce eoseTimeout from default to 600ms
Respect content-warning tags on events. Users can choose how to handle
them in Settings > Content > Sensitive Content:
- Blur (default): Shows a grey placeholder overlay with the warning
reason in quotes. Media is not loaded until the user clicks reveal.
- Hide: Removes content-warned posts from the feed entirely.
- Show: Ignores content warnings and displays everything normally.
The setting syncs across devices via encrypted NIP-78 settings.
Implement kind 30311 live streaming events: HLS video player (hls.js),
kind 1311 live chat with real-time subscriptions, stream detail page with
collapsible description on mobile, desktop sidebar chat, streams feed page,
and full extra-kinds integration with sidebar nav and settings.
- Add blossomServers (array), defaultZapComment, faviconProvider, corsProxy to AppConfig
- Blossom servers: multi-server support with add/remove UI matching relay list pattern
- Default zap comment changed from 'Zapped with MKStack!' to 'Zapped with Mew!'
- NIP-46 nostrconnect now uses the user's write relay list instead of hardcoded relay
- CORS proxy and favicon provider use {href} URI template pattern
- New 'System' section in Advanced Settings for zap comment, favicon provider, CORS proxy
- Fix ghost button hover text color on light/pink themes across settings panels
- Fix pre-existing TSC errors: appRelays array typing, muteHelpers dead code, ProfilePage FeedItem type, EditProfileForm useFieldArray, FeedSettingsForm/ContentSettings feedKey narrowing, LeftSidebar icon type
This reverts the codebase back to the state at commit 5fd4ff4.
Reverted 1 commit(s):
- a3d2492: Add three-mode stats calculation: NIP-85 Only, Manual Only, and Both
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Replace the binary nip85OnlyMode boolean with a statsMode enum that supports three options:
- "nip85-only": Show only pre-computed NIP-85 stats (fastest, may be empty)
- "manual-only": Always calculate stats from relay queries (slower, guaranteed)
- "both": Use NIP-85 when available with manual fallback (recommended default)
Updated components:
- AppContext: Changed nip85OnlyMode to statsMode with StatsMode type
- AppProvider: Updated Zod schema to validate three-mode enum
- useTrending.ts: Implemented logic for all three modes in useEventStats and useBatchEventStats
- AdvancedSettings: Replaced switch with radio group for better UX
Default remains "nip85-only" for optimal performance.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add nip85OnlyMode setting to disable manual stat calculation fallback
- When enabled, stats only show if NIP-85 pubkey provides them (no fallback queries)
- Allow clearing NIP-85 pubkey to disable it entirely (empty string now valid)
- Update Zod schema to accept empty string or 64-char hex pubkey
- Match ContentSettings presentation style (no boxes, simple switches)
- Show toast notifications when enabling/disabling NIP-85 only mode
- Disable NIP-85 only mode toggle when no pubkey is configured
- Update useEventStats and useBatchEventStats to respect nip85OnlyMode
- Add nip85StatsPubkey to AppConfig (default: 5f68e85ee174102ca8978eef302129f081f03456c884185d5ec1c1224ab633ea)
- Create useNip85Stats hook for querying NIP-85 events (kinds 30382, 30383, 30384)
- Update useEventInteractions to fetch NIP-85 event stats and reduce query limits when available
- Update useComments to fetch NIP-85 stats for events and addressable events
- Add NIP-85 Stats Source section to Advanced Settings with pubkey input field
- Falls back to manual stats calculation when NIP-85 events are not available
- Strip out View Transitions API and RAF complexity
- Just apply theme class change synchronously
- Debounce still prevents signer spam
- Let browser handle the paint naturally
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Use native View Transitions API when available for smooth theme switch
- Fall back to manual transitions with double requestAnimationFrame
- Double RAF ensures theme change happens after browser paint cycle
- Prevents glitching even when React re-renders from signer prompt
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Apply 300ms transition to background and text colors during theme switch
- Prevents jarring flash when signer prompt appears
- Transition removed after completion to avoid affecting other animations
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add `useAppRelays` boolean option to AppConfig
- Define APP_RELAYS constant with default app relays:
- wss://relay.ditto.pub
- wss://relay.primal.net
- wss://relay.damus.io
- Update NostrProvider to use getEffectiveRelays() which merges
app relays with user relays when useAppRelays is true
- Redesign RelayListManager with two sections:
- App Relays: Read-only list with enable/disable toggle
- Your Relays: User's personal NIP-65 relay list (editable)
- User relays now stored separately from app relays in config
- Default config starts with empty user relays and useAppRelays=true
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Split Treasures into two sub-kinds: Geocaches (kind 37516) and Found Logs (kind 7516)
- Add `showTreasureGeocaches`, `showTreasureFoundLogs`, `feedIncludeTreasureGeocaches`, `feedIncludeTreasureFoundLogs` to FeedSettings
- Extend ExtraKindDef with optional `subKinds` array for nested settings
- Update FeedSettingsForm to render nested sub-kind toggles under parent entries
- Update useStreamKind to accept single kind or array of kinds
- Create TreasuresPage that filters kinds based on sub-kind settings
- Add getPageKinds() utility for resolving sub-kind-aware page kinds
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add kind 39089 (starter packs) to EXTRA_KINDS with sidebar/feed toggles
- Create FollowPackContent component for NoteCard rendering with title,
description, member count badge, cover image, and avatar stack preview
- Add kind dispatch in NoteCard for 39089 and 30000
- Add /packs route using KindFeedPage
- Add PartyPopper icon in sidebar and feed settings
- Default showPacks to true so it appears in sidebar
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add FeedSettings to AppConfig for persisting user preferences
- Create Settings > Feed tab with two sections:
- Sidebar Links: toggle visibility of Vines, Polls, Treasures, Colors in nav
- Feed Content: include these kinds in Follows/Global feeds
- Update LeftSidebar and MobileBottomNav to conditionally render nav items
- Update useFeed hook to include extra kinds (1068 Polls, 3367 Colors,
34236 Vines, 37516 Treasures) based on feed settings
- Add routes for /polls, /treasures, /colors (placeholder pages)
- Vines enabled in sidebar by default; all others off by default
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Show current theme name and icon in dropdown trigger button
- Fix light theme not working (update class removal from 'mew' to 'dark')
- Remove opacity from logo in pink theme (keep full visibility)
- Logo now only uses opacity in light theme (70%)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>