diff --git a/src/services/mutate.ts b/src/services/mutate.ts index ec59552ea..269629a90 100644 --- a/src/services/mutate.ts +++ b/src/services/mutate.ts @@ -1,6 +1,5 @@ import { showNotice } from '@/services/notice-service' import { revalidateQueries } from '@/services/query-client' -import type { AppStoreAction } from '@/store/app-state' /** Anything a notice call accepts: translation key, ReactNode, or failure payload. */ type MutateNoticeInput = Parameters[0] @@ -8,18 +7,12 @@ type MutateNoticeInput = Parameters[0] export interface MutateOptions { /** Commands with the same identity execute in request order. */ id: string - /** Applied before the command runs; undone by `rollback` on failure. */ - optimistic?: () => void - /** Restores pre-optimistic state when the command rejects. */ - rollback?: () => void | Promise /** Runs after a fulfilled (non-busy) result, e.g. cache merges. */ onFulfilled?: (value: T) => void /** SWR keys revalidated after a fulfilled result. */ revalidate?: readonly (readonly unknown[] | string)[] /** Shown after a fulfilled result (most flows leave this to backend notices). */ successNotice?: MutateNoticeInput - /** Shown when the command resolves with `{ status: 'busy' }`. */ - busyNotice?: MutateNoticeInput /** `false` suppresses the default error toast. */ errorNotice?: false | MutateNoticeInput } @@ -35,20 +28,6 @@ const isBusyOutcome = (value: unknown): boolean => (value as { status?: unknown }).status === 'busy' const inFlight = new Map>() -let storeDispatch: ((action: AppStoreAction) => void) | null = null - -/** The app store registers its dispatcher so the funnel can record busy state. */ -export const bindStoreDispatch = ( - dispatch: ((action: AppStoreAction) => void) | null, -) => { - storeDispatch = dispatch -} - -/** - * The single funnel for "user intent → backend command": optimistic apply, - * invoke through the services layer, Busy handling, failure rollback, and the - * error toast, in that order, for every mutating call site. - */ export async function mutate( invoke: () => Promise, options: MutateOptions, @@ -61,15 +40,10 @@ export async function mutate( }) inFlight.set(id, settled) if (previous) await previous - storeDispatch?.({ type: 'busy/started', id }) try { - options.optimistic?.() const value = await invoke() if (isBusyOutcome(value)) { - if (options.busyNotice !== undefined) { - showNotice.warning(options.busyNotice) - } return { ok: false, busy: true, value } } options.onFulfilled?.(value) @@ -81,7 +55,6 @@ export async function mutate( } return { ok: true, value } } catch (error) { - await options.rollback?.() if (options.errorNotice !== false) { showNotice.error( options.errorNotice !== undefined ? options.errorNotice : error, @@ -91,6 +64,5 @@ export async function mutate( } finally { release() if (inFlight.get(id) === settled) inFlight.delete(id) - storeDispatch?.({ type: 'busy/settled', id }) } } diff --git a/src/store/app-state.ts b/src/store/app-state.ts index 8493c7769..c0e143308 100644 --- a/src/store/app-state.ts +++ b/src/store/app-state.ts @@ -20,8 +20,6 @@ export interface AppStoreState { readonly profileUpdates: ProfileUpdatesState /** Bumped once per test-all request; test items run their delay test per bump. */ readonly testAllCounter: number - /** Mutate-funnel bookkeeping: in-flight intent ids. */ - readonly busy: ReadonlySet } export const initialAppStoreState: AppStoreState = { @@ -34,7 +32,6 @@ export const initialAppStoreState: AppStoreState = { timerRevisions: new Map(), }, testAllCounter: 0, - busy: new Set(), } export type AppStoreAction = @@ -46,8 +43,6 @@ export type AppStoreAction = | { type: 'profileUpdate/timerTick'; uid: string } | { type: 'profileLoading/set'; uids: readonly string[]; loading: boolean } | { type: 'testAll/requested' } - | { type: 'busy/started'; id: string } - | { type: 'busy/settled'; id: string } const bumpRevision = ( map: ReadonlyMap, @@ -125,17 +120,5 @@ export function appStoreReducer( return withLoading(state, action.uids, action.loading) case 'testAll/requested': return { ...state, testAllCounter: state.testAllCounter + 1 } - case 'busy/started': { - if (state.busy.has(action.id)) return state - const busy = new Set(state.busy) - busy.add(action.id) - return { ...state, busy } - } - case 'busy/settled': { - if (!state.busy.has(action.id)) return state - const busy = new Set(state.busy) - busy.delete(action.id) - return { ...state, busy } - } } } diff --git a/src/store/app-store.tsx b/src/store/app-store.tsx index 5571e8de4..b84f404c1 100644 --- a/src/store/app-store.tsx +++ b/src/store/app-store.tsx @@ -1,8 +1,6 @@ import { useCallback, useEffect, useMemo, useReducer } from 'react' import type { ReactNode } from 'react' -import { bindStoreDispatch } from '@/services/mutate' - import { type AppStoreAction, appStoreReducer, @@ -34,11 +32,6 @@ export const AppStoreProvider = ({ children }: { children?: ReactNode }) => { [reads], ) - useEffect(() => { - bindStoreDispatch(dispatch) - return () => bindStoreDispatch(null) - }, []) - useEffect(() => { const readRunState = () => { void reads.readRunState().catch(() => {})