From 5fb1923a9a09434f1023fe6469ef6813a10ce4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Sun, 19 Jul 2026 14:21:36 +0100 Subject: [PATCH] feat: add explicit save for settings (#1686) --- apps/web/app/globals.css | 26 + .../[agentId]/agent-save-helpers.test.ts | 220 +++- .../agents/[agentId]/agent-save-helpers.ts | 291 ++++-- .../agents/[agentId]/agent-setup-parts.tsx | 96 +- .../app/settings/agents/[agentId]/page.tsx | 48 +- .../profile-mcp-config-card.test.tsx | 45 + .../[agentId]/profile-mcp-config-card.tsx | 86 +- .../[agentId]/use-profile-mcp-config.test.ts | 53 + .../[agentId]/use-profile-mcp-config.ts | 75 +- apps/web/app/settings/executor/[id]/page.tsx | 66 +- .../[id]/profile/[profileId]/page.tsx | 129 ++- .../settings/executors/[profileId]/page.tsx | 216 ++-- .../settings/executors/new/[type]/page.tsx | 179 ++-- .../executors/ssh/[executorId]/page.tsx | 1 + .../settings/general/task-actions/page.tsx | 13 + apps/web/app/settings/plugins/page.test.tsx | 13 +- .../workspace/use-workflow-creation.test.ts | 87 ++ .../workspace/use-workflow-creation.ts | 164 +++ .../workspace/workspace-edit-client.tsx | 78 +- .../workspace-repositories-client.tsx | 14 +- .../workspace-repositories-dirty.test.ts | 45 + .../workspace/workspace-repositories-dirty.ts | 25 + .../workspace/workspace-workflows-client.tsx | 463 +++++---- .../workspace/workspace-workflows-dialogs.tsx | 22 +- .../workspace-workflows-order.test.ts | 70 ++ .../automation-editor-sections.tsx | 291 ++++++ .../automations/automation-editor.tsx | 512 ++++------ .../automation-trigger-drafts.test.ts | 120 +++ .../automations/automation-trigger-drafts.ts | 171 ++++ .../automations/automations-list-page.tsx | 15 +- .../automations/automations-table.test.tsx | 47 + .../automations/automations-table.tsx | 12 +- .../components/automations/config-section.tsx | 44 +- .../components/automations/prompt-section.tsx | 23 +- .../automations/schedule-selector.tsx | 4 +- .../components/automations/trigger-card.tsx | 13 +- .../automations/triggers-section.tsx | 36 +- .../use-automation-enabled-drafts.test.tsx | 65 ++ .../use-automation-enabled-drafts.ts | 69 ++ .../config-chat/config-chat-panel.tsx | 72 +- .../config-chat/config-chat-provider.tsx | 17 +- .../github/action-presets-section.tsx | 220 ++-- .../github/default-queries-section.tsx | 188 +++- .../github/github-repo-scope-section.tsx | 127 ++- .../web/components/github/github-settings.tsx | 83 +- .../use-default-query-presets.test.ts | 21 +- .../my-github/use-default-query-presets.ts | 23 +- .../gitlab/gitlab-settings.test.tsx | 55 + .../web/components/gitlab/gitlab-settings.tsx | 174 ++-- .../drafted-integration-enabled-control.tsx | 33 + .../use-drafted-integration-enabled.test.tsx | 42 + .../use-drafted-integration-enabled.ts | 41 + .../use-watcher-enabled-drafts.test.tsx | 74 ++ .../use-watcher-enabled-drafts.ts | 67 ++ .../watcher-settings-card.test.tsx | 15 + .../integrations/watcher-settings-card.tsx | 33 + .../components/jira/jira-enabled-control.tsx | 9 + .../jira/jira-issue-watch-table.tsx | 16 +- .../jira/jira-issue-watchers-section.tsx | 60 +- apps/web/components/jira/jira-settings.tsx | 130 ++- .../jira/my-jira/use-task-presets.ts | 8 +- .../jira/task-presets-section.test.tsx | 67 ++ .../components/jira/task-presets-section.tsx | 92 +- .../linear/linear-issue-watch-table.tsx | 16 +- .../linear/linear-issue-watchers-section.tsx | 71 +- .../web/components/linear/linear-settings.tsx | 106 +- apps/web/components/routing/app-link.test.tsx | 23 + apps/web/components/routing/app-link.tsx | 8 +- .../sentry/sentry-instance-form.test.tsx | 91 +- .../sentry/sentry-instance-form.tsx | 184 +++- .../sentry/sentry-issue-watch-table.test.tsx | 15 + .../sentry/sentry-issue-watch-table.tsx | 16 +- .../sentry-issue-watchers-section.test.tsx | 13 +- .../sentry/sentry-issue-watchers-section.tsx | 60 +- .../sentry/sentry-settings.test.tsx | 26 +- .../web/components/sentry/sentry-settings.tsx | 46 +- .../settings/agent-profile-page.test.tsx | 48 +- .../settings/agent-profile-page.tsx | 82 +- .../archive-confirmation-settings.test.tsx | 23 +- .../archive-confirmation-settings.tsx | 59 +- .../settings/changelog-notification-card.tsx | 64 +- .../components/settings/cli-flags-field.tsx | 13 +- .../settings/config-chat-agent-section.tsx | 69 +- .../components/settings/editor-form.test.tsx | 120 +++ apps/web/components/settings/editor-form.tsx | 80 +- .../components/settings/editors-settings.tsx | 174 ++-- .../settings/executor-description.ts | 11 + .../components/settings/general-settings.tsx | 232 +++-- .../settings/keyboard-shortcuts-card.test.tsx | 24 + .../settings/keyboard-shortcuts-card.tsx | 61 +- .../settings/notification-events-table.tsx | 99 ++ .../notification-sound-section.test.tsx | 90 ++ .../settings/notification-sound-section.tsx | 48 +- .../notifications-settings-actions.test.tsx | 139 +++ .../notifications-settings-actions.ts | 128 ++- .../settings/notifications-settings.tsx | 215 ++-- .../plugins/plugin-config-form.test.tsx | 40 + .../settings/plugins/plugin-config-form.tsx | 23 +- .../settings/plugins/plugin-detail.tsx | 50 +- .../plugins/use-plugin-config-form.test.ts | 34 +- .../plugins/use-plugin-config-form.ts | 56 +- .../settings/profile-capability-helpers.tsx | 72 ++ .../settings/profile-edit/docker-sections.tsx | 18 +- .../settings/profile-edit/env-vars-card.tsx | 52 +- .../executor-profile-baselines.ts | 56 + .../profile-edit/git-identity-fields.tsx | 147 +++ .../profile-edit/inline-secret-select.tsx | 4 +- .../settings/profile-edit/mcp-policy-card.tsx | 17 +- .../profile-edit/profile-details-card.tsx | 18 +- .../profile-dirty-fields.test.tsx | 50 + .../profile-edit/profile-edit-page-chrome.tsx | 44 +- .../profile-edit/profile-env-vars-section.tsx | 25 +- .../profile-edit/profile-runtime-sections.tsx | 25 +- .../remote-credentials-card.test.tsx | 25 +- .../profile-edit/remote-credentials-card.tsx | 258 ++--- .../settings/profile-edit/script-card.tsx | 13 +- .../profile-edit/sprites-api-key-card.tsx | 17 +- .../profile-edit/sprites-sections.tsx | 40 +- .../settings/profile-form-fields.tsx | 166 +-- .../settings/prompts-settings.test.tsx | 88 ++ .../components/settings/prompts-settings.tsx | 96 +- .../components/settings/repository-card.tsx | 151 ++- .../settings/repository-copy-files-help.tsx | 9 +- .../settings/repository-custom-scripts.tsx | 107 ++ .../settings/secrets-settings.test.ts | 29 + .../components/settings/secrets-settings.tsx | 83 +- .../web/components/settings/settings-card.tsx | 10 + .../settings/settings-dirty.test.ts | 36 + .../web/components/settings/settings-dirty.ts | 37 + .../settings/settings-floating-save.tsx | 146 +++ .../settings/settings-layout-client.test.tsx | 30 + .../settings/settings-layout-client.tsx | 41 +- .../settings/settings-page-template.tsx | 59 +- .../settings/settings-save-provider.test.tsx | 366 +++++++ .../settings/settings-save-provider.tsx | 333 ++++++ .../settings/settings-save-revision.test.ts | 34 + .../settings/settings-save-revision.ts | 14 + .../components/settings/settings-section.tsx | 2 +- .../settings/shell-settings-card.tsx | 86 +- .../settings/ssh-agent-readiness-card.tsx | 14 +- .../settings/ssh-connection-card.test.tsx | 61 ++ .../settings/ssh-connection-card.tsx | 304 ++---- .../settings/ssh-connection-form.tsx | 197 ++++ .../system-metrics-settings-card.test.tsx | 50 + .../settings/system-metrics-settings-card.tsx | 137 +-- .../settings/system/feature-toggle-card.tsx | 17 +- .../system/feature-toggles-settings.test.tsx | 59 +- .../system/feature-toggles-settings.tsx | 175 ++-- .../storage-maintenance-settings.test.tsx | 109 +- .../storage/storage-maintenance-settings.tsx | 56 +- .../storage/storage-policy-card.test.tsx | 18 +- .../system/storage/storage-policy-card.tsx | 106 +- .../system/storage/storage-policy-fields.tsx | 16 +- .../components/settings/terminal-settings.tsx | 251 ++--- .../components/settings/unsaved-indicator.tsx | 6 +- .../use-serialized-mutation-queue.test.ts | 77 ++ .../settings/use-serialized-mutation-queue.ts | 101 ++ .../use-workflow-draft-contributor.test.ts | 169 ++++ .../use-workflow-draft-contributor.ts | 167 +++ .../settings/utility-agents-section.test.ts | 56 + .../settings/utility-agents-section.tsx | 226 ++++- apps/web/components/settings/utility-dirty.ts | 10 + .../components/settings/utility-sections.tsx | 41 +- .../settings/voice-mode-settings.test.tsx | 123 +++ .../settings/voice-mode-settings.tsx | 251 +++-- .../settings/workflow-card-actions.test.ts | 953 ++++++++---------- .../settings/workflow-card-actions.ts | 729 +++++++------- .../settings/workflow-card-dialogs.tsx | 54 +- .../workflow-card-header-actions.test.tsx | 36 + .../settings/workflow-card-header-actions.tsx | 85 ++ .../web/components/settings/workflow-card.tsx | 307 +++--- .../settings/workflow-dirty-state.test.ts | 53 + .../settings/workflow-dirty-state.ts | 28 + .../workflow-pipeline-editor-helpers.tsx | 10 + .../workflow-pipeline-editor-panels.tsx | 124 ++- .../workflow-pipeline-editor-step-actions.tsx | 142 ++- .../workflow-pipeline-editor-wip-controls.tsx | 20 +- .../settings/workflow-pipeline-editor.tsx | 106 +- .../workflow-read-only-actions.test.ts | 30 +- .../settings/workflow-reorder-actions.test.ts | 86 +- .../settings/workflow-synced-badge.tsx | 23 + apps/web/components/slack/slack-settings.tsx | 106 +- apps/web/components/theme/app-theme.test.tsx | 69 ++ apps/web/components/theme/app-theme.tsx | 36 +- apps/web/e2e/helpers/wait-for-mutation.ts | 8 + apps/web/e2e/pages/SSHSettingsPage.ts | 7 + apps/web/e2e/pages/automations-page.ts | 4 +- apps/web/e2e/pages/jira-settings-page.ts | 4 +- apps/web/e2e/pages/linear-settings-page.ts | 4 +- apps/web/e2e/pages/sentry-settings-page.ts | 2 +- apps/web/e2e/pages/workflow-settings-page.ts | 27 +- .../e2e/tests/automations-settings.spec.ts | 10 +- .../github-workspace-settings.spec.ts | 6 +- .../tests/integrations/jira-settings.spec.ts | 17 +- .../integrations/linear-settings.spec.ts | 6 +- .../mobile-linear-settings.spec.ts | 2 +- .../integrations/sentry-settings.spec.ts | 30 + apps/web/e2e/tests/plugins/plugins.spec.ts | 11 +- .../settings/config-chat-popover.spec.ts | 58 ++ .../settings/copy-files-symlink-helpers.ts | 5 +- .../docker-profile-persistence.spec.ts | 21 +- .../tests/settings/keyboard-shortcuts.spec.ts | 4 + .../mobile-config-chat-popover.spec.ts | 51 + .../settings/mobile-general-settings.spec.ts | 54 + .../tests/settings/prompts-settings.spec.ts | 20 +- .../settings/settings-manual-save.spec.ts | 126 +++ apps/web/e2e/tests/ssh/persistence.spec.ts | 34 + .../system/mobile-storage-maintenance.spec.ts | 9 + .../tests/system/storage-maintenance.spec.ts | 12 +- .../archive-confirmation-preference.spec.ts | 5 + ...le-archive-confirmation-preference.spec.ts | 5 + .../mobile-workflow-cycle-guardrails.spec.ts | 4 +- .../workflow/mobile-workflow-settings.spec.ts | 71 +- .../workflow/workflow-agent-profile.spec.ts | 21 +- .../workflow-cycle-guardrails.spec.ts | 10 +- .../tests/workflow/workflow-settings.spec.ts | 208 ++-- .../workflow-step-autocomplete.spec.ts | 1 + .../integrations/use-integration-enabled.ts | 11 +- .../settings/use-workflow-settings.test.ts | 26 + .../domains/settings/use-workflow-settings.ts | 74 +- .../system/use-storage-maintenance.test.tsx | 10 + .../domains/system/use-storage-maintenance.ts | 41 +- apps/web/lib/api/domains/workflow-api.test.ts | 27 + apps/web/lib/api/domains/workflow-api.ts | 4 +- apps/web/lib/plugins/host-api.test.ts | 12 +- apps/web/lib/routing/client-router.test.ts | 32 + apps/web/lib/routing/client-router.ts | 12 +- apps/web/lib/routing/navigation-guard.ts | 241 +++++ apps/web/lib/types/http.ts | 1 + apps/web/lib/user-settings-sync.test.ts | 12 +- apps/web/lib/user-settings-sync.ts | 5 +- .../0046-settings-route-save-coordinator.md | 32 + docs/decisions/INDEX.md | 1 + docs/plans/settings-manual-save/plan.md | 125 +++ .../task-01-save-coordinator.md | 57 ++ .../task-02-workflow-drafts.md | 60 ++ .../task-03-general-operational-drafts.md | 51 + .../task-04-manual-editor-migration.md | 67 ++ .../task-05-integration-settings.md | 65 ++ .../task-06-e2e-mobile-qa.md | 71 ++ docs/plans/workflow-settings-autosave/plan.md | 77 ++ .../task-01-autosave-state.md | 49 + .../task-02-responsive-layout.md | 47 + .../task-03-e2e-coverage.md | 46 + docs/specs/INDEX.md | 4 +- docs/specs/ui/settings-manual-save.md | 121 +++ docs/specs/workflow-cycle-guardrails/spec.md | 23 +- docs/specs/workflow-settings-autosave/spec.md | 53 + 248 files changed, 14103 insertions(+), 5080 deletions(-) create mode 100644 apps/web/app/settings/agents/[agentId]/profile-mcp-config-card.test.tsx create mode 100644 apps/web/app/settings/agents/[agentId]/use-profile-mcp-config.test.ts create mode 100644 apps/web/app/settings/general/task-actions/page.tsx create mode 100644 apps/web/app/settings/workspace/use-workflow-creation.test.ts create mode 100644 apps/web/app/settings/workspace/use-workflow-creation.ts create mode 100644 apps/web/app/settings/workspace/workspace-workflows-order.test.ts create mode 100644 apps/web/components/automations/automation-editor-sections.tsx create mode 100644 apps/web/components/automations/automation-trigger-drafts.test.ts create mode 100644 apps/web/components/automations/automation-trigger-drafts.ts create mode 100644 apps/web/components/automations/automations-table.test.tsx create mode 100644 apps/web/components/automations/use-automation-enabled-drafts.test.tsx create mode 100644 apps/web/components/automations/use-automation-enabled-drafts.ts create mode 100644 apps/web/components/gitlab/gitlab-settings.test.tsx create mode 100644 apps/web/components/integrations/drafted-integration-enabled-control.tsx create mode 100644 apps/web/components/integrations/use-drafted-integration-enabled.test.tsx create mode 100644 apps/web/components/integrations/use-drafted-integration-enabled.ts create mode 100644 apps/web/components/integrations/use-watcher-enabled-drafts.test.tsx create mode 100644 apps/web/components/integrations/use-watcher-enabled-drafts.ts create mode 100644 apps/web/components/integrations/watcher-settings-card.test.tsx create mode 100644 apps/web/components/integrations/watcher-settings-card.tsx create mode 100644 apps/web/components/jira/jira-enabled-control.tsx create mode 100644 apps/web/components/jira/task-presets-section.test.tsx create mode 100644 apps/web/components/settings/editor-form.test.tsx create mode 100644 apps/web/components/settings/executor-description.ts create mode 100644 apps/web/components/settings/keyboard-shortcuts-card.test.tsx create mode 100644 apps/web/components/settings/notification-events-table.tsx create mode 100644 apps/web/components/settings/notification-sound-section.test.tsx create mode 100644 apps/web/components/settings/notifications-settings-actions.test.tsx create mode 100644 apps/web/components/settings/plugins/plugin-config-form.test.tsx create mode 100644 apps/web/components/settings/profile-capability-helpers.tsx create mode 100644 apps/web/components/settings/profile-edit/executor-profile-baselines.ts create mode 100644 apps/web/components/settings/profile-edit/git-identity-fields.tsx create mode 100644 apps/web/components/settings/profile-edit/profile-dirty-fields.test.tsx create mode 100644 apps/web/components/settings/prompts-settings.test.tsx create mode 100644 apps/web/components/settings/repository-custom-scripts.tsx create mode 100644 apps/web/components/settings/secrets-settings.test.ts create mode 100644 apps/web/components/settings/settings-card.tsx create mode 100644 apps/web/components/settings/settings-dirty.test.ts create mode 100644 apps/web/components/settings/settings-dirty.ts create mode 100644 apps/web/components/settings/settings-floating-save.tsx create mode 100644 apps/web/components/settings/settings-save-provider.test.tsx create mode 100644 apps/web/components/settings/settings-save-provider.tsx create mode 100644 apps/web/components/settings/settings-save-revision.test.ts create mode 100644 apps/web/components/settings/settings-save-revision.ts create mode 100644 apps/web/components/settings/ssh-connection-card.test.tsx create mode 100644 apps/web/components/settings/ssh-connection-form.tsx create mode 100644 apps/web/components/settings/system-metrics-settings-card.test.tsx create mode 100644 apps/web/components/settings/use-serialized-mutation-queue.test.ts create mode 100644 apps/web/components/settings/use-serialized-mutation-queue.ts create mode 100644 apps/web/components/settings/use-workflow-draft-contributor.test.ts create mode 100644 apps/web/components/settings/use-workflow-draft-contributor.ts create mode 100644 apps/web/components/settings/utility-agents-section.test.ts create mode 100644 apps/web/components/settings/utility-dirty.ts create mode 100644 apps/web/components/settings/voice-mode-settings.test.tsx create mode 100644 apps/web/components/settings/workflow-card-header-actions.test.tsx create mode 100644 apps/web/components/settings/workflow-card-header-actions.tsx create mode 100644 apps/web/components/settings/workflow-dirty-state.test.ts create mode 100644 apps/web/components/settings/workflow-dirty-state.ts create mode 100644 apps/web/components/settings/workflow-synced-badge.tsx create mode 100644 apps/web/components/theme/app-theme.test.tsx create mode 100644 apps/web/e2e/helpers/wait-for-mutation.ts create mode 100644 apps/web/e2e/tests/settings/mobile-config-chat-popover.spec.ts create mode 100644 apps/web/e2e/tests/settings/settings-manual-save.spec.ts create mode 100644 apps/web/lib/api/domains/workflow-api.test.ts create mode 100644 apps/web/lib/routing/navigation-guard.ts create mode 100644 docs/decisions/0046-settings-route-save-coordinator.md create mode 100644 docs/plans/settings-manual-save/plan.md create mode 100644 docs/plans/settings-manual-save/task-01-save-coordinator.md create mode 100644 docs/plans/settings-manual-save/task-02-workflow-drafts.md create mode 100644 docs/plans/settings-manual-save/task-03-general-operational-drafts.md create mode 100644 docs/plans/settings-manual-save/task-04-manual-editor-migration.md create mode 100644 docs/plans/settings-manual-save/task-05-integration-settings.md create mode 100644 docs/plans/settings-manual-save/task-06-e2e-mobile-qa.md create mode 100644 docs/plans/workflow-settings-autosave/plan.md create mode 100644 docs/plans/workflow-settings-autosave/task-01-autosave-state.md create mode 100644 docs/plans/workflow-settings-autosave/task-02-responsive-layout.md create mode 100644 docs/plans/workflow-settings-autosave/task-03-e2e-coverage.md create mode 100644 docs/specs/ui/settings-manual-save.md create mode 100644 docs/specs/workflow-settings-autosave/spec.md diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index cdef271424..46a7bb23f7 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -202,6 +202,32 @@ } } +@layer components { + [data-settings-dirty="true"] { + border-color: color-mix(in oklab, var(--success) 65%, var(--border)) !important; + } + + [data-settings-dirty="true"]:not(:focus-visible) { + @apply ring-1 ring-success/15; + } + + [data-settings-dirty="true"][data-settings-dirty-level="container"] { + @apply ring-0; + border-color: color-mix(in oklab, var(--success) 45%, var(--border)) !important; + } + + [data-slot="card"]:has([data-settings-dirty="true"]), + [data-settings-dirty="true"][data-settings-dirty-level="card"] { + @apply ring-0; + border-color: color-mix(in oklab, var(--success) 35%, var(--border)) !important; + } + + [data-workflow-replay-cycle="true"] { + @apply ring-1 ring-warning/20; + border-color: color-mix(in oklab, var(--warning) 65%, var(--border)) !important; + } +} + /* iOS Safari auto-zooms a focused form control whose computed font-size is < 16px (native