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,
getQueryClient,
} from '$shared/api/queryClient';
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
import {
type InfiniteData,
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 {
return (_catalog ??= new FontCatalogStore({ limit: 50 }));
}
export const getFontCatalog = catalog.get;
// test-only reset, so specs don't share a live observer
export function __resetFontCatalog() {
_catalog?.destroy();
_catalog = undefined;
}
export const __resetFontCatalog = catalog.reset;
@@ -1,3 +1,4 @@
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
import { SvelteMap } from 'svelte/reactivity';
import {
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
* AbortController / FontFace bookkeeping isn't set up at module load.
*/
export function getFontLifecycleManager(): FontLifecycleManager {
return (_fontLifecycleManager ??= new FontLifecycleManager());
}
const fontLifecycleManager = createSingleton(
() => new FontLifecycleManager(),
instance => instance.destroy(),
);
export const getFontLifecycleManager = fontLifecycleManager.get;
// test-only reset, so specs don't share loaded-font/eviction state
export function __resetFontLifecycleManager() {
_fontLifecycleManager?.destroy();
_fontLifecycleManager = undefined;
}
export const __resetFontLifecycleManager = fontLifecycleManager.reset;
@@ -19,6 +19,7 @@ import {
import {
type PersistentStore,
createPersistentStore,
createSingleton,
} from '$shared/lib';
import type { NumericControl } from '$shared/ui';
import { SvelteMap } from 'svelte/reactivity';
@@ -349,22 +350,17 @@ export function createTypographySettingsStore(
export type TypographySettingsStoreInstance = ReturnType<typeof createTypographySettingsStore>;
let _typographySettingsStore: TypographySettingsStoreInstance | undefined;
/**
* App-wide typography settings store, keyed for the comparison view.
* Created on first access so its persistent-store sync effects aren't set up
* at module load.
*/
export function getTypographySettingsStore(): TypographySettingsStoreInstance {
return (_typographySettingsStore ??= createTypographySettingsStore(
DEFAULT_TYPOGRAPHY_CONTROLS_DATA,
COMPARISON_STORAGE_KEY,
));
}
const typographySettingsStore = createSingleton(
() => createTypographySettingsStore(DEFAULT_TYPOGRAPHY_CONTROLS_DATA, COMPARISON_STORAGE_KEY),
instance => instance.destroy(),
);
export const getTypographySettingsStore = typographySettingsStore.get;
// test-only reset, so specs don't share persisted typography state or leak effects
export function __resetTypographySettingsStore() {
_typographySettingsStore?.destroy();
_typographySettingsStore = undefined;
}
export const __resetTypographySettingsStore = typographySettingsStore.reset;
@@ -1,3 +1,5 @@
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
/**
* Scroll-based breadcrumb tracking store
*
@@ -279,17 +281,15 @@ export function createScrollBreadcrumbsStore(): ScrollBreadcrumbsStore {
return new ScrollBreadcrumbsStore();
}
let _scrollBreadcrumbsStore: ScrollBreadcrumbsStore | undefined;
/**
* App-wide scroll breadcrumbs store, created on first access.
*/
export function getScrollBreadcrumbsStore(): ScrollBreadcrumbsStore {
return (_scrollBreadcrumbsStore ??= createScrollBreadcrumbsStore());
}
const scrollBreadcrumbsStore = createSingleton(
() => createScrollBreadcrumbsStore(),
instance => instance.destroy(),
);
export const getScrollBreadcrumbsStore = scrollBreadcrumbsStore.get;
// test-only reset, so specs don't share observer/scroll state
export function __resetScrollBreadcrumbsStore() {
_scrollBreadcrumbsStore?.destroy();
_scrollBreadcrumbsStore = undefined;
}
export const __resetScrollBreadcrumbsStore = scrollBreadcrumbsStore.reset;
@@ -28,7 +28,10 @@
* ```
*/
import { createPersistentStore } from '$shared/lib';
import {
createPersistentStore,
createSingleton,
} from '$shared/lib';
export const STORAGE_KEY = 'glyphdiff:theme';
@@ -195,23 +198,18 @@ class ThemeManager {
}
}
let _themeManager: ThemeManager | undefined;
/**
* App-wide theme manager, created on first access.
*
* Lazy so its persistent-store subscription isn't set up at module load.
* Call init() on mount and destroy() on unmount (see Layout).
*/
export function getThemeManager(): ThemeManager {
return (_themeManager ??= new ThemeManager());
}
const themeManager = createSingleton(() => new ThemeManager(), instance => instance.destroy());
export const getThemeManager = themeManager.get;
// test-only reset, so specs don't share persisted theme state
export function __resetThemeManager() {
_themeManager?.destroy();
_themeManager = undefined;
}
export const __resetThemeManager = themeManager.reset;
/**
* 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 type {
FilterConfig,
@@ -129,8 +132,6 @@ export function createAppliedFilterStore<TValue extends string>(config: FilterCo
export type AppliedFilterStore = ReturnType<typeof createAppliedFilterStore>;
let _appliedFilterStore: AppliedFilterStore | undefined;
/**
* 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
* metadata arrives.
*/
export function getAppliedFilterStore(): AppliedFilterStore {
return (_appliedFilterStore ??= createAppliedFilterStore<string>({
const appliedFilterStore = createSingleton(() =>
createAppliedFilterStore<string>({
queryValue: '',
groups: [],
}));
}
})
);
export const getAppliedFilterStore = appliedFilterStore.get;
// test-only reset, so specs don't share filter/selection state
export function __resetAppliedFilterStore() {
_appliedFilterStore = undefined;
}
export const __resetAppliedFilterStore = appliedFilterStore.reset;
@@ -22,6 +22,7 @@ import {
DEFAULT_QUERY_STALE_TIME_MS,
getQueryClient,
} from '$shared/api/queryClient';
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
import {
type QueryKey,
QueryObserver,
@@ -126,18 +127,16 @@ export class AvailableFilterStore {
}
}
let _availableFilterStore: AvailableFilterStore | undefined;
/**
* App-wide filter-metadata store, created on first access. Lazy so the
* QueryObserver isn't constructed at module load.
*/
export function getAvailableFilterStore(): AvailableFilterStore {
return (_availableFilterStore ??= new AvailableFilterStore());
}
const availableFilterStore = createSingleton(
() => new AvailableFilterStore(),
instance => instance.destroy(),
);
export const getAvailableFilterStore = availableFilterStore.get;
// test-only reset, so specs don't share a live observer
export function __resetAvailableFilterStore() {
_availableFilterStore?.destroy();
_availableFilterStore = undefined;
}
export const __resetAvailableFilterStore = availableFilterStore.reset;
@@ -1,3 +1,5 @@
import { createSingleton } from '$shared/lib/helpers/createSingleton/createSingleton';
/**
* 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>;
let _sortStore: SortStore | undefined;
/**
* App-wide sort store, created on first access.
*/
export function getSortStore(): SortStore {
return (_sortStore ??= createSortStore());
}
const sortStore = createSingleton(() => createSortStore());
export const getSortStore = sortStore.get;
// test-only reset, so specs don't share selection state
export function __resetSortStore() {
_sortStore = undefined;
}
export const __resetSortStore = sortStore.reset;
@@ -16,8 +16,11 @@
* - Desktop Large (>= 1280px): 4 columns
*/
import { createPersistentStore } from '$shared/lib';
import { responsiveManager } from '$shared/lib';
import {
createPersistentStore,
createSingleton,
responsiveManager,
} from '$shared/lib';
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
* layout preference isn't read at module load.
*/
export function getLayoutManager(): LayoutManager {
return (_layoutManager ??= new LayoutManager());
}
const layoutManager = createSingleton(() => new LayoutManager(), instance => instance.destroy());
export const getLayoutManager = layoutManager.get;
// test-only reset, so specs don't share persisted layout state
export function __resetLayoutManager() {
_layoutManager = undefined;
}
export const __resetLayoutManager = layoutManager.reset;
// Export class for testing purposes
export { LayoutManager };