From 88e00370ee05d68edf4d3691656ed20ca08e2bab Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 19 Aug 2026 23:16:40 +0530 Subject: [PATCH 1/4] ENG-2154 Undecorate Obsidian node titles into core_title on publish --- apps/obsidian/src/utils/conceptConversion.ts | 6 ++++++ apps/obsidian/src/utils/syncDgNodesToSupabase.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/obsidian/src/utils/conceptConversion.ts b/apps/obsidian/src/utils/conceptConversion.ts index 339311234..931ff36ab 100644 --- a/apps/obsidian/src/utils/conceptConversion.ts +++ b/apps/obsidian/src/utils/conceptConversion.ts @@ -10,6 +10,7 @@ import type { DiscourseNodeInVault } from "./getDiscourseNodes"; import type { LocalConceptDataInput } from "@repo/database/inputTypes"; import type { ObsidianDiscourseNodeData } from "./syncDgNodesToSupabase"; import type { Json } from "@repo/database/dbTypes"; +import { extractContentFromTitle } from "./extractContentFromTitle"; /** * Get extra data (author, timestamps) from file metadata @@ -160,12 +161,17 @@ export const discourseRelationTripleSchemaToLocalConcept = ({ export const discourseNodeInstanceToLocalConcept = ( context: SupabaseContext, nodeData: ObsidianDiscourseNodeData, + nodeTypesById: Record, ): LocalConceptDataInput => { const extraData = getNodeExtraData(nodeData.file, context.userId); const { nodeInstanceId, nodeTypeId, importedFromRid, ...otherData } = nodeData.frontmatter; const literal_content: Record = { label: nodeData.file.basename, + core_title: extractContentFromTitle( + nodeTypesById[nodeData.nodeTypeId]?.format ?? "", + nodeData.file.basename, + ), source_data: otherData as unknown as Json, }; if (importedFromRid && typeof importedFromRid === "string") diff --git a/apps/obsidian/src/utils/syncDgNodesToSupabase.ts b/apps/obsidian/src/utils/syncDgNodesToSupabase.ts index 9eb73e40a..d3d0f80ba 100644 --- a/apps/obsidian/src/utils/syncDgNodesToSupabase.ts +++ b/apps/obsidian/src/utils/syncDgNodesToSupabase.ts @@ -594,7 +594,7 @@ const convertDgToSupabaseConcepts = async ({ .filter((n) => !!n); const nodeInstanceToLocalConcepts = nodesSince.map((node) => { - return discourseNodeInstanceToLocalConcept(context, node); + return discourseNodeInstanceToLocalConcept(context, node, nodeTypesById); }); const relationInstancesData = await loadRelations(plugin); From c8385a3625a6bdd16d56d596b69faea71987dc05 Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 24 Aug 2026 16:41:17 +0530 Subject: [PATCH 2/4] Extract {content} by placeholder position and preserve matched-empty captures --- .../src/utils/extractContentFromTitle.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/obsidian/src/utils/extractContentFromTitle.ts b/apps/obsidian/src/utils/extractContentFromTitle.ts index 1d5153d7d..f9b619ea8 100644 --- a/apps/obsidian/src/utils/extractContentFromTitle.ts +++ b/apps/obsidian/src/utils/extractContentFromTitle.ts @@ -1,10 +1,25 @@ import { getDiscourseNodeFormatExpression } from "./getDiscourseNodeFormatExpression"; -export const extractContentFromTitle = (format: string, title: string): string => { +export const extractContentFromTitle = ( + format: string, + title: string, +): string => { if (!format) return title; + const placeholderRegex = /{([a-zA-Z]+)}/g; + const placeholders: string[] = []; + let placeholderMatch: RegExpExecArray | null = null; + while ((placeholderMatch = placeholderRegex.exec(format))) { + const name = placeholderMatch[1]; + if (name !== undefined) placeholders.push(name); + } const regex = getDiscourseNodeFormatExpression(format); - const match = title.match(regex); + const match = regex.exec(title); + if (!match) return title; - return match?.[1]?.trim() || title; + const contentIndex = placeholders.findIndex( + (name) => name.toLowerCase() === "content", + ); + const capture = contentIndex >= 0 ? match[contentIndex + 1] : match[1]; + return capture === undefined ? title : capture.trim(); }; From 7fe37bb12970510086561c6bd29b4acd8aa8bbc3 Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 24 Aug 2026 16:41:18 +0530 Subject: [PATCH 3/4] Use named parameters for discourseNodeInstanceToLocalConcept and drop unused accountLocalId param --- apps/obsidian/src/utils/conceptConversion.ts | 14 +++++++++----- apps/obsidian/src/utils/syncDgNodesToSupabase.ts | 10 +++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/obsidian/src/utils/conceptConversion.ts b/apps/obsidian/src/utils/conceptConversion.ts index 931ff36ab..0d72c6e62 100644 --- a/apps/obsidian/src/utils/conceptConversion.ts +++ b/apps/obsidian/src/utils/conceptConversion.ts @@ -158,11 +158,15 @@ export const discourseRelationTripleSchemaToLocalConcept = ({ /** * Convert discourse node instance (file) to LocalConceptDataInput */ -export const discourseNodeInstanceToLocalConcept = ( - context: SupabaseContext, - nodeData: ObsidianDiscourseNodeData, - nodeTypesById: Record, -): LocalConceptDataInput => { +export const discourseNodeInstanceToLocalConcept = ({ + context, + nodeData, + nodeTypesById, +}: { + context: SupabaseContext; + nodeData: ObsidianDiscourseNodeData; + nodeTypesById: Record; +}): LocalConceptDataInput => { const extraData = getNodeExtraData(nodeData.file, context.userId); const { nodeInstanceId, nodeTypeId, importedFromRid, ...otherData } = nodeData.frontmatter; diff --git a/apps/obsidian/src/utils/syncDgNodesToSupabase.ts b/apps/obsidian/src/utils/syncDgNodesToSupabase.ts index d3d0f80ba..aa740ce13 100644 --- a/apps/obsidian/src/utils/syncDgNodesToSupabase.ts +++ b/apps/obsidian/src/utils/syncDgNodesToSupabase.ts @@ -452,7 +452,6 @@ export const syncAllNodesAndRelations = async ( nodesSince: changedNodeInstances, supabaseClient, context, - accountLocalId, plugin, allNodes, fullSync: true, @@ -470,7 +469,6 @@ const convertDgToSupabaseConcepts = async ({ nodesSince, supabaseClient, context, - accountLocalId, plugin, allNodes, fullSync, @@ -478,7 +476,6 @@ const convertDgToSupabaseConcepts = async ({ nodesSince: ObsidianDiscourseNodeData[]; supabaseClient: DGSupabaseClient; context: SupabaseContext; - accountLocalId: string; plugin: DiscourseGraphPlugin; allNodes?: DiscourseNodeInVault[]; fullSync?: boolean; @@ -594,7 +591,11 @@ const convertDgToSupabaseConcepts = async ({ .filter((n) => !!n); const nodeInstanceToLocalConcepts = nodesSince.map((node) => { - return discourseNodeInstanceToLocalConcept(context, node, nodeTypesById); + return discourseNodeInstanceToLocalConcept({ + context, + nodeData: node, + nodeTypesById, + }); }); const relationInstancesData = await loadRelations(plugin); @@ -817,7 +818,6 @@ const syncChangedNodesToSupabase = async ({ nodesSince: nodesNeedingConceptUpsert, supabaseClient, context, - accountLocalId, plugin, }); From 2ca1a8f54e173fbd31a076a371d68486ddf9c04a Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 24 Aug 2026 16:56:13 +0530 Subject: [PATCH 4/4] Keep placeholder index alignment on empty scan captures and drop dead initializer --- apps/obsidian/src/utils/extractContentFromTitle.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/obsidian/src/utils/extractContentFromTitle.ts b/apps/obsidian/src/utils/extractContentFromTitle.ts index f9b619ea8..4a4bd0042 100644 --- a/apps/obsidian/src/utils/extractContentFromTitle.ts +++ b/apps/obsidian/src/utils/extractContentFromTitle.ts @@ -8,10 +8,9 @@ export const extractContentFromTitle = ( const placeholderRegex = /{([a-zA-Z]+)}/g; const placeholders: string[] = []; - let placeholderMatch: RegExpExecArray | null = null; + let placeholderMatch: RegExpExecArray | null; while ((placeholderMatch = placeholderRegex.exec(format))) { - const name = placeholderMatch[1]; - if (name !== undefined) placeholders.push(name); + placeholders.push(placeholderMatch[1] ?? ""); } const regex = getDiscourseNodeFormatExpression(format); const match = regex.exec(title);