Per-app automation preferences for CoS task scheduling
+
Scheduled Task Options
+
+ Each toggle turns that CoS scheduled task on or off for this app. The controls beside it are optional —
+ leave one on Inherit and it follows the global schedule defaults.
+
-
+
- Tasks that analyze and improve PortOS and managed apps. Click a card to configure schedule and per-app overrides.
+ Tasks that analyze and improve PortOS and managed apps. Click a card to configure its schedule and to turn it on or off per app.
+ Each app's toggle turns {taskType} on or off for that app —
+ an app stays off until you switch it on here, whatever the rest of the row says. The other controls are
+ optional: leave one on Inherit and it follows the global defaults.
+
+
- {allEnabled ? 'Disable All' : 'Enable All'}
+ {allEnabled ? 'Disable for all apps' : 'Enable for all apps'}
diff --git a/client/src/components/cos/tabs/schedule/TaskConfigDrawer.jsx b/client/src/components/cos/tabs/schedule/TaskConfigDrawer.jsx
index 80b7652c1f..9626905092 100644
--- a/client/src/components/cos/tabs/schedule/TaskConfigDrawer.jsx
+++ b/client/src/components/cos/tabs/schedule/TaskConfigDrawer.jsx
@@ -16,8 +16,9 @@ import PerAppOverrideList from './PerAppOverrideList';
// - Stage config — per-stage provider/model (only when the task has a
// pipeline; count = number of stages)
// - Global defaults — schedule/provider/prompt controls (always present)
-// - Per-app overrides — per-app enablement (only when there are active apps;
-// count = number of active apps)
+// - Per-app options — turns the task on/off per app, plus that app's
+// optional per-app settings (only when there are
+// active apps; count = number of active apps)
// The active tab lives in the `taskTab` URL param so it survives reload and is
// shareable. TaskHeader (identity + badges) stays at the top of every tab.
export default function TaskConfigDrawer({
@@ -45,11 +46,11 @@ export default function TaskConfigDrawer({
const hasOverrides = activeApps.length > 0;
// Tabs are dynamic: a task without a pipeline hides Stage config, and an
- // install with no active apps hides Per-app overrides — never an empty tab.
+ // install with no active apps hides Per-app options — never an empty tab.
const tabs = [
hasStages && { id: 'stages', label: 'Stage config', count: stages.length },
{ id: 'global', label: 'Global defaults' },
- hasOverrides && { id: 'overrides', label: 'Per-app overrides', count: activeApps.length },
+ hasOverrides && { id: 'overrides', label: 'Per-app options', count: activeApps.length },
].filter(Boolean);
const tabIds = tabs.map(t => t.id);
const defaultTab = hasStages ? 'stages' : 'global';
diff --git a/client/src/components/cos/tabs/schedule/TaskConfigDrawer.test.jsx b/client/src/components/cos/tabs/schedule/TaskConfigDrawer.test.jsx
index 2c6f0f9621..a8a9e09608 100644
--- a/client/src/components/cos/tabs/schedule/TaskConfigDrawer.test.jsx
+++ b/client/src/components/cos/tabs/schedule/TaskConfigDrawer.test.jsx
@@ -65,10 +65,10 @@ describe('TaskConfigDrawer tabbed layout', () => {
renderDrawer();
expect(screen.getByRole('tab', { name: /Stage config/ })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /Global defaults/ })).toBeInTheDocument();
- expect(screen.getByRole('tab', { name: /Per-app overrides/ })).toBeInTheDocument();
+ expect(screen.getByRole('tab', { name: /Per-app options/ })).toBeInTheDocument();
// Counts surface: 2 stages, 1 active (non-archived) app.
expect(screen.getByRole('tab', { name: /Stage config/ })).toHaveTextContent('2');
- expect(screen.getByRole('tab', { name: /Per-app overrides/ })).toHaveTextContent('1');
+ expect(screen.getByRole('tab', { name: /Per-app options/ })).toHaveTextContent('1');
});
it('opens on Stage config and mounts only the active tab section', () => {
@@ -78,13 +78,13 @@ describe('TaskConfigDrawer tabbed layout', () => {
expect(screen.queryByTestId('override-list')).not.toBeInTheDocument();
});
- it('switches to Global defaults and Per-app overrides on tab click', () => {
+ it('switches to Global defaults and Per-app options on tab click', () => {
renderDrawer();
fireEvent.click(screen.getByRole('tab', { name: /Global defaults/ }));
expect(screen.getByTestId('global-config')).toHaveTextContent('global:do:next');
expect(screen.queryByTestId('stage-config')).not.toBeInTheDocument();
- fireEvent.click(screen.getByRole('tab', { name: /Per-app overrides/ }));
+ fireEvent.click(screen.getByRole('tab', { name: /Per-app options/ }));
expect(screen.getByTestId('override-list')).toHaveTextContent('overrides:do:next');
expect(screen.queryByTestId('global-config')).not.toBeInTheDocument();
});
@@ -106,9 +106,9 @@ describe('TaskConfigDrawer tabbed layout', () => {
expect(screen.getByTestId('global-config')).toBeInTheDocument();
});
- it('hides the Per-app overrides tab when there are no active apps', () => {
+ it('hides the Per-app options tab when there are no active apps', () => {
renderDrawer({ apps: [{ id: 'a', name: 'A', archived: true }] });
- expect(screen.queryByRole('tab', { name: /Per-app overrides/ })).not.toBeInTheDocument();
+ expect(screen.queryByRole('tab', { name: /Per-app options/ })).not.toBeInTheDocument();
});
it('renders nothing when config is not yet loaded', () => {