Compare commits

...

6 Commits

Author SHA1 Message Date
Test
b7d2304282 test: fix Sidebar tests after Favorites removal 2026-03-06 23:22:14 +01:00
Test
50b5fa9c2e refactor: remove Favorites and Untagged from sidebar
- Remove Favorites NavItem, Star icon import, go-favorites command
- Remove Untagged NavItem, TagSimple icon import
- Remove favorites from SidebarFilter union type
- Update tests: Sidebar.test.tsx, useMenuEvents.test.ts, App.test.tsx
- Remove GO_FAVORITES constant and menu item from menu.rs
2026-03-06 23:19:37 +01:00
Test
963e7cf111 refactor: rustfmt formatting fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:15:24 +01:00
Test
c9a5d20c12 test: Playwright smoke test for trash → Changes badge
Also track mock frontmatter writes in mockSavedSinceCommit so the
Changes panel updates correctly in browser dev mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:08:18 +01:00
Test
586e1fcde5 fix: refresh Changes panel after trash/archive operations
Trash, archive, restore, and unarchive wrote frontmatter to disk but
never called loadModifiedFiles, so the change didn't appear in the
Changes panel until the next manual refresh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:01:05 +01:00
Test
75d67623ce Pulse: fix slow note open — O(1) map lookup, no reloadVault on click
Root cause: clicking a note in Pulse used an inline arrow function that:
1. Was recreated on every render (new prop ref → PulseView memo bypassed)
2. Called vault.reloadVault() (full 9000-note rescan) when path didn't match

Fix:
- Add entriesByPath Map (useMemo) — O(1) lookups instead of O(n) .find()
- Add handlePulseOpenNote (useCallback) — stable ref, never triggers reloadVault
  (Pulse notes always exist in vault; no reload needed)
- Wire PulseView to handlePulseOpenNote instead of inline arrow
- Also use entriesByPath in openNoteByPath (MCP bridge)
2026-03-06 22:25:55 +01:00
20 changed files with 107 additions and 49 deletions

View File

@@ -1 +1 @@
33549
66070

View File

@@ -54,7 +54,11 @@ fn parse_file_status(code: &str) -> &str {
/// Get the pulse (commit activity feed) for a vault, showing only .md file changes.
/// `skip` offsets into the commit list for pagination; `limit` caps how many to return.
pub fn get_vault_pulse(vault_path: &str, limit: usize, skip: usize) -> Result<Vec<PulseCommit>, String> {
pub fn get_vault_pulse(
vault_path: &str,
limit: usize,
skip: usize,
) -> Result<Vec<PulseCommit>, String> {
let vault = Path::new(vault_path);
if !vault.join(".git").exists() {

View File

@@ -32,7 +32,6 @@ const VIEW_GO_BACK: &str = "view-go-back";
const VIEW_GO_FORWARD: &str = "view-go-forward";
const GO_ALL_NOTES: &str = "go-all-notes";
const GO_FAVORITES: &str = "go-favorites";
const GO_ARCHIVED: &str = "go-archived";
const GO_TRASH: &str = "go-trash";
const GO_CHANGES: &str = "go-changes";
@@ -75,7 +74,6 @@ const CUSTOM_IDS: &[&str] = &[
VIEW_GO_BACK,
VIEW_GO_FORWARD,
GO_ALL_NOTES,
GO_FAVORITES,
GO_ARCHIVED,
GO_TRASH,
GO_CHANGES,
@@ -249,9 +247,6 @@ fn build_go_menu(app: &App) -> MenuResult {
let all_notes = MenuItemBuilder::new("All Notes")
.id(GO_ALL_NOTES)
.build(app)?;
let favorites = MenuItemBuilder::new("Favorites")
.id(GO_FAVORITES)
.build(app)?;
let archived = MenuItemBuilder::new("Archived")
.id(GO_ARCHIVED)
.build(app)?;
@@ -268,7 +263,6 @@ fn build_go_menu(app: &App) -> MenuResult {
Ok(SubmenuBuilder::new(app, "Go")
.item(&all_notes)
.item(&favorites)
.item(&archived)
.item(&trash)
.item(&changes)
@@ -454,7 +448,6 @@ mod tests {
VIEW_GO_BACK,
VIEW_GO_FORWARD,
GO_ALL_NOTES,
GO_FAVORITES,
GO_ARCHIVED,
GO_TRASH,
GO_CHANGES,

View File

@@ -158,10 +158,7 @@ fn parse_files_at(vault: &Path, rel_paths: &[String]) -> Vec<VaultEntry> {
/// Machine-local files that should never be git-tracked in any vault.
/// These are either caches with absolute paths or per-machine settings.
const UNTRACKED_FILES: &[&str] = &[
".laputa-cache.json",
".laputa/settings.json",
];
const UNTRACKED_FILES: &[&str] = &[".laputa-cache.json", ".laputa/settings.json"];
/// Ensure machine-local files are excluded from git via `.git/info/exclude`
/// and un-tracked if they were previously committed (git rm --cached).
@@ -184,7 +181,11 @@ fn ensure_cache_excluded(vault: &Path) {
if !to_add.is_empty() {
to_add.sort();
let separator = if existing.ends_with('\n') || existing.is_empty() { "" } else { "\n" };
let separator = if existing.ends_with('\n') || existing.is_empty() {
""
} else {
"\n"
};
let additions = to_add.join("\n");
let _ = fs::write(&exclude_path, format!("{existing}{separator}{additions}\n"));
}

View File

@@ -179,7 +179,7 @@ describe('App', () => {
await waitFor(() => {
// "All Notes" should be rendered as the selected nav item
expect(screen.getByText('All Notes')).toBeInTheDocument()
expect(screen.getByText('Favorites')).toBeInTheDocument()
expect(screen.getByText('Archive')).toBeInTheDocument()
})
})

View File

@@ -230,19 +230,26 @@ function App() {
useNavigationGestures({ onGoBack: handleGoBack, onGoForward: handleGoForward })
// O(1) path lookup map — rebuilt only when vault.entries changes
const entriesByPath = useMemo(() => {
const map = new Map<string, VaultEntry>()
for (const e of vault.entries) map.set(e.path, e)
return map
}, [vault.entries])
// MCP UI bridge: react to AI-driven open/highlight/vault-change events
const openNoteByPath = useCallback((path: string) => {
const entry = vault.entries.find(e => e.path === path || e.path === `${resolvedPath}/${path}`)
const entry = entriesByPath.get(path) ?? entriesByPath.get(`${resolvedPath}/${path}`)
if (entry) {
notes.handleSelectNote(entry)
} else {
// Entry not yet in vault (just created) — reload then open
vault.reloadVault().then(freshEntries => {
const fresh = freshEntries.find((e: VaultEntry) => e.path === path || e.path === `${resolvedPath}/${path}`)
const fresh = (freshEntries as VaultEntry[]).find(e => e.path === path || e.path === `${resolvedPath}/${path}`)
if (fresh) notes.handleSelectNote(fresh)
})
}
}, [vault, notes, resolvedPath])
}, [entriesByPath, vault, notes, resolvedPath])
const aiActivity = useAiActivity({
onOpenNote: openNoteByPath,
@@ -253,10 +260,18 @@ function App() {
onVaultChanged: () => { vault.reloadVault() },
})
// Stable callback for Pulse "open note" — never triggers reloadVault.
// Pulse files always exist in the vault; if somehow not found, silently skip.
const handlePulseOpenNote = useCallback((relativePath: string) => {
const fullPath = `${resolvedPath}/${relativePath}`
const entry = entriesByPath.get(fullPath) ?? entriesByPath.get(relativePath)
if (entry) notes.handleSelectNote(entry)
}, [entriesByPath, resolvedPath, notes])
// Agent file operation handlers: auto-open created notes, live-refresh modified notes
const handleAgentFileCreated = useCallback((relativePath: string) => {
vault.reloadVault().then(freshEntries => {
const entry = freshEntries.find((e: VaultEntry) => e.path === relativePath || e.path === `${resolvedPath}/${relativePath}`)
const entry = (freshEntries as VaultEntry[]).find(e => e.path === relativePath || e.path === `${resolvedPath}/${relativePath}`)
if (entry) notes.handleSelectNote(entry)
})
}, [vault, notes, resolvedPath])
@@ -315,6 +330,7 @@ function App() {
handleUpdateFrontmatter: notes.handleUpdateFrontmatter,
handleDeleteProperty: notes.handleDeleteProperty, setToastMessage,
createTypeEntry: notes.createTypeEntrySilent,
onFrontmatterPersisted: vault.loadModifiedFiles,
})
const handleDeleteNote = useCallback(async (path: string) => {
@@ -509,11 +525,7 @@ function App() {
<>
<div className={`app__note-list${aiActivity.highlightElement === 'notelist' ? ' ai-highlight' : ''}`} style={{ width: layout.noteListWidth }}>
{selection.kind === 'filter' && selection.filter === 'pulse' ? (
<PulseView vaultPath={resolvedPath} onOpenNote={(relativePath) => {
const fullPath = `${resolvedPath}/${relativePath}`
const entry = vault.entries.find(e => e.path === fullPath || e.path === relativePath)
if (entry) notes.handleSelectNote(entry)
}} sidebarCollapsed={!sidebarVisible} onExpandSidebar={() => setViewMode('all')} />
<PulseView vaultPath={resolvedPath} onOpenNote={handlePulseOpenNote} sidebarCollapsed={!sidebarVisible} onExpandSidebar={() => setViewMode('all')} />
) : (
<NoteList entries={vault.entries} selection={selection} selectedNote={activeTab?.entry ?? null} allContent={vault.allContent} modifiedFiles={vault.modifiedFiles} modifiedFilesError={vault.modifiedFilesError} getNoteStatus={vault.getNoteStatus} sidebarCollapsed={!sidebarVisible} onSelectNote={notes.handleSelectNote} onReplaceActiveTab={notes.handleReplaceActiveTab} onCreateNote={notes.handleCreateNoteImmediate} onBulkArchive={bulkActions.handleBulkArchive} onBulkTrash={bulkActions.handleBulkTrash} onUpdateTypeSort={notes.handleUpdateFrontmatter} updateEntry={vault.updateEntry} />
)}

View File

@@ -233,10 +233,10 @@ const mockEntries: VaultEntry[] = [
const defaultSelection: SidebarSelection = { kind: 'filter', filter: 'all' }
describe('Sidebar', () => {
it('renders top nav items (All Notes and Favorites)', () => {
it('renders top nav items (All Notes)', () => {
render(<Sidebar entries={[]} selection={defaultSelection} onSelect={() => {}} />)
expect(screen.getByText('All Notes')).toBeInTheDocument()
expect(screen.getByText('Favorites')).toBeInTheDocument()
expect(screen.queryByText('Favorites')).not.toBeInTheDocument()
})
it('renders section group headers only for types present in entries', () => {
@@ -764,7 +764,7 @@ describe('Sidebar', () => {
]
render(<Sidebar entries={entries} selection={defaultSelection} onSelect={() => {}} />)
expect(screen.getByText('All Notes')).toBeInTheDocument()
expect(screen.getByText('Favorites')).toBeInTheDocument()
expect(screen.queryByText('Favorites')).not.toBeInTheDocument()
})
it('renders a "Customize sections" button', () => {

View File

@@ -13,8 +13,8 @@ import {
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import {
FileText, Star, Wrench, Flask, Target, ArrowsClockwise,
Users, CalendarBlank, Tag, TagSimple, Trash, StackSimple, Archive, CaretLeft, GitDiff, Pulse,
FileText, Wrench, Flask, Target, ArrowsClockwise,
Users, CalendarBlank, Tag, Trash, StackSimple, Archive, CaretLeft, GitDiff, Pulse,
} from '@phosphor-icons/react'
import { GitCommitHorizontal, SlidersHorizontal } from 'lucide-react'
import {
@@ -356,9 +356,7 @@ export const Sidebar = memo(function Sidebar({
{/* Top nav */}
<div className="border-b border-border" style={{ padding: '4px 6px' }}>
<NavItem icon={FileText} label="All Notes" count={activeCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'all' })} badgeClassName="bg-primary text-primary-foreground" onClick={() => onSelect({ kind: 'filter', filter: 'all' })} />
<NavItem icon={Star} label="Favorites" isActive={isSelectionActive(selection, { kind: 'filter', filter: 'favorites' })} onClick={() => onSelect({ kind: 'filter', filter: 'favorites' })} />
<NavItem icon={Archive} label="Archive" count={archivedCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'archived' })} badgeClassName="text-muted-foreground" badgeStyle={{ background: 'var(--muted)' }} onClick={() => onSelect({ kind: 'filter', filter: 'archived' })} />
<NavItem icon={TagSimple} label="Untagged" disabled />
<NavItem icon={Trash} label="Trash" count={trashedCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'trash' })} activeClassName="bg-destructive/10 text-destructive" badgeClassName="text-muted-foreground" badgeStyle={{ background: 'var(--muted)' }} onClick={() => onSelect({ kind: 'filter', filter: 'trash' })} />
{modifiedCount > 0 && (
<NavItem icon={GitDiff} label="Changes" count={modifiedCount} isActive={isSelectionActive(selection, { kind: 'filter', filter: 'changes' })} activeClassName="bg-[color:var(--accent-orange)]/10 text-[var(--accent-orange)]" badgeClassName="text-white" badgeStyle={{ background: 'var(--accent-orange)' }} onClick={() => onSelect({ kind: 'filter', filter: 'changes' })} />

View File

@@ -86,7 +86,7 @@ export function useAppCommands(config: AppCommandsConfig): CommandAction[] {
const { onSelect } = config
const selectFilter = useCallback((filter: 'all' | 'favorites' | 'archived' | 'trash' | 'changes' | 'pulse') => {
const selectFilter = useCallback((filter: 'all' | 'archived' | 'trash' | 'changes' | 'pulse') => {
onSelect({ kind: 'filter', filter })
}, [onSelect])

View File

@@ -214,7 +214,6 @@ export function useCommandRegistry(config: CommandRegistryConfig): CommandAction
// Navigation
{ id: 'search-notes', label: 'Search Notes', group: 'Navigation', shortcut: '⌘P', keywords: ['find', 'open', 'quick'], enabled: true, execute: onQuickOpen },
{ id: 'go-all', label: 'Go to All Notes', group: 'Navigation', keywords: ['filter'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'all' }) },
{ id: 'go-favorites', label: 'Go to Favorites', group: 'Navigation', keywords: ['starred'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'favorites' }) },
{ id: 'go-archived', label: 'Go to Archived', group: 'Navigation', keywords: [], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'archived' }) },
{ id: 'go-trash', label: 'Go to Trash', group: 'Navigation', keywords: ['deleted'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'trash' }) },
{ id: 'go-changes', label: 'Go to Changes', group: 'Navigation', keywords: ['git', 'modified', 'pending'], enabled: true, execute: () => onSelect({ kind: 'filter', filter: 'changes' }) },

View File

@@ -50,10 +50,13 @@ describe('useEntryActions', () => {
handleDeleteProperty,
setToastMessage,
createTypeEntry,
onFrontmatterPersisted,
})
)
}
const onFrontmatterPersisted = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
@@ -73,6 +76,7 @@ describe('useEntryActions', () => {
trashedAt: expect.any(Number),
})
expect(setToastMessage).toHaveBeenCalledWith('Note moved to trash')
expect(onFrontmatterPersisted).toHaveBeenCalledTimes(1)
})
})
@@ -91,6 +95,7 @@ describe('useEntryActions', () => {
trashedAt: null,
})
expect(setToastMessage).toHaveBeenCalledWith('Note restored from trash')
expect(onFrontmatterPersisted).toHaveBeenCalledTimes(1)
})
})
@@ -105,6 +110,7 @@ describe('useEntryActions', () => {
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', 'archived', true)
expect(updateEntry).toHaveBeenCalledWith('/vault/note/test.md', { archived: true })
expect(setToastMessage).toHaveBeenCalledWith('Note archived')
expect(onFrontmatterPersisted).toHaveBeenCalledTimes(1)
})
})
@@ -119,6 +125,7 @@ describe('useEntryActions', () => {
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/note/test.md', 'archived', false)
expect(updateEntry).toHaveBeenCalledWith('/vault/note/test.md', { archived: false })
expect(setToastMessage).toHaveBeenCalledWith('Note unarchived')
expect(onFrontmatterPersisted).toHaveBeenCalledTimes(1)
})
})

View File

@@ -8,6 +8,7 @@ interface EntryActionsConfig {
handleDeleteProperty: (path: string, key: string) => Promise<void>
setToastMessage: (msg: string | null) => void
createTypeEntry: (typeName: string) => Promise<VaultEntry>
onFrontmatterPersisted?: () => void
}
function findTypeEntry(entries: VaultEntry[], typeName: string): VaultEntry | undefined {
@@ -15,7 +16,7 @@ function findTypeEntry(entries: VaultEntry[], typeName: string): VaultEntry | un
}
export function useEntryActions({
entries, updateEntry, handleUpdateFrontmatter, handleDeleteProperty, setToastMessage, createTypeEntry,
entries, updateEntry, handleUpdateFrontmatter, handleDeleteProperty, setToastMessage, createTypeEntry, onFrontmatterPersisted,
}: EntryActionsConfig) {
const handleTrashNote = useCallback(async (path: string) => {
const now = new Date().toISOString().slice(0, 10)
@@ -23,26 +24,30 @@ export function useEntryActions({
await handleUpdateFrontmatter(path, 'Trashed at', now)
updateEntry(path, { trashed: true, trashedAt: Date.now() / 1000 })
setToastMessage('Note moved to trash')
}, [handleUpdateFrontmatter, updateEntry, setToastMessage])
onFrontmatterPersisted?.()
}, [handleUpdateFrontmatter, updateEntry, setToastMessage, onFrontmatterPersisted])
const handleRestoreNote = useCallback(async (path: string) => {
await handleUpdateFrontmatter(path, 'Trashed', false)
await handleDeleteProperty(path, 'Trashed at')
updateEntry(path, { trashed: false, trashedAt: null })
setToastMessage('Note restored from trash')
}, [handleUpdateFrontmatter, handleDeleteProperty, updateEntry, setToastMessage])
onFrontmatterPersisted?.()
}, [handleUpdateFrontmatter, handleDeleteProperty, updateEntry, setToastMessage, onFrontmatterPersisted])
const handleArchiveNote = useCallback(async (path: string) => {
await handleUpdateFrontmatter(path, 'archived', true)
updateEntry(path, { archived: true })
setToastMessage('Note archived')
}, [handleUpdateFrontmatter, updateEntry, setToastMessage])
onFrontmatterPersisted?.()
}, [handleUpdateFrontmatter, updateEntry, setToastMessage, onFrontmatterPersisted])
const handleUnarchiveNote = useCallback(async (path: string) => {
await handleUpdateFrontmatter(path, 'archived', false)
updateEntry(path, { archived: false })
setToastMessage('Note unarchived')
}, [handleUpdateFrontmatter, updateEntry, setToastMessage])
onFrontmatterPersisted?.()
}, [handleUpdateFrontmatter, updateEntry, setToastMessage, onFrontmatterPersisted])
const handleCustomizeType = useCallback(async (typeName: string, icon: string, color: string) => {
let typeEntry = findTypeEntry(entries, typeName)

View File

@@ -226,12 +226,6 @@ describe('dispatchMenuEvent', () => {
expect(h.onSelectFilter).toHaveBeenCalledWith('all')
})
it('go-favorites selects favorites filter', () => {
const h = makeHandlers()
dispatchMenuEvent('go-favorites', h)
expect(h.onSelectFilter).toHaveBeenCalledWith('favorites')
})
it('go-archived selects archived filter', () => {
const h = makeHandlers()
dispatchMenuEvent('go-archived', h)

View File

@@ -67,7 +67,6 @@ const SIMPLE_EVENT_MAP: Record<string, SimpleHandler> = {
const FILTER_MAP: Record<string, SidebarFilter> = {
'go-all-notes': 'all',
'go-favorites': 'favorites',
'go-archived': 'archived',
'go-trash': 'trash',
'go-changes': 'changes',

View File

@@ -28,6 +28,7 @@ vi.mock('../mock-tauri', () => ({
isTauri: vi.fn(() => false),
addMockEntry: vi.fn(),
updateMockContent: vi.fn(),
trackMockChange: vi.fn(),
mockInvoke: vi.fn().mockResolvedValue(''),
}))
vi.mock('./mockFrontmatterHelpers', () => ({

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { isTauri, mockInvoke, addMockEntry, updateMockContent } from '../mock-tauri'
import { isTauri, mockInvoke, addMockEntry, updateMockContent, trackMockChange } from '../mock-tauri'
import type { VaultEntry } from '../types'
import type { FrontmatterValue } from '../components/Inspector'
import { useTabManagement } from './useTabManagement'
@@ -111,12 +111,14 @@ async function invokeFrontmatter(command: string, args: Record<string, unknown>)
function applyMockFrontmatterUpdate(path: string, key: string, value: FrontmatterValue): string {
const content = updateMockFrontmatter(path, key, value)
updateMockContent(path, content)
trackMockChange(path)
return content
}
function applyMockFrontmatterDelete(path: string, key: string): string {
const content = deleteMockFrontmatterProperty(path, key)
updateMockContent(path, content)
trackMockChange(path)
return content
}

View File

@@ -5,10 +5,10 @@
*/
import { MOCK_CONTENT } from './mock-content'
import { mockHandlers, addMockEntry, updateMockContent } from './mock-handlers'
import { mockHandlers, addMockEntry, updateMockContent, trackMockChange } from './mock-handlers'
import { tryVaultApi } from './vault-api'
export { addMockEntry, updateMockContent }
export { addMockEntry, updateMockContent, trackMockChange }
export function isTauri(): boolean {
return typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)

View File

@@ -347,3 +347,7 @@ export function updateMockContent(path: string, content: string): void {
MOCK_CONTENT[path] = content
syncWindowContent()
}
export function trackMockChange(path: string): void {
mockSavedSinceCommit.add(path)
}

View File

@@ -170,7 +170,7 @@ export interface PulseCommit {
deleted: number
}
export type SidebarFilter = 'all' | 'favorites' | 'archived' | 'trash' | 'changes' | 'pulse'
export type SidebarFilter = 'all' | 'archived' | 'trash' | 'changes' | 'pulse'
export type SidebarSelection =
| { kind: 'filter'; filter: SidebarFilter }

View File

@@ -0,0 +1,39 @@
import { test, expect } from '@playwright/test'
import { openCommandPalette, executeCommand } from './helpers'
test.describe('Trash/archive notes appear in Changes', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('trashing a note increments the Changes badge', async ({ page }) => {
const sidebar = page.locator('.app__sidebar')
// Wait for Changes nav item (mock starts with 3 modified files)
const changesRow = sidebar.locator('div', { hasText: /^Changes/ }).first()
await changesRow.waitFor({ timeout: 5000 })
// Read the initial badge count from the Changes row
const badge = changesRow.locator('span').last()
const initialCount = Number(await badge.textContent())
expect(initialCount).toBeGreaterThan(0)
// Click the first note in the list to select it
const noteListContainer = page.locator('[data-testid="note-list-container"]')
await noteListContainer.waitFor({ timeout: 5000 })
// Click on the first visible note item (border-b items inside the list)
const firstNote = noteListContainer.locator('.cursor-pointer').first()
await firstNote.click()
// Trash the active note via command palette
await openCommandPalette(page)
await executeCommand(page, 'Trash Note')
// Wait for the badge count to increase
await expect(async () => {
const text = await badge.textContent()
expect(Number(text)).toBeGreaterThan(initialCount)
}).toPass({ timeout: 5000 })
})
})