From cd16a666e0a77b4a5398eda1a0b407d2d7fa107e Mon Sep 17 00:00:00 2001 From: sid597 Date: Tue, 10 Mar 2026 14:42:50 +0530 Subject: [PATCH 1/5] ENG-1519: Add legacy-to-blockprops migration, remove backedBy from schema --- .../settings/DiscourseNodeConfigPanel.tsx | 1 - .../components/settings/utils/accessors.ts | 41 ++- .../src/components/settings/utils/init.ts | 7 +- .../utils/migrateLegacyToBlockProps.ts | 269 ++++++++++++++++++ .../settings/utils/zodSchema.example.ts | 1 - .../components/settings/utils/zodSchema.ts | 4 - 6 files changed, 315 insertions(+), 8 deletions(-) create mode 100644 apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts diff --git a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx index c1b2d7cc9..1556b3042 100644 --- a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx +++ b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx @@ -107,7 +107,6 @@ const DiscourseNodeConfigPanel: React.FC = ({ type: valueUid, shortcut, format, - backedBy: "user", }), ); setNodes([ diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index d2f85bcbb..61263b955 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -10,6 +10,7 @@ import { getSubTree } from "roamjs-components/util"; import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree"; import internalError from "~/utils/internalError"; import { getSetting } from "~/utils/extensionSettings"; +import { USE_REIFIED_RELATIONS } from "~/data/userSettings"; import { getFormattedConfigTree } from "~/utils/discourseConfigRef"; import { roamNodeToCondition } from "~/utils/parseQuery"; import type { DiscourseRelation } from "~/utils/getDiscourseRelations"; @@ -32,7 +33,7 @@ import { type DiscourseNodeSettings, type Condition as SchemaCondition, } from "./zodSchema"; -import { PERSONAL_KEYS, QUERY_KEYS } from "./settingKeys"; +import { PERSONAL_KEYS, QUERY_KEYS, GLOBAL_KEYS } from "./settingKeys"; const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); @@ -718,6 +719,44 @@ export const isNewSettingsStoreEnabled = (): boolean => { return getFeatureFlag("Use new settings store"); }; +export const readAllLegacyFeatureFlags = (): Partial => { + const flags: Partial = {}; + for (const [key, reader] of Object.entries(FEATURE_FLAG_LEGACY_MAP)) { + flags[key as keyof FeatureFlags] = reader(); + } + flags["Reified relation triples"] = getSetting( + USE_REIFIED_RELATIONS, + false, + ); + flags["Use new settings store"] = false; + return flags; +}; + +export const readAllLegacyGlobalSettings = (): Record => { + const result: Record = {}; + for (const key of Object.values(GLOBAL_KEYS)) { + result[key] = getLegacyGlobalSetting([key]); + } + return result; +}; + +export const readAllLegacyPersonalSettings = (): Record => { + const result: Record = {}; + for (const key of Object.values(PERSONAL_KEYS)) { + result[key] = getLegacyPersonalSetting([key]); + } + return result; +}; + +export const readAllLegacyDiscourseNodeSettings = ( + nodeType: string, + nodeTitle: string, +): Record | undefined => { + const raw = getLegacyDiscourseNodeSetting(nodeType, []); + if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined; + return { ...(raw as Record), text: nodeTitle }; +}; + export const setFeatureFlag = ( key: keyof FeatureFlags, value: boolean, diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 90536774f..86e19ed82 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -7,6 +7,10 @@ import type { json } from "~/utils/getBlockProps"; import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes"; import DEFAULT_RELATIONS_BLOCK_PROPS from "~/components/settings/data/defaultRelationsBlockProps"; import { getAllDiscourseNodes } from "./accessors"; +import { + migrateGraphLevel, + migratePersonalSettings, +} from "./migrateLegacyToBlockProps"; import { DiscourseNodeSchema, getTopLevelBlockPropsConfig, @@ -147,7 +151,6 @@ const initSingleDiscourseNode = async ( tag: node.tag || "", graphOverview: node.graphOverview ?? false, canvasSettings: node.canvasSettings || {}, - backedBy: "user", }); setBlockProps(pageUid, nodeData, false); @@ -256,6 +259,8 @@ export type InitSchemaResult = { export const initSchema = async (): Promise => { const blockUids = await initSettingsPageBlocks(); + await migrateGraphLevel(blockUids); const nodePageUids = await initDiscourseNodePages(); + await migratePersonalSettings(blockUids); return { blockUids, nodePageUids }; }; diff --git a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts new file mode 100644 index 000000000..c9d942fb6 --- /dev/null +++ b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts @@ -0,0 +1,269 @@ +import getBlockProps from "~/utils/getBlockProps"; +import type { json } from "~/utils/getBlockProps"; +import setBlockProps from "~/utils/setBlockProps"; +import getBlockUidByTextOnPage from "roamjs-components/queries/getBlockUidByTextOnPage"; +import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; +import { createBlock } from "roamjs-components/writes"; +import { getSetting, setSetting } from "~/utils/extensionSettings"; +import { + readAllLegacyFeatureFlags, + readAllLegacyGlobalSettings, + readAllLegacyPersonalSettings, + readAllLegacyDiscourseNodeSettings, +} from "./accessors"; +import { + FeatureFlagsSchema, + GlobalSettingsSchema, + PersonalSettingsSchema, + DiscourseNodeSchema, + DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, + DISCOURSE_NODE_PAGE_PREFIX, + TOP_LEVEL_BLOCK_PROP_KEYS, + getPersonalSettingsKey, +} from "./zodSchema"; +import type { z } from "zod"; + +const LOG_PREFIX = "[DG BlockProps Migration]"; +const GRAPH_MIGRATION_MARKER = "Block props migrated"; +const PERSONAL_MIGRATION_MARKER = "dg-personal-settings-migrated"; + +const hasGraphMigrationMarker = (): boolean => + !!getBlockUidByTextOnPage({ + text: GRAPH_MIGRATION_MARKER, + title: DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, + }); + +const isPropsValid = ( + schema: z.ZodTypeAny, + props: Record | null, +): boolean => + !!props && Object.keys(props).length > 0 && schema.safeParse(props).success; + +const shouldWrite = ( + schema: z.ZodTypeAny, + currentProps: Record | null, + parsedLegacy: Record, +): boolean => { + if (!isPropsValid(schema, currentProps)) { + return true; + } + + // safeParse because some schemas (DiscourseNodeSchema) have required fields + // with no defaults, so parse({}) would throw. + const defaultsResult = schema.safeParse({}); + if (!defaultsResult.success) { + // Can't determine schema defaults (e.g. DiscourseNodeSchema). + // Compare Zod-normalized parsed legacy against current props directly. + // Both sides are normalized so the comparison is apples-to-apples. + // Safe on retry: if prior run already wrote parsedLegacy, they'll match + // → skip. If user edited via settings UI, dual-write keeps both sides in + // sync → match → skip. The only write happens when legacy genuinely + // differs from current (first migration or tree-only edit). + return JSON.stringify(parsedLegacy) !== JSON.stringify(currentProps); + } + + const defaults = defaultsResult.data as Record; + const propsMatch = JSON.stringify(currentProps) === JSON.stringify(defaults); + const legacyDiffers = + JSON.stringify(parsedLegacy) !== JSON.stringify(defaults); + + return propsMatch && legacyDiffers; +}; + +const migrateSection = ({ + label, + blockUid, + schema, + legacyData, +}: { + label: string; + blockUid: string; + schema: z.ZodTypeAny; + legacyData: Record; +}): boolean => { + const currentProps = getBlockProps(blockUid); + + const parseResult = schema.safeParse(legacyData); + if (!parseResult.success) { + // Legacy malformed — succeed if current props are already valid. + // migrateGraphLevel runs before initDiscourseNodePages, so valid props + // at this point were written by a prior migration run, not init-seeded. + if (isPropsValid(schema, currentProps)) { + console.log( + `${LOG_PREFIX} ${label}: legacy malformed but props already valid, skipping`, + ); + return true; + } + console.warn(`${LOG_PREFIX} ${label}: Zod validation failed, skipping`, { + error: parseResult.error.message, + }); + return false; + } + + const parsedLegacy = parseResult.data as Record; + if (!shouldWrite(schema, currentProps, parsedLegacy)) { + console.log(`${LOG_PREFIX} ${label}: props already non-default, skipping`); + return true; + } + + setBlockProps(blockUid, parsedLegacy, false); + console.log(`${LOG_PREFIX} ${label}: migrated`); + return true; +}; + +const migrateDiscourseNodes = (): boolean => { + const nodePages = window.roamAlphaAPI.data.fast.q(` + [:find ?uid ?title + :where + [?page :node/title ?title] + [?page :block/uid ?uid] + [(clojure.string/starts-with? ?title "${DISCOURSE_NODE_PAGE_PREFIX}")]] + `) as [string, string][]; + + let allOk = true; + + for (const [nodePageUid, title] of nodePages) { + if (typeof nodePageUid !== "string" || typeof title !== "string") continue; + + const nodeText = title.replace(DISCOURSE_NODE_PAGE_PREFIX, ""); + const legacyData = readAllLegacyDiscourseNodeSettings( + nodePageUid, + nodeText, + ); + if (!legacyData) { + // Legacy unreadable — if current props are already valid, treat as + // success so a missing/malformed legacy tree doesn't block the marker. + if (isPropsValid(DiscourseNodeSchema, getBlockProps(nodePageUid))) { + console.log( + `${LOG_PREFIX} Discourse Node (${nodeText}): legacy unreadable but props already valid, skipping`, + ); + continue; + } + console.warn( + `${LOG_PREFIX} Discourse Node (${nodeText}): legacy data unreadable`, + ); + allOk = false; + continue; + } + + if ( + !migrateSection({ + label: `Discourse Node (${nodeText})`, + blockUid: nodePageUid, + schema: DiscourseNodeSchema, + legacyData, + }) + ) { + allOk = false; + } + } + + return allOk; +}; + +export const migrateGraphLevel = async ( + blockUids: Record, +): Promise => { + const pageUid = getPageUidByPageTitle(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE); + if (!pageUid) return; + + if (hasGraphMigrationMarker()) { + console.log(`${LOG_PREFIX} graph-level: skipped (already migrated)`); + return; + } + + let failures = 0; + + // Feature flags + const featureFlagUid = blockUids[TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags]; + if (featureFlagUid) { + const legacyFlags = readAllLegacyFeatureFlags(); + if ( + !migrateSection({ + label: "Feature Flags", + blockUid: featureFlagUid, + schema: FeatureFlagsSchema, + legacyData: legacyFlags as Record, + }) + ) { + failures++; + } + } + + // Global settings + const globalUid = blockUids[TOP_LEVEL_BLOCK_PROP_KEYS.global]; + if (globalUid) { + const legacyGlobal = readAllLegacyGlobalSettings(); + if ( + !migrateSection({ + label: "Global", + blockUid: globalUid, + schema: GlobalSettingsSchema, + legacyData: legacyGlobal, + }) + ) { + failures++; + } + } + + // Discourse nodes + if (!migrateDiscourseNodes()) { + failures++; + } + + if (failures === 0) { + try { + await createBlock({ + parentUid: pageUid, + node: { text: GRAPH_MIGRATION_MARKER }, + }); + console.log(`${LOG_PREFIX} graph-level: completed`); + } catch (e) { + console.warn( + `${LOG_PREFIX} graph-level: data migrated but marker write failed (will retry next load)`, + e, + ); + } + } else { + console.warn( + `${LOG_PREFIX} graph-level: ${failures} section(s) failed, marker not created (will retry next load)`, + ); + } +}; + +export const migratePersonalSettings = async ( + blockUids: Record, +): Promise => { + if (getSetting(PERSONAL_MIGRATION_MARKER, false)) { + console.log(`${LOG_PREFIX} personal: skipped (already migrated)`); + return; + } + + const personalKey = getPersonalSettingsKey(); + const personalUid = blockUids[personalKey]; + if (!personalUid) return; + + const legacyPersonal = readAllLegacyPersonalSettings(); + const ok = migrateSection({ + label: "Personal", + blockUid: personalUid, + schema: PersonalSettingsSchema, + legacyData: legacyPersonal, + }); + + if (ok) { + try { + await setSetting(PERSONAL_MIGRATION_MARKER, true); + console.log(`${LOG_PREFIX} personal: completed`); + } catch (e) { + console.warn( + `${LOG_PREFIX} personal: data migrated but marker write failed (will retry next load)`, + e, + ); + } + } else { + console.warn( + `${LOG_PREFIX} personal: failed, marker not created (will retry next load)`, + ); + } +}; diff --git a/apps/roam/src/components/settings/utils/zodSchema.example.ts b/apps/roam/src/components/settings/utils/zodSchema.example.ts index 7a9041666..861e25508 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.example.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.example.ts @@ -83,7 +83,6 @@ const discourseNodeSettings: DiscourseNodeSettings = { returnNode: "node", }, suggestiveRules, - backedBy: "user", }; const featureFlags: FeatureFlags = { diff --git a/apps/roam/src/components/settings/utils/zodSchema.ts b/apps/roam/src/components/settings/utils/zodSchema.ts index 4df25e6c7..dcae8449f 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.ts @@ -140,10 +140,6 @@ export const DiscourseNodeSchema = z.object({ .optional() .transform((val) => val ?? defaultNodeIndex()), suggestiveRules: SuggestiveRulesSchema.default({}), - backedBy: z - .enum(["user", "default", "relation"]) - .nullable() - .transform((val) => val ?? "user"), }); export const TripleSchema = z.tuple([z.string(), z.string(), z.string()]); From b97def31566e27794778f4ad28e2f2922767aa43 Mon Sep 17 00:00:00 2001 From: sid597 Date: Tue, 10 Mar 2026 22:06:33 +0530 Subject: [PATCH 2/5] Fix shouldWrite skipping after reconciliation, warn on missing personal block --- .../utils/migrateLegacyToBlockProps.ts | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts index c9d942fb6..674a27f3f 100644 --- a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts +++ b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts @@ -48,26 +48,12 @@ const shouldWrite = ( return true; } - // safeParse because some schemas (DiscourseNodeSchema) have required fields - // with no defaults, so parse({}) would throw. - const defaultsResult = schema.safeParse({}); - if (!defaultsResult.success) { - // Can't determine schema defaults (e.g. DiscourseNodeSchema). - // Compare Zod-normalized parsed legacy against current props directly. - // Both sides are normalized so the comparison is apples-to-apples. - // Safe on retry: if prior run already wrote parsedLegacy, they'll match - // → skip. If user edited via settings UI, dual-write keeps both sides in - // sync → match → skip. The only write happens when legacy genuinely - // differs from current (first migration or tree-only edit). - return JSON.stringify(parsedLegacy) !== JSON.stringify(currentProps); - } - - const defaults = defaultsResult.data as Record; - const propsMatch = JSON.stringify(currentProps) === JSON.stringify(defaults); - const legacyDiffers = - JSON.stringify(parsedLegacy) !== JSON.stringify(defaults); - - return propsMatch && legacyDiffers; + // Compare Zod-normalized parsed legacy against current props directly. + // Safe on retry: if prior run already wrote parsedLegacy, they'll match + // → skip. If user edited via settings UI, dual-write keeps both sides in + // sync → match → skip. The only write happens when legacy genuinely + // differs from current (first migration or tree-only edit). + return JSON.stringify(parsedLegacy) !== JSON.stringify(currentProps); }; const migrateSection = ({ @@ -241,7 +227,12 @@ export const migratePersonalSettings = async ( const personalKey = getPersonalSettingsKey(); const personalUid = blockUids[personalKey]; - if (!personalUid) return; + if (!personalUid) { + console.warn( + `${LOG_PREFIX} personal: block not found for key "${personalKey}", skipping`, + ); + return; + } const legacyPersonal = readAllLegacyPersonalSettings(); const ok = migrateSection({ From 14e72c9ecffeaf4e292374845819d18301a5b20f Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 11 Mar 2026 01:31:04 +0530 Subject: [PATCH 3/5] Add migration telemetry for legacy parse failures --- .../utils/migrateLegacyToBlockProps.ts | 90 +++++++++++++++---- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts index 674a27f3f..59d71e0f2 100644 --- a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts +++ b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts @@ -5,6 +5,7 @@ import getBlockUidByTextOnPage from "roamjs-components/queries/getBlockUidByText import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; import { createBlock } from "roamjs-components/writes"; import { getSetting, setSetting } from "~/utils/extensionSettings"; +import internalError from "~/utils/internalError"; import { readAllLegacyFeatureFlags, readAllLegacyGlobalSettings, @@ -26,6 +27,7 @@ import type { z } from "zod"; const LOG_PREFIX = "[DG BlockProps Migration]"; const GRAPH_MIGRATION_MARKER = "Block props migrated"; const PERSONAL_MIGRATION_MARKER = "dg-personal-settings-migrated"; +const MAX_ERROR_CONTEXT_LENGTH = 5000; const hasGraphMigrationMarker = (): boolean => !!getBlockUidByTextOnPage({ @@ -39,6 +41,14 @@ const isPropsValid = ( ): boolean => !!props && Object.keys(props).length > 0 && schema.safeParse(props).success; +const serializeErrorContext = (value: unknown): string => { + try { + return JSON.stringify(value).slice(0, MAX_ERROR_CONTEXT_LENGTH); + } catch { + return String(value); + } +}; + const shouldWrite = ( schema: z.ZodTypeAny, currentProps: Record | null, @@ -48,11 +58,6 @@ const shouldWrite = ( return true; } - // Compare Zod-normalized parsed legacy against current props directly. - // Safe on retry: if prior run already wrote parsedLegacy, they'll match - // → skip. If user edited via settings UI, dual-write keeps both sides in - // sync → match → skip. The only write happens when legacy genuinely - // differs from current (first migration or tree-only edit). return JSON.stringify(parsedLegacy) !== JSON.stringify(currentProps); }; @@ -71,9 +76,6 @@ const migrateSection = ({ const parseResult = schema.safeParse(legacyData); if (!parseResult.success) { - // Legacy malformed — succeed if current props are already valid. - // migrateGraphLevel runs before initDiscourseNodePages, so valid props - // at this point were written by a prior migration run, not init-seeded. if (isPropsValid(schema, currentProps)) { console.log( `${LOG_PREFIX} ${label}: legacy malformed but props already valid, skipping`, @@ -83,6 +85,17 @@ const migrateSection = ({ console.warn(`${LOG_PREFIX} ${label}: Zod validation failed, skipping`, { error: parseResult.error.message, }); + internalError({ + error: parseResult.error, + type: "DG Block Props Migration", + context: { + label, + blockUid, + legacyData: serializeErrorContext(legacyData), + currentProps: serializeErrorContext(currentProps), + }, + sendEmail: false, + }); return false; } @@ -117,8 +130,6 @@ const migrateDiscourseNodes = (): boolean => { nodeText, ); if (!legacyData) { - // Legacy unreadable — if current props are already valid, treat as - // success so a missing/malformed legacy tree doesn't block the marker. if (isPropsValid(DiscourseNodeSchema, getBlockProps(nodePageUid))) { console.log( `${LOG_PREFIX} Discourse Node (${nodeText}): legacy unreadable but props already valid, skipping`, @@ -128,6 +139,16 @@ const migrateDiscourseNodes = (): boolean => { console.warn( `${LOG_PREFIX} Discourse Node (${nodeText}): legacy data unreadable`, ); + internalError({ + error: `Legacy discourse node data unreadable: ${nodeText}`, + type: "DG Block Props Migration", + context: { + label: `Discourse Node (${nodeText})`, + blockUid: nodePageUid, + currentProps: serializeErrorContext(getBlockProps(nodePageUid)), + }, + sendEmail: false, + }); allOk = false; continue; } @@ -151,7 +172,15 @@ export const migrateGraphLevel = async ( blockUids: Record, ): Promise => { const pageUid = getPageUidByPageTitle(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE); - if (!pageUid) return; + if (!pageUid) { + internalError({ + error: `Settings page not found: ${DG_BLOCK_PROP_SETTINGS_PAGE_TITLE}`, + type: "DG Block Props Migration", + context: { scope: "graph" }, + sendEmail: false, + }); + return; + } if (hasGraphMigrationMarker()) { console.log(`${LOG_PREFIX} graph-level: skipped (already migrated)`); @@ -160,9 +189,19 @@ export const migrateGraphLevel = async ( let failures = 0; - // Feature flags const featureFlagUid = blockUids[TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags]; - if (featureFlagUid) { + if (!featureFlagUid) { + internalError({ + error: `Missing block uid for ${TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags}`, + type: "DG Block Props Migration", + context: { + scope: "graph", + blockUids: serializeErrorContext(blockUids), + }, + sendEmail: false, + }); + failures++; + } else { const legacyFlags = readAllLegacyFeatureFlags(); if ( !migrateSection({ @@ -176,9 +215,19 @@ export const migrateGraphLevel = async ( } } - // Global settings const globalUid = blockUids[TOP_LEVEL_BLOCK_PROP_KEYS.global]; - if (globalUid) { + if (!globalUid) { + internalError({ + error: `Missing block uid for ${TOP_LEVEL_BLOCK_PROP_KEYS.global}`, + type: "DG Block Props Migration", + context: { + scope: "graph", + blockUids: serializeErrorContext(blockUids), + }, + sendEmail: false, + }); + failures++; + } else { const legacyGlobal = readAllLegacyGlobalSettings(); if ( !migrateSection({ @@ -192,7 +241,6 @@ export const migrateGraphLevel = async ( } } - // Discourse nodes if (!migrateDiscourseNodes()) { failures++; } @@ -231,6 +279,16 @@ export const migratePersonalSettings = async ( console.warn( `${LOG_PREFIX} personal: block not found for key "${personalKey}", skipping`, ); + internalError({ + error: `Missing personal settings block for key "${personalKey}"`, + type: "DG Block Props Migration", + context: { + scope: "personal", + personalKey, + blockUids: serializeErrorContext(blockUids), + }, + sendEmail: false, + }); return; } From def877f9cb6c544d061a62d328b4605b52b34468 Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 11 Mar 2026 01:56:34 +0530 Subject: [PATCH 4/5] Use backend query for node migration --- .../settings/utils/migrateLegacyToBlockProps.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts index 59d71e0f2..06bd379a3 100644 --- a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts +++ b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts @@ -110,14 +110,14 @@ const migrateSection = ({ return true; }; -const migrateDiscourseNodes = (): boolean => { - const nodePages = window.roamAlphaAPI.data.fast.q(` +const migrateDiscourseNodes = async (): Promise => { + const nodePages = (await window.roamAlphaAPI.data.backend.q(` [:find ?uid ?title :where [?page :node/title ?title] [?page :block/uid ?uid] [(clojure.string/starts-with? ?title "${DISCOURSE_NODE_PAGE_PREFIX}")]] - `) as [string, string][]; + `)) as [string, string][]; let allOk = true; @@ -241,7 +241,7 @@ export const migrateGraphLevel = async ( } } - if (!migrateDiscourseNodes()) { + if (!(await migrateDiscourseNodes())) { failures++; } From f62b3f8a038dcfeeebee5756dba3ee74782220c8 Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 13 Mar 2026 09:32:07 +0530 Subject: [PATCH 5/5] fix review --- .../src/components/settings/utils/migrateLegacyToBlockProps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts index 06bd379a3..8ca1fe496 100644 --- a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts +++ b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts @@ -111,7 +111,7 @@ const migrateSection = ({ }; const migrateDiscourseNodes = async (): Promise => { - const nodePages = (await window.roamAlphaAPI.data.backend.q(` + const nodePages = (await window.roamAlphaAPI.data.async.fast.q(` [:find ?uid ?title :where [?page :node/title ?title]