fix: resolve 3 real bugs found in PR review
- ConfigEditorDialog: wrap api.config.get() in try/catch to prevent dialog from getting stuck in loading state on IPC failure - CreateTaskDialog: clear description and chips when dialog opens without defaults, preventing stale state from previous opens - useAttachments: handle persistenceKey→undefined transition by flushing pending saves and clearing stale attachment state
This commit is contained in:
parent
64f2d3f413
commit
4cc297690a
3 changed files with 64 additions and 47 deletions
|
|
@ -175,57 +175,63 @@ export const ConfigEditorDialog = ({
|
||||||
setJsonError(null);
|
setJsonError(null);
|
||||||
|
|
||||||
const init = async (): Promise<void> => {
|
const init = async (): Promise<void> => {
|
||||||
const config = await api.config.get();
|
try {
|
||||||
if (destroyed) return;
|
const config = await api.config.get();
|
||||||
|
if (destroyed) return;
|
||||||
|
|
||||||
const jsonText = JSON.stringify(config, null, 2);
|
const jsonText = JSON.stringify(config, null, 2);
|
||||||
initialConfigRef.current = jsonText;
|
initialConfigRef.current = jsonText;
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
// Wait for DOM render
|
// Wait for DOM render
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (destroyed || !editorRef.current) return;
|
if (destroyed || !editorRef.current) return;
|
||||||
|
|
||||||
// Clean up existing view
|
// Clean up existing view
|
||||||
if (viewRef.current) {
|
if (viewRef.current) {
|
||||||
viewRef.current.destroy();
|
viewRef.current.destroy();
|
||||||
viewRef.current = null;
|
viewRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
doc: jsonText,
|
doc: jsonText,
|
||||||
extensions: [
|
extensions: [
|
||||||
lineNumbers(),
|
lineNumbers(),
|
||||||
highlightActiveLineGutter(),
|
highlightActiveLineGutter(),
|
||||||
highlightActiveLine(),
|
highlightActiveLine(),
|
||||||
history(),
|
history(),
|
||||||
foldGutter(),
|
foldGutter(),
|
||||||
indentOnInput(),
|
indentOnInput(),
|
||||||
bracketMatching(),
|
bracketMatching(),
|
||||||
json(),
|
json(),
|
||||||
syntaxHighlighting(oneDarkHighlightStyle),
|
syntaxHighlighting(oneDarkHighlightStyle),
|
||||||
jsonLinter,
|
jsonLinter,
|
||||||
lintGutter(),
|
lintGutter(),
|
||||||
search(),
|
search(),
|
||||||
keymap.of([...defaultKeymap, ...historyKeymap, ...foldKeymap, ...searchKeymap]),
|
keymap.of([...defaultKeymap, ...historyKeymap, ...foldKeymap, ...searchKeymap]),
|
||||||
baseEditorTheme,
|
baseEditorTheme,
|
||||||
configEditorTheme,
|
configEditorTheme,
|
||||||
// eslint-disable-next-line sonarjs/no-nested-functions -- CodeMirror listener callback within useEffect setup
|
// eslint-disable-next-line sonarjs/no-nested-functions -- CodeMirror listener callback within useEffect setup
|
||||||
EditorView.updateListener.of((update) => {
|
EditorView.updateListener.of((update) => {
|
||||||
if (update.docChanged) {
|
if (update.docChanged) {
|
||||||
const text = update.state.doc.toString();
|
const text = update.state.doc.toString();
|
||||||
scheduleSave(text);
|
scheduleSave(text);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const view = new EditorView({
|
||||||
|
state,
|
||||||
|
parent: editorRef.current,
|
||||||
|
});
|
||||||
|
viewRef.current = view;
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
const view = new EditorView({
|
if (destroyed) return;
|
||||||
state,
|
setLoading(false);
|
||||||
parent: editorRef.current,
|
setJsonError(e instanceof Error ? e.message : 'Failed to load config');
|
||||||
});
|
}
|
||||||
viewRef.current = view;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,10 @@ export const CreateTaskDialog = ({
|
||||||
descChipDraft.setChips([defaultChip]);
|
descChipDraft.setChips([defaultChip]);
|
||||||
} else if (defaultDescription) {
|
} else if (defaultDescription) {
|
||||||
descriptionDraft.setValue(defaultDescription);
|
descriptionDraft.setValue(defaultDescription);
|
||||||
|
descChipDraft.clearChipDraft();
|
||||||
|
} else {
|
||||||
|
descriptionDraft.clearDraft();
|
||||||
|
descChipDraft.clearChipDraft();
|
||||||
}
|
}
|
||||||
setOwner(defaultOwner);
|
setOwner(defaultOwner);
|
||||||
setBlockedBy([]);
|
setBlockedBy([]);
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,14 @@ export function useAttachments(options?: UseAttachmentsOptions): UseAttachmentsR
|
||||||
|
|
||||||
// Load persisted attachments on mount
|
// Load persisted attachments on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!persistenceKey) return;
|
if (!persistenceKey) {
|
||||||
|
// Transitioning to non-persistent context: flush pending save and clear stale state
|
||||||
|
flushPending();
|
||||||
|
attachmentsRef.current = [];
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional sync reset on key transition
|
||||||
|
setAttachments([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
// Flush any pending debounced save for the previous key before switching.
|
// Flush any pending debounced save for the previous key before switching.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue