From aeac25900fbff6503109881db31d08db271aef22 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Tue, 28 Jul 2026 22:39:33 -0400 Subject: [PATCH 01/10] ENG-2084 Add schema import UI for Obsidian --- .../src/components/GeneralSettings.tsx | 21 ++ .../components/ImportSchemaPreviewSummary.tsx | 59 +++++ .../src/components/ImportSpecsModal.tsx | 213 ++++++++++++++++++ apps/obsidian/src/utils/registerCommands.ts | 9 + 4 files changed, 302 insertions(+) create mode 100644 apps/obsidian/src/components/ImportSchemaPreviewSummary.tsx create mode 100644 apps/obsidian/src/components/ImportSpecsModal.tsx diff --git a/apps/obsidian/src/components/GeneralSettings.tsx b/apps/obsidian/src/components/GeneralSettings.tsx index 9c05c07d2..563fb6fdb 100644 --- a/apps/obsidian/src/components/GeneralSettings.tsx +++ b/apps/obsidian/src/components/GeneralSettings.tsx @@ -4,6 +4,7 @@ import { setIcon } from "obsidian"; import SuggestInput from "./SuggestInput"; import { DiscourseGraphLogoIcon, SlackLogoIcon } from "./Icons"; import { openExportSpecsModal } from "./ExportSpecsModal"; +import { openImportSpecsModal } from "./ImportSpecsModal"; import { getDgSchemaFileName } from "~/utils/specValidation"; const DOCS_URL = "https://discoursegraphs.com/docs/obsidian"; @@ -273,6 +274,26 @@ const GeneralSettings = () => { +
+
+
Import discourse graph schema
+
+ Choose a schema JSON file from your computer and preview how it maps + to your existing node types, relation types, relation triples, and + templates. +
+
+
+ +
+
+
Node tag hotkey
diff --git a/apps/obsidian/src/components/ImportSchemaPreviewSummary.tsx b/apps/obsidian/src/components/ImportSchemaPreviewSummary.tsx new file mode 100644 index 000000000..5ba1f0e5f --- /dev/null +++ b/apps/obsidian/src/components/ImportSchemaPreviewSummary.tsx @@ -0,0 +1,59 @@ +import type { ImportPreviewStats, LoadedSchemaFile } from "~/utils/specImport"; + +export const ImportSchemaPreviewSummary = ({ + loadedSchemaFile, + previewStats, +}: { + loadedSchemaFile: LoadedSchemaFile; + previewStats: ImportPreviewStats; +}) => { + return ( + <> +
+
Schema file metadata
+
+ Vault:{" "} + + {loadedSchemaFile.schemaFile.vaultName} + +
+
+ Exported at:{" "} + + {loadedSchemaFile.schemaFile.exportedAt} + +
+
+ Plugin version:{" "} + + {loadedSchemaFile.schemaFile.pluginVersion} + +
+
+ +
+
Preview (full schema file)
+
+ Node types: {previewStats.nodeTypes.total} total ( + {previewStats.nodeTypes.new} new, {previewStats.nodeTypes.existing}{" "} + existing) +
+
+ Relation types: {previewStats.relationTypes.total} total ( + {previewStats.relationTypes.new} new,{" "} + {previewStats.relationTypes.existing} existing) +
+
+ Relation triples: {previewStats.discourseRelations.total} total ( + {previewStats.discourseRelations.new} new,{" "} + {previewStats.discourseRelations.existing} existing) +
+
+ Templates: {previewStats.templates.total} total ( + {previewStats.templates.new} new, {previewStats.templates.existing}{" "} + existing) +
+
+ + ); +}; diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx new file mode 100644 index 000000000..7558080e8 --- /dev/null +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -0,0 +1,213 @@ +import { App, Notice } from "obsidian"; +import { useMemo, useState } from "react"; +import type DiscourseGraphPlugin from "~/index"; +import { + applySchemaImportSelection, + pickAndPreviewSchemaImport, + type ImportPreviewStats, + type LoadedSchemaFile, + type SpecImportPreview, +} from "~/utils/specImport"; +import { NativeFileDialogCancelledError } from "~/utils/nativeJsonFileDialogs"; +import { + useSchemaSelection, + type SchemaSelectionSource, +} from "~/components/useSchemaSelection"; +import { SchemaSelectionModalBody } from "~/components/SchemaSelectionModalBody"; +import { ImportSchemaPreviewSummary } from "~/components/ImportSchemaPreviewSummary"; +import { ReactRootModal } from "~/components/ReactRootModal"; + +type ImportSpecsModalProps = { + plugin: DiscourseGraphPlugin; + onClose: () => void; +}; + +export const openImportSpecsModal = (plugin: DiscourseGraphPlugin): void => { + new ImportSpecsModal(plugin.app, plugin).open(); +}; + +const ImportPreviewSelection = ({ + plugin, + loadedSchemaFile, + previewStats, + isApplyingImport, + setIsApplyingImport, + onResetPreview, + onClose, +}: { + plugin: DiscourseGraphPlugin; + loadedSchemaFile: LoadedSchemaFile; + previewStats: ImportPreviewStats; + isApplyingImport: boolean; + setIsApplyingImport: (value: boolean) => void; + onResetPreview: () => void; + onClose: () => void; +}) => { + const source = useMemo(() => { + const schemaFile = loadedSchemaFile.schemaFile; + return { + nodeTypes: schemaFile.nodeTypes, + relationTypes: schemaFile.relationTypes, + relationTriples: schemaFile.discourseRelations, + templateNames: schemaFile.templates.map((template) => template.name), + }; + }, [loadedSchemaFile]); + + const selection = useSchemaSelection({ + source, + resetKey: loadedSchemaFile.sourcePath, + }); + + const handleApplyImport = async (): Promise => { + const selected = selection.asSelectionPayload(); + const hasAnySelection = + selected.nodeTypeIds.length > 0 || + selected.relationTypeIds.length > 0 || + selected.relationIds.length > 0 || + selected.templateNames.length > 0; + if (!hasAnySelection) { + new Notice("Select at least one item to import."); + return; + } + + setIsApplyingImport(true); + try { + const result = await applySchemaImportSelection({ + plugin, + loadedSchemaFile, + selection: { + nodeTypeIds: selected.nodeTypeIds, + relationTypeIds: selected.relationTypeIds, + discourseRelationIds: selected.relationIds, + templateNames: selected.templateNames, + }, + }); + + const { created } = result; + new Notice( + `Import complete: ${created.nodeTypes} node type(s), ${created.relationTypes} relation type(s), ${created.discourseRelations} relation triple(s), and ${created.templates} template(s) created.`, + 7000, + ); + + if (result.warnings.length > 0) { + new Notice( + `Import completed with ${result.warnings.length} warning(s).`, + 6000, + ); + for (const warning of result.warnings) { + new Notice(warning, 6000); + } + } + onClose(); + } catch (error) { + console.error("Failed to apply schema import:", error); + const message = error instanceof Error ? error.message : String(error); + new Notice(`Failed to import schema: ${message}`, 6000); + } finally { + setIsApplyingImport(false); + } + }; + + return ( + new Notice(message)} + beforePanel={ + + } + footerSecondaryLabel="Choose another file" + onFooterSecondaryClick={onResetPreview} + footerPrimaryLabel={isApplyingImport ? "Importing..." : "Import selected"} + onFooterPrimaryClick={() => void handleApplyImport()} + isFooterSecondaryDisabled={isApplyingImport} + isFooterPrimaryDisabled={isApplyingImport} + /> + ); +}; + +const ImportSpecsContent = ({ plugin, onClose }: ImportSpecsModalProps) => { + const [preview, setPreview] = useState(null); + const [isSelectingFile, setIsSelectingFile] = useState(false); + const [isApplyingImport, setIsApplyingImport] = useState(false); + + const handleSelectSchemaFile = async (): Promise => { + setIsSelectingFile(true); + try { + const nextPreview = await pickAndPreviewSchemaImport({ plugin }); + setPreview(nextPreview); + } catch (error) { + if (error instanceof NativeFileDialogCancelledError) { + return; + } + console.error("Failed to load schema import file:", error); + const message = error instanceof Error ? error.message : String(error); + new Notice(`Failed to load schema file: ${message}`, 6000); + } finally { + setIsSelectingFile(false); + } + }; + + if (!preview) { + return ( +
+

Import discourse graph schema

+

+ Pick a dg-schema-*.json file from your computer to + preview and choose exactly what to import. +

+ +
+ Same dependency rules as export apply here during selection. +
+ +
+ + +
+
+ ); + } + + return ( + setPreview(null)} + onClose={onClose} + /> + ); +}; + +export class ImportSpecsModal extends ReactRootModal { + private plugin: DiscourseGraphPlugin; + + constructor(app: App, plugin: DiscourseGraphPlugin) { + super(app); + this.plugin = plugin; + } + + protected renderContent() { + return ( + this.close()} /> + ); + } +} diff --git a/apps/obsidian/src/utils/registerCommands.ts b/apps/obsidian/src/utils/registerCommands.ts index 29e7285d1..4d39c9ff4 100644 --- a/apps/obsidian/src/utils/registerCommands.ts +++ b/apps/obsidian/src/utils/registerCommands.ts @@ -15,6 +15,7 @@ import { addRelationIfRequested } from "~/components/canvas/utils/relationJsonUt import type { DiscourseNode } from "~/types"; import { TldrawView } from "~/components/canvas/TldrawView"; import { createBaseForNodeType } from "./baseForNodeType"; +import { openImportSpecsModal } from "~/components/ImportSpecsModal"; type ModifyNodeSubmitParams = { nodeType: DiscourseNode; @@ -201,6 +202,14 @@ export const registerCommands = (plugin: DiscourseGraphPlugin) => { callback: () => openExportSpecsModal(plugin), }); + plugin.addCommand({ + id: "import-dg-schema", + name: "Import discourse graph schema", + callback: () => { + openImportSpecsModal(plugin); + }, + }); + plugin.addCommand({ id: "toggle-discourse-context", name: "Toggle discourse context", From 5105e044c2ec3f16c494a389fe37dedd8b5af9f5 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Wed, 29 Jul 2026 15:44:10 -0400 Subject: [PATCH 02/10] ENG-2084 Inline Modal boilerplate in ImportSpecsModal (remove ReactRootModal abstraction) --- .../src/components/ImportSpecsModal.tsx | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index 7558080e8..7195c3be0 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -1,5 +1,6 @@ -import { App, Notice } from "obsidian"; -import { useMemo, useState } from "react"; +import { App, Modal, Notice } from "obsidian"; +import { StrictMode, useMemo, useState } from "react"; +import { createRoot, type Root } from "react-dom/client"; import type DiscourseGraphPlugin from "~/index"; import { applySchemaImportSelection, @@ -15,7 +16,6 @@ import { } from "~/components/useSchemaSelection"; import { SchemaSelectionModalBody } from "~/components/SchemaSelectionModalBody"; import { ImportSchemaPreviewSummary } from "~/components/ImportSchemaPreviewSummary"; -import { ReactRootModal } from "~/components/ReactRootModal"; type ImportSpecsModalProps = { plugin: DiscourseGraphPlugin; @@ -197,17 +197,29 @@ const ImportSpecsContent = ({ plugin, onClose }: ImportSpecsModalProps) => { ); }; -export class ImportSpecsModal extends ReactRootModal { +export class ImportSpecsModal extends Modal { private plugin: DiscourseGraphPlugin; + private root: Root | null = null; constructor(app: App, plugin: DiscourseGraphPlugin) { super(app); this.plugin = plugin; } - protected renderContent() { - return ( - this.close()} /> + onOpen(): void { + this.contentEl.empty(); + this.root = createRoot(this.contentEl); + this.root.render( + + this.close()} /> + , ); } + + onClose(): void { + if (this.root) { + this.root.unmount(); + this.root = null; + } + } } From 044c3462ce8cda633471f0f973b13d4a177478f3 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Wed, 29 Jul 2026 18:32:24 -0400 Subject: [PATCH 03/10] ENG-2084 Show human-readable error when schema file fails Zod validation --- apps/obsidian/src/components/ImportSpecsModal.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index 7195c3be0..b0a4727a7 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -1,6 +1,7 @@ import { App, Modal, Notice } from "obsidian"; import { StrictMode, useMemo, useState } from "react"; import { createRoot, type Root } from "react-dom/client"; +import { ZodError } from "zod"; import type DiscourseGraphPlugin from "~/index"; import { applySchemaImportSelection, @@ -143,10 +144,15 @@ const ImportSpecsContent = ({ plugin, onClose }: ImportSpecsModalProps) => { const nextPreview = await pickAndPreviewSchemaImport({ plugin }); setPreview(nextPreview); } catch (error) { - if (error instanceof NativeFileDialogCancelledError) { + if (error instanceof NativeFileDialogCancelledError) return; + if (error instanceof ZodError) { + const fields = error.issues.map((i) => i.path.join(".")).join(", "); + new Notice( + `Schema file is incompatible with this version of the plugin. Invalid or missing fields: ${fields}`, + 8000, + ); return; } - console.error("Failed to load schema import file:", error); const message = error instanceof Error ? error.message : String(error); new Notice(`Failed to load schema file: ${message}`, 6000); } finally { From 33264539a8cb8b4f0c1d840dbce26103cef55adb Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Wed, 29 Jul 2026 22:13:42 -0400 Subject: [PATCH 04/10] ENG-2084 Remove console.error (violates Obsidian plugin guidelines) --- apps/obsidian/src/components/ImportSpecsModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index b0a4727a7..f575bdaf0 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -101,7 +101,6 @@ const ImportPreviewSelection = ({ } onClose(); } catch (error) { - console.error("Failed to apply schema import:", error); const message = error instanceof Error ? error.message : String(error); new Notice(`Failed to import schema: ${message}`, 6000); } finally { From de4f3848d9dd0c6b15f1aa1cacbdcf6a319e4980 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Wed, 29 Jul 2026 23:16:31 -0400 Subject: [PATCH 05/10] ENG-2084 Remove emptyTemplateText/beforePanel props; compose ImportSchemaPreviewSummary as sibling Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/ImportSpecsModal.tsx | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index f575bdaf0..cad542594 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -109,26 +109,27 @@ const ImportPreviewSelection = ({ }; return ( - new Notice(message)} - beforePanel={ - - } - footerSecondaryLabel="Choose another file" - onFooterSecondaryClick={onResetPreview} - footerPrimaryLabel={isApplyingImport ? "Importing..." : "Import selected"} - onFooterPrimaryClick={() => void handleApplyImport()} - isFooterSecondaryDisabled={isApplyingImport} - isFooterPrimaryDisabled={isApplyingImport} - /> + <> + + new Notice(message)} + footerSecondaryLabel="Choose another file" + onFooterSecondaryClick={onResetPreview} + footerPrimaryLabel={ + isApplyingImport ? "Importing..." : "Import selected" + } + onFooterPrimaryClick={() => void handleApplyImport()} + isFooterSecondaryDisabled={isApplyingImport} + isFooterPrimaryDisabled={isApplyingImport} + /> + ); }; From 8ff65bc81639542add1dc8688de06085d51391ee Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 30 Jul 2026 12:53:07 -0400 Subject: [PATCH 06/10] ENG-2084 Address review: simplify ImportSpecsModal constructor, use SchemaSelection directly, batch warnings - Constructor takes only plugin (extracts app internally), matching ExportSpecsModal pattern - Pass payload directly to applySchemaImportSelection (no intermediate field mapping) - Batch import warnings into one Notice Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/ImportSpecsModal.tsx | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index cad542594..db41b9438 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -1,4 +1,4 @@ -import { App, Modal, Notice } from "obsidian"; +import { Modal, Notice } from "obsidian"; import { StrictMode, useMemo, useState } from "react"; import { createRoot, type Root } from "react-dom/client"; import { ZodError } from "zod"; @@ -24,7 +24,7 @@ type ImportSpecsModalProps = { }; export const openImportSpecsModal = (plugin: DiscourseGraphPlugin): void => { - new ImportSpecsModal(plugin.app, plugin).open(); + new ImportSpecsModal(plugin).open(); }; const ImportPreviewSelection = ({ @@ -64,7 +64,7 @@ const ImportPreviewSelection = ({ const hasAnySelection = selected.nodeTypeIds.length > 0 || selected.relationTypeIds.length > 0 || - selected.relationIds.length > 0 || + selected.discourseRelationIds.length > 0 || selected.templateNames.length > 0; if (!hasAnySelection) { new Notice("Select at least one item to import."); @@ -76,12 +76,7 @@ const ImportPreviewSelection = ({ const result = await applySchemaImportSelection({ plugin, loadedSchemaFile, - selection: { - nodeTypeIds: selected.nodeTypeIds, - relationTypeIds: selected.relationTypeIds, - discourseRelationIds: selected.relationIds, - templateNames: selected.templateNames, - }, + selection: selected, }); const { created } = result; @@ -92,12 +87,9 @@ const ImportPreviewSelection = ({ if (result.warnings.length > 0) { new Notice( - `Import completed with ${result.warnings.length} warning(s).`, + `Import warnings:\n${result.warnings.join("\n")}`, 6000, ); - for (const warning of result.warnings) { - new Notice(warning, 6000); - } } onClose(); } catch (error) { @@ -207,8 +199,8 @@ export class ImportSpecsModal extends Modal { private plugin: DiscourseGraphPlugin; private root: Root | null = null; - constructor(app: App, plugin: DiscourseGraphPlugin) { - super(app); + constructor(plugin: DiscourseGraphPlugin) { + super(plugin.app); this.plugin = plugin; } From cd7e537f54276d584e5ef31f9f003f474223eca2 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 30 Jul 2026 13:06:37 -0400 Subject: [PATCH 07/10] =?UTF-8?q?ENG-2084=20Remove=20useMemo=20from=20sour?= =?UTF-8?q?ce=20=E2=80=94=20schema=20file=20is=20stable=20for=20component?= =?UTF-8?q?=20lifetime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/ImportSpecsModal.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index db41b9438..40e8c6542 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -1,5 +1,5 @@ import { Modal, Notice } from "obsidian"; -import { StrictMode, useMemo, useState } from "react"; +import { StrictMode, useState } from "react"; import { createRoot, type Root } from "react-dom/client"; import { ZodError } from "zod"; import type DiscourseGraphPlugin from "~/index"; @@ -11,10 +11,7 @@ import { type SpecImportPreview, } from "~/utils/specImport"; import { NativeFileDialogCancelledError } from "~/utils/nativeJsonFileDialogs"; -import { - useSchemaSelection, - type SchemaSelectionSource, -} from "~/components/useSchemaSelection"; +import { useSchemaSelection } from "~/components/useSchemaSelection"; import { SchemaSelectionModalBody } from "~/components/SchemaSelectionModalBody"; import { ImportSchemaPreviewSummary } from "~/components/ImportSchemaPreviewSummary"; @@ -44,15 +41,13 @@ const ImportPreviewSelection = ({ onResetPreview: () => void; onClose: () => void; }) => { - const source = useMemo(() => { - const schemaFile = loadedSchemaFile.schemaFile; - return { - nodeTypes: schemaFile.nodeTypes, - relationTypes: schemaFile.relationTypes, - relationTriples: schemaFile.discourseRelations, - templateNames: schemaFile.templates.map((template) => template.name), - }; - }, [loadedSchemaFile]); + const schemaFile = loadedSchemaFile.schemaFile; + const source = { + nodeTypes: schemaFile.nodeTypes, + relationTypes: schemaFile.relationTypes, + relationTriples: schemaFile.discourseRelations, + templateNames: schemaFile.templates.map((template) => template.name), + }; const selection = useSchemaSelection({ source, From 69deb70c508f3991ec37ca845841e01546fb7b02 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 30 Jul 2026 13:18:14 -0400 Subject: [PATCH 08/10] ENG-2084 Use onWarning callback in applySchemaImportSelection Co-Authored-By: Claude Sonnet 4.6 --- apps/obsidian/src/components/ImportSpecsModal.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index 40e8c6542..888acb1fe 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -67,24 +67,21 @@ const ImportPreviewSelection = ({ } setIsApplyingImport(true); + const warnings: string[] = []; try { - const result = await applySchemaImportSelection({ + const { created } = await applySchemaImportSelection({ plugin, loadedSchemaFile, selection: selected, + onWarning: (message) => warnings.push(message), }); - const { created } = result; new Notice( `Import complete: ${created.nodeTypes} node type(s), ${created.relationTypes} relation type(s), ${created.discourseRelations} relation triple(s), and ${created.templates} template(s) created.`, 7000, ); - - if (result.warnings.length > 0) { - new Notice( - `Import warnings:\n${result.warnings.join("\n")}`, - 6000, - ); + if (warnings.length > 0) { + new Notice(`Import warnings:\n${warnings.join("\n")}`, 6000); } onClose(); } catch (error) { From f8d4c97934dc30b7c7149e79597ce1af09e8ad48 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 30 Jul 2026 16:23:05 -0400 Subject: [PATCH 09/10] ENG-2084 Drop schema import entry from settings Import stays reachable through the import-dg-schema command in the palette; the settings entry duplicated it. Co-Authored-By: Claude Opus 5 --- .../src/components/GeneralSettings.tsx | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/apps/obsidian/src/components/GeneralSettings.tsx b/apps/obsidian/src/components/GeneralSettings.tsx index 563fb6fdb..9c05c07d2 100644 --- a/apps/obsidian/src/components/GeneralSettings.tsx +++ b/apps/obsidian/src/components/GeneralSettings.tsx @@ -4,7 +4,6 @@ import { setIcon } from "obsidian"; import SuggestInput from "./SuggestInput"; import { DiscourseGraphLogoIcon, SlackLogoIcon } from "./Icons"; import { openExportSpecsModal } from "./ExportSpecsModal"; -import { openImportSpecsModal } from "./ImportSpecsModal"; import { getDgSchemaFileName } from "~/utils/specValidation"; const DOCS_URL = "https://discoursegraphs.com/docs/obsidian"; @@ -274,26 +273,6 @@ const GeneralSettings = () => {
-
-
-
Import discourse graph schema
-
- Choose a schema JSON file from your computer and preview how it maps - to your existing node types, relation types, relation triples, and - templates. -
-
-
- -
-
-
Node tag hotkey
From b75cbd1144547325bca0aff104f6303bca6d6c3f Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Thu, 30 Jul 2026 16:27:27 -0400 Subject: [PATCH 10/10] ENG-2084 Reset applying flag only on the failure path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The success path calls onClose() and unmounts, so the finally block was setting state on an unmounted component. Harmless — React removed that warning in 18.0 and it is a silent no-op — but the ordering read as if the reset mattered after close. Only the catch stays mounted, so only it resets. Co-Authored-By: Claude Opus 5 --- apps/obsidian/src/components/ImportSpecsModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/obsidian/src/components/ImportSpecsModal.tsx b/apps/obsidian/src/components/ImportSpecsModal.tsx index 888acb1fe..0af30bef4 100644 --- a/apps/obsidian/src/components/ImportSpecsModal.tsx +++ b/apps/obsidian/src/components/ImportSpecsModal.tsx @@ -87,7 +87,7 @@ const ImportPreviewSelection = ({ } catch (error) { const message = error instanceof Error ? error.message : String(error); new Notice(`Failed to import schema: ${message}`, 6000); - } finally { + // Only the failure path stays mounted; the success path unmounted at onClose() setIsApplyingImport(false); } };