From c6c425293380502b093012e7db1860d5bdb60cf2 Mon Sep 17 00:00:00 2001 From: sid597 Date: Thu, 26 Mar 2026 22:59:45 +0530 Subject: [PATCH 1/6] ENG-1574: Add dual-read console logs to setting setters Log legacy and block prop values with match/mismatch status when a setting is changed. Fix broken import in storedRelations. --- .../components/settings/utils/accessors.ts | 51 +++++++++++++++++++ apps/roam/src/utils/storedRelations.ts | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index ad53253e6..b1eeb37c5 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -777,6 +777,18 @@ export const setFeatureFlag = ( ): void => { const validatedValue = z.boolean().parse(value); + const legacyReader = FEATURE_FLAG_LEGACY_MAP[key]; + const legacyValue = legacyReader ? legacyReader() : undefined; + const currentBlockProps = getFeatureFlags()[key]; + const match = currentBlockProps === legacyValue; + console.groupCollapsed( + `[DG Dual-Read] Set Feature Flag > ${key} — ${match ? "Settings match" : "Settings don't match"}`, + ); + console.log("Legacy:", legacyValue); + console.log("Block props:", currentBlockProps); + console.log("New value:", validatedValue); + console.groupEnd(); + setBlockPropBasedSettings({ keys: [TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags, key], value: validatedValue, @@ -832,6 +844,18 @@ export const setGlobalSetting = (keys: string[], value: json): void => { return; } + const currentBlockProps = readPathValue(getGlobalSettings(), keys); + const legacyValue = getLegacyGlobalSetting(keys); + const match = + JSON.stringify(currentBlockProps) === JSON.stringify(legacyValue); + console.groupCollapsed( + `[DG Dual-Read] Set Global > ${formatSettingPath(keys)} — ${match ? "Settings match" : "Settings don't match"}`, + ); + console.log("Legacy:", legacyValue); + console.log("Block props:", currentBlockProps); + console.log("New value:", value); + console.groupEnd(); + setBlockPropBasedSettings({ keys: [TOP_LEVEL_BLOCK_PROP_KEYS.global, ...keys], value, @@ -906,6 +930,18 @@ export const setPersonalSetting = (keys: string[], value: json): void => { return; } + const currentBlockProps = readPathValue(getPersonalSettings(), keys); + const legacyValue = getLegacyPersonalSetting(keys); + const match = + JSON.stringify(currentBlockProps) === JSON.stringify(legacyValue); + console.groupCollapsed( + `[DG Dual-Read] Set Personal > ${formatSettingPath(keys)} — ${match ? "Settings match" : "Settings don't match"}`, + ); + console.log("Legacy:", legacyValue); + console.log("Block props:", currentBlockProps); + console.log("New value:", value); + console.groupEnd(); + setBlockPropBasedSettings({ keys: [personalKey, ...keys], value, @@ -1013,6 +1049,21 @@ export const setDiscourseNodeSetting = ( return; } + const currentSettings = getDiscourseNodeSettings(nodeType); + const currentBlockPropsValue = currentSettings + ? readPathValue(currentSettings, keys) + : undefined; + const legacyValue = getLegacyDiscourseNodeSetting(nodeType, keys); + const match = + JSON.stringify(currentBlockPropsValue) === JSON.stringify(legacyValue); + console.groupCollapsed( + `[DG Dual-Read] Set Discourse Node (${nodeType}) > ${formatSettingPath(keys)} — ${match ? "Settings match" : "Settings don't match"}`, + ); + console.log("Legacy:", legacyValue); + console.log("Block props:", currentBlockPropsValue); + console.log("New value:", value); + console.groupEnd(); + setBlockPropAtPath(pageUid, keys, value); }; diff --git a/apps/roam/src/utils/storedRelations.ts b/apps/roam/src/utils/storedRelations.ts index c32522c43..67ff11b53 100644 --- a/apps/roam/src/utils/storedRelations.ts +++ b/apps/roam/src/utils/storedRelations.ts @@ -4,7 +4,7 @@ import { USE_STORED_RELATIONS } from "~/data/userSettings"; import { getSetting, setSetting } from "./extensionSettings"; -import { DISCOURSE_CONFIG_PAGE_TITLE } from "./renderNodeConfigPage"; +import { DISCOURSE_CONFIG_PAGE_TITLE } from "~/data/constants"; const INSTALL_CUTOFF = Date.parse("2026-03-01T00:00:00.000Z"); From 699e47516419be5a7d9c39fde5bc5537d5f75487 Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 27 Mar 2026 17:07:34 +0530 Subject: [PATCH 2/6] ENG-1574: Add init-time dual-read log and window.dgDualReadLog() Log all legacy vs block prop settings on init. Remove setter logging. Expose dgDualReadLog() on window for on-demand use. --- .../components/settings/utils/accessors.ts | 51 ---------- .../src/components/settings/utils/init.ts | 97 ++++++++++++++++++- 2 files changed, 96 insertions(+), 52 deletions(-) diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 942eba8d7..808be1a25 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -802,18 +802,6 @@ export const setFeatureFlag = ( ): void => { const validatedValue = z.boolean().parse(value); - const legacyReader = FEATURE_FLAG_LEGACY_MAP[key]; - const legacyValue = legacyReader ? legacyReader() : undefined; - const currentBlockProps = getFeatureFlags()[key]; - const match = currentBlockProps === legacyValue; - console.groupCollapsed( - `[DG Dual-Read] Set Feature Flag > ${key} — ${match ? "Settings match" : "Settings don't match"}`, - ); - console.log("Legacy:", legacyValue); - console.log("Block props:", currentBlockProps); - console.log("New value:", validatedValue); - console.groupEnd(); - setBlockPropBasedSettings({ keys: [TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags, key], value: validatedValue, @@ -869,18 +857,6 @@ export const setGlobalSetting = (keys: string[], value: json): void => { return; } - const currentBlockProps = readPathValue(getGlobalSettings(), keys); - const legacyValue = getLegacyGlobalSetting(keys); - const match = - JSON.stringify(currentBlockProps) === JSON.stringify(legacyValue); - console.groupCollapsed( - `[DG Dual-Read] Set Global > ${formatSettingPath(keys)} — ${match ? "Settings match" : "Settings don't match"}`, - ); - console.log("Legacy:", legacyValue); - console.log("Block props:", currentBlockProps); - console.log("New value:", value); - console.groupEnd(); - setBlockPropBasedSettings({ keys: [TOP_LEVEL_BLOCK_PROP_KEYS.global, ...keys], value, @@ -955,18 +931,6 @@ export const setPersonalSetting = (keys: string[], value: json): void => { return; } - const currentBlockProps = readPathValue(getPersonalSettings(), keys); - const legacyValue = getLegacyPersonalSetting(keys); - const match = - JSON.stringify(currentBlockProps) === JSON.stringify(legacyValue); - console.groupCollapsed( - `[DG Dual-Read] Set Personal > ${formatSettingPath(keys)} — ${match ? "Settings match" : "Settings don't match"}`, - ); - console.log("Legacy:", legacyValue); - console.log("Block props:", currentBlockProps); - console.log("New value:", value); - console.groupEnd(); - setBlockPropBasedSettings({ keys: [personalKey, ...keys], value, @@ -1078,21 +1042,6 @@ export const setDiscourseNodeSetting = ( return; } - const currentSettings = getDiscourseNodeSettings(nodeType); - const currentBlockPropsValue = currentSettings - ? readPathValue(currentSettings, keys) - : undefined; - const legacyValue = getLegacyDiscourseNodeSetting(nodeType, keys); - const match = - JSON.stringify(currentBlockPropsValue) === JSON.stringify(legacyValue); - console.groupCollapsed( - `[DG Dual-Read] Set Discourse Node (${nodeType}) > ${formatSettingPath(keys)} — ${match ? "Settings match" : "Settings don't match"}`, - ); - console.log("Legacy:", legacyValue); - console.log("Block props:", currentBlockPropsValue); - console.log("New value:", value); - console.groupEnd(); - setBlockPropAtPath(pageUid, keys, value); }; diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 38c2731e8..30241c812 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -7,7 +7,18 @@ import type { json } from "~/utils/getBlockProps"; import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes"; import DEFAULT_RELATION_VALUES from "~/data/defaultDiscourseRelations"; import DEFAULT_RELATIONS_BLOCK_PROPS from "~/components/settings/data/defaultRelationsBlockProps"; -import { getAllDiscourseNodes } from "./accessors"; +import { + getAllDiscourseNodes, + isNewSettingsStoreEnabled, + getFeatureFlags, + getGlobalSettings, + getPersonalSettings, + getDiscourseNodeSettings, + readAllLegacyFeatureFlags, + readAllLegacyGlobalSettings, + readAllLegacyPersonalSettings, + readAllLegacyDiscourseNodeSettings, +} from "./accessors"; import { migrateGraphLevel, migratePersonalSettings, @@ -324,10 +335,94 @@ export type InitSchemaResult = { nodePageUids: Record; }; +const logDualReadComparison = (): void => { + if (!isNewSettingsStoreEnabled()) return; + + const legacyFlags = readAllLegacyFeatureFlags(); + const blockFlags = getFeatureFlags(); + const flagsMatch = JSON.stringify(blockFlags) === JSON.stringify(legacyFlags); + + const legacyGlobal = readAllLegacyGlobalSettings(); + const blockGlobal = getGlobalSettings(); + const globalMatch = + JSON.stringify(blockGlobal) === JSON.stringify(legacyGlobal); + + const legacyPersonal = readAllLegacyPersonalSettings(); + const blockPersonal = getPersonalSettings(); + const personalMatch = + JSON.stringify(blockPersonal) === JSON.stringify(legacyPersonal); + + const nodes = getAllDiscourseNodes(); + const nodeResults: { + name: string; + match: boolean; + legacy: unknown; + blockProps: unknown; + }[] = []; + for (const node of nodes) { + const legacy = readAllLegacyDiscourseNodeSettings(node.type, node.text); + const blockProps = getDiscourseNodeSettings(node.type); + nodeResults.push({ + name: node.text, + match: JSON.stringify(blockProps) === JSON.stringify(legacy), + legacy, + blockProps, + }); + } + + const mismatches = [ + !flagsMatch && "Feature Flags", + !globalMatch && "Global", + !personalMatch && "Personal", + ...nodeResults.filter((n) => !n.match).map((n) => n.name), + ].filter(Boolean) as string[]; + + const summary = + mismatches.length === 0 + ? "All settings match" + : `(${mismatches.length}) Settings don't match: ${mismatches.join(", ")}`; + + const nodeLegacy: Record = {}; + const nodeBlockProps: Record = {}; + for (const node of nodeResults) { + nodeLegacy[node.name] = node.legacy; + nodeBlockProps[node.name] = node.blockProps; + } + + console.log(`[DG Dual-Read] ${summary}`); + console.log("[DG Dual-Read] Legacy:", { + "Feature Flags": legacyFlags, + Global: legacyGlobal, + Personal: legacyPersonal, + ...nodeResults.reduce( + (acc, n) => { + acc[n.name] = n.legacy; + return acc; + }, + {} as Record, + ), + }); + console.log("[DG Dual-Read] Block props:", { + "Feature Flags": blockFlags, + Global: blockGlobal, + Personal: blockPersonal, + ...nodeResults.reduce( + (acc, n) => { + acc[n.name] = n.blockProps; + return acc; + }, + {} as Record, + ), + }); +}; + export const initSchema = async (): Promise => { const blockUids = await initSettingsPageBlocks(); await migrateGraphLevel(blockUids); const nodePageUids = await initDiscourseNodePages(); await migratePersonalSettings(blockUids); + logDualReadComparison(); + (window as unknown as Record).dgDualReadLog = + logDualReadComparison; return { blockUids, nodePageUids }; }; From b876cc9bbc8bae2870b0753a8359f7947c12db74 Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 27 Mar 2026 17:16:10 +0530 Subject: [PATCH 3/6] ENG-1574: Fix eslint naming-convention warnings in init.ts --- apps/roam/src/components/settings/utils/init.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 30241c812..3d80be341 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -389,6 +389,7 @@ const logDualReadComparison = (): void => { nodeBlockProps[node.name] = node.blockProps; } + /* eslint-disable @typescript-eslint/naming-convention */ console.log(`[DG Dual-Read] ${summary}`); console.log("[DG Dual-Read] Legacy:", { "Feature Flags": legacyFlags, @@ -414,6 +415,7 @@ const logDualReadComparison = (): void => { {} as Record, ), }); + /* eslint-enable @typescript-eslint/naming-convention */ }; export const initSchema = async (): Promise => { From 1567b3d50e727165fb765d5b156556ffa5cb2d0c Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 27 Mar 2026 17:19:24 +0530 Subject: [PATCH 4/6] ENG-1574: Use deepEqual instead of JSON.stringify for comparison JSON.stringify is key-order dependent, causing false mismatches when legacy and block props return keys in different order. --- apps/roam/src/components/settings/utils/accessors.ts | 2 +- apps/roam/src/components/settings/utils/init.ts | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 808be1a25..2eb2c0b2a 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -45,7 +45,7 @@ import { PERSONAL_KEYS, QUERY_KEYS, GLOBAL_KEYS } from "./settingKeys"; const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); -const deepEqual = (a: unknown, b: unknown): boolean => { +export const deepEqual = (a: unknown, b: unknown): boolean => { if (a === b) return true; const isEmpty = (v: unknown) => v === undefined || v === null || v === "" || v === false; diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 3d80be341..3383dfbee 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -10,6 +10,7 @@ import DEFAULT_RELATIONS_BLOCK_PROPS from "~/components/settings/data/defaultRel import { getAllDiscourseNodes, isNewSettingsStoreEnabled, + deepEqual, getFeatureFlags, getGlobalSettings, getPersonalSettings, @@ -340,17 +341,15 @@ const logDualReadComparison = (): void => { const legacyFlags = readAllLegacyFeatureFlags(); const blockFlags = getFeatureFlags(); - const flagsMatch = JSON.stringify(blockFlags) === JSON.stringify(legacyFlags); + const flagsMatch = deepEqual(blockFlags, legacyFlags); const legacyGlobal = readAllLegacyGlobalSettings(); const blockGlobal = getGlobalSettings(); - const globalMatch = - JSON.stringify(blockGlobal) === JSON.stringify(legacyGlobal); + const globalMatch = deepEqual(blockGlobal, legacyGlobal); const legacyPersonal = readAllLegacyPersonalSettings(); const blockPersonal = getPersonalSettings(); - const personalMatch = - JSON.stringify(blockPersonal) === JSON.stringify(legacyPersonal); + const personalMatch = deepEqual(blockPersonal, legacyPersonal); const nodes = getAllDiscourseNodes(); const nodeResults: { @@ -364,7 +363,7 @@ const logDualReadComparison = (): void => { const blockProps = getDiscourseNodeSettings(node.type); nodeResults.push({ name: node.text, - match: JSON.stringify(blockProps) === JSON.stringify(legacy), + match: deepEqual(blockProps, legacy), legacy, blockProps, }); From e807ddf1f4d6fa98f6f1990d08f4040d2b0358c3 Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 27 Mar 2026 17:26:14 +0530 Subject: [PATCH 5/6] ENG-1574: Remove dead code, use deepEqual for comparison --- .../src/components/settings/utils/init.ts | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 3383dfbee..efb578c0b 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -381,12 +381,8 @@ const logDualReadComparison = (): void => { ? "All settings match" : `(${mismatches.length}) Settings don't match: ${mismatches.join(", ")}`; - const nodeLegacy: Record = {}; - const nodeBlockProps: Record = {}; - for (const node of nodeResults) { - nodeLegacy[node.name] = node.legacy; - nodeBlockProps[node.name] = node.blockProps; - } + const nodeMap = (key: "legacy" | "blockProps") => + Object.fromEntries(nodeResults.map((n) => [n.name, n[key]])); /* eslint-disable @typescript-eslint/naming-convention */ console.log(`[DG Dual-Read] ${summary}`); @@ -394,25 +390,13 @@ const logDualReadComparison = (): void => { "Feature Flags": legacyFlags, Global: legacyGlobal, Personal: legacyPersonal, - ...nodeResults.reduce( - (acc, n) => { - acc[n.name] = n.legacy; - return acc; - }, - {} as Record, - ), + ...nodeMap("legacy"), }); console.log("[DG Dual-Read] Block props:", { "Feature Flags": blockFlags, Global: blockGlobal, Personal: blockPersonal, - ...nodeResults.reduce( - (acc, n) => { - acc[n.name] = n.blockProps; - return acc; - }, - {} as Record, - ), + ...nodeMap("blockProps"), }); /* eslint-enable @typescript-eslint/naming-convention */ }; From 2397446025d3a0ec5489f0467c50d278da04dcdd Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 27 Mar 2026 17:32:16 +0530 Subject: [PATCH 6/6] =?UTF-8?q?ENG-1574:=20Fix=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20try-catch,=20flag=20exclusion,=20type=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/settings/utils/init.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index efb578c0b..07a15f1d6 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -341,7 +341,16 @@ const logDualReadComparison = (): void => { const legacyFlags = readAllLegacyFeatureFlags(); const blockFlags = getFeatureFlags(); - const flagsMatch = deepEqual(blockFlags, legacyFlags); + const omitStoreFlag = ( + flags: Record, + ): Record => + Object.fromEntries( + Object.entries(flags).filter(([k]) => k !== "Use new settings store"), + ); + const flagsMatch = deepEqual( + omitStoreFlag(blockFlags), + omitStoreFlag(legacyFlags), + ); const legacyGlobal = readAllLegacyGlobalSettings(); const blockGlobal = getGlobalSettings(); @@ -374,7 +383,7 @@ const logDualReadComparison = (): void => { !globalMatch && "Global", !personalMatch && "Personal", ...nodeResults.filter((n) => !n.match).map((n) => n.name), - ].filter(Boolean) as string[]; + ].filter((x): x is string => Boolean(x)); const summary = mismatches.length === 0 @@ -406,7 +415,11 @@ export const initSchema = async (): Promise => { await migrateGraphLevel(blockUids); const nodePageUids = await initDiscourseNodePages(); await migratePersonalSettings(blockUids); - logDualReadComparison(); + try { + logDualReadComparison(); + } catch (e) { + console.warn("[DG Dual-Read] Comparison failed:", e); + } (window as unknown as Record).dgDualReadLog = logDualReadComparison; return { blockUids, nodePageUids };