+
+
+Review the diff, then:
+
+- Click **Save & deploy** (or **Save**) to confirm and write the changes to disk.
+- Click **Cancel** to return to the editor without saving.
+
+If there are no unsaved changes the modal is skipped and the save proceeds directly.
+
+The toggle is off by default and saved per browser. Each device remembers its own setting independently. Enable it in **Settings → Appearance → Diff preview before save**.
+
## Container panel
The left column lists all containers that belong to the selected stack under the **CONTAINERS** heading. Each container shows:
diff --git a/docs/images/compose-diff-preview/diff-modal.png b/docs/images/compose-diff-preview/diff-modal.png
new file mode 100644
index 00000000..88679ba8
Binary files /dev/null and b/docs/images/compose-diff-preview/diff-modal.png differ
diff --git a/docs/images/compose-diff-preview/settings-toggle.png b/docs/images/compose-diff-preview/settings-toggle.png
new file mode 100644
index 00000000..788dbcbd
Binary files /dev/null and b/docs/images/compose-diff-preview/settings-toggle.png differ
diff --git a/docs/reference/settings.mdx b/docs/reference/settings.mdx
index b913df38..62586f05 100644
--- a/docs/reference/settings.mdx
+++ b/docs/reference/settings.mdx
@@ -71,6 +71,28 @@ Control how much information Sencho packs on screen. Each browser you sign in fr
Density affects the dashboard stack table, the resource gauge strip, the Settings Hub sidebar, the Schedules and Audit Log tables, and every other data table in Sencho. Typography, color, and layout structure stay the same; only vertical padding compresses.
+### Deploy progress modal
+
+When enabled, Sencho streams live output in a modal whenever you deploy, restart, update, install, or run a Git operation. The modal closes automatically on success or stays open on failure so you can read the error output.
+
+| Value | Behavior |
+|-------|----------|
+| **Enabled** | A progress modal opens for every long-running operation |
+| **Disabled** (default) | Operations run silently; results surface via toast notifications only |
+
+### Diff preview before save
+
+When enabled, clicking **Save & Deploy** or **Save Only** in the compose or env editor opens a side-by-side diff modal before writing anything to disk. The left pane shows the current on-disk content; the right pane shows your unsaved edits, with additions highlighted green and removals highlighted red.
+
+| Value | Behavior |
+|-------|----------|
+| **Enabled** | Diff modal opens on every save that has unsaved changes |
+| **Disabled** (default) | File is saved directly without a review step |
+
+If there are no unsaved changes the modal is skipped and the save proceeds immediately.
+
+See [Diff preview before save](/features/editor#diff-preview-before-save) in the Editor guide for screenshots and the full workflow.
+
---
## License
diff --git a/frontend/src/components/ComposeDiffPreviewDialog.tsx b/frontend/src/components/ComposeDiffPreviewDialog.tsx
new file mode 100644
index 00000000..8e632ad6
--- /dev/null
+++ b/frontend/src/components/ComposeDiffPreviewDialog.tsx
@@ -0,0 +1,103 @@
+import { Suspense } from 'react';
+import { DiffEditor } from '@/lib/monacoLoader';
+import { FileDiff, Loader2 } from 'lucide-react';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from '@/components/ui/dialog';
+import { Button } from '@/components/ui/button';
+
+export interface ComposeDiffPreviewDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ stackName: string;
+ fileName: string;
+ language: 'yaml' | 'ini';
+ original: string;
+ modified: string;
+ actionLabel: 'Save' | 'Save & deploy';
+ confirming: boolean;
+ isDarkMode: boolean;
+ onConfirm: () => void | Promise
diff --git a/frontend/src/hooks/use-compose-diff-preview-enabled.ts b/frontend/src/hooks/use-compose-diff-preview-enabled.ts
new file mode 100644
index 00000000..c6658599
--- /dev/null
+++ b/frontend/src/hooks/use-compose-diff-preview-enabled.ts
@@ -0,0 +1,46 @@
+import { useCallback, useEffect, useState } from 'react';
+import { SENCHO_SETTINGS_CHANGED } from '@/lib/events';
+
+export const COMPOSE_DIFF_PREVIEW_KEY = 'sencho.compose-editor.diff-preview.enabled';
+
+function readStored(): boolean {
+ if (typeof window === 'undefined') return false;
+ try {
+ return window.localStorage.getItem(COMPOSE_DIFF_PREVIEW_KEY) === 'true';
+ } catch {
+ return false;
+ }
+}
+
+export function useComposeDiffPreviewEnabled(): [boolean, (next: boolean) => void] {
+ const [enabled, setEnabledState] = useState