Browse Source

bug-fixes

reinstate Discussions button
imwald
Silberengel 3 weeks ago
parent
commit
e86aeb9a22
  1. 8
      src/components/NoteStats/LikeButton.tsx
  2. 2
      src/components/NoteStats/ZapButton.tsx
  3. 31
      src/components/NoteStats/index.tsx
  4. 56
      src/components/Sidebar/DiscussionsButton.tsx
  5. 2
      src/components/Sidebar/index.tsx
  6. 4
      src/hooks/useConsoleLogBuffer.ts
  7. 4
      src/lib/console-log-buffer.ts
  8. 56
      src/lib/error-suppression.ts
  9. 2
      src/pages/primary/NoteListPage/index.tsx

8
src/components/NoteStats/LikeButton.tsx

@ -272,7 +272,7 @@ export function LikeButtonWithStats({
// Discussions (kind 11) and kind 1111 under a discussion: only +/- vote reactions // Discussions (kind 11) and kind 1111 under a discussion: only +/- vote reactions
if (showDiscussionVotes) { if (showDiscussionVotes) {
return ( return (
<div className="flex items-center gap-1"> <div className="flex max-w-full items-center gap-0.5 sm:gap-1">
{DISCUSSION_VOTE_EMOJIS.map((emoji, index) => { {DISCUSSION_VOTE_EMOJIS.map((emoji, index) => {
const isSelected = const isSelected =
index === 0 ? isDiscussionUpvoteEmoji(myLastEmoji) : isDiscussionDownvoteEmoji(myLastEmoji) index === 0 ? isDiscussionUpvoteEmoji(myLastEmoji) : isDiscussionDownvoteEmoji(myLastEmoji)
@ -282,13 +282,13 @@ export function LikeButtonWithStats({
<div <div
key={emoji} key={emoji}
className={cn( className={cn(
'flex h-full items-center rounded', 'flex h-full shrink-0 items-center rounded',
isSelected ? 'bg-muted text-primary' : 'text-muted-foreground' isSelected ? 'bg-muted text-primary' : 'text-muted-foreground'
)} )}
> >
<button <button
type="button" type="button"
className="flex h-full items-center px-2 enabled:hover:text-primary" className="flex h-full shrink-0 items-center px-1.5 sm:px-2 enabled:hover:text-primary"
title={emoji === '+' ? t('Upvote') : t('Downvote')} title={emoji === '+' ? t('Upvote') : t('Downvote')}
disabled={liking} disabled={liking}
onClick={() => { onClick={() => {
@ -305,7 +305,7 @@ export function LikeButtonWithStats({
</button> </button>
{!hideCount && (noteStats?.updatedAt != null || count > 0) ? ( {!hideCount && (noteStats?.updatedAt != null || count > 0) ? (
<DiscussionVoteCountHover noteStats={noteStats} vote={index === 0 ? 'up' : 'down'}> <DiscussionVoteCountHover noteStats={noteStats} vote={index === 0 ? 'up' : 'down'}>
<div className="pr-2 text-sm tabular-nums"> <div className="pr-1 text-sm tabular-nums sm:pr-2">
{count >= 100 ? '99+' : count} {count >= 100 ? '99+' : count}
</div> </div>
</DiscussionVoteCountHover> </DiscussionVoteCountHover>

2
src/components/NoteStats/ZapButton.tsx

@ -221,7 +221,7 @@ export function ZapButtonWithStats({ event, hideCount = false, noteStats }: ZapB
return ( return (
<> <>
<div className="flex h-full min-w-0 select-none items-center"> <div className="flex h-full min-w-0 shrink-0 select-none items-center">
<button <button
type="button" type="button"
className={cn( className={cn(

31
src/components/NoteStats/index.tsx

@ -19,10 +19,21 @@ import { ReplyButtonWithStats } from './ReplyButton'
import { RepostButtonWithStats } from './RepostButton' import { RepostButtonWithStats } from './RepostButton'
import { ZapButtonWithStats } from './ZapButton' import { ZapButtonWithStats } from './ZapButton'
/** One equal-width column in the note action bar; keeps icons centered as button count varies. */ /** One column in the note action bar; default equal flex, or sized via `className` (discussions need wider vote slot). */
function NoteStatsBarItem({ children }: { children: ReactNode }) { function NoteStatsBarItem({
children,
className
}: {
children: ReactNode
className?: string
}) {
return ( return (
<div className="flex min-w-0 flex-1 basis-0 items-center justify-center [&>*]:min-w-0"> <div
className={cn(
'flex min-w-0 flex-1 basis-0 items-center justify-center overflow-hidden [&>*]:min-w-0 [&>*]:max-w-full',
className
)}
>
{children} {children}
</div> </div>
) )
@ -135,9 +146,13 @@ export default function NoteStats({
const bookmarksContext = useBookmarksOptional() const bookmarksContext = useBookmarksOptional()
const showThreadWatchButtons = Boolean(watch && pubkey) const showThreadWatchButtons = Boolean(watch && pubkey)
const showBookmarkButton = Boolean(bookmarksContext && pubkey) const showBookmarkButton = Boolean(bookmarksContext && pubkey)
/** Kind 11 / 1111 under a discussion: up+down votes need more width than a single like button. */
const isDiscussionBar = isDiscussion || isReplyToDiscussion
const compactBarItem = isDiscussionBar ? 'shrink-0 flex-none basis-auto' : undefined
const voteBarItem = isDiscussionBar ? 'min-w-[6.75rem] flex-[2] basis-28 sm:min-w-[7.25rem]' : undefined
const barItems: ReactNode[] = [ const barItems: ReactNode[] = [
<NoteStatsBarItem key="reply"> <NoteStatsBarItem key="reply" className={compactBarItem}>
<ReplyButtonWithStats event={event} noteStats={noteStats} /> <ReplyButtonWithStats event={event} noteStats={noteStats} />
</NoteStatsBarItem> </NoteStatsBarItem>
] ]
@ -151,7 +166,7 @@ export default function NoteStats({
} }
barItems.push( barItems.push(
<NoteStatsBarItem key="like"> <NoteStatsBarItem key="like" className={voteBarItem}>
<LikeButtonWithStats <LikeButtonWithStats
event={event} event={event}
noteStats={noteStats} noteStats={noteStats}
@ -163,7 +178,7 @@ export default function NoteStats({
if (!isRssArticleRoot) { if (!isRssArticleRoot) {
barItems.push( barItems.push(
<NoteStatsBarItem key="tip"> <NoteStatsBarItem key="tip" className={compactBarItem}>
<ZapButtonWithStats event={event} noteStats={noteStats} /> <ZapButtonWithStats event={event} noteStats={noteStats} />
</NoteStatsBarItem> </NoteStatsBarItem>
) )
@ -171,7 +186,7 @@ export default function NoteStats({
if (!isRssArticleRoot && showThreadWatchButtons) { if (!isRssArticleRoot && showThreadWatchButtons) {
barItems.push( barItems.push(
<NoteStatsBarItem key="thread-watch"> <NoteStatsBarItem key="thread-watch" className={compactBarItem}>
<div className="flex items-center justify-center gap-0.5"> <div className="flex items-center justify-center gap-0.5">
<NotificationThreadWatchButtons event={event} /> <NotificationThreadWatchButtons event={event} />
</div> </div>
@ -181,7 +196,7 @@ export default function NoteStats({
if (!isRssArticleRoot && showBookmarkButton) { if (!isRssArticleRoot && showBookmarkButton) {
barItems.push( barItems.push(
<NoteStatsBarItem key="bookmark"> <NoteStatsBarItem key="bookmark" className={compactBarItem}>
<BookmarkButton event={event} /> <BookmarkButton event={event} />
</NoteStatsBarItem> </NoteStatsBarItem>
) )

56
src/components/Sidebar/DiscussionsButton.tsx

@ -0,0 +1,56 @@
import { Button } from '@/components/ui/button'
import { usePrimaryPage } from '@/contexts/primary-page-context'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
import { cn } from '@/lib/utils'
import { MessageSquare } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import SidebarItem from './SidebarItem'
function useDiscussionsSpellNav() {
const { navigate, current, currentPageProps, display } = usePrimaryPage()
const { primaryViewType, setPrimaryNoteView } = usePrimaryNoteView()
const spell = (currentPageProps as { spell?: string } | undefined)?.spell
const active =
display && current === 'spells' && primaryViewType === null && spell === 'discussions'
const go = () => {
if (primaryViewType !== null) {
setPrimaryNoteView(null)
}
navigate('spells', { spell: 'discussions' })
}
return { active, go }
}
export default function DiscussionsButton() {
const { active, go } = useDiscussionsSpellNav()
return (
<SidebarItem title="Discussions" onClick={go} active={active}>
<MessageSquare strokeWidth={3} />
</SidebarItem>
)
}
export function DiscussionsTitlebarButton({ className }: { className?: string }) {
const { t } = useTranslation()
const { active, go } = useDiscussionsSpellNav()
return (
<Button
variant="ghost"
size="titlebar-icon"
title={t('Discussions')}
aria-label={t('Discussions')}
className={cn('shrink-0', active && 'bg-accent/50', className)}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
go()
}}
>
<MessageSquare />
</Button>
)
}

2
src/components/Sidebar/index.tsx

@ -9,6 +9,7 @@ import PostButton from './PostButton'
import RssButton from './RssButton' import RssButton from './RssButton'
import SearchButton from './SearchButton' import SearchButton from './SearchButton'
import FavoritesButton from './FavoritesButton' import FavoritesButton from './FavoritesButton'
import DiscussionsButton from './DiscussionsButton'
import SpellsButton from './SpellsButton' import SpellsButton from './SpellsButton'
import { ConnectedRelaysSidebarStrip } from '@/components/ConnectedRelays/ConnectedRelaysSidebarStrip' import { ConnectedRelaysSidebarStrip } from '@/components/ConnectedRelays/ConnectedRelaysSidebarStrip'
import PaneModeToggle from './PaneModeToggle' import PaneModeToggle from './PaneModeToggle'
@ -43,6 +44,7 @@ export default function PrimaryPageSidebar() {
<NotificationButton /> <NotificationButton />
<SearchButton /> <SearchButton />
<FavoritesButton /> <FavoritesButton />
<DiscussionsButton />
<SpellsButton /> <SpellsButton />
<RssButton /> <RssButton />
<ConnectedRelaysSidebarStrip /> <ConnectedRelaysSidebarStrip />

4
src/hooks/useConsoleLogBuffer.ts

@ -9,11 +9,11 @@ function subscribe(onStoreChange: () => void) {
return subscribeConsoleLogBuffer(onStoreChange) return subscribeConsoleLogBuffer(onStoreChange)
} }
function getSnapshot(): ConsoleLogEntry[] { function getSnapshot(): readonly ConsoleLogEntry[] {
return getConsoleLogBuffer() return getConsoleLogBuffer()
} }
/** Live view of the global console log ring buffer (see Settings → Cache). */ /** Live view of the global console log ring buffer (see Settings → Cache). */
export function useConsoleLogBuffer(): ConsoleLogEntry[] { export function useConsoleLogBuffer(): readonly ConsoleLogEntry[] {
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot) return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
} }

4
src/lib/console-log-buffer.ts

@ -78,6 +78,10 @@ function formatArgs(args: unknown[]): { message: string; formattedParts: Array<{
function captureLog(type: string, ...args: unknown[]) { function captureLog(type: string, ...args: unknown[]) {
const { message, formattedParts } = formatArgs(args) const { message, formattedParts } = formatArgs(args)
// nostr-tools emits relay NOTICE via console.debug; keep buffer useful for real diagnostics.
if (message.includes('NOTICE from')) {
return
}
buffer.push({ type, message, formattedParts, timestamp: Date.now() }) buffer.push({ type, message, formattedParts, timestamp: Date.now() })
if (buffer.length > MAX_ENTRIES) { if (buffer.length > MAX_ENTRIES) {
buffer.splice(0, buffer.length - MAX_ENTRIES) buffer.splice(0, buffer.length - MAX_ENTRIES)

56
src/lib/error-suppression.ts

@ -106,6 +106,17 @@ function isExpectedDevAppNoise(message: string): boolean {
return false return false
} }
/** nostr-tools logs relay NOTICE via console.debug; timeout / too-many-steps are normal under load. */
function isExpectedNostrRelayNotice(message: string): boolean {
return (
message.includes('NOTICE from') ||
message.includes('Too many subscriptions') ||
message.includes('Subscription rejected') ||
message.includes('too many concurrent REQs') ||
message.includes('too many kinds')
)
}
function isExpectedRelayWebSocketNoise(message: string): boolean { function isExpectedRelayWebSocketNoise(message: string): boolean {
if (message.includes('WebSocket connection to') || message.includes('Close received after close')) { if (message.includes('WebSocket connection to') || message.includes('Close received after close')) {
return true return true
@ -236,17 +247,16 @@ function suppressExpectedErrors() {
return return
} }
// Suppress nostr-tools "too many concurrent REQs" errors // Suppress nostr-tools relay NOTICE / overload errors
if (message.includes('NOTICE from') && message.includes('ERROR: too many concurrent REQs')) { if (isExpectedNostrRelayNotice(message)) {
return return
} }
if (
// Suppress nostr-tools connection errors message.includes('NOTICE from') &&
if (message.includes('NOTICE from') && ( (message.includes('ERROR:') ||
message.includes('ERROR:') ||
message.includes('connection closed') || message.includes('connection closed') ||
message.includes('connection errored') message.includes('connection errored'))
)) { ) {
return return
} }
@ -398,12 +408,7 @@ function suppressExpectedErrors() {
return return
} }
// Suppress Nostr relay NOTICE messages (too many subscriptions, too many REQs, etc.) if (isExpectedNostrRelayNotice(message)) {
if (message.includes('NOTICE from') ||
message.includes('Too many subscriptions') ||
message.includes('Subscription rejected') ||
message.includes('too many concurrent REQs') ||
message.includes('too many kinds')) {
return return
} }
@ -449,12 +454,7 @@ function suppressExpectedErrors() {
return return
} }
// Suppress nostr-tools / relay NOTICE messages (subscription limits, REQ limits, etc.) if (isExpectedNostrRelayNotice(message)) {
if (message.includes('NOTICE from') ||
message.includes('Too many subscriptions') ||
message.includes('Subscription rejected') ||
message.includes('too many concurrent REQs') ||
message.includes('too many kinds')) {
return return
} }
@ -466,6 +466,22 @@ function suppressExpectedErrors() {
// Call original console.log for unexpected logs // Call original console.log for unexpected logs
originalConsoleLog.apply(console, args) originalConsoleLog.apply(console, args)
} }
const originalConsoleDebug = console.debug
console.debug = (...args: any[]) => {
const message = formatConsoleArgs(args)
if (isExpectedNostrRelayNotice(message)) {
return
}
if (import.meta.env.DEV && isExpectedDevAppNoise(message)) {
return
}
originalConsoleDebug.apply(console, args)
}
} }
// Suppress unhandled promise rejections that are expected (e.g. SW "operation is insecure" in dev) // Suppress unhandled promise rejections that are expected (e.g. SW "operation is insecure" in dev)

2
src/pages/primary/NoteListPage/index.tsx

@ -20,6 +20,7 @@ import React, {
} from 'react' } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Logo from '@/assets/Logo' import Logo from '@/assets/Logo'
import { DiscussionsTitlebarButton } from '@/components/Sidebar/DiscussionsButton'
import RelaysFeed from './RelaysFeed' import RelaysFeed from './RelaysFeed'
import { usePrimaryPage } from '@/contexts/primary-page-context' import { usePrimaryPage } from '@/contexts/primary-page-context'
import { usePrimaryNoteView } from '@/contexts/primary-note-view-context' import { usePrimaryNoteView } from '@/contexts/primary-note-view-context'
@ -191,6 +192,7 @@ function NoteListPageTitlebar({
> >
<Logo className="max-h-7 w-full min-w-0 object-contain object-center sm:max-h-8" /> <Logo className="max-h-7 w-full min-w-0 object-contain object-center sm:max-h-8" />
</button> </button>
<DiscussionsTitlebarButton />
<Button <Button
variant="ghost" variant="ghost"
size="titlebar-icon" size="titlebar-icon"

Loading…
Cancel
Save