From b7c773ea850721797212b540aec58e9eca57f1dd Mon Sep 17 00:00:00 2001 From: Matt Dawkins Date: Fri, 25 Sep 2026 12:27:09 -0400 Subject: [PATCH 1/3] Remember hierarchy expansion per sequence before rendering --- client/src/components/FilterList.spec.ts | 46 ++++++++++++++++++- client/src/components/FilterList.vue | 29 ++++++++---- client/src/typeListViewState.spec.ts | 43 ++++++++++++++++++ client/src/typeListViewState.ts | 58 ++++++++++++++++++++++++ docs/UI-Type-List.md | 11 +++++ 5 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 client/src/typeListViewState.spec.ts create mode 100644 client/src/typeListViewState.ts diff --git a/client/src/components/FilterList.spec.ts b/client/src/components/FilterList.spec.ts index 34524593d..77b4505ad 100644 --- a/client/src/components/FilterList.spec.ts +++ b/client/src/components/FilterList.spec.ts @@ -2,7 +2,9 @@ import { defineComponent, h, nextTick, ref, reactive, } from 'vue'; import { shallowMount } from '@vue/test-utils'; +import { compileHierarchy } from 'dive-common/typeHierarchy'; import { clientSettings } from 'dive-common/store/settings'; +import { typeListViewStore } from '../typeListViewState'; import TrackFilterControls from '../TrackFilterControls'; import Track, { Feature } from '../track'; import BaseFilterControls from '../BaseFilterControls'; @@ -180,6 +182,7 @@ function makeCountHierarchyFixture({ } describe('FilterList hierarchy members', () => { + beforeEach(() => typeListViewStore.clear()); beforeEach(() => { provideMocks.seekFrame.mockReset(); provideMocks.intervalSearch.mockReset().mockReturnValue([]); @@ -191,6 +194,45 @@ describe('FilterList hierarchy members', () => { clientSettings.typeSettings.showFrameCount = true; }); + it('restores expanded parents and collapsed branches before rendering a returning sequence', async () => { + const hierarchy = { + leaf: 'branch', sibling: 'branch', branch: 'parent', parent: 'root', + }; + const firstFixture = makeHierarchyFixture(hierarchy); + const first = mountFilterList({ ...firstFixture, showEmptyTypes: true, height: 240 }); + first.vm.toggleSharedLineage(); + first.vm.toggleExpanded('branch'); + first.wrapper.destroy(); + + const secondFixture = makeHierarchyFixture(hierarchy); + const second = mountFilterList({ ...secondFixture, showEmptyTypes: true, height: 240 }); + // Assert immediately, before nextTick: no compact frame should be rendered. + expect(second.vm.compactSharedLineage).toBe(false); + expect(second.wrapper.text()).toContain('Compact Parents'); + expect(second.wrapper.text()).not.toContain('Expand Parents'); + expect(second.vm.virtualTypes.map(({ type }) => type)).toEqual(['root', 'parent', 'branch']); + secondFixture.filterControls.typeHierarchy.value = { ...hierarchy }; + secondFixture.filterControls.hierarchyIndex.value = compileHierarchy(hierarchy); + await nextTick(); + expect(second.vm.compactSharedLineage).toBe(false); + expect(second.vm.virtualTypes.map(({ type }) => type)).toEqual(['root', 'parent', 'branch']); + second.wrapper.destroy(); + }); + + it('restores each dataset independently and does not overwrite its state during hydration', async () => { + const fixture = makeHierarchyFixture({ leaf: 'branch', branch: 'root' }); + const { wrapper, vm } = mountFilterList({ ...fixture, showEmptyTypes: true, height: 240 }); + vm.toggleSharedLineage(); + if (!provideMocks.datasetIdRef) throw new Error('No dataset ID'); + provideMocks.datasetIdRef.value = 'dataset-b'; + expect(vm.compactSharedLineage).toBe(true); + provideMocks.datasetIdRef.value = 'dataset-a'; + expect(vm.compactSharedLineage).toBe(false); + await nextTick(); + expect(vm.compactSharedLineage).toBe(false); + wrapper.destroy(); + }); + it('discards an open Type Editor draft when the dataset changes', async () => { const { filterControls, styleManager } = makeHierarchyFixture(); const { vm, wrapper } = mountFilterList({ @@ -504,7 +546,7 @@ describe('FilterList hierarchy members', () => { expect(vm.virtualTypes.map(({ type }) => type)).toEqual(['root', 'branch', 'leaf']); }); - it('resets collapsed branches when a dataset hierarchy is loaded', async () => { + it('preserves collapsed branches when the same dataset hierarchy reloads', async () => { clientSettings.typeSettings.trackSortDir = 'a-z'; clientSettings.typeSettings.filterTypesByFrame = false; const { filterControls, styleManager } = makeHierarchyFixture(); @@ -525,7 +567,7 @@ describe('FilterList hierarchy members', () => { }; await nextTick(); - expect(vm.virtualTypes.map(({ type }) => type)).toEqual(['root', 'branch', 'leaf']); + expect(vm.virtualTypes.map(({ type }) => type)).toEqual(['root']); }); it('keeps the header query-scoped while a context parent owns its full subtree', async () => { diff --git a/client/src/components/FilterList.vue b/client/src/components/FilterList.vue index b800638b0..b5e1b016f 100644 --- a/client/src/components/FilterList.vue +++ b/client/src/components/FilterList.vue @@ -9,7 +9,9 @@ import { import { usePrompt } from 'dive-common/vue-utilities/prompt-service'; import { clientSettings } from 'dive-common/store/settings'; +import { parentDatasetId } from 'dive-common/compositeDatasetId'; import { compileHierarchy } from 'dive-common/typeHierarchy'; +import { typeListViewStore } from '../typeListViewState'; import { useCameraStore, useDatasetId, useHandler, useReadOnlyMode, useTime, usePendingSaveCount, @@ -143,20 +145,29 @@ export default defineComponent({ const typeStylingRef = props.styleManager.typeStyling; const filteredTracksRef = trackFilters.filteredAnnotations; const confidenceFiltersRef = trackFilters.confidenceFilters; - const collapsedTypes: Ref> = ref(new Set()); - const compactSharedLineage = ref(true); + const viewKey = computed(() => (datasetId.value + ? JSON.stringify([parentDatasetId(datasetId.value), props.group ? 'groups' : 'tracks']) : '')); + const initialView = typeListViewStore.read(viewKey.value); + const collapsedTypes: Ref> = ref(new Set(initialView.collapsed)); + const compactSharedLineage = ref(initialView.compact); + function saveView() { + typeListViewStore.write(viewKey.value, { + compact: compactSharedLineage.value, collapsed: [...collapsedTypes.value], + }); + } + // Restore before a render, including when the dataset ID arrives before its + // hierarchy. Metadata hydration must not reset the user's presentation. + watch(viewKey, (key) => { + const saved = typeListViewStore.read(key); + collapsedTypes.value = new Set(saved.collapsed); + compactSharedLineage.value = saved.compact; + }, { flush: 'sync' }); const hierarchyIndexRef = computed(() => ( !props.group && trackFilters instanceof TrackFilterControls ? trackFilters.hierarchyIndex.value : undefined )); const hierarchyActive = computed(() => hierarchyIndexRef.value !== undefined); - if (trackFilters instanceof TrackFilterControls) { - watch(trackFilters.typeHierarchy, () => { - collapsedTypes.value = new Set(); - compactSharedLineage.value = true; - }); - } watch(datasetId, () => { data.showPicker = false; data.selectedType = ''; @@ -457,10 +468,12 @@ export default defineComponent({ next.add(type); } collapsedTypes.value = next; + saveView(); } function toggleSharedLineage() { compactSharedLineage.value = !compactSharedLineage.value; + saveView(); } const showSharedLineageControl = computed(() => ( diff --git a/client/src/typeListViewState.spec.ts b/client/src/typeListViewState.spec.ts new file mode 100644 index 000000000..576e4b9b4 --- /dev/null +++ b/client/src/typeListViewState.spec.ts @@ -0,0 +1,43 @@ +import { createTypeListViewStore } from './typeListViewState'; + +function storage() { + let value: string | null = null; + return { + getItem: vi.fn(() => value), + setItem: vi.fn((_key: string, next: string) => { value = next; }), + removeItem: vi.fn(() => { value = null; }), + }; +} + +it('restores saved views across remounts/restarts without sharing mutable arrays', () => { + const disk = storage(); + const store = createTypeListViewStore(disk); + const state = { compact: false, collapsed: ['fish'] }; + store.write('dataset-a', state); + state.collapsed.push('shark'); + const reopened = createTypeListViewStore(disk); + expect(reopened.read('dataset-a')).toEqual({ compact: false, collapsed: ['fish'] }); + reopened.read('dataset-a').collapsed.push('salmon'); + expect(reopened.read('dataset-a').collapsed).toEqual(['fish']); + expect(reopened.read('dataset-b')).toEqual({ compact: true, collapsed: [] }); +}); + +it('retains navigation state when browser storage is blocked', () => { + const disk = storage(); + disk.getItem.mockImplementation(() => { throw new Error('blocked'); }); + disk.setItem.mockImplementation(() => { throw new Error('quota'); }); + const store = createTypeListViewStore(disk); + store.write('a', { compact: false, collapsed: ['root'] }); + expect(store.read('a')).toEqual({ compact: false, collapsed: ['root'] }); +}); + +it('ignores malformed saved state and bounds retained datasets', () => { + const disk = storage(); + disk.setItem('', '{broken'); + const store = createTypeListViewStore(disk); + expect(store.read('a')).toEqual({ compact: true, collapsed: [] }); + for (let i = 0; i < 110; i += 1) store.write(String(i), { compact: false, collapsed: [] }); + const reopened = createTypeListViewStore(disk); + expect(reopened.read('0').compact).toBe(true); + expect(reopened.read('109').compact).toBe(false); +}); diff --git a/client/src/typeListViewState.ts b/client/src/typeListViewState.ts new file mode 100644 index 000000000..235230247 --- /dev/null +++ b/client/src/typeListViewState.ts @@ -0,0 +1,58 @@ +/** Presentation only: never changes category definitions, selection, or thresholds. */ +export interface TypeListViewState { + compact: boolean; + collapsed: string[]; +} +const STORAGE_KEY = 'dive.typeListViews.v1'; +const MAX_VIEWS = 100; +type StorageLike = Pick; + +function defaultView(): TypeListViewState { return { compact: true, collapsed: [] }; } +function copy(view: TypeListViewState): TypeListViewState { + return { compact: view.compact, collapsed: [...view.collapsed] }; +} +function browserStorage(): StorageLike | undefined { + try { return typeof localStorage === 'undefined' ? undefined : localStorage; } catch { return undefined; } +} + +export function createTypeListViewStore(storage = browserStorage()) { + const views = new Map(); + let loaded = false; + function load() { + if (loaded) return; + loaded = true; + try { + const saved: unknown = JSON.parse(storage?.getItem(STORAGE_KEY) ?? '[]'); + if (!Array.isArray(saved)) return; + saved.slice(-MAX_VIEWS).forEach((entry) => { + if (!Array.isArray(entry) || typeof entry[0] !== 'string') return; + const [key, view] = entry; + if (view && typeof view.compact === 'boolean' && Array.isArray(view.collapsed) + && view.collapsed.length <= 10000 && view.collapsed.every((name: unknown) => typeof name === 'string')) { + views.set(key, copy(view)); + } + }); + } catch { /* Storage unavailable or malformed: keep navigation state in memory. */ } + } + return { + read(key: string): TypeListViewState { + load(); + return copy(views.get(key) ?? defaultView()); + }, + write(key: string, view: TypeListViewState) { + if (!key) return; + load(); + views.delete(key); + views.set(key, copy(view)); + if (views.size > MAX_VIEWS) views.delete(views.keys().next().value!); + try { storage?.setItem(STORAGE_KEY, JSON.stringify([...views])); } catch { /* Keep the memory copy. */ } + }, + clear() { + loaded = true; + views.clear(); + try { storage?.removeItem(STORAGE_KEY); } catch { /* Storage may be disabled. */ } + }, + }; +} + +export const typeListViewStore = createTypeListViewStore(); diff --git a/docs/UI-Type-List.md b/docs/UI-Type-List.md index f11cee0ce..613df216d 100644 --- a/docs/UI-Type-List.md +++ b/docs/UI-Type-List.md @@ -134,3 +134,14 @@ Type settings also configure [suppression](UI-Suppression.md): * **Suppression Overlap (%)** — minimum percent of a detection that must lie under suppression regions for it to be hidden (default **99**). Shown only when a suppression type is set. The active suppression type shows an ==:material-eye-off:== icon in the type list. Detections may also be flagged with an attribute of the same name; see [Suppression](UI-Suppression.md) for region vs attribute behavior and display options. + + +### Remembered hierarchy view + +The type list remembers **Expand Parents / Compact Parents** and each collapsed +branch per sequence. Returning from Review or another page, changing between +sidebar and bottom layouts, and reopening the application restores that view +before the list renders. Reloading hierarchy metadata does not reset expansion. +Other sequences keep their own view; category definitions, checked types, and +confidence thresholds are unchanged. Preferences are saved locally for the most +recent 100 sequences, with an in-memory fallback if browser storage is unavailable. From 4051fd027d8f0d4a1253c395ef0b56a50749345f Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Fri, 25 Sep 2026 15:19:14 -0400 Subject: [PATCH 2/3] restructure types into subfolders --- .../components/{ => Types}/CategoryImportDialog.spec.ts | 0 .../dive-common/components/{ => Types}/CategoryImportDialog.vue | 0 client/dive-common/components/{ => Types}/SavedStylesEditor.vue | 0 .../dive-common/components/{ => Types}/TypeSettingsPanel.spec.ts | 0 client/dive-common/components/{ => Types}/TypeSettingsPanel.vue | 0 client/dive-common/components/{ => Types}/TypeThreshold.vue | 0 client/dive-common/components/{ => Types}/WormsImport.spec.ts | 0 client/dive-common/components/{ => Types}/WormsImport.vue | 0 client/src/components/{ => Types}/FilterList.spec.ts | 0 client/src/components/{ => Types}/FilterList.vue | 0 client/src/components/{ => Types}/ParentTypePicker.spec.ts | 0 client/src/components/{ => Types}/ParentTypePicker.vue | 0 client/src/components/{ => Types}/TypeEditor.spec.ts | 0 client/src/components/{ => Types}/TypeEditor.vue | 0 client/src/components/{ => Types}/TypeItem.spec.ts | 0 client/src/components/{ => Types}/TypeItem.vue | 0 client/src/components/{ => Types}/TypePicker.vue | 0 client/src/{ => components/Types}/typeListHierarchy.spec.ts | 0 client/src/{ => components/Types}/typeListHierarchy.ts | 0 client/src/{ => components/Types}/typeListViewState.spec.ts | 0 client/src/{ => components/Types}/typeListViewState.ts | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename client/dive-common/components/{ => Types}/CategoryImportDialog.spec.ts (100%) rename client/dive-common/components/{ => Types}/CategoryImportDialog.vue (100%) rename client/dive-common/components/{ => Types}/SavedStylesEditor.vue (100%) rename client/dive-common/components/{ => Types}/TypeSettingsPanel.spec.ts (100%) rename client/dive-common/components/{ => Types}/TypeSettingsPanel.vue (100%) rename client/dive-common/components/{ => Types}/TypeThreshold.vue (100%) rename client/dive-common/components/{ => Types}/WormsImport.spec.ts (100%) rename client/dive-common/components/{ => Types}/WormsImport.vue (100%) rename client/src/components/{ => Types}/FilterList.spec.ts (100%) rename client/src/components/{ => Types}/FilterList.vue (100%) rename client/src/components/{ => Types}/ParentTypePicker.spec.ts (100%) rename client/src/components/{ => Types}/ParentTypePicker.vue (100%) rename client/src/components/{ => Types}/TypeEditor.spec.ts (100%) rename client/src/components/{ => Types}/TypeEditor.vue (100%) rename client/src/components/{ => Types}/TypeItem.spec.ts (100%) rename client/src/components/{ => Types}/TypeItem.vue (100%) rename client/src/components/{ => Types}/TypePicker.vue (100%) rename client/src/{ => components/Types}/typeListHierarchy.spec.ts (100%) rename client/src/{ => components/Types}/typeListHierarchy.ts (100%) rename client/src/{ => components/Types}/typeListViewState.spec.ts (100%) rename client/src/{ => components/Types}/typeListViewState.ts (100%) diff --git a/client/dive-common/components/CategoryImportDialog.spec.ts b/client/dive-common/components/Types/CategoryImportDialog.spec.ts similarity index 100% rename from client/dive-common/components/CategoryImportDialog.spec.ts rename to client/dive-common/components/Types/CategoryImportDialog.spec.ts diff --git a/client/dive-common/components/CategoryImportDialog.vue b/client/dive-common/components/Types/CategoryImportDialog.vue similarity index 100% rename from client/dive-common/components/CategoryImportDialog.vue rename to client/dive-common/components/Types/CategoryImportDialog.vue diff --git a/client/dive-common/components/SavedStylesEditor.vue b/client/dive-common/components/Types/SavedStylesEditor.vue similarity index 100% rename from client/dive-common/components/SavedStylesEditor.vue rename to client/dive-common/components/Types/SavedStylesEditor.vue diff --git a/client/dive-common/components/TypeSettingsPanel.spec.ts b/client/dive-common/components/Types/TypeSettingsPanel.spec.ts similarity index 100% rename from client/dive-common/components/TypeSettingsPanel.spec.ts rename to client/dive-common/components/Types/TypeSettingsPanel.spec.ts diff --git a/client/dive-common/components/TypeSettingsPanel.vue b/client/dive-common/components/Types/TypeSettingsPanel.vue similarity index 100% rename from client/dive-common/components/TypeSettingsPanel.vue rename to client/dive-common/components/Types/TypeSettingsPanel.vue diff --git a/client/dive-common/components/TypeThreshold.vue b/client/dive-common/components/Types/TypeThreshold.vue similarity index 100% rename from client/dive-common/components/TypeThreshold.vue rename to client/dive-common/components/Types/TypeThreshold.vue diff --git a/client/dive-common/components/WormsImport.spec.ts b/client/dive-common/components/Types/WormsImport.spec.ts similarity index 100% rename from client/dive-common/components/WormsImport.spec.ts rename to client/dive-common/components/Types/WormsImport.spec.ts diff --git a/client/dive-common/components/WormsImport.vue b/client/dive-common/components/Types/WormsImport.vue similarity index 100% rename from client/dive-common/components/WormsImport.vue rename to client/dive-common/components/Types/WormsImport.vue diff --git a/client/src/components/FilterList.spec.ts b/client/src/components/Types/FilterList.spec.ts similarity index 100% rename from client/src/components/FilterList.spec.ts rename to client/src/components/Types/FilterList.spec.ts diff --git a/client/src/components/FilterList.vue b/client/src/components/Types/FilterList.vue similarity index 100% rename from client/src/components/FilterList.vue rename to client/src/components/Types/FilterList.vue diff --git a/client/src/components/ParentTypePicker.spec.ts b/client/src/components/Types/ParentTypePicker.spec.ts similarity index 100% rename from client/src/components/ParentTypePicker.spec.ts rename to client/src/components/Types/ParentTypePicker.spec.ts diff --git a/client/src/components/ParentTypePicker.vue b/client/src/components/Types/ParentTypePicker.vue similarity index 100% rename from client/src/components/ParentTypePicker.vue rename to client/src/components/Types/ParentTypePicker.vue diff --git a/client/src/components/TypeEditor.spec.ts b/client/src/components/Types/TypeEditor.spec.ts similarity index 100% rename from client/src/components/TypeEditor.spec.ts rename to client/src/components/Types/TypeEditor.spec.ts diff --git a/client/src/components/TypeEditor.vue b/client/src/components/Types/TypeEditor.vue similarity index 100% rename from client/src/components/TypeEditor.vue rename to client/src/components/Types/TypeEditor.vue diff --git a/client/src/components/TypeItem.spec.ts b/client/src/components/Types/TypeItem.spec.ts similarity index 100% rename from client/src/components/TypeItem.spec.ts rename to client/src/components/Types/TypeItem.spec.ts diff --git a/client/src/components/TypeItem.vue b/client/src/components/Types/TypeItem.vue similarity index 100% rename from client/src/components/TypeItem.vue rename to client/src/components/Types/TypeItem.vue diff --git a/client/src/components/TypePicker.vue b/client/src/components/Types/TypePicker.vue similarity index 100% rename from client/src/components/TypePicker.vue rename to client/src/components/Types/TypePicker.vue diff --git a/client/src/typeListHierarchy.spec.ts b/client/src/components/Types/typeListHierarchy.spec.ts similarity index 100% rename from client/src/typeListHierarchy.spec.ts rename to client/src/components/Types/typeListHierarchy.spec.ts diff --git a/client/src/typeListHierarchy.ts b/client/src/components/Types/typeListHierarchy.ts similarity index 100% rename from client/src/typeListHierarchy.ts rename to client/src/components/Types/typeListHierarchy.ts diff --git a/client/src/typeListViewState.spec.ts b/client/src/components/Types/typeListViewState.spec.ts similarity index 100% rename from client/src/typeListViewState.spec.ts rename to client/src/components/Types/typeListViewState.spec.ts diff --git a/client/src/typeListViewState.ts b/client/src/components/Types/typeListViewState.ts similarity index 100% rename from client/src/typeListViewState.ts rename to client/src/components/Types/typeListViewState.ts From 2187f7e27efc890610c6cb660a182809c42bab04 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Fri, 25 Sep 2026 15:19:57 -0400 Subject: [PATCH 3/3] restructure types into subfolders --- client/dive-common/components/BottomPanel.vue | 4 ++-- .../components/GroupSidebar.spec.ts | 2 +- .../dive-common/components/GroupSidebar.vue | 2 +- client/dive-common/components/Sidebar.vue | 2 +- .../components/TrackDetailsPanel.vue | 2 +- .../components/Types/CategoryImportDialog.vue | 6 +++--- .../components/Types/SavedStylesEditor.vue | 2 +- .../components/Types/WormsImport.spec.ts | 6 +++--- .../components/Types/WormsImport.vue | 2 +- .../components/UserSettingsDialog.vue | 2 +- client/dive-common/components/Viewer.vue | 2 +- client/dive-common/store/context.ts | 2 +- client/src/components/GroupItem.vue | 2 +- .../Tracks/sidebar/SideBarTrackItemView.vue | 2 +- .../src/components/Types/FilterList.spec.ts | 14 ++++++------- client/src/components/Types/FilterList.vue | 20 +++++++++---------- .../src/components/Types/TypeEditor.spec.ts | 6 +++--- client/src/components/Types/TypeEditor.vue | 12 +++++------ client/src/components/Types/TypeItem.vue | 2 +- client/src/components/index.ts | 8 ++++---- 20 files changed, 50 insertions(+), 50 deletions(-) diff --git a/client/dive-common/components/BottomPanel.vue b/client/dive-common/components/BottomPanel.vue index 9d6740791..e24f03c9d 100644 --- a/client/dive-common/components/BottomPanel.vue +++ b/client/dive-common/components/BottomPanel.vue @@ -4,8 +4,8 @@ import ControlsContainer from 'dive-common/components/ControlsContainer.vue'; import TrackSettingsPanel from 'dive-common/components/TrackSettingsPanel.vue'; import TrackListColumnSettings from 'dive-common/components/TrackListColumnSettings.vue'; import TrackList from 'vue-media-annotator/components/Tracks/TrackList.vue'; -import FilterList from 'vue-media-annotator/components/FilterList.vue'; -import TypeSettingsPanel from 'dive-common/components/TypeSettingsPanel.vue'; +import FilterList from 'vue-media-annotator/components/Types/FilterList.vue'; +import TypeSettingsPanel from 'dive-common/components/Types/TypeSettingsPanel.vue'; import ConfidenceFilter from 'dive-common/components/ConfidenceFilter.vue'; import ConfidenceSubsection from 'dive-common/components/ConfidenceSubsection.vue'; import AttributeSubsection from 'dive-common/components/Attributes/AttributesSubsection.vue'; diff --git a/client/dive-common/components/GroupSidebar.spec.ts b/client/dive-common/components/GroupSidebar.spec.ts index 53d37c6dd..cdeb353a5 100644 --- a/client/dive-common/components/GroupSidebar.spec.ts +++ b/client/dive-common/components/GroupSidebar.spec.ts @@ -4,7 +4,7 @@ import { } from 'vue'; import { shallowMount } from '@vue/test-utils'; import { clientSettings } from 'dive-common/store/settings'; -import FilterList from 'vue-media-annotator/components/FilterList.vue'; +import FilterList from 'vue-media-annotator/components/Types/FilterList.vue'; import GroupSidebar from './GroupSidebar.vue'; const provideMocks = vi.hoisted(() => ({ diff --git a/client/dive-common/components/GroupSidebar.vue b/client/dive-common/components/GroupSidebar.vue index da35496fd..ee21eda0a 100644 --- a/client/dive-common/components/GroupSidebar.vue +++ b/client/dive-common/components/GroupSidebar.vue @@ -1,6 +1,6 @@