From 0b4217fcb35dfd08b7a62231af73270ebc9a7cc0 Mon Sep 17 00:00:00 2001 From: sid597 Date: Thu, 26 Mar 2026 23:40:38 +0530 Subject: [PATCH 1/5] =?UTF-8?q?ENG-1579:=20Fix=20plugin=20init=20performan?= =?UTF-8?q?ce=20=E2=80=94=20cache=20accessor=20layer=20and=20hot=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cache isNewSettingsStoreEnabled(), getFeatureFlags(), getGlobalSettings(), getPersonalSettings(), and all legacy setting readers to avoid redundant Roam DB queries during init. Cache getDiscourseNodes/Relations and fix findDiscourseNode default param that evaluated getDiscourseNodes() before cache check. Add console.time instrumentation for ongoing diagnostics. Init: 27s → 1.1s on megacoglab. Settings panel: 17s → 11ms. --- .../roam/src/components/settings/Settings.tsx | 10 +- .../components/settings/utils/accessors.ts | 385 +++++++++++------- .../src/components/settings/utils/init.ts | 30 +- .../components/settings/utils/pullWatchers.ts | 9 + apps/roam/src/index.ts | 40 +- apps/roam/src/utils/findDiscourseNode.ts | 5 +- apps/roam/src/utils/getDiscourseNodes.ts | 21 +- apps/roam/src/utils/getDiscourseRelations.ts | 32 +- .../utils/initializeObserversAndListeners.ts | 19 + apps/roam/src/utils/refreshConfigTree.ts | 7 + 10 files changed, 399 insertions(+), 159 deletions(-) diff --git a/apps/roam/src/components/settings/Settings.tsx b/apps/roam/src/components/settings/Settings.tsx index 80c35bdcd..8360e1a44 100644 --- a/apps/roam/src/components/settings/Settings.tsx +++ b/apps/roam/src/components/settings/Settings.tsx @@ -72,6 +72,7 @@ export const SettingsDialog = ({ onClose?: () => void; selectedTabId?: TabId; }) => { + console.time("[DG Perf] SettingsDialog render setup"); const extensionAPI = onloadArgs.extensionAPI; const grammarNode = discourseConfigRef.tree.find( (node) => node.text === "grammar", @@ -87,6 +88,7 @@ export const SettingsDialog = ({ const [showAdminPanel, setShowAdminPanel] = useState( window.roamAlphaAPI.graph.name === "discourse-graphs" || false, ); + console.timeEnd("[DG Perf] SettingsDialog render setup"); useEffect(() => { posthog.capture("Settings: Dialog Opened", { @@ -282,10 +284,14 @@ export const SettingsDialog = ({ type Props = { onloadArgs: OnloadArgs; }; -export const render = (props: Props) => - renderOverlay({ +export const render = (props: Props) => { + console.time("[DG Perf] Settings: renderOverlay"); + const result = renderOverlay({ Overlay: SettingsDialog, props: { onloadArgs: props.onloadArgs, }, }); + console.timeEnd("[DG Perf] Settings: renderOverlay"); + return result; +}; diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 808be1a25..23c078873 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -155,6 +155,31 @@ const readPathValue = (root: unknown, keys: string[]): unknown => return current[key]; }, root); +const setPathValue = ( + root: Record, + keys: string[], + value: unknown, +): void => { + if (keys.length === 0) return; + + let current: Record = root; + const lastIndex = keys.length - 1; + + keys.forEach((key, index) => { + if (index === lastIndex) { + current[key] = value; + return; + } + + const next = current[key]; + if (!isRecord(next)) { + current[key] = {}; + } + + current = current[key] as Record; + }); +}; + const pathKey = (keys: string[]): string => keys.join("::"); const validateSettingValue = ({ @@ -258,45 +283,89 @@ const getLegacyPersonalLeftSidebarSetting = (): unknown[] => { /* eslint-enable @typescript-eslint/naming-convention */ }; -const getLegacyPersonalSetting = (keys: string[]): unknown => { - if (keys.length === 0) return undefined; +const _settingsBlockUidCache = new Map(); +let _featureFlagsCache: FeatureFlags | null = null; +let _globalSettingsCache: GlobalSettings | null = null; +let _personalSettingsCache: PersonalSettings | null = null; +let _newSettingsStoreCache: boolean | null = null; +let _legacyGlobalSettingsCache: Record | null = null; +let _legacyPersonalSettingsCache: Record | null = null; +const _legacyDiscourseNodeSettingsCache = new Map< + string, + Record | undefined +>(); +const _rawDiscourseNodeBlockPropsCache = new Map< + string, + Record | undefined +>(); +let _allDiscourseNodesCache: DiscourseNode[] | null = null; + +const getTopLevelSettingsBlockUid = (key: string): string => { + const cachedUid = _settingsBlockUidCache.get(key); + if (cachedUid) { + return cachedUid; + } - const mappedOldKey = PERSONAL_SCHEMA_PATH_TO_LEGACY_KEY.get(pathKey(keys)); - if (mappedOldKey) { - return getSetting( - mappedOldKey, - readPathValue(DEFAULT_PERSONAL_SETTINGS, keys), - ); + const blockUid = getBlockUidByTextOnPage({ + text: key, + title: DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, + }); + + if (blockUid) { + _settingsBlockUidCache.set(key, blockUid); } - if (keys.length === 1 && keys[0] === "Query") { - const querySettings: Record = {}; - querySettings["Hide query metadata"] = getLegacyPersonalSetting([ - "Query", - "Hide query metadata", - ]); - querySettings["Default page size"] = getLegacyPersonalSetting([ - "Query", - "Default page size", - ]); - querySettings["Query pages"] = getLegacyPersonalSetting([ - "Query", - "Query pages", - ]); - querySettings["Default filters"] = getLegacyPersonalSetting([ - "Query", - "Default filters", - ]); - return querySettings; + return blockUid || ""; +}; + +export const invalidateSettingsAccessorCaches = (): void => { + _settingsBlockUidCache.clear(); + _featureFlagsCache = null; + _globalSettingsCache = null; + _personalSettingsCache = null; + _newSettingsStoreCache = null; + _legacyGlobalSettingsCache = null; + _legacyPersonalSettingsCache = null; + _legacyDiscourseNodeSettingsCache.clear(); + _rawDiscourseNodeBlockPropsCache.clear(); + _allDiscourseNodesCache = null; +}; + +const buildLegacyPersonalSettings = (): Record => { + const personalSettings: Record = {}; + + for (const [ + path, + legacyKey, + ] of PERSONAL_SCHEMA_PATH_TO_LEGACY_KEY.entries()) { + const keys = path.split("::"); + setPathValue( + personalSettings, + keys, + getSetting( + legacyKey, + readPathValue(DEFAULT_PERSONAL_SETTINGS, keys), + ), + ); } - if (keys[0] === "Left sidebar") { - const leftSidebarSettings = getLegacyPersonalLeftSidebarSetting(); - if (keys.length === 1) return leftSidebarSettings; - return readPathValue(leftSidebarSettings, keys.slice(1)); + setPathValue( + personalSettings, + [PERSONAL_KEYS.leftSidebar], + getLegacyPersonalLeftSidebarSetting(), + ); + + return personalSettings; +}; + +const getLegacyPersonalSetting = (keys: string[]): unknown => { + if (keys.length === 0) return undefined; + + if (!_legacyPersonalSettingsCache) { + _legacyPersonalSettingsCache = buildLegacyPersonalSettings(); } - return undefined; + return readPathValue(_legacyPersonalSettingsCache, keys); }; const getLegacyRelationsSetting = (): Record => { @@ -365,101 +434,80 @@ const getLegacyRelationsSetting = (): Record => { ); }; -// Reconstructs global settings from legacy Roam tree to match block-props schema shape -const getLegacyGlobalSetting = (keys: string[]): unknown => { - if (keys.length === 0) return undefined; - +const buildLegacyGlobalSettings = (): Record => { const tree = discourseConfigRef.tree; - const firstKey = keys[0]; + const sidebar = getLeftSidebarSettings(tree); + const exp = getExportSettingsAndUids(); + const sm = getSuggestiveModeConfigAndUids(tree); - if (firstKey === "Trigger") { - return ( + return { + [GLOBAL_KEYS.trigger]: getUidAndStringSetting({ tree, text: "trigger" }).value || - DEFAULT_GLOBAL_SETTINGS.Trigger - ); - } - - if (firstKey === "Canvas page format") { - return ( + DEFAULT_GLOBAL_SETTINGS.Trigger, + [GLOBAL_KEYS.canvasPageFormat]: getUidAndStringSetting({ tree, text: "Canvas Page Format" }).value || - DEFAULT_GLOBAL_SETTINGS["Canvas page format"] - ); - } - - if (firstKey === "Left sidebar") { - const sidebar = getLeftSidebarSettings(tree); - const leftSidebarSettings: Record = {}; - leftSidebarSettings["Children"] = sidebar.global.children.map( - (c) => c.text, - ); - const sidebarSettingValues: Record = {}; - sidebarSettingValues["Collapsable"] = - sidebar.global.settings?.collapsable.value ?? - DEFAULT_GLOBAL_SETTINGS["Left sidebar"].Settings.Collapsable; - sidebarSettingValues["Folded"] = - sidebar.global.settings?.folded.value ?? - DEFAULT_GLOBAL_SETTINGS["Left sidebar"].Settings.Folded; - leftSidebarSettings["Settings"] = sidebarSettingValues; - if (keys.length === 1) return leftSidebarSettings; - return readPathValue(leftSidebarSettings, keys.slice(1)); - } - - if (firstKey === "Export") { - const exp = getExportSettingsAndUids(); - const exportSettings: Record = {}; - exportSettings["Remove special characters"] = - exp.removeSpecialCharacters.value ?? - DEFAULT_GLOBAL_SETTINGS.Export["Remove special characters"]; - exportSettings["Resolve block references"] = - exp.optsRefs.value ?? - DEFAULT_GLOBAL_SETTINGS.Export["Resolve block references"]; - exportSettings["Resolve block embeds"] = - exp.optsEmbeds.value ?? - DEFAULT_GLOBAL_SETTINGS.Export["Resolve block embeds"]; - exportSettings["Append referenced node"] = - exp.appendRefNodeContext.value ?? - DEFAULT_GLOBAL_SETTINGS.Export["Append referenced node"]; - exportSettings["Link type"] = - exp.linkType.value || DEFAULT_GLOBAL_SETTINGS.Export["Link type"]; - exportSettings["Max filename length"] = - exp.maxFilenameLength.value ?? - DEFAULT_GLOBAL_SETTINGS.Export["Max filename length"]; - exportSettings["Frontmatter"] = - exp.frontmatter.values ?? DEFAULT_GLOBAL_SETTINGS.Export.Frontmatter; - if (keys.length === 1) return exportSettings; - return readPathValue(exportSettings, keys.slice(1)); - } - - if (firstKey === "Suggestive mode") { - const sm = getSuggestiveModeConfigAndUids(tree); - const suggestiveModeSettings: Record = {}; - suggestiveModeSettings["Include current page relations"] = - sm.includePageRelations.value ?? - DEFAULT_GLOBAL_SETTINGS["Suggestive mode"][ - "Include current page relations" - ]; - suggestiveModeSettings["Include parent and child blocks"] = - sm.includeParentAndChildren.value ?? - DEFAULT_GLOBAL_SETTINGS["Suggestive mode"][ - "Include parent and child blocks" - ]; - suggestiveModeSettings["Page groups"] = sm.pageGroups.groups.map( - (group) => ({ + DEFAULT_GLOBAL_SETTINGS["Canvas page format"], + [GLOBAL_KEYS.leftSidebar]: { + Children: sidebar.global.children.map((c) => c.text), + Settings: { + Collapsable: + sidebar.global.settings?.collapsable.value ?? + DEFAULT_GLOBAL_SETTINGS["Left sidebar"].Settings.Collapsable, + Folded: + sidebar.global.settings?.folded.value ?? + DEFAULT_GLOBAL_SETTINGS["Left sidebar"].Settings.Folded, + }, + }, + [GLOBAL_KEYS.export]: { + "Remove special characters": + exp.removeSpecialCharacters.value ?? + DEFAULT_GLOBAL_SETTINGS.Export["Remove special characters"], + "Resolve block references": + exp.optsRefs.value ?? + DEFAULT_GLOBAL_SETTINGS.Export["Resolve block references"], + "Resolve block embeds": + exp.optsEmbeds.value ?? + DEFAULT_GLOBAL_SETTINGS.Export["Resolve block embeds"], + "Append referenced node": + exp.appendRefNodeContext.value ?? + DEFAULT_GLOBAL_SETTINGS.Export["Append referenced node"], + "Link type": + exp.linkType.value || DEFAULT_GLOBAL_SETTINGS.Export["Link type"], + "Max filename length": + exp.maxFilenameLength.value ?? + DEFAULT_GLOBAL_SETTINGS.Export["Max filename length"], + Frontmatter: + exp.frontmatter.values ?? DEFAULT_GLOBAL_SETTINGS.Export.Frontmatter, + }, + [GLOBAL_KEYS.suggestiveMode]: { + "Include current page relations": + sm.includePageRelations.value ?? + DEFAULT_GLOBAL_SETTINGS["Suggestive mode"][ + "Include current page relations" + ], + "Include parent and child blocks": + sm.includeParentAndChildren.value ?? + DEFAULT_GLOBAL_SETTINGS["Suggestive mode"][ + "Include parent and child blocks" + ], + "Page groups": sm.pageGroups.groups.map((group) => ({ name: group.name, pages: group.pages.map((page) => page.name), - }), - ); - if (keys.length === 1) return suggestiveModeSettings; - return readPathValue(suggestiveModeSettings, keys.slice(1)); - } + })), + }, + [GLOBAL_KEYS.relations]: getLegacyRelationsSetting(), + }; +}; + +// Reconstructs global settings from legacy Roam tree to match block-props schema shape +const getLegacyGlobalSetting = (keys: string[]): unknown => { + if (keys.length === 0) return undefined; - if (firstKey === "Relations") { - const relationsSettings = getLegacyRelationsSetting(); - if (keys.length === 1) return relationsSettings; - return readPathValue(relationsSettings, keys.slice(1)); + if (!_legacyGlobalSettingsCache) { + _legacyGlobalSettingsCache = buildLegacyGlobalSettings(); } - return undefined; + return readPathValue(_legacyGlobalSettingsCache, keys); }; const getLegacyQuerySettingByParentUid = (parentUid: string) => { @@ -488,11 +536,9 @@ const getLegacyQuerySettingByParentUid = (parentUid: string) => { }; }; -// Reconstructs per-node settings from Roam tree structure to match block-props schema shape -const getLegacyDiscourseNodeSetting = ( +const buildLegacyDiscourseNodeSettings = ( nodeType: string, - keys: string[], -): unknown => { +): Record | undefined => { let nodeUid = nodeType; let tree = getBasicTreeByParentUid(nodeUid); @@ -575,7 +621,33 @@ const getLegacyDiscourseNodeSetting = ( }, }; - return readPathValue(legacySettings, keys); + return legacySettings; +}; + +// Reconstructs per-node settings from Roam tree structure to match block-props schema shape +const getLegacyDiscourseNodeSetting = ( + nodeType: string, + keys: string[], +): unknown => { + if (!_legacyDiscourseNodeSettingsCache.has(nodeType)) { + const legacySettings = buildLegacyDiscourseNodeSettings(nodeType); + _legacyDiscourseNodeSettingsCache.set(nodeType, legacySettings); + + const resolvedNodeType = + legacySettings && typeof legacySettings.type === "string" + ? legacySettings.type + : undefined; + if (resolvedNodeType && resolvedNodeType !== nodeType) { + _legacyDiscourseNodeSettingsCache.set(resolvedNodeType, legacySettings); + } + } + + const legacySettings = _legacyDiscourseNodeSettingsCache.get(nodeType); + if (!legacySettings) return undefined; + + return keys.length === 0 + ? legacySettings + : readPathValue(legacySettings, keys); }; const getBlockPropsByUid = ( @@ -648,6 +720,7 @@ const setBlockPropAtPath = ( }, updatedProps); setBlockProps(blockUid, updatedProps, false); + invalidateSettingsAccessorCaches(); }; const getBlockPropBasedSettings = ({ @@ -663,10 +736,7 @@ const getBlockPropBasedSettings = ({ return { blockProps: undefined, blockUid: "" }; } - const blockUid = getBlockUidByTextOnPage({ - text: keys[0], - title: DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, - }); + const blockUid = getTopLevelSettingsBlockUid(keys[0]); if (!blockUid) { return { blockProps: undefined, blockUid: "" }; @@ -692,10 +762,7 @@ const setBlockPropBasedSettings = ({ return; } - const blockUid = getBlockUidByTextOnPage({ - text: keys[0], - title: DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, - }); + const blockUid = getTopLevelSettingsBlockUid(keys[0]); if (!blockUid) { internalError({ @@ -709,11 +776,16 @@ const setBlockPropBasedSettings = ({ }; export const getFeatureFlags = (): FeatureFlags => { + if (_featureFlagsCache) { + return _featureFlagsCache; + } + const { blockProps } = getBlockPropBasedSettings({ keys: [TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags], }); - return FeatureFlagsSchema.parse(blockProps || {}); + _featureFlagsCache = FeatureFlagsSchema.parse(blockProps || {}); + return _featureFlagsCache; }; /* eslint-disable @typescript-eslint/naming-convention */ @@ -759,7 +831,13 @@ export const getFeatureFlag = (key: keyof FeatureFlags): boolean => { }; export const isNewSettingsStoreEnabled = (): boolean => { - return getFeatureFlag("Use new settings store"); + if (_newSettingsStoreCache !== null) return _newSettingsStoreCache; + _newSettingsStoreCache = getFeatureFlag("Use new settings store"); + return _newSettingsStoreCache; +}; + +export const invalidateNewSettingsStoreCache = (): void => { + invalidateSettingsAccessorCaches(); }; export const readAllLegacyFeatureFlags = (): Partial => { @@ -806,14 +884,23 @@ export const setFeatureFlag = ( keys: [TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags, key], value: validatedValue, }); + + if (key === "Use new settings store") { + invalidateNewSettingsStoreCache(); + } }; export const getGlobalSettings = (): GlobalSettings => { + if (_globalSettingsCache) { + return _globalSettingsCache; + } + const { blockProps } = getBlockPropBasedSettings({ keys: [TOP_LEVEL_BLOCK_PROP_KEYS.global], }); - return GlobalSettingsSchema.parse(blockProps || {}); + _globalSettingsCache = GlobalSettingsSchema.parse(blockProps || {}); + return _globalSettingsCache; }; export const getGlobalSetting = ( @@ -879,13 +966,18 @@ export const getAllRelations = (): DiscourseRelation[] => { }; export const getPersonalSettings = (): PersonalSettings => { + if (_personalSettingsCache) { + return _personalSettingsCache; + } + const personalKey = getPersonalSettingsKey(); const { blockProps } = getBlockPropBasedSettings({ keys: [personalKey], }); - return PersonalSettingsSchema.parse(blockProps || {}); + _personalSettingsCache = PersonalSettingsSchema.parse(blockProps || {}); + return _personalSettingsCache; }; export const getPersonalSetting = ( @@ -940,6 +1032,10 @@ export const setPersonalSetting = (keys: string[], value: json): void => { const getRawDiscourseNodeBlockProps = ( nodeType: string, ): Record | undefined => { + if (_rawDiscourseNodeBlockPropsCache.has(nodeType)) { + return _rawDiscourseNodeBlockPropsCache.get(nodeType); + } + let pageUid = nodeType; let blockProps = getBlockPropsByUid(pageUid, []); @@ -953,9 +1049,17 @@ const getRawDiscourseNodeBlockProps = ( } } - return isRecord(blockProps) && Object.keys(blockProps).length > 0 - ? (blockProps as Record) - : undefined; + const result = + isRecord(blockProps) && Object.keys(blockProps).length > 0 + ? (blockProps as Record) + : undefined; + + _rawDiscourseNodeBlockPropsCache.set(nodeType, result); + if (pageUid !== nodeType) { + _rawDiscourseNodeBlockPropsCache.set(pageUid, result); + } + + return result; }; export const getDiscourseNodeSettings = ( @@ -1152,6 +1256,10 @@ const migrateNodeBlockProps = ( }; export const getAllDiscourseNodes = (): DiscourseNode[] => { + if (_allDiscourseNodesCache) { + return _allDiscourseNodesCache; + } + const results = window.roamAlphaAPI.data.fast.q(` [:find ?uid ?title (pull ?page [:block/props]) :where @@ -1211,5 +1319,6 @@ export const getAllDiscourseNodes = (): DiscourseNode[] => { } } + _allDiscourseNodesCache = nodes; return nodes; }; diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 38c2731e8..5cc027628 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -7,7 +7,10 @@ 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, + invalidateSettingsAccessorCaches, +} from "./accessors"; import { migrateGraphLevel, migratePersonalSettings, @@ -177,15 +180,26 @@ const initializeSettingsBlockProps = ( }; const initSettingsPageBlocks = async (): Promise> => { + console.time("[DG Perf] initSchema > ensurePageExists"); const pageUid = await ensurePageExists(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE); + console.timeEnd("[DG Perf] initSchema > ensurePageExists"); + + console.time("[DG Perf] initSchema > buildBlockMap"); const blockMap = buildBlockMap(pageUid); + console.timeEnd("[DG Perf] initSchema > buildBlockMap"); + console.time("[DG Perf] initSchema > ensureBlocksExist"); const topLevelBlocks = getTopLevelBlockPropsConfig().map(({ key }) => key); await ensureBlocksExist(pageUid, topLevelBlocks, blockMap); + console.timeEnd("[DG Perf] initSchema > ensureBlocksExist"); + console.time("[DG Perf] initSchema > ensureLegacyConfigBlocks"); await ensureLegacyConfigBlocks(pageUid); + console.timeEnd("[DG Perf] initSchema > ensureLegacyConfigBlocks"); + console.time("[DG Perf] initSchema > initializeSettingsBlockProps"); initializeSettingsBlockProps(pageUid, blockMap); + console.timeEnd("[DG Perf] initSchema > initializeSettingsBlockProps"); return blockMap; }; @@ -325,9 +339,23 @@ export type InitSchemaResult = { }; export const initSchema = async (): Promise => { + console.time("[DG Perf] initSchema > initSettingsPageBlocks"); const blockUids = await initSettingsPageBlocks(); + console.timeEnd("[DG Perf] initSchema > initSettingsPageBlocks"); + + console.time("[DG Perf] initSchema > migrateGraphLevel"); await migrateGraphLevel(blockUids); + console.timeEnd("[DG Perf] initSchema > migrateGraphLevel"); + + console.time("[DG Perf] initSchema > initDiscourseNodePages"); const nodePageUids = await initDiscourseNodePages(); + console.timeEnd("[DG Perf] initSchema > initDiscourseNodePages"); + + console.time("[DG Perf] initSchema > migratePersonalSettings"); await migratePersonalSettings(blockUids); + console.timeEnd("[DG Perf] initSchema > migratePersonalSettings"); + + invalidateSettingsAccessorCaches(); + return { blockUids, nodePageUids }; }; diff --git a/apps/roam/src/components/settings/utils/pullWatchers.ts b/apps/roam/src/components/settings/utils/pullWatchers.ts index c9529e944..87e218ee0 100644 --- a/apps/roam/src/components/settings/utils/pullWatchers.ts +++ b/apps/roam/src/components/settings/utils/pullWatchers.ts @@ -13,6 +13,10 @@ import { type DiscourseNodeSettings, } from "./zodSchema"; import { emitSettingChange, settingKeys } from "./settingsEmitter"; +import { + invalidateNewSettingsStoreCache, + invalidateSettingsAccessorCaches, +} from "./accessors"; type PullWatchCallback = Parameters[2]; @@ -64,6 +68,8 @@ const createSettingsWatchCallback = ( if (!afterResult.success) return; + invalidateSettingsAccessorCaches(); + const oldSettings = beforeResult.success ? (beforeResult.data ?? null) : null; @@ -92,6 +98,9 @@ export const featureFlagHandlers: Partial< > > = { /* eslint-disable @typescript-eslint/naming-convention */ + "Use new settings store": () => { + invalidateNewSettingsStoreCache(); + }, "Enable left sidebar": (newValue, oldValue) => { emitSettingChange(settingKeys.leftSidebarFlag, newValue, oldValue); }, diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index ff8e95d9a..c909aded4 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -49,6 +49,9 @@ import { mountLeftSidebar } from "./components/LeftSidebarView"; export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; export default runExtension(async (onloadArgs) => { + console.time("[DG Perf] Total init"); + + console.time("[DG Perf] initPostHog"); const isEncrypted = window.roamAlphaAPI.graph.isEncrypted; const isOffline = window.roamAlphaAPI.graph.type === "offline"; const disallowDiagnostics = getPersonalSetting([ @@ -57,8 +60,11 @@ export default runExtension(async (onloadArgs) => { if (!isEncrypted && !isOffline && !disallowDiagnostics) { initPostHog(); } + console.timeEnd("[DG Perf] initPostHog"); + console.time("[DG Perf] initFeedbackWidget"); initFeedbackWidget(); + console.timeEnd("[DG Perf] initFeedbackWidget"); if (window?.roamjs?.loaded?.has("query-builder")) { renderToast({ @@ -81,21 +87,40 @@ export default runExtension(async (onloadArgs) => { initPluginTimer(); + console.time("[DG Perf] initializeDiscourseNodes"); await initializeDiscourseNodes(); + console.timeEnd("[DG Perf] initializeDiscourseNodes"); + + console.time("[DG Perf] refreshConfigTree"); refreshConfigTree(); + console.timeEnd("[DG Perf] refreshConfigTree"); + console.time("[DG Perf] addGraphViewNodeStyling"); addGraphViewNodeStyling(); + console.timeEnd("[DG Perf] addGraphViewNodeStyling"); + + console.time("[DG Perf] registerCommandPaletteCommands"); registerCommandPaletteCommands(onloadArgs); + console.timeEnd("[DG Perf] registerCommandPaletteCommands"); + + console.time("[DG Perf] createSettingsPanel"); createSettingsPanel(onloadArgs); + console.timeEnd("[DG Perf] createSettingsPanel"); + + console.time("[DG Perf] registerSmartBlock"); registerSmartBlock(onloadArgs); + console.timeEnd("[DG Perf] registerSmartBlock"); + + console.time("[DG Perf] setInitialQueryPages"); setInitialQueryPages(onloadArgs); + console.timeEnd("[DG Perf] setInitialQueryPages"); + console.time("[DG Perf] styles injection"); const style = addStyle(styles); const discourseGraphStyle = addStyle(discourseGraphStyles); const settingsStyle = addStyle(settingsStyles); const discourseFloatingMenuStyle = addStyle(discourseFloatingMenuStyles); - // Add streamline styling only if enabled const isStreamlineStylingEnabled = getPersonalSetting([ PERSONAL_KEYS.streamlineStyling, ]); @@ -104,8 +129,12 @@ export default runExtension(async (onloadArgs) => { streamlineStyleElement = addStyle(streamlineStyling); streamlineStyleElement.id = "streamline-styling"; } + console.timeEnd("[DG Perf] styles injection"); + console.time("[DG Perf] initObservers"); const { observers, listeners, cleanups } = initObservers({ onloadArgs }); + console.timeEnd("[DG Perf] initObservers"); + const { pageActionListener, hashChangeListener, @@ -150,7 +179,9 @@ export default runExtension(async (onloadArgs) => { getDiscourseNodes: getDiscourseNodes, }; + console.time("[DG Perf] installDiscourseFloatingMenu"); installDiscourseFloatingMenu(onloadArgs); + console.timeEnd("[DG Perf] installDiscourseFloatingMenu"); const leftSidebarScript = document.querySelector( 'script#roam-left-sidebar[src="https://sid597.github.io/roam-left-sidebar/js/main.js"]', @@ -189,8 +220,15 @@ export default runExtension(async (onloadArgs) => { }, ); + console.time("[DG Perf] initSchema"); const { blockUids } = await initSchema(); + console.timeEnd("[DG Perf] initSchema"); + + console.time("[DG Perf] setupPullWatchOnSettingsPage"); const cleanupPullWatchers = setupPullWatchOnSettingsPage(blockUids); + console.timeEnd("[DG Perf] setupPullWatchOnSettingsPage"); + + console.timeEnd("[DG Perf] Total init"); return { elements: [ diff --git a/apps/roam/src/utils/findDiscourseNode.ts b/apps/roam/src/utils/findDiscourseNode.ts index 345d0b41d..ab6f83c24 100644 --- a/apps/roam/src/utils/findDiscourseNode.ts +++ b/apps/roam/src/utils/findDiscourseNode.ts @@ -6,7 +6,7 @@ const discourseNodeTypeCache: Record = {}; const findDiscourseNode = ({ uid, title, - nodes = getDiscourseNodes(), + nodes, }: { uid: string; title?: string; @@ -16,8 +16,9 @@ const findDiscourseNode = ({ return discourseNodeTypeCache[uid]; } + const resolvedNodes = nodes ?? getDiscourseNodes(); const matchingNode = - nodes.find((node) => + resolvedNodes.find((node) => title === undefined ? matchDiscourseNode({ ...node, uid }) : matchDiscourseNode({ ...node, title }), diff --git a/apps/roam/src/utils/getDiscourseNodes.ts b/apps/roam/src/utils/getDiscourseNodes.ts index cd83f940f..cfbeb83c7 100644 --- a/apps/roam/src/utils/getDiscourseNodes.ts +++ b/apps/roam/src/utils/getDiscourseNodes.ts @@ -106,7 +106,18 @@ const getUidAndBooleanSetting = ({ }; }; -const getDiscourseNodes = (relations = getDiscourseRelations()) => { +let cachedNodes: DiscourseNode[] | null = null; + +export const invalidateDiscourseNodesCache = () => { + cachedNodes = null; +}; + +const getDiscourseNodes = ( + relations?: ReturnType, +) => { + if (!relations && cachedNodes) return cachedNodes; + + const resolvedRelations = relations ?? getDiscourseRelations(); const configuredNodes = ( isNewSettingsStoreEnabled() ? getAllDiscourseNodes() @@ -158,7 +169,7 @@ const getDiscourseNodes = (relations = getDiscourseRelations()) => { }, ) ).concat( - relations + resolvedRelations .filter((r) => r.triples.some((t) => t.some((n) => /anchor/i.test(n)))) .map((r) => ({ format: "", @@ -188,7 +199,11 @@ const getDiscourseNodes = (relations = getDiscourseRelations()) => { const defaultNodes = DEFAULT_NODES.filter( (n) => !configuredNodeTexts.has(n.text), ); - return configuredNodes.concat(defaultNodes); + const result = configuredNodes.concat(defaultNodes); + if (!relations) { + cachedNodes = result; + } + return result; }; export default getDiscourseNodes; diff --git a/apps/roam/src/utils/getDiscourseRelations.ts b/apps/roam/src/utils/getDiscourseRelations.ts index c9f24a911..02ba1b866 100644 --- a/apps/roam/src/utils/getDiscourseRelations.ts +++ b/apps/roam/src/utils/getDiscourseRelations.ts @@ -35,16 +35,23 @@ export const getRelationsNode = (grammarNode = getGrammarNode()) => { return grammarNode?.children.find(matchNodeText("relations")); }; -const getDiscourseRelations = () => { - if (isNewSettingsStoreEnabled()) { - return getAllRelations(); - } +let cachedRelations: DiscourseRelation[] | null = null; + +export const invalidateDiscourseRelationsCache = () => { + cachedRelations = null; +}; - const grammarNode = getGrammarNode(); - const relationsNode = getRelationsNode(grammarNode); - const relationNodes = relationsNode?.children || DEFAULT_RELATION_VALUES; - const discourseRelations = relationNodes.flatMap( - (r: InputTextNode, i: number) => { +const getDiscourseRelations = (): DiscourseRelation[] => { + if (cachedRelations) return cachedRelations; + + let result: DiscourseRelation[]; + if (isNewSettingsStoreEnabled()) { + result = getAllRelations(); + } else { + const grammarNode = getGrammarNode(); + const relationsNode = getRelationsNode(grammarNode); + const relationNodes = relationsNode?.children || DEFAULT_RELATION_VALUES; + result = relationNodes.flatMap((r: InputTextNode, i: number) => { const tree = (r?.children || []) as TextNode[]; const data = { id: r.uid || `${r.text}-${i}`, @@ -63,10 +70,11 @@ const getDiscourseRelations = () => { return [t.text, t.children[0]?.text, target] as const; }), })); - }, - ); + }); + } - return discourseRelations; + cachedRelations = result; + return result; }; export default getDiscourseRelations; diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index c946134ce..c7cee295c 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -101,6 +101,7 @@ export const initObservers = ({ }; cleanups: Array<() => void>; } => { + console.time("[DG Perf] observer: pageTitleObserver"); const pageTitleObserver = createHTMLObserver({ tag: "H1", className: "rm-title-display", @@ -131,12 +132,16 @@ export const initObservers = ({ else if (isSidebarCanvas(props)) renderTldrawCanvasInSidebar(props); }, }); + console.timeEnd("[DG Perf] observer: pageTitleObserver"); + console.time("[DG Perf] observer: queryBlockObserver"); const queryBlockObserver = createButtonObserver({ attribute: "query-block", render: (b) => renderQueryBlock(b, onloadArgs), }); + console.timeEnd("[DG Perf] observer: queryBlockObserver"); + console.time("[DG Perf] observer: nodeTagPopupButtonObserver"); const nodeTagPopupButtonObserver = createHTMLObserver({ className: "rm-page-ref--tag", tag: "SPAN", @@ -179,6 +184,7 @@ export const initObservers = ({ } }, }); + console.timeEnd("[DG Perf] observer: nodeTagPopupButtonObserver"); const pageActionListener = (( e: CustomEvent<{ @@ -201,6 +207,7 @@ export const initObservers = ({ }); }) as EventListener; + console.time("[DG Perf] observer: suggestiveOverlaySetup"); const suggestiveHandler = getSuggestiveOverlayHandler(onloadArgs); const toggleSuggestiveOverlay = onPageRefObserverChange(suggestiveHandler); if (getPersonalSetting([PERSONAL_KEYS.suggestiveModeOverlay])) { @@ -213,7 +220,9 @@ export const initObservers = ({ toggleSuggestiveOverlay(Boolean(newValue)); }, ); + console.timeEnd("[DG Perf] observer: suggestiveOverlaySetup"); + console.time("[DG Perf] observer: graphOverviewExportObserver"); const graphOverviewExportObserver = createHTMLObserver({ tag: "DIV", className: "rm-graph-view-control-panel__main-options", @@ -222,7 +231,9 @@ export const initObservers = ({ renderGraphOverviewExport(div); }, }); + console.timeEnd("[DG Perf] observer: graphOverviewExportObserver"); + console.time("[DG Perf] observer: imageMenuObserver"); const imageMenuObserver = createHTMLObserver({ tag: "IMG", className: "rm-inline-img", @@ -232,7 +243,9 @@ export const initObservers = ({ } }, }); + console.timeEnd("[DG Perf] observer: imageMenuObserver"); + console.time("[DG Perf] observer: pageRefObserverSetup"); if (getPersonalSetting([PERSONAL_KEYS.pagePreview])) addPageRefObserver(previewPageRefHandler); if (getPersonalSetting([PERSONAL_KEYS.discourseContextOverlay])) { @@ -240,7 +253,9 @@ export const initObservers = ({ onPageRefObserverChange(overlayHandler)(true); } if (getPageRefObserversSize()) enablePageRefObserver(); + console.timeEnd("[DG Perf] observer: pageRefObserverSetup"); + console.time("[DG Perf] observer: listenersAndTriggerSetup"); const configPageUid = getPageUidByPageTitle(DISCOURSE_CONFIG_PAGE_TITLE); const hashChangeListener = (e: Event) => { @@ -285,6 +300,9 @@ export const initObservers = ({ }, ); + console.timeEnd("[DG Perf] observer: listenersAndTriggerSetup"); + + console.time("[DG Perf] observer: leftSidebarObserver"); const leftSidebarObserver = createHTMLObserver({ tag: "DIV", useBody: true, @@ -300,6 +318,7 @@ export const initObservers = ({ })(); }, }); + console.timeEnd("[DG Perf] observer: leftSidebarObserver"); const handleNodeMenuRender = (target: HTMLElement, evt: KeyboardEvent) => { if ( diff --git a/apps/roam/src/utils/refreshConfigTree.ts b/apps/roam/src/utils/refreshConfigTree.ts index 606d8097c..e6b030c81 100644 --- a/apps/roam/src/utils/refreshConfigTree.ts +++ b/apps/roam/src/utils/refreshConfigTree.ts @@ -6,6 +6,9 @@ import registerDiscourseDatalogTranslators from "./registerDiscourseDatalogTrans import { unregisterDatalogTranslator } from "./conditionToDatalog"; import type { PullBlock } from "roamjs-components/types/native"; import { DISCOURSE_CONFIG_PAGE_TITLE } from "~/data/constants"; +import { invalidateDiscourseRelationsCache } from "./getDiscourseRelations"; +import { invalidateDiscourseNodesCache } from "./getDiscourseNodes"; +import { invalidateSettingsAccessorCaches } from "~/components/settings/utils/accessors"; const getPagesStartingWithPrefix = (prefix: string) => ( @@ -18,6 +21,10 @@ const getPagesStartingWithPrefix = (prefix: string) => })); const refreshConfigTree = () => { + invalidateSettingsAccessorCaches(); + invalidateDiscourseRelationsCache(); + invalidateDiscourseNodesCache(); + getDiscourseRelationLabels().forEach((key) => unregisterDatalogTranslator({ key }), ); From b9e611067a39ef73a57a95c4bb00f164fac65717 Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 27 Mar 2026 17:28:36 +0530 Subject: [PATCH 2/5] =?UTF-8?q?ENG-1579:=20Review=20fixes=20=E2=80=94=20re?= =?UTF-8?q?move=20diagnostic=20logs,=20rename=20caches,=20add=20version-ba?= =?UTF-8?q?sed=20invalidation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove all console.time instrumentation, replace with single performance.now() total init log - Rename _underscore cache variables to camelCase (drop eslint-disable) - Add configCacheVersion module for cross-module cache staleness without circular imports - Remove redundant invalidateNewSettingsStoreCache (shared watcher path already handles it) - Add TODO(ENG-1471) on legacy dual-read caches - Rename render → openSettingsDialog, measure settings open time via requestAnimationFrame --- apps/roam/src/components/LeftSidebarView.tsx | 12 +- .../roam/src/components/settings/Settings.tsx | 35 +++-- .../components/settings/utils/accessors.ts | 121 +++++++++--------- .../src/components/settings/utils/init.ts | 18 --- .../components/settings/utils/pullWatchers.ts | 8 +- apps/roam/src/index.ts | 34 +---- apps/roam/src/utils/configCacheVersion.ts | 9 ++ apps/roam/src/utils/getDiscourseNodes.ts | 9 +- apps/roam/src/utils/getDiscourseRelations.ts | 9 +- .../utils/initializeObserversAndListeners.ts | 19 --- 10 files changed, 117 insertions(+), 157 deletions(-) create mode 100644 apps/roam/src/utils/configCacheVersion.ts diff --git a/apps/roam/src/components/LeftSidebarView.tsx b/apps/roam/src/components/LeftSidebarView.tsx index 46ca39cc5..a3306f66c 100644 --- a/apps/roam/src/components/LeftSidebarView.tsx +++ b/apps/roam/src/components/LeftSidebarView.tsx @@ -52,9 +52,8 @@ import deleteBlock from "roamjs-components/writes/deleteBlock"; import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid"; import refreshConfigTree from "~/utils/refreshConfigTree"; import { Dispatch, SetStateAction } from "react"; -import { SettingsDialog } from "./settings/Settings"; +import { openSettingsDialog } from "./settings/Settings"; import { OnloadArgs } from "roamjs-components/types"; -import renderOverlay from "roamjs-components/util/renderOverlay"; import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid"; import { DISCOURSE_CONFIG_PAGE_TITLE } from "~/data/constants"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; @@ -445,12 +444,9 @@ const FavoritesPopover = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { }, [handleGlobalPointerDownCapture, isMenuOpen]); const renderSettingsDialog = (tabId: TabId) => { - renderOverlay({ - Overlay: SettingsDialog, - props: { - onloadArgs, - selectedTabId: tabId, - }, + openSettingsDialog({ + onloadArgs, + selectedTabId: tabId, }); }; diff --git a/apps/roam/src/components/settings/Settings.tsx b/apps/roam/src/components/settings/Settings.tsx index 8360e1a44..581ad91f7 100644 --- a/apps/roam/src/components/settings/Settings.tsx +++ b/apps/roam/src/components/settings/Settings.tsx @@ -50,7 +50,7 @@ export const SettingsPanel = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => {