Compare commits

...

12 Commits

Author SHA1 Message Date
Gigi
432715efb6 chore: bump version to 0.6.19 2025-10-15 20:01:07 +02:00
Gigi
8b2b954dde fix: prevent useBookmarksData from overwriting external URL highlights
The issue was that useBookmarksData was fetching general highlights
whenever there was no naddr, which included external URL routes (/r/*).
This caused the URL-specific highlights loaded by useExternalUrlLoader
to be overwritten after a couple seconds.

Now we skip fetching general highlights when viewing external URLs,
letting useExternalUrlLoader manage those highlights instead.
2025-10-15 19:59:54 +02:00
Gigi
c2d2bd8106 fix: prevent highlights from disappearing on external URLs
- Improve error handling in fetchHighlightsForUrl to prevent silent failures
- Remove redundant setHighlights call that was overwriting streamed highlights
- Add logging to help diagnose highlight fetching issues
- Isolate rebroadcast errors so they don't break highlight display
2025-10-15 19:56:07 +02:00
Gigi
a5c3085c59 docs: update CHANGELOG.md for v0.6.18 2025-10-15 19:49:13 +02:00
Gigi
c0332f08d6 chore: bump version to 0.6.18 2025-10-15 19:48:00 +02:00
Gigi
38a1d6caec fix: always show PWA install section with disabled button states 2025-10-15 19:43:44 +02:00
Gigi
39dd607e7b style: make zap preset buttons expand to match slider width on desktop 2025-10-15 19:43:11 +02:00
Gigi
9dc0db3e06 fix: always show App & Airplane Mode section regardless of PWA status 2025-10-15 19:42:27 +02:00
Gigi
b1eb58a385 fix: display zap split share and percentage on same line 2025-10-15 19:41:26 +02:00
Gigi
f3c6404f76 refactor: simplify zap split labels and update terminology 2025-10-15 19:39:04 +02:00
Gigi
1a42a6422d fix: disable PWA install button when installation is not possible on device 2025-10-15 19:37:57 +02:00
Gigi
2e2de4ccda docs: update CHANGELOG.md for v0.6.17 2025-10-15 19:36:50 +02:00
10 changed files with 133 additions and 26 deletions

View File

@@ -7,6 +7,100 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.6.18] - 2025-10-15
### Changed
- Zap split labels simplified and terminology updated
- Removed redundant "Weight: xy" label to save space
- Changed "Author(s) Share" to "Author's Share" (possessive singular)
- Changed "Support Boris" to "Boris' Share" for consistency
- Weight value now shown directly in label (e.g., "Your Share: 50")
- Share and percentage now displayed on same line for cleaner layout
- Zap preset buttons on desktop now expand to match slider width
- Added `flex: 1` to buttons for equal width distribution
- Buttons still wrap properly on smaller screens
- PWA install section now always visible in settings
- Section shows regardless of installation or device capability status
- Button adapts with proper disabled states and visual feedback
- "Installed" state shows checkmark icon and disabled button
- Non-installable state shows disabled button
### Fixed
- PWA install button now properly disabled when installation is not possible on device
- Button only enabled when browser fires `beforeinstallprompt` event
- Removed hardcoded testing state that always showed button as installable
- App & Airplane Mode section now always visible regardless of PWA status
- Image cache and local relay settings always accessible
- Previously entire section was hidden if PWA not installable/installed
- Only PWA-specific install button is conditionally affected
## [0.6.17] - 2025-10-15
### Added
- PWA settings illustration (`pwa.svg`) displayed on right side of section
- Responsive design: hidden on mobile, 30% width on desktop
- Visual enhancement for App & Airplane Mode section
- Zaps illustration (`zaps.svg`) displayed on right side of Zap Splits section
- Matching responsive layout and styling as PWA illustration
- Visual 50% indicators on zap split sliders
- Linear gradient background using highlight colors (yellow/orange) at 50% opacity
- Datalist tick marks at 50% for "Your Share" and "Author(s) Share" sliders
- Tick mark at 5 for "Support Boris" slider
- Lightning bolt icons as slider thumbs for zap splits
- Replaces default circular slider handles
- White lightning bolt SVG embedded in slider thumb background
- 24px square thumb with 4px border radius
- Offline-first description paragraph at beginning of App & Airplane Mode section
- Explains Boris's offline capabilities upfront
- Settings page width constraint (900px max-width)
- Matches article view max-width for consistent reading experience
- Centered layout with proper margins
### Changed
- Settings section reorganization
- "PWA & Flight Mode" merged into single "App & Airplane Mode" section
- "Layout & Navigation" and "Startup & Behavior" merged into "Layout & Behavior"
- Section order: Theme → Reading & Display → Zap Splits → Layout & Behavior → App & Airplane Mode → Relays
- "Startup & Behavior" moved after "Zap Splits"
- "Layout & Navigation" moved below "Zap Splits"
- PWA settings section restructure
- Checkboxes moved to top (image cache, local relays)
- Descriptive paragraphs in middle
- Install button at bottom
- Note about local relays moved before install paragraph
- Zap split sliders styling
- Left side (0-50%): highlight color (yellow) at 50% opacity
- Right side (50-100%): friend-highlight color (orange) at 50% opacity
- Creates visual distinction tied to app's highlight color scheme
- Zap split description text styling
- Now matches offline-first paragraph style with secondary color and smaller font size
- Clear cache button styling
- Replaced `IconButton` with plain `FontAwesomeIcon` for subtler appearance
- No border or background, just icon with opacity
- Font Size buttons alignment
- Now properly align to the right using `setting-control` wrapper
- Matches alignment of highlight color picker buttons
- Default Highlight Visibility position
- Moved back to original position after "Paragraph Alignment"
- Grouped with other reading display controls
- Spacing adjustments in App & Airplane Mode section
- Reduced gap between elements from 1rem → 0.5rem → 0.25rem for tighter layout
### Fixed
- PWA settings paragraph wrapping
- Moved offline-first paragraph inside flex container to prevent extending above image
- Font Size buttons alignment issues
- Properly implemented `setting-control` wrapper for right alignment
- Previously attempted alignment didn't work correctly
- Slider thumb icon centering
- Lightning bolt icons properly centered vertically on slider
- Added `position: relative`, `top: 0`, `margin-top: 0` for accurate positioning
## [0.6.16] - 2025-10-15
### Changed
@@ -1501,7 +1595,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Optimize relay usage following applesauce-relay best practices
- Use applesauce-react event models for better profile handling
[Unreleased]: https://github.com/dergigi/boris/compare/v0.6.16...HEAD
[Unreleased]: https://github.com/dergigi/boris/compare/v0.6.18...HEAD
[0.6.18]: https://github.com/dergigi/boris/compare/v0.6.17...v0.6.18
[0.6.17]: https://github.com/dergigi/boris/compare/v0.6.16...v0.6.17
[0.6.16]: https://github.com/dergigi/boris/compare/v0.6.15...v0.6.16
[0.6.15]: https://github.com/dergigi/boris/compare/v0.6.14...v0.6.15
[0.6.14]: https://github.com/dergigi/boris/compare/v0.6.13...v0.6.14

View File

@@ -1,6 +1,6 @@
{
"name": "boris",
"version": "0.6.17",
"version": "0.6.19",
"description": "A minimal nostr client for bookmark management",
"homepage": "https://read.withboris.com/",
"type": "module",

View File

@@ -167,6 +167,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
activeAccount,
accountManager,
naddr,
externalUrl,
currentArticleCoordinate,
currentArticleEventId,
settings

View File

@@ -56,10 +56,6 @@ const PWASettings: React.FC<PWASettingsProps> = ({ settings, onUpdate, onClose }
return () => clearInterval(interval)
}, [])
if (!isInstallable && !isInstalled) {
return null
}
return (
<div className="settings-section">
<h3 className="section-title">App & Airplane Mode</h3>
@@ -190,7 +186,7 @@ const PWASettings: React.FC<PWASettingsProps> = ({ settings, onUpdate, onClose }
onClick={handleInstall}
className="zap-preset-btn"
style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}
disabled={isInstalled}
disabled={isInstalled || !isInstallable}
>
<FontAwesomeIcon icon={isInstalled ? faCheckCircle : faDownload} />
{isInstalled ? 'Installed' : 'Install App'}

View File

@@ -81,10 +81,9 @@ const ZapSettings: React.FC<ZapSettingsProps> = ({ settings, onUpdate }) => {
</div>
<div className="setting-group">
<label className="setting-label">Your Share</label>
<div className="zap-split-container">
<div className="zap-split-labels">
<span className="zap-split-label">Weight: {highlighterWeight}</span>
<span className="zap-split-label">Your Share: {highlighterWeight}</span>
<span className="zap-split-label">({highlighterPercentage.toFixed(1)}%)</span>
</div>
<input
@@ -103,10 +102,9 @@ const ZapSettings: React.FC<ZapSettingsProps> = ({ settings, onUpdate }) => {
</div>
<div className="setting-group">
<label className="setting-label">Author(s) Share</label>
<div className="zap-split-container">
<div className="zap-split-labels">
<span className="zap-split-label">Weight: {authorWeight}</span>
<span className="zap-split-label">Author's Share: {authorWeight}</span>
<span className="zap-split-label">({authorPercentage.toFixed(1)}%)</span>
</div>
<input
@@ -125,10 +123,9 @@ const ZapSettings: React.FC<ZapSettingsProps> = ({ settings, onUpdate }) => {
</div>
<div className="setting-group">
<label className="setting-label">Support Boris</label>
<div className="zap-split-container">
<div className="zap-split-labels">
<span className="zap-split-label">Weight: {borisWeight.toFixed(1)}</span>
<span className="zap-split-label">Boris' Share: {borisWeight.toFixed(1)}</span>
<span className="zap-split-label">({borisPercentage.toFixed(1)}%)</span>
</div>
<input

View File

@@ -13,6 +13,7 @@ interface UseBookmarksDataParams {
activeAccount: IAccount | undefined
accountManager: AccountManager
naddr?: string
externalUrl?: string
currentArticleCoordinate?: string
currentArticleEventId?: string
settings?: UserSettings
@@ -23,6 +24,7 @@ export const useBookmarksData = ({
activeAccount,
accountManager,
naddr,
externalUrl,
currentArticleCoordinate,
currentArticleEventId,
settings
@@ -115,11 +117,13 @@ export const useBookmarksData = ({
// Fetch highlights/contacts independently to avoid disturbing bookmarks
useEffect(() => {
if (!relayPool || !activeAccount) return
if (!naddr) {
// Only fetch general highlights when not viewing an article (naddr) or external URL
// External URLs have their highlights fetched by useExternalUrlLoader
if (!naddr && !externalUrl) {
handleFetchHighlights()
}
handleFetchContacts()
}, [relayPool, activeAccount, naddr, handleFetchHighlights, handleFetchContacts])
}, [relayPool, activeAccount, naddr, externalUrl, handleFetchHighlights, handleFetchContacts])
return {
bookmarks,

View File

@@ -71,7 +71,7 @@ export function useExternalUrlLoader({
// Check if fetchHighlightsForUrl exists, otherwise skip
if (typeof fetchHighlightsForUrl === 'function') {
const seen = new Set<string>()
const highlightsList = await fetchHighlightsForUrl(
await fetchHighlightsForUrl(
relayPool,
url,
(highlight) => {
@@ -84,9 +84,9 @@ export function useExternalUrlLoader({
})
}
)
// Ensure final list is sorted and contains all items
setHighlights(highlightsList.sort((a, b) => b.created_at - a.created_at))
console.log(`📌 Found ${highlightsList.length} highlights for URL`)
// Highlights are already set via the streaming callback
// No need to set them again as that could cause a flash/disappearance
console.log(`📌 Finished fetching highlights for URL`)
} else {
console.log('📌 Highlight fetching for URLs not yet implemented')
}

View File

@@ -7,8 +7,7 @@ interface BeforeInstallPromptEvent extends Event {
export function usePWAInstall() {
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null)
// TODO: Remove this - temporarily always showing for testing/styling
const [isInstallable, setIsInstallable] = useState(true)
const [isInstallable, setIsInstallable] = useState(false)
const [isInstalled, setIsInstalled] = useState(false)
useEffect(() => {

View File

@@ -14,10 +14,11 @@ export const fetchHighlightsForUrl = async (
onHighlight?: (highlight: Highlight) => void,
settings?: UserSettings
): Promise<Highlight[]> => {
const seenIds = new Set<string>()
const orderedRelaysUrl = prioritizeLocalRelays(RELAYS)
const { local: localRelaysUrl, remote: remoteRelaysUrl } = partitionRelays(orderedRelaysUrl)
try {
const seenIds = new Set<string>()
const orderedRelaysUrl = prioritizeLocalRelays(RELAYS)
const { local: localRelaysUrl, remote: remoteRelaysUrl } = partitionRelays(orderedRelaysUrl)
const local$ = localRelaysUrl.length > 0
? relayPool
.req(localRelaysUrl, { kinds: [9802], '#r': [url] })
@@ -45,11 +46,23 @@ export const fetchHighlightsForUrl = async (
)
: new Observable<NostrEvent>((sub) => sub.complete())
const rawEvents: NostrEvent[] = await lastValueFrom(merge(local$, remote$).pipe(toArray()))
await rebroadcastEvents(rawEvents, relayPool, settings)
console.log(`📌 Fetched ${rawEvents.length} highlight events for URL:`, url)
// Rebroadcast events - but don't let errors here break the highlight display
try {
await rebroadcastEvents(rawEvents, relayPool, settings)
} catch (err) {
console.warn('Failed to rebroadcast highlight events:', err)
}
const uniqueEvents = dedupeHighlights(rawEvents)
const highlights: Highlight[] = uniqueEvents.map(eventToHighlight)
return sortHighlights(highlights)
} catch {
} catch (err) {
console.error('Error fetching highlights for URL:', err)
// Return highlights that were already streamed via callback
// Don't return empty array as that would clear already-displayed highlights
return []
}
}

View File

@@ -19,6 +19,7 @@
/* Zap splits preset buttons */
.zap-preset-buttons { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.zap-preset-btn {
flex: 1;
padding: 0.625rem 1.25rem;
background: var(--color-bg-elevated);
border: 1px solid var(--color-border-subtle);