From 465331d6721cce6f39a9fd6eac7022d9ce27b734 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:42:18 +0000 Subject: [PATCH 1/5] Initial plan From bac0794808c02d7553d9a8d12529c1f2fda982a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:47:47 +0000 Subject: [PATCH 2/5] refactor(toast): encapsulate toast settings infra exports Co-authored-by: marcuscastelo <27441558+marcuscastelo@users.noreply.github.com> Agent-Logs-Url: https://github.com/marcuscastelo/macroflows/sessions/1bb80835-9ffd-40bd-b872-1bbad478e7b9 --- .../toast/application/toastSettings.ts | 36 +++++++++ .../toast/infrastructure/toastSettings.ts | 78 ++++++------------- src/modules/toast/tests/toastSettings.test.ts | 34 +++----- .../settings/components/ToastSettings.tsx | 2 +- 4 files changed, 74 insertions(+), 76 deletions(-) create mode 100644 src/modules/toast/application/toastSettings.ts diff --git a/src/modules/toast/application/toastSettings.ts b/src/modules/toast/application/toastSettings.ts new file mode 100644 index 000000000..60d60f010 --- /dev/null +++ b/src/modules/toast/application/toastSettings.ts @@ -0,0 +1,36 @@ +import { + createToastSettingsStore, + type ToastSettings, +} from '~/modules/toast/infrastructure/toastSettings' + +const toastSettingsStore = createToastSettingsStore() + +/** + * Gets the current toast settings from the default runtime store. + * + * @returns The current toast settings. + */ +export function getToastSettings(): ToastSettings { + return toastSettingsStore.getToastSettings() +} + +/** + * Updates the current toast settings in the default runtime store. + * + * @param updates Partial toast settings to apply. + * @returns Nothing. + */ +export function updateToastSettings(updates: Partial): void { + toastSettingsStore.updateToastSettings(updates) +} + +/** + * Resets the current toast settings in the default runtime store. + * + * @returns Nothing. + */ +export function resetToastSettings(): void { + toastSettingsStore.resetToastSettings() +} + +export type { ToastSettings } diff --git a/src/modules/toast/infrastructure/toastSettings.ts b/src/modules/toast/infrastructure/toastSettings.ts index 6c4e945eb..5d38ac36e 100644 --- a/src/modules/toast/infrastructure/toastSettings.ts +++ b/src/modules/toast/infrastructure/toastSettings.ts @@ -5,7 +5,7 @@ * Settings are persisted in local storage. */ -import { createEffect, createSignal } from 'solid-js' +import { createEffect, createRoot, createSignal } from 'solid-js' import { jsonParseWithStack } from '~/shared/utils/jsonParseWithStack' @@ -69,63 +69,35 @@ function loadSettings(): ToastSettings { return { ...DEFAULT_SETTINGS } } -// Create reactive signal for settings -const [settings, setSettings] = createSignal(loadSettings()) - -// Persist settings to local storage when they change -createEffect(() => { - localStorage.setItem(STORAGE_KEY, JSON.stringify(settings())) -}) - -/** - * Gets the current toast settings. - * @returns The current ToastSettings object. - */ -export function getToastSettings(): ToastSettings { - return settings() -} - -/** - * Updates toast settings. - * @param updates Partial settings to update. - */ -export function updateToastSettings(updates: Partial): void { - setSettings((current) => ({ ...current, ...updates })) -} - -/** - * Resets toast settings to defaults. - */ -export function resetToastSettings(): void { - setSettings({ ...DEFAULT_SETTINGS }) -} - /** - * Helper functions for specific settings + * Factory that creates a toast settings store backed by localStorage. + * + * @returns A store with read, update, and reset operations for toast settings. */ +export function createToastSettingsStore() { + return createRoot(() => { + const [settings, setSettings] = createSignal(loadSettings()) -export function setShowBackgroundSuccess(value: boolean): void { - updateToastSettings({ showBackgroundSuccess: value }) -} + createEffect(() => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(settings())) + }) -export function setShowBackgroundLoading(value: boolean): void { - updateToastSettings({ showBackgroundLoading: value }) -} - -export function setAutoDismissErrors(value: boolean): void { - updateToastSettings({ autoDismissErrors: value }) -} + function getToastSettings(): ToastSettings { + return settings() + } -export function setDefaultDuration(value: number): void { - updateToastSettings({ defaultDuration: value }) -} + function updateToastSettings(updates: Partial): void { + setSettings((current) => ({ ...current, ...updates })) + } -export function setGroupSimilarToasts(value: boolean): void { - updateToastSettings({ groupSimilarToasts: value }) -} + function resetToastSettings(): void { + setSettings({ ...DEFAULT_SETTINGS }) + } -export function setShowDetailedErrors(value: boolean): void { - updateToastSettings({ showDetailedErrors: value }) + return { + getToastSettings, + updateToastSettings, + resetToastSettings, + } + }) } - -// Add JSDoc for all exported types and functions for better maintainability diff --git a/src/modules/toast/tests/toastSettings.test.ts b/src/modules/toast/tests/toastSettings.test.ts index 6f2910406..7eb5f8d8d 100644 --- a/src/modules/toast/tests/toastSettings.test.ts +++ b/src/modules/toast/tests/toastSettings.test.ts @@ -4,17 +4,12 @@ vi.mock('~/shared/config/env', () => ({ isDevelopment: vi.fn(() => false), })) +import { createToastSettingsStore } from '~/modules/toast/infrastructure/toastSettings' import { getToastSettings, resetToastSettings, - setAutoDismissErrors, - setDefaultDuration, - setGroupSimilarToasts, - setShowBackgroundLoading, - setShowBackgroundSuccess, - setShowDetailedErrors, updateToastSettings, -} from '~/modules/toast/infrastructure/toastSettings' +} from '~/modules/toast/application/toastSettings' const DEFAULTS = { showBackgroundSuccess: false, @@ -67,19 +62,14 @@ describe('toastSettings', () => { expect(getToastSettings().showBackgroundSuccess).toBe(true) }) - it('setters update only their value', () => { - setShowBackgroundSuccess(true) - expect(getToastSettings().showBackgroundSuccess).toBe(true) - setShowBackgroundLoading(true) - expect(getToastSettings().showBackgroundLoading).toBe(true) - setAutoDismissErrors(true) - expect(getToastSettings().autoDismissErrors).toBe(true) - setDefaultDuration(9999) - expect(getToastSettings().defaultDuration).toBe(9999) - setGroupSimilarToasts(false) - expect(getToastSettings().groupSimilarToasts).toBe(false) - setShowDetailedErrors(false) - expect(getToastSettings().showDetailedErrors).toBe(false) + it('createToastSettingsStore returns isolated stores', () => { + const firstStore = createToastSettingsStore() + const secondStore = createToastSettingsStore() + + firstStore.updateToastSettings({ showBackgroundSuccess: true }) + + expect(firstStore.getToastSettings().showBackgroundSuccess).toBe(true) + expect(secondStore.getToastSettings()).toEqual(DEFAULTS) }) it('resetToastSettings restores defaults', () => { @@ -97,7 +87,7 @@ describe('toastSettings', () => { vi.resetModules() setMockLocalStorage() const toastSettingsModule = - await import('~/modules/toast/infrastructure/toastSettings') + await import('~/modules/toast/application/toastSettings') expect(toastSettingsModule.getToastSettings().showBackgroundSuccess).toBe( true, ) @@ -109,7 +99,7 @@ describe('toastSettings', () => { vi.resetModules() setMockLocalStorage() const toastSettingsModule = - await import('~/modules/toast/infrastructure/toastSettings') + await import('~/modules/toast/application/toastSettings') expect(toastSettingsModule.getToastSettings()).toEqual(DEFAULTS) }) }) diff --git a/src/sections/settings/components/ToastSettings.tsx b/src/sections/settings/components/ToastSettings.tsx index b94c57786..f52943ed3 100644 --- a/src/sections/settings/components/ToastSettings.tsx +++ b/src/sections/settings/components/ToastSettings.tsx @@ -9,7 +9,7 @@ import { getToastSettings, resetToastSettings, updateToastSettings, -} from '~/modules/toast/infrastructure/toastSettings' +} from '~/modules/toast/application/toastSettings' export function ToastSettings() { const [settings, setSettings] = createSignal(getToastSettings()) From b3d6a96eb02b916224f2b8aec65e67e74df82837 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:49:05 +0000 Subject: [PATCH 3/5] chore: apply linted toast settings test import order Co-authored-by: marcuscastelo <27441558+marcuscastelo@users.noreply.github.com> Agent-Logs-Url: https://github.com/marcuscastelo/macroflows/sessions/1bb80835-9ffd-40bd-b872-1bbad478e7b9 --- src/modules/toast/tests/toastSettings.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/toast/tests/toastSettings.test.ts b/src/modules/toast/tests/toastSettings.test.ts index 7eb5f8d8d..43da49edf 100644 --- a/src/modules/toast/tests/toastSettings.test.ts +++ b/src/modules/toast/tests/toastSettings.test.ts @@ -4,12 +4,12 @@ vi.mock('~/shared/config/env', () => ({ isDevelopment: vi.fn(() => false), })) -import { createToastSettingsStore } from '~/modules/toast/infrastructure/toastSettings' import { getToastSettings, resetToastSettings, updateToastSettings, } from '~/modules/toast/application/toastSettings' +import { createToastSettingsStore } from '~/modules/toast/infrastructure/toastSettings' const DEFAULTS = { showBackgroundSuccess: false, From 75df337d5a0af28de19ec5649897cded80f2f9f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:50:54 +0000 Subject: [PATCH 4/5] docs(toast): align void JSDoc in toast settings wrapper Co-authored-by: marcuscastelo <27441558+marcuscastelo@users.noreply.github.com> Agent-Logs-Url: https://github.com/marcuscastelo/macroflows/sessions/1bb80835-9ffd-40bd-b872-1bbad478e7b9 --- src/modules/toast/application/toastSettings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/toast/application/toastSettings.ts b/src/modules/toast/application/toastSettings.ts index 60d60f010..f30e9a23f 100644 --- a/src/modules/toast/application/toastSettings.ts +++ b/src/modules/toast/application/toastSettings.ts @@ -18,7 +18,7 @@ export function getToastSettings(): ToastSettings { * Updates the current toast settings in the default runtime store. * * @param updates Partial toast settings to apply. - * @returns Nothing. + * @returns void. */ export function updateToastSettings(updates: Partial): void { toastSettingsStore.updateToastSettings(updates) @@ -27,7 +27,7 @@ export function updateToastSettings(updates: Partial): void { /** * Resets the current toast settings in the default runtime store. * - * @returns Nothing. + * @returns void. */ export function resetToastSettings(): void { toastSettingsStore.resetToastSettings() From 913f45fb9b32a82d636874f48a3b2e38270abfcb Mon Sep 17 00:00:00 2001 From: marcuscastelo Date: Tue, 24 Mar 2026 18:10:08 -0300 Subject: [PATCH 5/5] chore(pr): apply PR #1470 suggestions --- .../toast/application/toastSettings.ts | 6 +- src/modules/toast/domain/toastSettings.ts | 17 ++++++ .../toast/infrastructure/toastSettings.ts | 60 ++++++++----------- src/modules/toast/tests/toastSettings.test.ts | 22 ++++++- 4 files changed, 63 insertions(+), 42 deletions(-) create mode 100644 src/modules/toast/domain/toastSettings.ts diff --git a/src/modules/toast/application/toastSettings.ts b/src/modules/toast/application/toastSettings.ts index f30e9a23f..ef2f8d94a 100644 --- a/src/modules/toast/application/toastSettings.ts +++ b/src/modules/toast/application/toastSettings.ts @@ -1,7 +1,5 @@ -import { - createToastSettingsStore, - type ToastSettings, -} from '~/modules/toast/infrastructure/toastSettings' +import { type ToastSettings } from '~/modules/toast/domain/toastSettings' +import { createToastSettingsStore } from '~/modules/toast/infrastructure/toastSettings' const toastSettingsStore = createToastSettingsStore() diff --git a/src/modules/toast/domain/toastSettings.ts b/src/modules/toast/domain/toastSettings.ts new file mode 100644 index 000000000..5a92fb6f8 --- /dev/null +++ b/src/modules/toast/domain/toastSettings.ts @@ -0,0 +1,17 @@ +/** + * User-configurable toast settings. + */ +export type ToastSettings = { + /** Show success toasts for background operations */ + showBackgroundSuccess: boolean + /** Show loading toasts for background operations */ + showBackgroundLoading: boolean + /** Automatically dismiss error toasts */ + autoDismissErrors: boolean + /** Default duration for toasts in milliseconds */ + defaultDuration: number + /** Group similar toasts together */ + groupSimilarToasts: boolean + /** Show detailed error information in toasts */ + showDetailedErrors: boolean +} diff --git a/src/modules/toast/infrastructure/toastSettings.ts b/src/modules/toast/infrastructure/toastSettings.ts index 5d38ac36e..a70b2eed9 100644 --- a/src/modules/toast/infrastructure/toastSettings.ts +++ b/src/modules/toast/infrastructure/toastSettings.ts @@ -5,34 +5,11 @@ * Settings are persisted in local storage. */ -import { createEffect, createRoot, createSignal } from 'solid-js' +import { createRoot, createSignal } from 'solid-js' +import { type ToastSettings } from '~/modules/toast/domain/toastSettings' import { jsonParseWithStack } from '~/shared/utils/jsonParseWithStack' -/** - * User-configurable toast settings. - * @property showBackgroundSuccess Whether to show success toasts for background operations - * @property showBackgroundLoading Whether to show loading toasts for background operations - * @property autoDismissErrors Whether to automatically dismiss error toasts - * @property defaultDuration Default duration for toasts in milliseconds - * @property groupSimilarToasts Whether to group similar toasts together - * @property showDetailedErrors Whether to show detailed error information in toasts - */ -export type ToastSettings = { - /** Show success toasts for background operations */ - showBackgroundSuccess: boolean - /** Show loading toasts for background operations */ - showBackgroundLoading: boolean - /** Automatically dismiss error toasts */ - autoDismissErrors: boolean - /** Default duration for toasts in milliseconds */ - defaultDuration: number - /** Group similar toasts together */ - groupSimilarToasts: boolean - /** Show detailed error information in toasts */ - showDetailedErrors: boolean -} - /** * Default toast settings */ @@ -48,11 +25,15 @@ const DEFAULT_SETTINGS: ToastSettings = { // Local storage key for persisting settings const STORAGE_KEY = 'macroflows:toast-settings' +type ToastSettingsStoreConfig = { + storageKey?: string +} + /** * Load settings from local storage */ -function loadSettings(): ToastSettings { - const stored = localStorage.getItem(STORAGE_KEY) +function loadSettings(storageKey: string): ToastSettings { + const stored = localStorage.getItem(storageKey) if (stored !== null && stored.length > 0) { try { const parsed = jsonParseWithStack(stored) @@ -69,29 +50,38 @@ function loadSettings(): ToastSettings { return { ...DEFAULT_SETTINGS } } +function persistSettings(storageKey: string, settings: ToastSettings): void { + localStorage.setItem(storageKey, JSON.stringify(settings)) +} + /** * Factory that creates a toast settings store backed by localStorage. * + * @param config Optional storage configuration for alternate wiring or tests. * @returns A store with read, update, and reset operations for toast settings. */ -export function createToastSettingsStore() { - return createRoot(() => { - const [settings, setSettings] = createSignal(loadSettings()) +export function createToastSettingsStore(config?: ToastSettingsStoreConfig) { + const storageKey = config?.storageKey ?? STORAGE_KEY - createEffect(() => { - localStorage.setItem(STORAGE_KEY, JSON.stringify(settings())) - }) + return createRoot(() => { + const initialSettings = loadSettings(storageKey) + const [settings, setSettings] = createSignal(initialSettings) + persistSettings(storageKey, initialSettings) function getToastSettings(): ToastSettings { return settings() } function updateToastSettings(updates: Partial): void { - setSettings((current) => ({ ...current, ...updates })) + const nextSettings = { ...settings(), ...updates } + setSettings(nextSettings) + persistSettings(storageKey, nextSettings) } function resetToastSettings(): void { - setSettings({ ...DEFAULT_SETTINGS }) + const nextSettings = { ...DEFAULT_SETTINGS } + setSettings(nextSettings) + persistSettings(storageKey, nextSettings) } return { diff --git a/src/modules/toast/tests/toastSettings.test.ts b/src/modules/toast/tests/toastSettings.test.ts index 43da49edf..bd701461d 100644 --- a/src/modules/toast/tests/toastSettings.test.ts +++ b/src/modules/toast/tests/toastSettings.test.ts @@ -21,6 +21,8 @@ const DEFAULTS = { } const STORAGE_KEY = 'macroflows:toast-settings' +const FIRST_STORAGE_KEY = 'macroflows:toast-settings:first' +const SECOND_STORAGE_KEY = 'macroflows:toast-settings:second' let localStorageMock: Record = {} @@ -62,14 +64,28 @@ describe('toastSettings', () => { expect(getToastSettings().showBackgroundSuccess).toBe(true) }) - it('createToastSettingsStore returns isolated stores', () => { - const firstStore = createToastSettingsStore() - const secondStore = createToastSettingsStore() + it('createToastSettingsStore keeps persistence isolated per storage key', () => { + const firstStore = createToastSettingsStore({ + storageKey: FIRST_STORAGE_KEY, + }) + const secondStore = createToastSettingsStore({ + storageKey: SECOND_STORAGE_KEY, + }) + + expect(localStorageMock[FIRST_STORAGE_KEY]).toBe(JSON.stringify(DEFAULTS)) + expect(localStorageMock[SECOND_STORAGE_KEY]).toBe(JSON.stringify(DEFAULTS)) firstStore.updateToastSettings({ showBackgroundSuccess: true }) expect(firstStore.getToastSettings().showBackgroundSuccess).toBe(true) expect(secondStore.getToastSettings()).toEqual(DEFAULTS) + expect(localStorageMock[FIRST_STORAGE_KEY]).toBe( + JSON.stringify({ + ...DEFAULTS, + showBackgroundSuccess: true, + }), + ) + expect(localStorageMock[SECOND_STORAGE_KEY]).toBe(JSON.stringify(DEFAULTS)) }) it('resetToastSettings restores defaults', () => {