diff --git a/src/features/AdjustTypography/model/store/typographySettingsStore/typographySettingsStore.svelte.ts b/src/features/AdjustTypography/model/store/typographySettingsStore/typographySettingsStore.svelte.ts index 5df2a8b..58eabd9 100644 --- a/src/features/AdjustTypography/model/store/typographySettingsStore/typographySettingsStore.svelte.ts +++ b/src/features/AdjustTypography/model/store/typographySettingsStore/typographySettingsStore.svelte.ts @@ -28,6 +28,14 @@ import { } from '$shared/lib'; import { SvelteMap } from 'svelte/reactivity'; +/** + * Epsilon for detecting "significant" base-size changes when reconciling + * the multiplier-derived display value back to the underlying baseSize. + * Differences below this threshold are treated as rounding jitter and + * skipped to avoid spurious storage writes. + */ +const BASE_SIZE_EPSILON = 0.01; + type ControlOnlyFields = Omit, keyof ControlDataModel>; /** @@ -139,7 +147,7 @@ export class TypographySettingsStore { const calculatedBase = currentDisplayValue / this.#multiplier; // Only update if the difference is significant (prevents rounding jitter) - if (Math.abs(this.#baseSize - calculatedBase) > 0.01) { + if (Math.abs(this.#baseSize - calculatedBase) > BASE_SIZE_EPSILON) { this.#baseSize = calculatedBase; } }); diff --git a/src/shared/lib/helpers/CharacterComparisonEngine/CharacterComparisonEngine.svelte.ts b/src/shared/lib/helpers/CharacterComparisonEngine/CharacterComparisonEngine.svelte.ts index c61d58c..1816218 100644 --- a/src/shared/lib/helpers/CharacterComparisonEngine/CharacterComparisonEngine.svelte.ts +++ b/src/shared/lib/helpers/CharacterComparisonEngine/CharacterComparisonEngine.svelte.ts @@ -4,6 +4,20 @@ import { prepareWithSegments, } from '@chenglou/pretext'; +/** + * Width of the character morph "halo" around the slider thumb, in percent + * of container width. Characters within this window get partial blending + * instead of a hard A→B flip. + */ +const CHAR_PROXIMITY_RANGE_PCT = 5; + +/** + * Default render size in px when callers omit the `size` arg on `layout()`. + * Kept as a local constant to avoid pulling `$entities/Font` into + * `$shared/lib` (would create an FSD-illegal upward import cycle). + */ +const DEFAULT_RENDER_SIZE_PX = 16; + /** * A single laid-out line produced by dual-font comparison layout. * @@ -129,7 +143,7 @@ export class CharacterComparisonEngine { width: number, lineHeight: number, spacing: number = 0, - size: number = 16, + size: number = DEFAULT_RENDER_SIZE_PX, ): ComparisonResult { if (!text) { return { lines: [], totalHeight: 0 }; @@ -260,7 +274,7 @@ export class CharacterComparisonEngine { const chars = line.chars; const n = chars.length; const sliderX = (sliderPos / 100) * containerWidth; - const range = 5; + const range = CHAR_PROXIMITY_RANGE_PCT; // Prefix sums of widthA (left chars will be past → use widthA). // Suffix sums of widthB (right chars will not be past → use widthB). // This lets us compute, for each char i, what the total line width and @@ -291,6 +305,7 @@ export class CharacterComparisonEngine { const totalRendered = chars.reduce((s, c, i) => s + (isPastArr[i] ? c.widthA : c.widthB), 0); const xOffset = (containerWidth - totalRendered) / 2; let currentX = xOffset; + return chars.map((char, i) => { const isPast = isPastArr[i] === 1; const charWidth = isPast ? char.widthA : char.widthB; diff --git a/src/shared/lib/helpers/createDebouncedState/createDebouncedState.svelte.ts b/src/shared/lib/helpers/createDebouncedState/createDebouncedState.svelte.ts index 7115d31..79ead26 100644 --- a/src/shared/lib/helpers/createDebouncedState/createDebouncedState.svelte.ts +++ b/src/shared/lib/helpers/createDebouncedState/createDebouncedState.svelte.ts @@ -1,5 +1,11 @@ import { debounce } from '$shared/lib/utils'; +/** + * Default debounce delay used when no wait is provided. Picked to feel + * snappy for typing while still coalescing API-bound side effects. + */ +export const DEFAULT_DEBOUNCE_MS = 300; + /** * Creates reactive state with immediate and debounced values. * @@ -23,7 +29,7 @@ import { debounce } from '$shared/lib/utils'; *

Searching: {search.debounced}

* ``` */ -export function createDebouncedState(initialValue: T, wait: number = 300) { +export function createDebouncedState(initialValue: T, wait: number = DEFAULT_DEBOUNCE_MS) { let immediate = $state(initialValue); let debounced = $state(initialValue); diff --git a/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts b/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts index 4a55458..f18b9a8 100644 --- a/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts +++ b/src/shared/lib/helpers/createPerspectiveManager/createPerspectiveManager.svelte.ts @@ -34,6 +34,13 @@ import { Spring } from 'svelte/motion'; */ const PERSPECTIVE_SPRING_CONFIG = { stiffness: 0.2, damping: 0.8 } as const; +/** + * Halfway threshold on the 0→1 spring value. Above flips `isBack`, + * below flips `isFront`. Picking 0.5 means both states flip at the + * exact midpoint of the animation. + */ +const PERSPECTIVE_TOGGLE_THRESHOLD = 0.5; + /** * Configuration options for perspective effects */ @@ -107,7 +114,7 @@ export class PerspectiveManager { * Content should appear blurred, scaled down, and less interactive * when this is true. Derived from spring value > 0.5. */ - isBack = $derived(this.spring.current > 0.5); + isBack = $derived(this.spring.current > PERSPECTIVE_TOGGLE_THRESHOLD); /** * Reactive state: true when in front position @@ -115,7 +122,7 @@ export class PerspectiveManager { * Content should be fully visible, sharp, and interactive * when this is true. Derived from spring value < 0.5. */ - isFront = $derived(this.spring.current < 0.5); + isFront = $derived(this.spring.current < PERSPECTIVE_TOGGLE_THRESHOLD); /** * Internal configuration with defaults applied diff --git a/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts b/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts index e4051cd..949d991 100644 --- a/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts +++ b/src/shared/lib/helpers/createVirtualizer/createVirtualizer.svelte.ts @@ -4,6 +4,12 @@ * Used to render visible items with absolute positioning based on computed offsets. */ +/** + * Minimum height delta (in px) required to commit a re-measured row height. + * Sub-pixel diffs are treated as measurement noise to avoid spurious re-flows. + */ +const MEASUREMENT_EPSILON_PX = 0.5; + export interface VirtualItem { /** * Index of the item in the data array @@ -381,8 +387,8 @@ export function createVirtualizer( if (!isNaN(index)) { const oldHeight = measuredSizes[index]; - // Only update if the height difference is significant (> 0.5px) - if (oldHeight === undefined || Math.abs(oldHeight - height) > 0.5) { + // Only update if the height difference is significant + if (oldHeight === undefined || Math.abs(oldHeight - height) > MEASUREMENT_EPSILON_PX) { measurementBuffer[index] = height; } }