Refactor/reacrhitecture to fsd+ #49

Merged
ilia merged 70 commits from refactor/reacrhitecture-to-fsd+ into main 2026-06-03 09:55:47 +00:00
9 changed files with 74 additions and 85 deletions
Showing only changes of commit fe07c60dd4 - Show all commits
@@ -3,6 +3,7 @@ import {
DEFAULT_QUERY_STALE_TIME_MS, DEFAULT_QUERY_STALE_TIME_MS,
getQueryClient, getQueryClient,
} from '$shared/api/queryClient'; } from '$shared/api/queryClient';
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
import { import {
type InfiniteData, type InfiniteData,
InfiniteQueryObserver, InfiniteQueryObserver,
@@ -483,14 +484,12 @@ export class FontCatalogStore {
} }
} }
let _catalog: FontCatalogStore | undefined; const catalog = createSingleton(
() => new FontCatalogStore({ limit: 50 }),
instance => instance.destroy(),
);
export function getFontCatalog(): FontCatalogStore { export const getFontCatalog = catalog.get;
return (_catalog ??= new FontCatalogStore({ limit: 50 }));
}
// test-only reset, so specs don't share a live observer // test-only reset, so specs don't share a live observer
export function __resetFontCatalog() { export const __resetFontCatalog = catalog.reset;
_catalog?.destroy();
_catalog = undefined;
}
@@ -1,3 +1,4 @@
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
import { SvelteMap } from 'svelte/reactivity'; import { SvelteMap } from 'svelte/reactivity';
import { import {
type FontLoadRequestConfig, type FontLoadRequestConfig,
@@ -419,18 +420,16 @@ export class FontLifecycleManager {
} }
} }
let _fontLifecycleManager: FontLifecycleManager | undefined;
/** /**
* App-wide font lifecycle manager, created on first access. Lazy so its * App-wide font lifecycle manager, created on first access. Lazy so its
* AbortController / FontFace bookkeeping isn't set up at module load. * AbortController / FontFace bookkeeping isn't set up at module load.
*/ */
export function getFontLifecycleManager(): FontLifecycleManager { const fontLifecycleManager = createSingleton(
return (_fontLifecycleManager ??= new FontLifecycleManager()); () => new FontLifecycleManager(),
} instance => instance.destroy(),
);
export const getFontLifecycleManager = fontLifecycleManager.get;
// test-only reset, so specs don't share loaded-font/eviction state // test-only reset, so specs don't share loaded-font/eviction state
export function __resetFontLifecycleManager() { export const __resetFontLifecycleManager = fontLifecycleManager.reset;
_fontLifecycleManager?.destroy();
_fontLifecycleManager = undefined;
}
@@ -19,6 +19,7 @@ import {
import { import {
type PersistentStore, type PersistentStore,
createPersistentStore, createPersistentStore,
createSingleton,
} from '$shared/lib'; } from '$shared/lib';
import type { NumericControl } from '$shared/ui'; import type { NumericControl } from '$shared/ui';
import { SvelteMap } from 'svelte/reactivity'; import { SvelteMap } from 'svelte/reactivity';
@@ -349,22 +350,17 @@ export function createTypographySettingsStore(
export type TypographySettingsStoreInstance = ReturnType<typeof createTypographySettingsStore>; export type TypographySettingsStoreInstance = ReturnType<typeof createTypographySettingsStore>;
let _typographySettingsStore: TypographySettingsStoreInstance | undefined;
/** /**
* App-wide typography settings store, keyed for the comparison view. * App-wide typography settings store, keyed for the comparison view.
* Created on first access so its persistent-store sync effects aren't set up * Created on first access so its persistent-store sync effects aren't set up
* at module load. * at module load.
*/ */
export function getTypographySettingsStore(): TypographySettingsStoreInstance { const typographySettingsStore = createSingleton(
return (_typographySettingsStore ??= createTypographySettingsStore( () => createTypographySettingsStore(DEFAULT_TYPOGRAPHY_CONTROLS_DATA, COMPARISON_STORAGE_KEY),
DEFAULT_TYPOGRAPHY_CONTROLS_DATA, instance => instance.destroy(),
COMPARISON_STORAGE_KEY, );
));
} export const getTypographySettingsStore = typographySettingsStore.get;
// test-only reset, so specs don't share persisted typography state or leak effects // test-only reset, so specs don't share persisted typography state or leak effects
export function __resetTypographySettingsStore() { export const __resetTypographySettingsStore = typographySettingsStore.reset;
_typographySettingsStore?.destroy();
_typographySettingsStore = undefined;
}
@@ -1,3 +1,5 @@
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
/** /**
* Scroll-based breadcrumb tracking store * Scroll-based breadcrumb tracking store
* *
@@ -279,17 +281,15 @@ export function createScrollBreadcrumbsStore(): ScrollBreadcrumbsStore {
return new ScrollBreadcrumbsStore(); return new ScrollBreadcrumbsStore();
} }
let _scrollBreadcrumbsStore: ScrollBreadcrumbsStore | undefined;
/** /**
* App-wide scroll breadcrumbs store, created on first access. * App-wide scroll breadcrumbs store, created on first access.
*/ */
export function getScrollBreadcrumbsStore(): ScrollBreadcrumbsStore { const scrollBreadcrumbsStore = createSingleton(
return (_scrollBreadcrumbsStore ??= createScrollBreadcrumbsStore()); () => createScrollBreadcrumbsStore(),
} instance => instance.destroy(),
);
export const getScrollBreadcrumbsStore = scrollBreadcrumbsStore.get;
// test-only reset, so specs don't share observer/scroll state // test-only reset, so specs don't share observer/scroll state
export function __resetScrollBreadcrumbsStore() { export const __resetScrollBreadcrumbsStore = scrollBreadcrumbsStore.reset;
_scrollBreadcrumbsStore?.destroy();
_scrollBreadcrumbsStore = undefined;
}
@@ -28,7 +28,10 @@
* ``` * ```
*/ */
import { createPersistentStore } from '$shared/lib'; import {
createPersistentStore,
createSingleton,
} from '$shared/lib';
export const STORAGE_KEY = 'glyphdiff:theme'; export const STORAGE_KEY = 'glyphdiff:theme';
@@ -195,23 +198,18 @@ class ThemeManager {
} }
} }
let _themeManager: ThemeManager | undefined;
/** /**
* App-wide theme manager, created on first access. * App-wide theme manager, created on first access.
* *
* Lazy so its persistent-store subscription isn't set up at module load. * Lazy so its persistent-store subscription isn't set up at module load.
* Call init() on mount and destroy() on unmount (see Layout). * Call init() on mount and destroy() on unmount (see Layout).
*/ */
export function getThemeManager(): ThemeManager { const themeManager = createSingleton(() => new ThemeManager(), instance => instance.destroy());
return (_themeManager ??= new ThemeManager());
} export const getThemeManager = themeManager.get;
// test-only reset, so specs don't share persisted theme state // test-only reset, so specs don't share persisted theme state
export function __resetThemeManager() { export const __resetThemeManager = themeManager.reset;
_themeManager?.destroy();
_themeManager = undefined;
}
/** /**
* ThemeManager class exported for testing purposes * ThemeManager class exported for testing purposes
@@ -23,7 +23,10 @@
* ``` * ```
*/ */
import { createFilter } from '$shared/lib'; import {
createFilter,
createSingleton,
} from '$shared/lib';
import { createDebouncedState } from '$shared/lib/helpers'; import { createDebouncedState } from '$shared/lib/helpers';
import type { import type {
FilterConfig, FilterConfig,
@@ -129,8 +132,6 @@ export function createAppliedFilterStore<TValue extends string>(config: FilterCo
export type AppliedFilterStore = ReturnType<typeof createAppliedFilterStore>; export type AppliedFilterStore = ReturnType<typeof createAppliedFilterStore>;
let _appliedFilterStore: AppliedFilterStore | undefined;
/** /**
* App-wide filter manager, created on first access. * App-wide filter manager, created on first access.
* *
@@ -138,14 +139,14 @@ let _appliedFilterStore: AppliedFilterStore | undefined;
* lives in `./bindings.svelte` and populates groups once backend filter * lives in `./bindings.svelte` and populates groups once backend filter
* metadata arrives. * metadata arrives.
*/ */
export function getAppliedFilterStore(): AppliedFilterStore { const appliedFilterStore = createSingleton(() =>
return (_appliedFilterStore ??= createAppliedFilterStore<string>({ createAppliedFilterStore<string>({
queryValue: '', queryValue: '',
groups: [], groups: [],
})); })
} );
export const getAppliedFilterStore = appliedFilterStore.get;
// test-only reset, so specs don't share filter/selection state // test-only reset, so specs don't share filter/selection state
export function __resetAppliedFilterStore() { export const __resetAppliedFilterStore = appliedFilterStore.reset;
_appliedFilterStore = undefined;
}
@@ -22,6 +22,7 @@ import {
DEFAULT_QUERY_STALE_TIME_MS, DEFAULT_QUERY_STALE_TIME_MS,
getQueryClient, getQueryClient,
} from '$shared/api/queryClient'; } from '$shared/api/queryClient';
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
import { import {
type QueryKey, type QueryKey,
QueryObserver, QueryObserver,
@@ -126,18 +127,16 @@ export class AvailableFilterStore {
} }
} }
let _availableFilterStore: AvailableFilterStore | undefined;
/** /**
* App-wide filter-metadata store, created on first access. Lazy so the * App-wide filter-metadata store, created on first access. Lazy so the
* QueryObserver isn't constructed at module load. * QueryObserver isn't constructed at module load.
*/ */
export function getAvailableFilterStore(): AvailableFilterStore { const availableFilterStore = createSingleton(
return (_availableFilterStore ??= new AvailableFilterStore()); () => new AvailableFilterStore(),
} instance => instance.destroy(),
);
export const getAvailableFilterStore = availableFilterStore.get;
// test-only reset, so specs don't share a live observer // test-only reset, so specs don't share a live observer
export function __resetAvailableFilterStore() { export const __resetAvailableFilterStore = availableFilterStore.reset;
_availableFilterStore?.destroy();
_availableFilterStore = undefined;
}
@@ -1,3 +1,5 @@
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
/** /**
* Sort store manages the current sort option for font listings. * Sort store manages the current sort option for font listings.
* *
@@ -46,16 +48,12 @@ export function createSortStore(initial: SortOption = 'Popularity') {
export type SortStore = ReturnType<typeof createSortStore>; export type SortStore = ReturnType<typeof createSortStore>;
let _sortStore: SortStore | undefined;
/** /**
* App-wide sort store, created on first access. * App-wide sort store, created on first access.
*/ */
export function getSortStore(): SortStore { const sortStore = createSingleton(() => createSortStore());
return (_sortStore ??= createSortStore());
} export const getSortStore = sortStore.get;
// test-only reset, so specs don't share selection state // test-only reset, so specs don't share selection state
export function __resetSortStore() { export const __resetSortStore = sortStore.reset;
_sortStore = undefined;
}
@@ -16,8 +16,11 @@
* - Desktop Large (>= 1280px): 4 columns * - Desktop Large (>= 1280px): 4 columns
*/ */
import { createPersistentStore } from '$shared/lib'; import {
import { responsiveManager } from '$shared/lib'; createPersistentStore,
createSingleton,
responsiveManager,
} from '$shared/lib';
export type LayoutMode = 'list' | 'grid'; export type LayoutMode = 'list' | 'grid';
@@ -153,20 +156,16 @@ class LayoutManager {
} }
} }
let _layoutManager: LayoutManager | undefined;
/** /**
* App-wide layout manager, created on first access. Lazy so its persisted * App-wide layout manager, created on first access. Lazy so its persisted
* layout preference isn't read at module load. * layout preference isn't read at module load.
*/ */
export function getLayoutManager(): LayoutManager { const layoutManager = createSingleton(() => new LayoutManager(), instance => instance.destroy());
return (_layoutManager ??= new LayoutManager());
} export const getLayoutManager = layoutManager.get;
// test-only reset, so specs don't share persisted layout state // test-only reset, so specs don't share persisted layout state
export function __resetLayoutManager() { export const __resetLayoutManager = layoutManager.reset;
_layoutManager = undefined;
}
// Export class for testing purposes // Export class for testing purposes
export { LayoutManager }; export { LayoutManager };