feat(editor): opt-in diff preview before save (#855)

* feat(editor): add useComposeDiffPreviewEnabled hook

* feat(editor): add ComposeDiffPreviewDialog component

* fix(editor): replace HTML entity with Unicode arrow in ComposeDiffPreviewDialog

* feat(editor): add diff preview toggle to Appearance settings

Added a new 'Diff preview before save' toggle in the Display section of the Appearance settings panel. Users can now enable or disable the side-by-side diff view before compose and env file edits are saved to disk.

* feat(editor): wire diff preview dialog into compose save flow

* fix(editor): snapshot diff content at open time and fix event name

- Fix useComposeDiffPreviewEnabled and useDeployFeedbackEnabled to use
  the canonical SENCHO_SETTINGS_CHANGED constant from @/lib/events
  instead of the hardcoded string literal (wrong value)
- Snapshot language, original, modified, and fileName into diffPreview
  state at click time to prevent tab-switching from corrupting dialog
  content mid-review
- Remove React.MouseEvent from diffPreview state; pass a no-op stub to
  deployStack in the confirm path (preventDefault/stopPropagation are
  no-ops on an already-settled event anyway)
- Add diff-modal screenshot and document the feature in editor.mdx and
  settings.mdx

* docs(editor): add settings-toggle screenshot for diff preview feature
This commit is contained in:
Anso
2026-04-30 22:01:17 -04:00
committed by GitHub
parent 3e01daf76f
commit a25acbec7c
9 changed files with 290 additions and 5 deletions
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
import { SENCHO_SETTINGS_CHANGED } from '@/lib/events';
export const DEPLOY_FEEDBACK_KEY = 'sencho.deploy-feedback.enabled';
@@ -18,8 +19,8 @@ export function useDeployFeedbackEnabled(): [boolean, (next: boolean) => void] {
function onSettingsChanged() {
setEnabledState(readStored());
}
window.addEventListener('SENCHO_SETTINGS_CHANGED', onSettingsChanged);
return () => window.removeEventListener('SENCHO_SETTINGS_CHANGED', onSettingsChanged);
window.addEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
return () => window.removeEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
}, []);
useEffect(() => {
@@ -38,7 +39,7 @@ export function useDeployFeedbackEnabled(): [boolean, (next: boolean) => void] {
// ignore; localStorage may be unavailable (private mode, quota)
}
setEnabledState(next);
window.dispatchEvent(new CustomEvent('SENCHO_SETTINGS_CHANGED'));
window.dispatchEvent(new CustomEvent(SENCHO_SETTINGS_CHANGED));
}, []);
return [enabled, setEnabled];