Files
sencho/frontend/src/hooks/use-deploy-feedback-enabled.ts
T
Anso a25acbec7c 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
2026-04-30 22:01:17 -04:00

47 lines
1.6 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { SENCHO_SETTINGS_CHANGED } from '@/lib/events';
export const DEPLOY_FEEDBACK_KEY = 'sencho.deploy-feedback.enabled';
function readStored(): boolean {
if (typeof window === 'undefined') return false;
try {
return window.localStorage.getItem(DEPLOY_FEEDBACK_KEY) === 'true';
} catch {
return false;
}
}
export function useDeployFeedbackEnabled(): [boolean, (next: boolean) => void] {
const [enabled, setEnabledState] = useState<boolean>(readStored);
useEffect(() => {
function onSettingsChanged() {
setEnabledState(readStored());
}
window.addEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
return () => window.removeEventListener(SENCHO_SETTINGS_CHANGED, onSettingsChanged);
}, []);
useEffect(() => {
function onStorage(event: StorageEvent) {
if (event.key !== DEPLOY_FEEDBACK_KEY) return;
setEnabledState(event.newValue === 'true');
}
window.addEventListener('storage', onStorage);
return () => window.removeEventListener('storage', onStorage);
}, []);
const setEnabled = useCallback((next: boolean) => {
try {
window.localStorage.setItem(DEPLOY_FEEDBACK_KEY, next ? 'true' : 'false');
} catch {
// ignore; localStorage may be unavailable (private mode, quota)
}
setEnabledState(next);
window.dispatchEvent(new CustomEvent(SENCHO_SETTINGS_CHANGED));
}, []);
return [enabled, setEnabled];
}