From 3d23d535551e51b9812486536eae8c02cc6cb273 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 19:13:48 +0000 Subject: [PATCH] refactor(automation): colocate the automation helpers and refresh on toggle Two things from review. The lifecycle labels and the output summariser were put in common/constants and common/utils, where they read as generic abstractions the whole client can build on. They are neither: both exist to describe an automation. The architecture guide is explicit that common is for genuinely cross-feature code and that feature-specific helpers stay with their owner even when another file uses them, so both move next to the automations panel. Their one outside caller is the event editor's trigger list, which is the same feature seen from the rundown, and which already reaches into app-settings for its navigation helper. common/constants is gone with them: it held nothing else, because this branch invented it. The automations list keeps its original wording for the disabled notice. What was wrong was underneath it: the settings form saved the master switch and never refetched, so every other part of the panel kept the stale answer until the slow poll came round and turning automations on appeared to do nothing. Every other form in this panel refetches after saving; this one now does too. Verified in the running app: with automations off and one automation in the list, flipping the switch and saving clears the notice in about 250ms rather than waiting out the poll. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AfDKsy6PE3Rbyt32Fg4YKf --- .../AutomationSettingsForm.tsx | 6 ++++++ .../panel/automations-panel/AutomationsList.tsx | 17 +++++------------ .../DeleteAutomationDialog.tsx | 2 +- .../automations-panel/NewAutomationDialog.tsx | 4 ++-- .../__tests__/automationOutputs.test.ts | 0 .../automations-panel}/automationOutputs.ts | 0 .../panel/automations-panel/automationUtils.ts | 2 +- .../panel/automations-panel}/timerLifecycle.ts | 0 .../composite/EventEditorTriggers.tsx | 4 ++-- 9 files changed, 17 insertions(+), 18 deletions(-) rename apps/client/src/{common/utils => features/app-settings/panel/automations-panel}/__tests__/automationOutputs.test.ts (100%) rename apps/client/src/{common/utils => features/app-settings/panel/automations-panel}/automationOutputs.ts (100%) rename apps/client/src/{common/constants => features/app-settings/panel/automations-panel}/timerLifecycle.ts (100%) diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettingsForm.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettingsForm.tsx index 3ae8ed702..b0a5872fd 100644 --- a/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettingsForm.tsx +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettingsForm.tsx @@ -8,6 +8,7 @@ import Input from '../../../../common/components/input/input/Input'; import ExternalLink from '../../../../common/components/link/external-link/ExternalLink'; import Switch from '../../../../common/components/switch/Switch'; import Tag from '../../../../common/components/tag/Tag'; +import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings'; import { preventEscape } from '../../../../common/utils/keyEvent'; import { isOnlyNumbers } from '../../../../common/utils/regex'; import { isOntimeCloud } from '../../../../externals'; @@ -32,6 +33,7 @@ export default function AutomationSettingsForm({ oscInputState, isLoading, }: AutomationSettingsProps) { + const { refetch } = useAutomationSettings(); const { handleSubmit, reset, @@ -52,6 +54,10 @@ export default function AutomationSettingsForm({ try { await editAutomationSettings(formData); reset(formData); + // the rest of the panel reads these settings from the query, and the automations list + // greys itself out while they are off. Without this it keeps the stale answer until the + // slow poll comes round, so turning automations on appears to do nothing + await refetch(); } catch (error) { const message = maybeAxiosError(error); setError('root', { message }); diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx index 387c76ef9..05817750b 100644 --- a/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx +++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsList.tsx @@ -6,16 +6,15 @@ import Button from '../../../../common/components/buttons/Button'; import IconButton from '../../../../common/components/buttons/IconButton'; import Info from '../../../../common/components/info/Info'; import Tag from '../../../../common/components/tag/Tag'; -import { getLifecycleLabel } from '../../../../common/constants/timerLifecycle'; import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings'; -import { summariseOutputs } from '../../../../common/utils/automationOutputs'; import { cx } from '../../../../common/utils/styleUtils'; import * as Panel from '../../panel-utils/PanelUtils'; -import useAppSettingsNavigation from '../../useAppSettingsNavigation'; import AutomationForm from './AutomationForm'; +import { summariseOutputs } from './automationOutputs'; import { groupTriggersByAutomation, isAutomation } from './automationUtils'; import DeleteAutomationDialog from './DeleteAutomationDialog'; import NewAutomationDialog from './NewAutomationDialog'; +import { getLifecycleLabel } from './timerLifecycle'; import style from './AutomationsList.module.scss'; @@ -40,7 +39,6 @@ export default function AutomationsList({ isLoading, }: AutomationsListProps) { const { refetch } = useAutomationSettings(); - const { setLocation } = useAppSettingsNavigation(); const [editing, setEditing] = useState(null); const [isPickingStart, setIsPickingStart] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); @@ -103,14 +101,9 @@ export default function AutomationsList({ {enabledAutomations === false && ( - - Automations are off, so nothing in this list will run. - - {/* the master switch is at the top of the panel, out of sight once the list has rows */} - - + + Automations are disabled. You can still manage automation definitions here, but they will not run until + enabled. )} diff --git a/apps/client/src/features/app-settings/panel/automations-panel/DeleteAutomationDialog.tsx b/apps/client/src/features/app-settings/panel/automations-panel/DeleteAutomationDialog.tsx index ed16eb99d..d620dbc9e 100644 --- a/apps/client/src/features/app-settings/panel/automations-panel/DeleteAutomationDialog.tsx +++ b/apps/client/src/features/app-settings/panel/automations-panel/DeleteAutomationDialog.tsx @@ -6,8 +6,8 @@ import { maybeAxiosError } from '../../../../common/api/utils'; import Button from '../../../../common/components/buttons/Button'; import Dialog from '../../../../common/components/dialog/Dialog'; import Info from '../../../../common/components/info/Info'; -import { getLifecycleLabel } from '../../../../common/constants/timerLifecycle'; import * as Panel from '../../panel-utils/PanelUtils'; +import { getLifecycleLabel } from './timerLifecycle'; interface DeleteAutomationDialogProps { automation: Automation; diff --git a/apps/client/src/features/app-settings/panel/automations-panel/NewAutomationDialog.tsx b/apps/client/src/features/app-settings/panel/automations-panel/NewAutomationDialog.tsx index a4b7deea2..721944f3a 100644 --- a/apps/client/src/features/app-settings/panel/automations-panel/NewAutomationDialog.tsx +++ b/apps/client/src/features/app-settings/panel/automations-panel/NewAutomationDialog.tsx @@ -11,11 +11,10 @@ import Modal from '../../../../common/components/modal/Modal'; import ScrollArea from '../../../../common/components/scroll-area/ScrollArea'; import Select from '../../../../common/components/select/Select'; import Tag from '../../../../common/components/tag/Tag'; -import { getLifecycleLabel } from '../../../../common/constants/timerLifecycle'; -import { summariseOutputs } from '../../../../common/utils/automationOutputs'; import { cx } from '../../../../common/utils/styleUtils'; import { isOntimeCloud } from '../../../../externals'; import * as Panel from '../../panel-utils/PanelUtils'; +import { summariseOutputs } from './automationOutputs'; import { automationRecipes, defaultValues, @@ -26,6 +25,7 @@ import { type RecipeValues, } from './automationRecipes'; import { makeTriggerTitle } from './automationUtils'; +import { getLifecycleLabel } from './timerLifecycle'; import style from './NewAutomationDialog.module.scss'; diff --git a/apps/client/src/common/utils/__tests__/automationOutputs.test.ts b/apps/client/src/features/app-settings/panel/automations-panel/__tests__/automationOutputs.test.ts similarity index 100% rename from apps/client/src/common/utils/__tests__/automationOutputs.test.ts rename to apps/client/src/features/app-settings/panel/automations-panel/__tests__/automationOutputs.test.ts diff --git a/apps/client/src/common/utils/automationOutputs.ts b/apps/client/src/features/app-settings/panel/automations-panel/automationOutputs.ts similarity index 100% rename from apps/client/src/common/utils/automationOutputs.ts rename to apps/client/src/features/app-settings/panel/automations-panel/automationOutputs.ts diff --git a/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts b/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts index 36d41c2f1..c046561bd 100644 --- a/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts +++ b/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts @@ -1,6 +1,6 @@ import { Automation, AutomationDTO, AutomationFilter, CustomFields, TimerLifeCycle, Trigger } from 'ontime-types'; -import { getLifecycleLabel, lifecycleLabels } from '../../../../common/constants/timerLifecycle'; +import { getLifecycleLabel, lifecycleLabels } from './timerLifecycle'; /** * Names a trigger created from an automation's lifecycle picker. diff --git a/apps/client/src/common/constants/timerLifecycle.ts b/apps/client/src/features/app-settings/panel/automations-panel/timerLifecycle.ts similarity index 100% rename from apps/client/src/common/constants/timerLifecycle.ts rename to apps/client/src/features/app-settings/panel/automations-panel/timerLifecycle.ts diff --git a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx index fbe747658..5ee61f0da 100644 --- a/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx +++ b/apps/client/src/features/rundown/entry-editor/composite/EventEditorTriggers.tsx @@ -7,10 +7,10 @@ import IconButton from '../../../../common/components/buttons/IconButton'; import Info from '../../../../common/components/info/Info'; import Select from '../../../../common/components/select/Select'; import Tag from '../../../../common/components/tag/Tag'; -import { getLifecycleLabel } from '../../../../common/constants/timerLifecycle'; import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext'; import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings'; -import { summariseOutputs } from '../../../../common/utils/automationOutputs'; +import { summariseOutputs } from '../../../app-settings/panel/automations-panel/automationOutputs'; +import { getLifecycleLabel } from '../../../app-settings/panel/automations-panel/timerLifecycle'; import { eventTriggerOptions } from './eventTrigger.constants'; import style from './EventEditorTriggers.module.scss';