The wrapRef div had no dimensions, so max-w-full/max-h-full on the img
resolved against the image's own natural size rather than the available
space. Give wrapRef full width/height so percentage constraints work
correctly against the padded slot area.
Browsers (especially Safari/Firefox) require pushManager.subscribe() to be
synchronously reachable from a user gesture. The previous code awaited a
getVapidKey() network call inside enable(), which broke the activation chain
and threw 'DOMException: The operation is insecure'.
Fix: fetch and cache the VAPID key during SW registration on mount so it is
ready in vapidKeyRef before the user ever clicks Enable, making
pushManager.subscribe() the first async call after the user gesture.
The nullish coalescing operator (??) treats null as nullish, so
`null ?? <RightSidebar />` was falling through to the default.
Use an explicit null check so pages can hide the sidebar by
passing rightSidebar: null.
- Remove redundant Notification.requestPermission() from enable() since
the caller (NotificationSettings) already handles it from the user gesture.
On iOS this second call could behave unexpectedly.
- Add console.debug logging throughout the enable flow and RPC send for
diagnosing push registration issues in production.
- NotificationSettings: on web, drive the push toggle from the hook's
enabled state (actual browser subscription) instead of the NIP-78
notificationsEnabled setting which can be stale across devices.
NIP-78 is still used for the Capacitor/Android path.
- useNativeNotifications: remove the web push auto-disable effect that
raced against encrypted settings loading, causing disablePush() on
every page load before settings resolved.
- usePushNotifications: remove REGISTERED_KEY localStorage check from
the mount restore logic; derive enabled purely from
pushManager.getSubscription() like the working Ghastly reference.
Remove dead REGISTERED_KEY code entirely.
Implement NIP-53 staleness heuristic (24h threshold) to mark abandoned
status=live streams as ended. Fetch all streams globally and filter
follows client-side (including p-tag participants for streaming services).
Add Live/Planned/Past pill tabs to the streams strip on /videos.
Also fix pre-existing bug where the volume icon showed unmuted while the
video was actually muted (autoplay requires muted), by syncing the
initial muted state from the media element in usePlayerControls.
Closes#88
Add a Vite plugin that writes a sitemap.xml containing all static
routes to the build output. Enabled by setting the PUBLIC_URL
environment variable (e.g. PUBLIC_URL=https://ditto.pub) in .env
or the shell. When PUBLIC_URL is not set, no sitemap is generated.
Also fix a pre-existing LSP type error caused by importing Plugin
from 'vite' while defineConfig came from 'vitest/config' (which
bundles a different vite version). Now both come from 'vite'.
Add explicit 'import process' for node:process.
Conditionally render the Media section and footer only when mediaEvents
is explicitly passed, so the fields-only mobile preview shows just the
profile fields without an empty Media box or the Shakespeare link.
Only pass fields to the mobile ProfileRightSidebar preview, omitting
mediaEvents and mediaLoading so the media collage section doesn't
render in the inline mobile preview.
Swap Wallet and Link pill button order. Replace the barebones mobile
preview with the real ProfileRightSidebar component (with media collage,
audio players, etc.) by adding a className prop to override the default
hidden xl:flex classes. Rename 'Sidebar Preview' to 'Profile Fields
Preview' since on mobile it's not in a sidebar.
Use the same useProfileMedia query as the profile page to fetch the
current user's media events and pass them to ProfileRightSidebar in
the settings page. The sidebar preview now shows the media collage
section just like it appears on the actual profile.
Place the pill buttons directly after the 'Profile Fields' heading so
users see the available content types immediately, before the existing
Website and Lightning fields.
Replace the hidden dropdown menu with a wrapping row of small pill-shaped
buttons, each showing the preset icon and label (Music, Photo, Video,
Email, Link, Wallet, Custom). Users can now see all available field types
at a glance and add one with a single tap. Descriptions are preserved as
hover tooltips on each pill.
Track which field index is currently uploading and replace the upload
button with a spinning Loader2 icon on that specific field row during
upload, giving immediate local feedback instead of relying on the
distant save button spinner.
Add isImageUrl and isVideoUrl helpers alongside the existing isAudioUrl.
Profile field values that point to image files now render as inline images
(clickable to open full size), and video files render using the VideoPlayer
component. Both the desktop sidebar (ProfileFieldRow) and mobile inline
view (ProfileFieldInline) support the new rendering.
Show an inline amber warning below media field inputs when the pasted URL
doesn't have the right file extension for the field type. For example,
pasting a YouTube link in a Music field warns that it won't work as an
audio player and suggests uploading a file instead. Handles three cases:
wrong type (e.g. image URL in audio field), no recognizable extension
(e.g. youtube.com), and Blossom URLs (always allowed, type can't be
determined from URL).
Replace misleading 'URL' placeholder on media fields with action-oriented
text like 'Upload audio or paste direct file link' per preset type. Update
upload button tooltips to say 'Choose file to upload'. Update Music preset
description to say 'Upload' consistently. Each preset now carries its own
valuePlaceholder through the form schema to the row component.
Use useLayoutOptions({ rightSidebar }) to render the live ProfileRightSidebar
preview in the actual app sidebar, replacing the previous approach that put
a fake two-column layout inside the center content area. The form returns
to its original max-w-xl centered layout. Mobile collapsible preview is
retained for screens below xl where the sidebar is hidden.
Replace the generic 3-item Add Field dropdown with 7 discoverable presets
(Music, Photo, Video, Email, Link, Wallet, Custom) that pre-configure
field type, default emoji labels, and scoped file-accept filters.
- Add tooltips on upload buttons showing accepted file formats per type
- Add HelpTip to Profile Fields section header with new FAQ entries
- Add live sidebar preview panel (right column on xl+, collapsible on mobile)
- Scope file picker accept attribute dynamically per field preset
- No changes to the underlying data model — presets are purely client-side UX