Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a301d4b
docs: add search results panel requirements and templates
koreyba Mar 4, 2026
139914d
docs: add design document for search results panel
koreyba Mar 4, 2026
262c37b
feat: Add SearchResultsPanel and modify layout to use it
koreyba Mar 4, 2026
1776f5f
Implementation
koreyba Mar 4, 2026
5f0d66c
improvements
koreyba Mar 4, 2026
16b1ddc
More improvements
koreyba Mar 4, 2026
b60057e
mobile site improvement
koreyba Mar 4, 2026
76f0e3a
improved chunks view
koreyba Mar 4, 2026
b90fdd4
more
koreyba Mar 4, 2026
93ce63f
fix visual
koreyba Mar 4, 2026
34e963e
simple search is removed, bug on search not working on input is fixed
koreyba Mar 4, 2026
0db78b4
+ docs (duplicate bulk edit to search panel)
koreyba Mar 5, 2026
8b4bd08
updated docs
koreyba Mar 5, 2026
d1c1bf4
implemented + fixes
koreyba Mar 5, 2026
051ac25
fix tag filter and bulk delete flow
koreyba Mar 5, 2026
172046d
tooltips
koreyba Mar 5, 2026
6c54e4f
Fix current tests + fix review + update docs
koreyba Mar 5, 2026
6e05572
tests docs updated
koreyba Mar 5, 2026
d077183
+ a lot of tests
koreyba Mar 5, 2026
7ec0157
More tersts and data-ids
koreyba Mar 5, 2026
010edf9
Review fixes
koreyba Mar 5, 2026
a9d2a5a
Fix AI pagination identity reset lint-safe flow
koreyba Mar 5, 2026
18b04c8
fix double scroll and review fixes
koreyba Mar 5, 2026
fac4d97
new tests
koreyba Mar 5, 2026
9301e81
more reviews fixes
koreyba Mar 5, 2026
2f5923c
doc update
koreyba Mar 5, 2026
306cb38
Fix review issues for search UX and selection flows
koreyba Mar 5, 2026
9c5ca4d
Update SearchResultsPanel tests for local AI reset behavior
koreyba Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cypress/component/features/mobile/MobileLayout.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ describe('Mobile Layout Adaptation', () => {
selectedNote: null,
isEditing: false,
setIsEditing: cy.stub(),
isSearchPanelOpen: false,
setIsSearchPanelOpen: cy.stub(),
searchQuery: '',
filterByTag: null,
deleteDialogOpen: false,
Expand All @@ -34,10 +36,16 @@ describe('Mobile Layout Adaptation', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ftsSearchResult: { isLoading: false, data: [] } as any,
showFTSResults: false,
showTagOnlyResults: false,
ftsData: undefined,
ftsResults: [],
ftsHasMore: false,
ftsLoadingMore: false,
tagOnlyResults: [],
tagOnlyTotal: 0,
tagOnlyLoading: false,
tagOnlyHasMore: false,
tagOnlyLoadingMore: false,
ftsObserverTarget: { current: null },
observerTarget: { current: null },
deleteAccountLoading: false,
Expand Down Expand Up @@ -66,6 +74,8 @@ describe('Mobile Layout Adaptation', () => {
selectAllVisible: cy.stub(),
clearSelection: cy.stub(),
loadMoreFts: cy.stub(),
loadMoreTagOnly: cy.stub(),
loadMoreAI: cy.stub(),
toggleNoteSelection: cy.stub(),
deleteSelectedNotes: cy.stub(),
totalNotes: 0,
Expand All @@ -82,6 +92,10 @@ describe('Mobile Layout Adaptation', () => {

// Required by controller type (used by NotesShell to register editor ref)
registerNoteEditorRef: overrides.registerNoteEditorRef ?? cy.stub(),
resetFtsResults: overrides.resetFtsResults ?? cy.stub(),
resetAIResults: overrides.resetAIResults ?? cy.stub(),
registerAIPaginationControls: overrides.registerAIPaginationControls ?? cy.stub(),
deleteNotesByIds: overrides.deleteNotesByIds ?? cy.stub().resolves({ total: 0, failed: 0, queuedOffline: false }),
})

mockSupabase = {
Expand Down
85 changes: 13 additions & 72 deletions cypress/component/features/notes/Sidebar.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ describe('Sidebar Component', () => {
const props = {
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch: cy.stub(),
onOpenSearch: cy.stub(),
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
Expand All @@ -77,8 +76,7 @@ describe('Sidebar Component', () => {
const props = {
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch: cy.stub(),
onOpenSearch: cy.stub(),
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
Expand All @@ -100,8 +98,7 @@ describe('Sidebar Component', () => {
const props = {
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch: cy.stub(),
onOpenSearch: cy.stub(),
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
Expand All @@ -119,13 +116,12 @@ describe('Sidebar Component', () => {
cy.contains('Notes displayed: 5 out of unknown').should('be.visible')
})

it('handles search input', () => {
const onSearch = cy.spy().as('onSearch')
it('opens search panel when trigger is clicked', () => {
const onOpenSearch = cy.spy().as('onOpenSearch')
const props = {
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch,
onOpenSearch,
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
Expand All @@ -138,17 +134,16 @@ describe('Sidebar Component', () => {
}
cy.mount(wrapWithProvider(<Sidebar {...props} />))

cy.get('input[placeholder="Search notes..."]').type('test query')
cy.get('@onSearch').should('have.been.called')
cy.contains('Search notes...').click()
cy.get('@onOpenSearch').should('have.been.calledOnce')
})

it('shows tag filter when active', () => {
const onClearTagFilter = cy.spy().as('onClearTagFilter')
const props = {
user: mockUser,
filterByTag: "work",
searchQuery: '',
onSearch: cy.stub(),
onOpenSearch: cy.stub(),
onClearTagFilter,
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
Expand All @@ -164,58 +159,7 @@ describe('Sidebar Component', () => {
cy.contains('work').should('be.visible')
cy.contains('Clear Tags').click()
cy.get('@onClearTagFilter').should('have.been.called')
cy.get('input').should('have.attr', 'placeholder', 'Search in "work" notes...')
})

it('handles clear search button', () => {
const onSearch = cy.spy().as('onSearch')
const props = {
user: mockUser,
filterByTag: null,
searchQuery: 'test query',
onSearch,
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
onDeleteAccount: cy.stub(),
deleteAccountLoading: false,
onImportComplete: cy.stub(),
onExportComplete: cy.stub(),
...createSelectionProps(),
children: <div data-testid="note-list">Note List Content</div>
}
cy.mount(wrapWithProvider(<Sidebar {...props} />))

// Button should be visible when there is a query
cy.get('button').find('.lucide-x').should('exist')

// Tooltip check (might need trigger)
cy.get('button').find('.lucide-x').parent().trigger('mouseenter')
cy.contains('Clear Search').should('exist')

cy.get('button').find('.lucide-x').parent().click()
cy.get('@onSearch').should('have.been.calledWith', '')
})

it('does not show clear search button when query is empty', () => {
const props = {
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch: cy.stub(),
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
onDeleteAccount: cy.stub(),
deleteAccountLoading: false,
onImportComplete: cy.stub(),
onExportComplete: cy.stub(),
...createSelectionProps(),
children: <div data-testid="note-list">Note List Content</div>
}
cy.mount(wrapWithProvider(<Sidebar {...props} />))

cy.get('button').find('.lucide-x').should('not.exist')
cy.contains('Search in "work" notes...').should('be.visible')
})

it('handles actions', () => {
Expand All @@ -224,8 +168,7 @@ describe('Sidebar Component', () => {
const props = {
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch: cy.stub(),
onOpenSearch: cy.stub(),
onClearTagFilter: cy.stub(),
onCreateNote,
onSignOut,
Expand All @@ -250,8 +193,7 @@ describe('Sidebar Component', () => {
const createBaseProps = () => ({
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch: cy.stub(),
onOpenSearch: cy.stub(),
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
Expand Down Expand Up @@ -348,8 +290,7 @@ describe('Sidebar Component', () => {
const props = {
user: mockUser,
filterByTag: null,
searchQuery: '',
onSearch: cy.stub(),
onOpenSearch: cy.stub(),
onClearTagFilter: cy.stub(),
onCreateNote: cy.stub(),
onSignOut: cy.stub(),
Expand Down
17 changes: 7 additions & 10 deletions cypress/component/ui/web/hooks/useNoteData.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ type SinonStub = any
interface TestComponentProps {
offlineOverlay?: CachedNote[]
selectedNoteIds?: Set<string>
showFTSResults?: boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
aggregatedFtsData?: any
}

const TestComponent = ({
offlineOverlay = [],
selectedNoteIds = new Set(),
showFTSResults = false,
aggregatedFtsData = undefined,
}: TestComponentProps) => {
const notesQuery = useNotesQuery({ userId: 'test-user', searchQuery: '', selectedTag: null, enabled: true })
Expand All @@ -30,7 +28,6 @@ const TestComponent = ({
offlineOverlay,
aggregatedFtsData,
selectedNoteIds,
showFTSResults,
})

return (
Expand Down Expand Up @@ -152,7 +149,7 @@ describe('useNoteData', () => {
cy.get('[data-cy="selected-count"]').should('contain', '3')
})

it('uses notes.length for notesDisplayed when showFTSResults is false', () => {
it('uses notes.length for notesDisplayed', () => {
const serverNotes = [
{ id: 'note-1', title: 'Note 1', description: '', tags: [], user_id: 'test-user', created_at: '2024-01-01', updated_at: '2024-01-01' },
]
Expand All @@ -161,15 +158,15 @@ describe('useNoteData', () => {
cy.mount(
<SupabaseTestProvider supabase={mockSupabase}>
<QueryProvider>
<TestComponent showFTSResults={false} />
<TestComponent />
</QueryProvider>
</SupabaseTestProvider>
)

cy.get('[data-cy="notes-displayed"]').should('contain', '1')
})

it('uses FTS result count for notesDisplayed when showFTSResults is true', () => {
it('keeps merged FTS data while notesDisplayed stays tied to regular notes list', () => {
const ftsData = {
results: [
{ id: 'note-1', title: 'Note 1', description: '', tags: [], user_id: 'test-user', created_at: '2024-01-01', updated_at: '2024-01-01', rank: 1, headline: '' },
Expand All @@ -184,20 +181,20 @@ describe('useNoteData', () => {
cy.mount(
<SupabaseTestProvider supabase={mockSupabase}>
<QueryProvider>
<TestComponent showFTSResults aggregatedFtsData={ftsData} />
<TestComponent aggregatedFtsData={ftsData} />
</QueryProvider>
</SupabaseTestProvider>
)

cy.get('[data-cy="notes-displayed"]').should('contain', '2')
cy.get('[data-cy="notes-displayed"]').should('contain', '0')
cy.get('[data-cy="merged-fts-count"]').should('contain', '2')
})

it('returns undefined mergedFtsData when aggregatedFtsData is undefined', () => {
cy.mount(
<SupabaseTestProvider supabase={mockSupabase}>
<QueryProvider>
<TestComponent showFTSResults={false} aggregatedFtsData={undefined} />
<TestComponent aggregatedFtsData={undefined} />
</QueryProvider>
</SupabaseTestProvider>
)
Expand Down Expand Up @@ -231,7 +228,7 @@ describe('useNoteData', () => {
cy.mount(
<SupabaseTestProvider supabase={mockSupabase}>
<QueryProvider>
<TestComponent showFTSResults offlineOverlay={overlay} aggregatedFtsData={ftsData} />
<TestComponent offlineOverlay={overlay} aggregatedFtsData={ftsData} />
</QueryProvider>
</SupabaseTestProvider>
)
Expand Down
Loading
Loading