Remove nostr from useEffect dependencies (replacing with a ref) so the
subscription is not torn down and re-opened on every render cycle.
Add a seenIds Set to guard against the same event ID firing multiple
notifications even if the relay delivers it more than once.
Delete the separate config/schema.ts that duplicated every Zod schema.
DittoConfigSchema is now simply AppConfigSchema.partial().strict(),
defined in src/lib/schemas.ts alongside everything else. vite.config.ts
imports it directly via a relative path.
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.
When switching to builtin themes (Light/Dark/System), setTheme doesn't
clear customTheme, so the FontPicker and BackgroundPicker were still
showing the previous preset's font/background. Now they check that the
active theme is 'custom' before displaying the current selection.
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.
Also fix all preset application sites (ThemeSelector, SidebarThemeDropdown,
LeftSidebar, InitialSyncGate) to pass the font along with colors when
selecting a preset.
- Add notificationsEnabled to EncryptedSettings schema and interface
- NotificationSettings now persists the toggle to encrypted settings (synced across devices) instead of ephemeral local state
- useNativeNotifications: Capacitor path skips DittoNotification.configure when notificationsEnabled is false
- useNativeNotifications: web/PWA path opens a live Nostr req subscription and fires browser Notification API events when permission is granted and the setting is enabled
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.
ThemeSelector was constructing a new ThemeConfig with only colors when
sharing, discarding the fonts and background from the customTheme.
Now passes the full customTheme (which already is a ThemeConfig).
- 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
Loads any Google Font on demand, applies it site-wide via injected
stylesheet, and persists the selection in localStorage. Available
in Settings > Theme alongside the existing color theme selector.
When disableEmbeds is true, image-embed tokens now return null instead of
rendering the raw URL as a link. This fixes the triple-dot (more menu)
preview showing bare blossom/image URLs instead of clean post text.
- Images now render inline via NoteContent at the position of their URL
rather than collected into a gallery block below the text
- Clicking an inline image opens a full lightbox with navigation across
all images in the post; lightbox index is tracked by position so
duplicate URLs open at the correct index
- Removed separate ImageGallery/extractImages from NoteCard,
PostDetailPage, and ComposeBox to prevent double-rendering
- Added disableEmbeds to NoteMoreMenu clamped preview to avoid block
embeds inside a line-clamp-3 container
- Fixed hasPreviewableContent in ComposeBox to detect image-only posts
so the Preview tab appears correctly
- Removed mp3/ogg/wav/pdf from MEDIA_URL_REGEX suppression list; those
URLs now render as clickable links instead of being silently dropped
- Added avif to IMAGE_URL_REGEX; added cursor-pointer to InlineImage