From 343dc117bef4337edf77151ab568d910ed5c96be Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 6 Apr 2026 17:00:13 +0530 Subject: [PATCH 1/6] ENG-1616: Bulk-read settings + thread snapshot (with timing logs) Cut plugin load from ~20925ms to ~1327ms (94%) on a real graph by collapsing per-call settings accessors into a single bulk read at init and threading that snapshot through the init chain + observer callbacks. Key changes: - accessors.ts: bulkReadSettings() runs ONE pull query against the settings page's direct children and returns { featureFlags, globalSettings, personalSettings } parsed via Zod. readPathValue exported. - getDiscourseNodes / getDiscourseRelations / getAllRelations: optional snapshot param threaded through, no breaking changes to existing callers. - initializeDiscourseNodes + refreshConfigTree (+ registerDiscourseDatalog- Translators, getDiscourseRelationLabels): accept and forward snapshot. - index.ts: bulkReadSettings() at the top of init; snapshot threaded into initializeDiscourseNodes, refreshConfigTree, initObservers, installDiscourseFloatingMenu, setInitialQueryPages, and the 3 sync sites inside index.ts itself. - initializeObserversAndListeners.ts: snapshot threaded into the sync-init body; pageTitleObserver + leftSidebarObserver callbacks call bulkReadSettings() per fire (fresh, not stale); nodeTagPopupButtonObserver uses per-sync-batch memoization via queueMicrotask; hashChangeListener and nodeCreationPopoverListener use bulkReadSettings() per fire. - findDiscourseNode: snapshot param added; getDiscourseNodes() default-arg moved inside the cache-miss branch so cache hits don't waste the call. - isQueryPage / isCanvasPage / QueryPagesPanel.getQueryPages: optional snapshot param. - LeftSidebarView.buildConfig / useConfig / mountLeftSidebar: optional initialSnapshot threaded for the first render; emitter-driven updates keep using live reads for post-mount reactivity. - DiscourseFloatingMenu.installDiscourseFloatingMenu: optional snapshot. - posthog.initPostHog: removed redundant internal getPersonalSetting check (caller already guards from the snapshot). - migrateLegacyToBlockProps.hasGraphMigrationMarker: accepts the existing blockMap and does an O(1) lookup instead of a getBlockUidByTextOnPage scan. Includes per-phase timing console.logs across index.ts, refreshConfigTree, init.ts, initSettingsPageBlocks, and initObservers. Committed as a checkpoint so we can reference measurements later; will be removed in the next commit. --- .../src/components/DiscourseFloatingMenu.tsx | 11 +- apps/roam/src/components/LeftSidebarView.tsx | 53 +++++-- .../components/settings/QueryPagesPanel.tsx | 17 ++- .../components/settings/utils/accessors.ts | 63 ++++++++- .../src/components/settings/utils/init.ts | 37 ++++- .../utils/migrateLegacyToBlockProps.ts | 10 +- apps/roam/src/index.ts | 86 +++++++++--- apps/roam/src/utils/findDiscourseNode.ts | 8 +- apps/roam/src/utils/getDiscourseNodes.ts | 14 +- .../src/utils/getDiscourseRelationLabels.ts | 17 ++- apps/roam/src/utils/getDiscourseRelations.ts | 10 +- .../src/utils/initializeDiscourseNodes.ts | 7 +- .../utils/initializeObserversAndListeners.ts | 130 ++++++++++++++---- apps/roam/src/utils/isCanvasPage.ts | 30 +++- apps/roam/src/utils/isQueryPage.ts | 11 +- apps/roam/src/utils/posthog.ts | 9 +- apps/roam/src/utils/refreshConfigTree.ts | 29 +++- .../registerDiscourseDatalogTranslators.ts | 7 +- apps/roam/src/utils/setQueryPages.ts | 12 +- 19 files changed, 441 insertions(+), 120 deletions(-) diff --git a/apps/roam/src/components/DiscourseFloatingMenu.tsx b/apps/roam/src/components/DiscourseFloatingMenu.tsx index 3a7e337b9..0f6c8e52d 100644 --- a/apps/roam/src/components/DiscourseFloatingMenu.tsx +++ b/apps/roam/src/components/DiscourseFloatingMenu.tsx @@ -13,7 +13,10 @@ import { import { FeedbackWidget } from "./BirdEatsBugs"; import { render as renderSettings } from "~/components/settings/Settings"; import posthog from "posthog-js"; -import { getPersonalSetting } from "./settings/utils/accessors"; +import { + getPersonalSetting, + type SettingsSnapshot, +} from "./settings/utils/accessors"; import { PERSONAL_KEYS } from "./settings/utils/settingKeys"; type DiscourseFloatingMenuProps = { @@ -118,6 +121,7 @@ export const showDiscourseFloatingMenu = () => { export const installDiscourseFloatingMenu = ( onLoadArgs: OnloadArgs, + snapshot?: SettingsSnapshot, props: DiscourseFloatingMenuProps = { position: "bottom-right", theme: "bp3-light", @@ -130,7 +134,10 @@ export const installDiscourseFloatingMenu = ( floatingMenuAnchor.id = ANCHOR_ID; document.getElementById("app")?.appendChild(floatingMenuAnchor); } - if (getPersonalSetting([PERSONAL_KEYS.hideFeedbackButton])) { + const hideFeedbackButton = snapshot + ? snapshot.personalSettings[PERSONAL_KEYS.hideFeedbackButton] + : getPersonalSetting([PERSONAL_KEYS.hideFeedbackButton]); + if (hideFeedbackButton) { floatingMenuAnchor.classList.add("hidden"); } ReactDOM.render( diff --git a/apps/roam/src/components/LeftSidebarView.tsx b/apps/roam/src/components/LeftSidebarView.tsx index 46ca39cc5..abb3b6680 100644 --- a/apps/roam/src/components/LeftSidebarView.tsx +++ b/apps/roam/src/components/LeftSidebarView.tsx @@ -39,6 +39,7 @@ import { getPersonalSettings, setGlobalSetting, setPersonalSetting, + type SettingsSnapshot, } from "~/components/settings/utils/accessors"; import { PERSONAL_KEYS, @@ -79,6 +80,10 @@ const truncate = (s: string, max: number | undefined): string => { }; const openTarget = async (e: React.MouseEvent, targetUid: string) => { + const _navStart = performance.now(); + console.log( + `[DG Nav] openTarget click t=${Math.round(_navStart)} target=${targetUid}`, + ); e.preventDefault(); e.stopPropagation(); const target = parseReference(targetUid); @@ -89,11 +94,17 @@ const openTarget = async (e: React.MouseEvent, targetUid: string) => { if (target.type === "block") { if (e.shiftKey) { await openBlockInSidebar(target.uid); + console.log( + `[DG Nav] openBlockInSidebar resolved +${Math.round(performance.now() - _navStart)}ms`, + ); return; } await window.roamAlphaAPI.ui.mainWindow.openBlock({ block: { uid: target.uid }, }); + console.log( + `[DG Nav] openBlock resolved +${Math.round(performance.now() - _navStart)}ms`, + ); return; } @@ -103,10 +114,16 @@ const openTarget = async (e: React.MouseEvent, targetUid: string) => { // eslint-disable-next-line @typescript-eslint/naming-convention window: { type: "outline", "block-uid": targetUid }, }); + console.log( + `[DG Nav] rightSidebar.addWindow resolved +${Math.round(performance.now() - _navStart)}ms`, + ); } else { await window.roamAlphaAPI.ui.mainWindow.openPage({ page: { uid: targetUid }, }); + console.log( + `[DG Nav] openPage resolved +${Math.round(performance.now() - _navStart)}ms`, + ); } }; @@ -336,14 +353,16 @@ const GlobalSection = ({ config }: { config: LeftSidebarConfig["global"] }) => { // TODO(ENG-1471): Remove old-system merge when migration complete — just use accessor values directly. // See mergeGlobalSectionWithAccessor/mergePersonalSectionsWithAccessor for why the merge exists. -const buildConfig = (): LeftSidebarConfig => { +const buildConfig = (snapshot?: SettingsSnapshot): LeftSidebarConfig => { // Read VALUES from accessor (handles flag routing + mismatch detection) - const globalValues = getGlobalSetting([ - GLOBAL_KEYS.leftSidebar, - ]); - const personalValues = getPersonalSetting< - ReturnType[typeof PERSONAL_KEYS.leftSidebar] - >([PERSONAL_KEYS.leftSidebar]); + const globalValues = snapshot + ? snapshot.globalSettings[GLOBAL_KEYS.leftSidebar] + : getGlobalSetting([GLOBAL_KEYS.leftSidebar]); + const personalValues = snapshot + ? snapshot.personalSettings[PERSONAL_KEYS.leftSidebar] + : getPersonalSetting< + ReturnType[typeof PERSONAL_KEYS.leftSidebar] + >([PERSONAL_KEYS.leftSidebar]); // Read UIDs from old system (needed for fold CRUD during dual-write) const oldConfig = getCurrentLeftSidebarConfig(); @@ -364,8 +383,8 @@ const buildConfig = (): LeftSidebarConfig => { }; }; -export const useConfig = () => { - const [config, setConfig] = useState(() => buildConfig()); +export const useConfig = (initialSnapshot?: SettingsSnapshot) => { + const [config, setConfig] = useState(() => buildConfig(initialSnapshot)); useEffect(() => { const handleUpdate = () => { setConfig(buildConfig()); @@ -504,8 +523,14 @@ const FavoritesPopover = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { ); }; -const LeftSidebarView = ({ onloadArgs }: { onloadArgs: OnloadArgs }) => { - const { config } = useConfig(); +const LeftSidebarView = ({ + onloadArgs, + initialSnapshot, +}: { + onloadArgs: OnloadArgs; + initialSnapshot?: SettingsSnapshot; +}) => { + const { config } = useConfig(initialSnapshot); return ( <> @@ -613,6 +638,7 @@ const migrateFavorites = async () => { export const mountLeftSidebar = async ( wrapper: HTMLElement, onloadArgs: OnloadArgs, + initialSnapshot?: SettingsSnapshot, ): Promise => { if (!wrapper) return; @@ -630,7 +656,10 @@ export const mountLeftSidebar = async ( } else { root.className = "starred-pages"; } - ReactDOM.render(, root); + ReactDOM.render( + , + root, + ); }; export default LeftSidebarView; diff --git a/apps/roam/src/components/settings/QueryPagesPanel.tsx b/apps/roam/src/components/settings/QueryPagesPanel.tsx index 49122bf5e..4966a4ef8 100644 --- a/apps/roam/src/components/settings/QueryPagesPanel.tsx +++ b/apps/roam/src/components/settings/QueryPagesPanel.tsx @@ -4,7 +4,9 @@ import React, { useState } from "react"; import type { OnloadArgs } from "roamjs-components/types"; import { getPersonalSetting, + readPathValue, setPersonalSetting, + type SettingsSnapshot, } from "~/components/settings/utils/accessors"; import { PERSONAL_KEYS, @@ -13,11 +15,16 @@ import { // Legacy extensionAPI stored query-pages as string | string[] | Record. // Coerce to string[] for backward compatibility with old stored formats. -export const getQueryPages = (): string[] => { - const value = getPersonalSetting>([ - PERSONAL_KEYS.query, - QUERY_KEYS.queryPages, - ]); +export const getQueryPages = (snapshot?: SettingsSnapshot): string[] => { + const value = snapshot + ? (readPathValue(snapshot.personalSettings, [ + PERSONAL_KEYS.query, + QUERY_KEYS.queryPages, + ]) as string[] | string | Record | undefined) + : getPersonalSetting>([ + PERSONAL_KEYS.query, + QUERY_KEYS.queryPages, + ]); return typeof value === "string" ? [value] : Array.isArray(value) diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 2eb2c0b2a..10cd32585 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -143,7 +143,7 @@ const getSchemaAtPath = ( const formatSettingPath = (keys: string[]): string => keys.length === 0 ? "(root)" : keys.join(" > "); -const readPathValue = (root: unknown, keys: string[]): unknown => +export const readPathValue = (root: unknown, keys: string[]): unknown => keys.reduce((current, key) => { if (Array.isArray(current)) { const index = Number(key); @@ -863,8 +863,10 @@ export const setGlobalSetting = (keys: string[], value: json): void => { }); }; -export const getAllRelations = (): DiscourseRelation[] => { - const settings = getGlobalSettings(); +export const getAllRelations = ( + snapshot?: SettingsSnapshot, +): DiscourseRelation[] => { + const settings = snapshot ? snapshot.globalSettings : getGlobalSettings(); return Object.entries(settings.Relations).flatMap(([id, relation]) => relation.ifConditions.map((ifCondition) => ({ @@ -909,6 +911,61 @@ export const getPersonalSetting = ( return blockPropsValue as T | undefined; }; +export type SettingsSnapshot = { + featureFlags: FeatureFlags; + globalSettings: GlobalSettings; + personalSettings: PersonalSettings; +}; + +export const bulkReadSettings = (): SettingsSnapshot => { + const start = performance.now(); + + const pageResult = window.roamAlphaAPI.pull( + "[{:block/children [:block/string :block/props]}]", + [":node/title", DG_BLOCK_PROP_SETTINGS_PAGE_TITLE], + ) as Record | null; + const afterQuery = performance.now(); + + const children = (pageResult?.[":block/children"] ?? []) as Record< + string, + json + >[]; + const personalKey = getPersonalSettingsKey(); + let featureFlagsProps: json = {}; + let globalProps: json = {}; + let personalProps: json = {}; + + for (const child of children) { + const text = child[":block/string"]; + if (typeof text !== "string") continue; + const rawBlockProps = child[":block/props"]; + const blockProps = + rawBlockProps && typeof rawBlockProps === "object" + ? normalizeProps(rawBlockProps) + : {}; + if (text === TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags) { + featureFlagsProps = blockProps; + } else if (text === TOP_LEVEL_BLOCK_PROP_KEYS.global) { + globalProps = blockProps; + } else if (text === personalKey) { + personalProps = blockProps; + } + } + + const snapshot: SettingsSnapshot = { + featureFlags: FeatureFlagsSchema.parse(featureFlagsProps || {}), + globalSettings: GlobalSettingsSchema.parse(globalProps || {}), + personalSettings: PersonalSettingsSchema.parse(personalProps || {}), + }; + + const end = performance.now(); + console.log( + `[DG Plugin] bulkReadSettings: ${Math.round(end - start)}ms (query ${Math.round(afterQuery - start)}ms, parse ${Math.round(end - afterQuery)}ms)`, + ); + + return snapshot; +}; + export const setPersonalSetting = (keys: string[], value: json): void => { if (keys.length === 0) { internalError({ diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 07a15f1d6..487314652 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -189,15 +189,29 @@ const initializeSettingsBlockProps = ( }; const initSettingsPageBlocks = async (): Promise> => { + let t = performance.now(); + const mark = (label: string) => { + const now = performance.now(); + console.log( + `[DG Plugin] initSettingsPageBlocks.${label}: ${Math.round(now - t)}ms`, + ); + t = now; + }; + const pageUid = await ensurePageExists(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE); + mark("ensurePageExists"); const blockMap = buildBlockMap(pageUid); + mark("buildBlockMap"); const topLevelBlocks = getTopLevelBlockPropsConfig().map(({ key }) => key); await ensureBlocksExist(pageUid, topLevelBlocks, blockMap); + mark("ensureBlocksExist (top-level)"); await ensureLegacyConfigBlocks(pageUid); + mark("ensureLegacyConfigBlocks"); initializeSettingsBlockProps(pageUid, blockMap); + mark("initializeSettingsBlockProps"); return blockMap; }; @@ -411,16 +425,25 @@ const logDualReadComparison = (): void => { }; export const initSchema = async (): Promise => { + console.log("[DG Plugin] Initializing schema..."); + let t = performance.now(); + const mark = (label: string) => { + const now = performance.now(); + console.log(`[DG Plugin] initSchema.${label}: ${Math.round(now - t)}ms`); + t = now; + }; + const blockUids = await initSettingsPageBlocks(); + mark("initSettingsPageBlocks"); + await migrateGraphLevel(blockUids); + mark("migrateGraphLevel"); + const nodePageUids = await initDiscourseNodePages(); + mark("initDiscourseNodePages"); + await migratePersonalSettings(blockUids); - try { - logDualReadComparison(); - } catch (e) { - console.warn("[DG Dual-Read] Comparison failed:", e); - } - (window as unknown as Record).dgDualReadLog = - logDualReadComparison; + mark("migratePersonalSettings"); + return { blockUids, nodePageUids }; }; diff --git a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts index 8ca1fe496..0e0fa45c9 100644 --- a/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts +++ b/apps/roam/src/components/settings/utils/migrateLegacyToBlockProps.ts @@ -1,7 +1,6 @@ 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"; @@ -29,11 +28,8 @@ 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({ - text: GRAPH_MIGRATION_MARKER, - title: DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, - }); +const hasGraphMigrationMarker = (blockMap: Record): boolean => + !!blockMap[GRAPH_MIGRATION_MARKER]; const isPropsValid = ( schema: z.ZodTypeAny, @@ -182,7 +178,7 @@ export const migrateGraphLevel = async ( return; } - if (hasGraphMigrationMarker()) { + if (hasGraphMigrationMarker(blockUids)) { console.log(`${LOG_PREFIX} graph-level: skipped (already migrated)`); return; } diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index ff8e95d9a..3f3ad01b9 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -31,12 +31,12 @@ import { initializeSupabaseSync, setSyncActivity, } from "./utils/syncDgNodesToSupabase"; -import { initPluginTimer } from "./utils/pluginTimer"; +import { getPluginElapsedTime, initPluginTimer } from "./utils/pluginTimer"; import { initPostHog } from "./utils/posthog"; import { initSchema } from "./components/settings/utils/init"; import { - getFeatureFlag, - getPersonalSetting, + bulkReadSettings, + readPathValue, } from "./components/settings/utils/accessors"; import { PERSONAL_KEYS } from "./components/settings/utils/settingKeys"; import { setupPullWatchOnSettingsPage } from "./components/settings/utils/pullWatchers"; @@ -49,17 +49,34 @@ import { mountLeftSidebar } from "./components/LeftSidebarView"; export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; export default runExtension(async (onloadArgs) => { + initPluginTimer(); + console.log("[DG Plugin] load: start"); + let lastMark = performance.now(); + const mark = (label: string) => { + const now = performance.now(); + console.log( + `[DG Plugin] ${label}: +${Math.round(now - lastMark)}ms (total ${getPluginElapsedTime()}ms)`, + ); + lastMark = now; + }; + + const settingsSnapshot = bulkReadSettings(); + mark("bulkReadSettings"); + const isEncrypted = window.roamAlphaAPI.graph.isEncrypted; + mark("encrypted check"); const isOffline = window.roamAlphaAPI.graph.type === "offline"; - const disallowDiagnostics = getPersonalSetting([ + mark("offline check"); + const disallowDiagnostics = readPathValue(settingsSnapshot.personalSettings, [ PERSONAL_KEYS.disableProductDiagnostics, - ]); + ]) as boolean | undefined; + mark("diagnostics check"); if (!isEncrypted && !isOffline && !disallowDiagnostics) { initPostHog(); } - + mark("posthog init"); initFeedbackWidget(); - + mark("feedback widget init"); if (window?.roamjs?.loaded?.has("query-builder")) { renderToast({ timeout: 10000, @@ -78,34 +95,51 @@ export default runExtension(async (onloadArgs) => { timeout: 500, }); } + mark("posthog + feedback widget + load check"); + await initializeDiscourseNodes(settingsSnapshot); + mark("initializeDiscourseNodes"); - initPluginTimer(); - - await initializeDiscourseNodes(); - refreshConfigTree(); + refreshConfigTree(settingsSnapshot); + mark("refreshConfigTree"); addGraphViewNodeStyling(); + mark("graph view styling"); registerCommandPaletteCommands(onloadArgs); + mark("command palette commands"); createSettingsPanel(onloadArgs); + mark("settings panel"); registerSmartBlock(onloadArgs); - setInitialQueryPages(onloadArgs); + mark("registerSmartBlock"); + + setInitialQueryPages(onloadArgs, settingsSnapshot); + mark("setInitialQueryPages"); const style = addStyle(styles); + mark("addStyle styles"); const discourseGraphStyle = addStyle(discourseGraphStyles); + mark("addStyle discourseGraphStyles"); const settingsStyle = addStyle(settingsStyles); + mark("addStyle settingsStyles"); const discourseFloatingMenuStyle = addStyle(discourseFloatingMenuStyles); + mark("addStyle discourseFloatingMenuStyles"); // Add streamline styling only if enabled - const isStreamlineStylingEnabled = getPersonalSetting([ - PERSONAL_KEYS.streamlineStyling, - ]); + const isStreamlineStylingEnabled = readPathValue( + settingsSnapshot.personalSettings, + [PERSONAL_KEYS.streamlineStyling], + ) as boolean | undefined; let streamlineStyleElement: HTMLStyleElement | null = null; if (isStreamlineStylingEnabled) { streamlineStyleElement = addStyle(streamlineStyling); streamlineStyleElement.id = "streamline-styling"; } + mark("streamline style check"); - const { observers, listeners, cleanups } = initObservers({ onloadArgs }); + const { observers, listeners, cleanups } = initObservers({ + onloadArgs, + settingsSnapshot, + }); + mark("initObservers"); const { pageActionListener, hashChangeListener, @@ -114,14 +148,20 @@ export default runExtension(async (onloadArgs) => { nodeCreationPopoverListener, } = listeners; document.addEventListener("roamjs:query-builder:action", pageActionListener); + mark("pageActionListener addEventListener"); window.addEventListener("hashchange", hashChangeListener); + mark("hashChangeListener addEventListener"); document.addEventListener("keydown", nodeMenuTriggerListener); + mark("nodeMenuTriggerListener addEventListener"); document.addEventListener("input", discourseNodeSearchTriggerListener); + mark("discourseNodeSearchTriggerListener addEventListener"); document.addEventListener("selectionchange", nodeCreationPopoverListener); + mark("document event listeners"); - if (getFeatureFlag("Suggestive mode enabled")) { + if (settingsSnapshot.featureFlags["Suggestive mode enabled"]) { initializeSupabaseSync(); } + mark("suggestive supabase init"); const unsubSuggestiveMode = onSettingChange( settingKeys.suggestiveModeEnabled, @@ -133,6 +173,7 @@ export default runExtension(async (onloadArgs) => { } }, ); + mark("unsubSuggestiveMode onSettingChange"); const { extensionAPI } = onloadArgs; window.roamjs.extension.queryBuilder = { @@ -149,12 +190,15 @@ export default runExtension(async (onloadArgs) => { // @ts-expect-error - we are still using roamjs-components global definition getDiscourseNodes: getDiscourseNodes, }; + mark("roamjs.extension.queryBuilder assign"); - installDiscourseFloatingMenu(onloadArgs); + installDiscourseFloatingMenu(onloadArgs, settingsSnapshot); + mark("installDiscourseFloatingMenu"); const leftSidebarScript = document.querySelector( 'script#roam-left-sidebar[src="https://sid597.github.io/roam-left-sidebar/js/main.js"]', ); + mark("leftSidebarScript querySelector"); if (leftSidebarScript) { renderToast({ @@ -165,6 +209,7 @@ export default runExtension(async (onloadArgs) => { "Discourse Graph detected the Roam left sidebar script. Running both sidebars may cause issues. Please remove the Roam left sidebar script from your Roam instance, and reload the graph.", }); } + mark("leftSidebarScript conflict toast"); const unsubLeftSidebarFlag = onSettingChange( settingKeys.leftSidebarFlag, @@ -188,9 +233,14 @@ export default runExtension(async (onloadArgs) => { } }, ); + mark("unsubLeftSidebarFlag onSettingChange"); const { blockUids } = await initSchema(); + mark("initSchema"); const cleanupPullWatchers = setupPullWatchOnSettingsPage(blockUids); + mark("setupPullWatchOnSettingsPage"); + + console.log(`[DG Plugin] load: done in ${getPluginElapsedTime()}ms`); return { elements: [ diff --git a/apps/roam/src/utils/findDiscourseNode.ts b/apps/roam/src/utils/findDiscourseNode.ts index 345d0b41d..e0af95981 100644 --- a/apps/roam/src/utils/findDiscourseNode.ts +++ b/apps/roam/src/utils/findDiscourseNode.ts @@ -1,23 +1,27 @@ import getDiscourseNodes, { type DiscourseNode } from "./getDiscourseNodes"; import matchDiscourseNode from "./matchDiscourseNode"; +import type { SettingsSnapshot } from "~/components/settings/utils/accessors"; const discourseNodeTypeCache: Record = {}; const findDiscourseNode = ({ uid, title, - nodes = getDiscourseNodes(), + nodes, + snapshot, }: { uid: string; title?: string; nodes?: DiscourseNode[]; + snapshot?: SettingsSnapshot; }): DiscourseNode | false => { if (typeof discourseNodeTypeCache[uid] !== "undefined") { return discourseNodeTypeCache[uid]; } + const resolvedNodes = nodes ?? getDiscourseNodes(undefined, snapshot); 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..765d12422 100644 --- a/apps/roam/src/utils/getDiscourseNodes.ts +++ b/apps/roam/src/utils/getDiscourseNodes.ts @@ -3,6 +3,7 @@ import getSubTree from "roamjs-components/util/getSubTree"; import { isNewSettingsStoreEnabled, getAllDiscourseNodes, + type SettingsSnapshot, } from "~/components/settings/utils/accessors"; import discourseConfigRef from "./discourseConfigRef"; import getDiscourseRelations from "./getDiscourseRelations"; @@ -106,9 +107,16 @@ const getUidAndBooleanSetting = ({ }; }; -const getDiscourseNodes = (relations = getDiscourseRelations()) => { +const getDiscourseNodes = ( + relations?: ReturnType, + snapshot?: SettingsSnapshot, +) => { + const resolvedRelations = relations ?? getDiscourseRelations(snapshot); + const newStoreEnabled = snapshot + ? snapshot.featureFlags["Use new settings store"] + : isNewSettingsStoreEnabled(); const configuredNodes = ( - isNewSettingsStoreEnabled() + newStoreEnabled ? getAllDiscourseNodes() : Object.entries(discourseConfigRef.nodes).map( ([type, { text, children }]): DiscourseNode => { @@ -158,7 +166,7 @@ const getDiscourseNodes = (relations = getDiscourseRelations()) => { }, ) ).concat( - relations + resolvedRelations .filter((r) => r.triples.some((t) => t.some((n) => /anchor/i.test(n)))) .map((r) => ({ format: "", diff --git a/apps/roam/src/utils/getDiscourseRelationLabels.ts b/apps/roam/src/utils/getDiscourseRelationLabels.ts index 3355b8f22..33089618c 100644 --- a/apps/roam/src/utils/getDiscourseRelationLabels.ts +++ b/apps/roam/src/utils/getDiscourseRelationLabels.ts @@ -1,8 +1,17 @@ import getDiscourseRelations from "./getDiscourseRelations"; +import type { SettingsSnapshot } from "~/components/settings/utils/accessors"; -const getDiscourseRelationLabels = (relations = getDiscourseRelations()) => - Array.from(new Set(relations.flatMap((r) => [r.label, r.complement]))).filter( - (s) => !!s, - ); +const getDiscourseRelationLabels = ( + relations?: ReturnType, + snapshot?: SettingsSnapshot, +) => + Array.from( + new Set( + (relations ?? getDiscourseRelations(snapshot)).flatMap((r) => [ + r.label, + r.complement, + ]), + ), + ).filter((s) => !!s); export default getDiscourseRelationLabels; diff --git a/apps/roam/src/utils/getDiscourseRelations.ts b/apps/roam/src/utils/getDiscourseRelations.ts index c9f24a911..d7e36cab7 100644 --- a/apps/roam/src/utils/getDiscourseRelations.ts +++ b/apps/roam/src/utils/getDiscourseRelations.ts @@ -9,6 +9,7 @@ import DEFAULT_RELATION_VALUES from "~/data/defaultDiscourseRelations"; import { isNewSettingsStoreEnabled, getAllRelations, + type SettingsSnapshot, } from "~/components/settings/utils/accessors"; import discourseConfigRef from "./discourseConfigRef"; @@ -35,9 +36,12 @@ export const getRelationsNode = (grammarNode = getGrammarNode()) => { return grammarNode?.children.find(matchNodeText("relations")); }; -const getDiscourseRelations = () => { - if (isNewSettingsStoreEnabled()) { - return getAllRelations(); +const getDiscourseRelations = (snapshot?: SettingsSnapshot) => { + const newStoreEnabled = snapshot + ? snapshot.featureFlags["Use new settings store"] + : isNewSettingsStoreEnabled(); + if (newStoreEnabled) { + return getAllRelations(snapshot); } const grammarNode = getGrammarNode(); diff --git a/apps/roam/src/utils/initializeDiscourseNodes.ts b/apps/roam/src/utils/initializeDiscourseNodes.ts index 46a832b48..6ab88ddf1 100644 --- a/apps/roam/src/utils/initializeDiscourseNodes.ts +++ b/apps/roam/src/utils/initializeDiscourseNodes.ts @@ -2,9 +2,12 @@ import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTit import { createPage } from "roamjs-components/writes"; import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes"; import getDiscourseNodes, { excludeDefaultNodes } from "./getDiscourseNodes"; +import type { SettingsSnapshot } from "~/components/settings/utils/accessors"; -const initializeDiscourseNodes = async () => { - const nodes = getDiscourseNodes().filter(excludeDefaultNodes); +const initializeDiscourseNodes = async (snapshot?: SettingsSnapshot) => { + const nodes = getDiscourseNodes(undefined, snapshot).filter( + excludeDefaultNodes, + ); if (nodes.length === 0) { await Promise.all( INITIAL_NODE_VALUES.map( diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index c946134ce..b49e08c79 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -25,7 +25,9 @@ import { onPageRefObserverChange, getSuggestiveOverlayHandler, } from "~/utils/pageRefObserverHandlers"; -import getDiscourseNodes from "~/utils/getDiscourseNodes"; +import getDiscourseNodes, { + type DiscourseNode, +} from "~/utils/getDiscourseNodes"; import { OnloadArgs } from "roamjs-components/types"; import refreshConfigTree from "~/utils/refreshConfigTree"; import { render as renderGraphOverviewExport } from "~/components/ExportDiscourseContext"; @@ -52,9 +54,9 @@ import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTit import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import findDiscourseNode from "./findDiscourseNode"; import { - getPersonalSetting, - getFeatureFlag, - getGlobalSetting, + readPathValue, + bulkReadSettings, + type SettingsSnapshot, } from "~/components/settings/utils/accessors"; import { onSettingChange, @@ -88,8 +90,10 @@ const getTitleAndUidFromHeader = (h1: HTMLHeadingElement) => { export const initObservers = ({ onloadArgs, + settingsSnapshot, }: { onloadArgs: OnloadArgs; + settingsSnapshot: SettingsSnapshot; }): { observers: MutationObserver[]; listeners: { @@ -101,17 +105,33 @@ export const initObservers = ({ }; cleanups: Array<() => void>; } => { + let markT = performance.now(); + const markPhase = (label: string) => { + const now = performance.now(); + console.log( + `[DG Plugin] initObservers.${label}: ${Math.round(now - markT)}ms`, + ); + markT = now; + }; + const pageTitleObserver = createHTMLObserver({ tag: "H1", className: "rm-title-display", callback: (e) => { const h1 = e as HTMLHeadingElement; const { title, uid } = getTitleAndUidFromHeader(h1); - const props = { title, h1, onloadArgs }; - const isSuggestiveModeEnabled = getFeatureFlag("Suggestive mode enabled"); + const callbackSnapshot = bulkReadSettings(); - const node = findDiscourseNode({ uid, title }); + const props = { title, h1, onloadArgs }; + + const isSuggestiveModeEnabled = + callbackSnapshot.featureFlags["Suggestive mode enabled"]; + const node = findDiscourseNode({ + uid, + title, + snapshot: callbackSnapshot, + }); const isDiscourseNode = node && node.backedBy !== "default"; if (isDiscourseNode) { renderDiscourseContext({ h1, uid }); @@ -125,17 +145,36 @@ export const initObservers = ({ renderCanvasReferences(linkedReferencesDiv, uid, onloadArgs); } } - - if (isQueryPage({ title })) renderQueryPage(props); - else if (isCurrentPageCanvas(props)) renderTldrawCanvas(props); - else if (isSidebarCanvas(props)) renderTldrawCanvasInSidebar(props); + if (isQueryPage({ title, snapshot: callbackSnapshot })) { + renderQueryPage(props); + } else if ( + isCurrentPageCanvas({ title, h1, snapshot: callbackSnapshot }) + ) { + renderTldrawCanvas(props); + } else if (isSidebarCanvas({ title, h1, snapshot: callbackSnapshot })) { + renderTldrawCanvasInSidebar(props); + } }, }); + markPhase("pageTitleObserver"); const queryBlockObserver = createButtonObserver({ attribute: "query-block", render: (b) => renderQueryBlock(b, onloadArgs), }); + markPhase("queryBlockObserver"); + + let batchedTagNodes: DiscourseNode[] | null = null; + const getNodesForTagBatch = (): DiscourseNode[] => { + if (batchedTagNodes === null) { + const snap = bulkReadSettings(); + batchedTagNodes = getDiscourseNodes(undefined, snap); + queueMicrotask(() => { + batchedTagNodes = null; + }); + } + return batchedTagNodes; + }; const nodeTagPopupButtonObserver = createHTMLObserver({ className: "rm-page-ref--tag", @@ -145,7 +184,7 @@ export const initObservers = ({ if (tag) { const normalizedTag = getCleanTagText(tag); - for (const node of getDiscourseNodes()) { + for (const node of getNodesForTagBatch()) { const normalizedNodeTag = node.tag ? getCleanTagText(node.tag) : ""; if (normalizedTag === normalizedNodeTag) { renderNodeTagPopupButton(s, node, onloadArgs.extensionAPI); @@ -179,6 +218,7 @@ export const initObservers = ({ } }, }); + markPhase("nodeTagPopupButtonObserver"); const pageActionListener = (( e: CustomEvent<{ @@ -203,7 +243,11 @@ export const initObservers = ({ const suggestiveHandler = getSuggestiveOverlayHandler(onloadArgs); const toggleSuggestiveOverlay = onPageRefObserverChange(suggestiveHandler); - if (getPersonalSetting([PERSONAL_KEYS.suggestiveModeOverlay])) { + if ( + readPathValue(settingsSnapshot.personalSettings, [ + PERSONAL_KEYS.suggestiveModeOverlay, + ]) + ) { addPageRefObserver(suggestiveHandler); } @@ -213,6 +257,7 @@ export const initObservers = ({ toggleSuggestiveOverlay(Boolean(newValue)); }, ); + markPhase("pageAction/suggestive handlers"); const graphOverviewExportObserver = createHTMLObserver({ tag: "DIV", @@ -232,35 +277,62 @@ export const initObservers = ({ } }, }); + markPhase("graphOverviewExport + imageMenu observers"); - if (getPersonalSetting([PERSONAL_KEYS.pagePreview])) + if ( + readPathValue(settingsSnapshot.personalSettings, [ + PERSONAL_KEYS.pagePreview, + ]) + ) addPageRefObserver(previewPageRefHandler); - if (getPersonalSetting([PERSONAL_KEYS.discourseContextOverlay])) { + + if ( + readPathValue(settingsSnapshot.personalSettings, [ + PERSONAL_KEYS.discourseContextOverlay, + ]) + ) { const overlayHandler = getOverlayHandler(onloadArgs); onPageRefObserverChange(overlayHandler)(true); } + if (getPageRefObserversSize()) enablePageRefObserver(); + markPhase("pageRef observer wiring"); const configPageUid = getPageUidByPageTitle(DISCOURSE_CONFIG_PAGE_TITLE); + markPhase("configPageUid lookup"); const hashChangeListener = (e: Event) => { const evt = e as HashChangeEvent; + const hashStart = performance.now(); + console.log( + `[DG Nav] hashChange fired t=${Math.round(hashStart)} old=${evt.oldURL} new=${evt.newURL}`, + ); + const navSnapshot = bulkReadSettings(); // Attempt to refresh config navigating away from config page // doesn't work if they update via sidebar if ( (configPageUid && evt.oldURL.endsWith(configPageUid)) || - getDiscourseNodes().some(({ type }) => evt.oldURL.endsWith(type)) + getDiscourseNodes(undefined, navSnapshot).some(({ type }) => + evt.oldURL.endsWith(type), + ) ) { - refreshConfigTree(); + refreshConfigTree(navSnapshot); + console.log( + `[DG Nav] hashChange refreshConfigTree +${Math.round(performance.now() - hashStart)}ms`, + ); } }; + markPhase("hashChangeListener closure"); let globalTrigger = ( - getGlobalSetting([GLOBAL_KEYS.trigger]) ?? "\\" + (readPathValue(settingsSnapshot.globalSettings, [GLOBAL_KEYS.trigger]) as + | string + | undefined) ?? "\\" ).trim(); - const personalTriggerCombo = getPersonalSetting([ - PERSONAL_KEYS.personalNodeMenuTrigger, - ]); + const personalTriggerCombo = readPathValue( + settingsSnapshot.personalSettings, + [PERSONAL_KEYS.personalNodeMenuTrigger], + ) as IKeyCombo | undefined; let personalTrigger = personalTriggerCombo?.key; let personalModifiers = personalTriggerCombo ? getModifiersFromCombo(personalTriggerCombo) @@ -284,6 +356,7 @@ export const initObservers = ({ personalModifiers = combo ? getModifiersFromCombo(combo) : []; }, ); + markPhase("trigger snapshot reads + onSettingChange"); const leftSidebarObserver = createHTMLObserver({ tag: "DIV", @@ -291,15 +364,18 @@ export const initObservers = ({ className: "starred-pages-wrapper", callback: (el) => { void (async () => { - const isLeftSidebarEnabled = getFeatureFlag("Enable left sidebar"); + const callbackSnapshot = bulkReadSettings(); + const isLeftSidebarEnabled = + callbackSnapshot.featureFlags["Enable left sidebar"]; const container = el as HTMLDivElement; if (isLeftSidebarEnabled) { container.style.padding = "0"; - await mountLeftSidebar(container, onloadArgs); + await mountLeftSidebar(container, onloadArgs, callbackSnapshot); } })(); }, }); + markPhase("leftSidebarObserver"); const handleNodeMenuRender = (target: HTMLElement, evt: KeyboardEvent) => { if ( @@ -343,7 +419,9 @@ export const initObservers = ({ }; let customTrigger = - getPersonalSetting([PERSONAL_KEYS.nodeSearchMenuTrigger]) ?? "@"; + (readPathValue(settingsSnapshot.personalSettings, [ + PERSONAL_KEYS.nodeSearchMenuTrigger, + ]) as string | undefined) ?? "@"; const unsubSearchTrigger = onSettingChange( settingKeys.nodeSearchMenuTrigger, @@ -403,8 +481,9 @@ export const initObservers = ({ }; const nodeCreationPopoverListener = debounce(() => { + const snap = bulkReadSettings(); const isTextSelectionPopupEnabled = - getPersonalSetting([PERSONAL_KEYS.textSelectionPopup]) !== false; + snap.personalSettings[PERSONAL_KEYS.textSelectionPopup] !== false; if (!isTextSelectionPopupEnabled) return; @@ -437,6 +516,7 @@ export const initObservers = ({ removeTextSelectionPopup(); } }, 150); + markPhase("listener closures (nodeMenu/search/creationPopover)"); return { observers: [ diff --git a/apps/roam/src/utils/isCanvasPage.ts b/apps/roam/src/utils/isCanvasPage.ts index a9a3b7a49..52e898a6c 100644 --- a/apps/roam/src/utils/isCanvasPage.ts +++ b/apps/roam/src/utils/isCanvasPage.ts @@ -1,10 +1,24 @@ import { DEFAULT_CANVAS_PAGE_FORMAT } from ".."; -import { getGlobalSetting } from "~/components/settings/utils/accessors"; +import { + getGlobalSetting, + readPathValue, + type SettingsSnapshot, +} from "~/components/settings/utils/accessors"; import { GLOBAL_KEYS } from "~/components/settings/utils/settingKeys"; -export const isCanvasPage = ({ title }: { title: string }) => { +export const isCanvasPage = ({ + title, + snapshot, +}: { + title: string; + snapshot?: SettingsSnapshot; +}) => { const format = - getGlobalSetting([GLOBAL_KEYS.canvasPageFormat]) || + (snapshot + ? (readPathValue(snapshot.globalSettings, [ + GLOBAL_KEYS.canvasPageFormat, + ]) as string | undefined) + : getGlobalSetting([GLOBAL_KEYS.canvasPageFormat])) || DEFAULT_CANVAS_PAGE_FORMAT; const canvasRegex = new RegExp(`^${format}$`.replace(/\*/g, ".+")); return canvasRegex.test(title); @@ -13,19 +27,25 @@ export const isCanvasPage = ({ title }: { title: string }) => { export const isCurrentPageCanvas = ({ title, h1, + snapshot, }: { title: string; h1: HTMLHeadingElement; + snapshot?: SettingsSnapshot; }) => { - return isCanvasPage({ title }) && !!h1.closest(".roam-article"); + return isCanvasPage({ title, snapshot }) && !!h1.closest(".roam-article"); }; export const isSidebarCanvas = ({ title, h1, + snapshot, }: { title: string; h1: HTMLHeadingElement; + snapshot?: SettingsSnapshot; }) => { - return isCanvasPage({ title }) && !!h1.closest(".rm-sidebar-outline"); + return ( + isCanvasPage({ title, snapshot }) && !!h1.closest(".rm-sidebar-outline") + ); }; diff --git a/apps/roam/src/utils/isQueryPage.ts b/apps/roam/src/utils/isQueryPage.ts index aa3df2255..af3a831bb 100644 --- a/apps/roam/src/utils/isQueryPage.ts +++ b/apps/roam/src/utils/isQueryPage.ts @@ -1,7 +1,14 @@ import { getQueryPages } from "~/components/settings/QueryPagesPanel"; +import type { SettingsSnapshot } from "~/components/settings/utils/accessors"; -export const isQueryPage = ({ title }: { title: string }): boolean => { - const queryPages = getQueryPages(); +export const isQueryPage = ({ + title, + snapshot, +}: { + title: string; + snapshot?: SettingsSnapshot; +}): boolean => { + const queryPages = getQueryPages(snapshot); const matchesQueryPage = queryPages.some((queryPage) => { const escapedPattern = queryPage diff --git a/apps/roam/src/utils/posthog.ts b/apps/roam/src/utils/posthog.ts index 407c8676d..1149e319a 100644 --- a/apps/roam/src/utils/posthog.ts +++ b/apps/roam/src/utils/posthog.ts @@ -2,8 +2,6 @@ import getCurrentUserUid from "roamjs-components/queries/getCurrentUserUid"; import { getVersionWithDate } from "./getVersion"; import posthog from "posthog-js"; import type { CaptureResult } from "posthog-js"; -import { getPersonalSetting } from "~/components/settings/utils/accessors"; -import { PERSONAL_KEYS } from "~/components/settings/utils/settingKeys"; let initialized = false; @@ -67,10 +65,5 @@ export const disablePostHog = (): void => { }; export const initPostHog = (): void => { - const disabled = getPersonalSetting([ - PERSONAL_KEYS.disableProductDiagnostics, - ]); - if (!disabled) { - doInitPostHog(); - } + doInitPostHog(); }; diff --git a/apps/roam/src/utils/refreshConfigTree.ts b/apps/roam/src/utils/refreshConfigTree.ts index 606d8097c..5f6d7c7c0 100644 --- a/apps/roam/src/utils/refreshConfigTree.ts +++ b/apps/roam/src/utils/refreshConfigTree.ts @@ -6,6 +6,7 @@ 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 type { SettingsSnapshot } from "~/components/settings/utils/accessors"; const getPagesStartingWithPrefix = (prefix: string) => ( @@ -17,14 +18,29 @@ const getPagesStartingWithPrefix = (prefix: string) => uid: r[0][":block/uid"] || "", })); -const refreshConfigTree = () => { - getDiscourseRelationLabels().forEach((key) => - unregisterDatalogTranslator({ key }), - ); +const refreshConfigTree = (snapshot?: SettingsSnapshot) => { + let t = performance.now(); + const mark = (label: string) => { + const now = performance.now(); + console.log( + `[DG Plugin] refreshConfigTree.${label}: ${Math.round(now - t)}ms`, + ); + t = now; + }; + + const labels = getDiscourseRelationLabels(undefined, snapshot); + mark("getDiscourseRelationLabels"); + labels.forEach((key) => unregisterDatalogTranslator({ key })); + mark("unregisterDatalogTranslator loop"); + discourseConfigRef.tree = getBasicTreeByParentUid( getPageUidByPageTitle(DISCOURSE_CONFIG_PAGE_TITLE), ); + mark("getBasicTreeByParentUid(DG config page)"); + const pages = getPagesStartingWithPrefix("discourse-graph/nodes"); + mark(`getPagesStartingWithPrefix (${pages.length} pages)`); + discourseConfigRef.nodes = Object.fromEntries( pages.map(({ title, uid }) => { return [ @@ -36,7 +52,10 @@ const refreshConfigTree = () => { ]; }), ); - registerDiscourseDatalogTranslators(); + mark(`getBasicTreeByParentUid per-page loop (${pages.length} pages)`); + + registerDiscourseDatalogTranslators(snapshot); + mark("registerDiscourseDatalogTranslators"); }; export default refreshConfigTree; diff --git a/apps/roam/src/utils/registerDiscourseDatalogTranslators.ts b/apps/roam/src/utils/registerDiscourseDatalogTranslators.ts index 091ec978d..b9bd53719 100644 --- a/apps/roam/src/utils/registerDiscourseDatalogTranslators.ts +++ b/apps/roam/src/utils/registerDiscourseDatalogTranslators.ts @@ -21,6 +21,7 @@ import { fireQuerySync, getWhereClauses } from "./fireQuery"; import { toVar } from "./compileDatalog"; import { getExistingRelationPageUid } from "./createReifiedBlock"; import { getStoredRelationsEnabled } from "./storedRelations"; +import type { SettingsSnapshot } from "~/components/settings/utils/accessors"; const hasTag = (node: DiscourseNode): node is DiscourseNode & { tag: string } => !!node.tag; @@ -87,9 +88,9 @@ const collectVariables = (clauses: DatalogClause[]): Set => const ANY_DISCOURSE_NODE = "Any discourse node"; -const registerDiscourseDatalogTranslators = () => { - const discourseRelations = getDiscourseRelations(); - const discourseNodes = getDiscourseNodes(discourseRelations); +const registerDiscourseDatalogTranslators = (snapshot?: SettingsSnapshot) => { + const discourseRelations = getDiscourseRelations(snapshot); + const discourseNodes = getDiscourseNodes(discourseRelations, snapshot); const isACallback: Parameters< typeof registerDatalogTranslator diff --git a/apps/roam/src/utils/setQueryPages.ts b/apps/roam/src/utils/setQueryPages.ts index cf5a8be13..bc39d5176 100644 --- a/apps/roam/src/utils/setQueryPages.ts +++ b/apps/roam/src/utils/setQueryPages.ts @@ -1,20 +1,24 @@ import { OnloadArgs } from "roamjs-components/types"; import { - getPersonalSetting, + readPathValue, setPersonalSetting, + type SettingsSnapshot, } from "~/components/settings/utils/accessors"; import { PERSONAL_KEYS, QUERY_KEYS, } from "~/components/settings/utils/settingKeys"; -export const setInitialQueryPages = (onloadArgs: OnloadArgs) => { +export const setInitialQueryPages = ( + onloadArgs: OnloadArgs, + settingsSnapshot: SettingsSnapshot, +) => { // Legacy extensionAPI stored query-pages as string | string[] | Record. // Coerce to string[] for backward compatibility with old stored formats. - const raw = getPersonalSetting>([ + const raw = readPathValue(settingsSnapshot.personalSettings, [ PERSONAL_KEYS.query, QUERY_KEYS.queryPages, - ]); + ]) as string[] | string | Record | undefined; const queryPageArray = Array.isArray(raw) ? raw : typeof raw === "string" && raw From 4d5c72040835e1ad09f70d06acfad1a2484dc141 Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 6 Apr 2026 17:04:06 +0530 Subject: [PATCH 2/6] ENG-1616: Remove plugin-load timing logs Removes the per-phase console.log instrumentation added in the previous commit. All the [DG Plugin] / [DG Nav] logs and their `mark()` / `markPhase()` helpers are gone. Code behavior unchanged. Dropped in this commit: - index.ts: mark() closure, load start/done logs, and all phase marks. - initializeObserversAndListeners.ts: markPhase() closure, per-observer marks, pageTitleObserver fire log, hashChangeListener [DG Nav] logs. - LeftSidebarView.tsx: openTarget [DG Nav] click/resolve logs. - refreshConfigTree.ts: mark() closure and all phase marks. - init.ts: mark() closures in initSchema and initSettingsPageBlocks. - accessors.ts: bulkReadSettings internal timing log. - index.ts: unused getPluginElapsedTime import. Previous commit (343dc117) kept as a checkpoint for future drill-downs. --- apps/roam/src/components/LeftSidebarView.tsx | 16 ------- .../components/settings/utils/accessors.ts | 12 +---- .../src/components/settings/utils/init.ts | 30 ------------ apps/roam/src/index.ts | 47 +------------------ .../utils/initializeObserversAndListeners.ts | 27 ----------- apps/roam/src/utils/refreshConfigTree.ts | 24 ++-------- 6 files changed, 5 insertions(+), 151 deletions(-) diff --git a/apps/roam/src/components/LeftSidebarView.tsx b/apps/roam/src/components/LeftSidebarView.tsx index abb3b6680..fc6946ba7 100644 --- a/apps/roam/src/components/LeftSidebarView.tsx +++ b/apps/roam/src/components/LeftSidebarView.tsx @@ -80,10 +80,6 @@ const truncate = (s: string, max: number | undefined): string => { }; const openTarget = async (e: React.MouseEvent, targetUid: string) => { - const _navStart = performance.now(); - console.log( - `[DG Nav] openTarget click t=${Math.round(_navStart)} target=${targetUid}`, - ); e.preventDefault(); e.stopPropagation(); const target = parseReference(targetUid); @@ -94,17 +90,11 @@ const openTarget = async (e: React.MouseEvent, targetUid: string) => { if (target.type === "block") { if (e.shiftKey) { await openBlockInSidebar(target.uid); - console.log( - `[DG Nav] openBlockInSidebar resolved +${Math.round(performance.now() - _navStart)}ms`, - ); return; } await window.roamAlphaAPI.ui.mainWindow.openBlock({ block: { uid: target.uid }, }); - console.log( - `[DG Nav] openBlock resolved +${Math.round(performance.now() - _navStart)}ms`, - ); return; } @@ -114,16 +104,10 @@ const openTarget = async (e: React.MouseEvent, targetUid: string) => { // eslint-disable-next-line @typescript-eslint/naming-convention window: { type: "outline", "block-uid": targetUid }, }); - console.log( - `[DG Nav] rightSidebar.addWindow resolved +${Math.round(performance.now() - _navStart)}ms`, - ); } else { await window.roamAlphaAPI.ui.mainWindow.openPage({ page: { uid: targetUid }, }); - console.log( - `[DG Nav] openPage resolved +${Math.round(performance.now() - _navStart)}ms`, - ); } }; diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 10cd32585..25ccf1f9d 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -918,13 +918,10 @@ export type SettingsSnapshot = { }; export const bulkReadSettings = (): SettingsSnapshot => { - const start = performance.now(); - const pageResult = window.roamAlphaAPI.pull( "[{:block/children [:block/string :block/props]}]", [":node/title", DG_BLOCK_PROP_SETTINGS_PAGE_TITLE], ) as Record | null; - const afterQuery = performance.now(); const children = (pageResult?.[":block/children"] ?? []) as Record< string, @@ -952,18 +949,11 @@ export const bulkReadSettings = (): SettingsSnapshot => { } } - const snapshot: SettingsSnapshot = { + return { featureFlags: FeatureFlagsSchema.parse(featureFlagsProps || {}), globalSettings: GlobalSettingsSchema.parse(globalProps || {}), personalSettings: PersonalSettingsSchema.parse(personalProps || {}), }; - - const end = performance.now(); - console.log( - `[DG Plugin] bulkReadSettings: ${Math.round(end - start)}ms (query ${Math.round(afterQuery - start)}ms, parse ${Math.round(end - afterQuery)}ms)`, - ); - - return snapshot; }; export const setPersonalSetting = (keys: string[], value: json): void => { diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index 487314652..c9be88963 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -189,29 +189,15 @@ const initializeSettingsBlockProps = ( }; const initSettingsPageBlocks = async (): Promise> => { - let t = performance.now(); - const mark = (label: string) => { - const now = performance.now(); - console.log( - `[DG Plugin] initSettingsPageBlocks.${label}: ${Math.round(now - t)}ms`, - ); - t = now; - }; - const pageUid = await ensurePageExists(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE); - mark("ensurePageExists"); const blockMap = buildBlockMap(pageUid); - mark("buildBlockMap"); const topLevelBlocks = getTopLevelBlockPropsConfig().map(({ key }) => key); await ensureBlocksExist(pageUid, topLevelBlocks, blockMap); - mark("ensureBlocksExist (top-level)"); await ensureLegacyConfigBlocks(pageUid); - mark("ensureLegacyConfigBlocks"); initializeSettingsBlockProps(pageUid, blockMap); - mark("initializeSettingsBlockProps"); return blockMap; }; @@ -425,25 +411,9 @@ const logDualReadComparison = (): void => { }; export const initSchema = async (): Promise => { - console.log("[DG Plugin] Initializing schema..."); - let t = performance.now(); - const mark = (label: string) => { - const now = performance.now(); - console.log(`[DG Plugin] initSchema.${label}: ${Math.round(now - t)}ms`); - t = now; - }; - const blockUids = await initSettingsPageBlocks(); - mark("initSettingsPageBlocks"); - await migrateGraphLevel(blockUids); - mark("migrateGraphLevel"); - const nodePageUids = await initDiscourseNodePages(); - mark("initDiscourseNodePages"); - await migratePersonalSettings(blockUids); - mark("migratePersonalSettings"); - return { blockUids, nodePageUids }; }; diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index 3f3ad01b9..4beaec802 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -31,7 +31,7 @@ import { initializeSupabaseSync, setSyncActivity, } from "./utils/syncDgNodesToSupabase"; -import { getPluginElapsedTime, initPluginTimer } from "./utils/pluginTimer"; +import { initPluginTimer } from "./utils/pluginTimer"; import { initPostHog } from "./utils/posthog"; import { initSchema } from "./components/settings/utils/init"; import { @@ -50,33 +50,18 @@ export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; export default runExtension(async (onloadArgs) => { initPluginTimer(); - console.log("[DG Plugin] load: start"); - let lastMark = performance.now(); - const mark = (label: string) => { - const now = performance.now(); - console.log( - `[DG Plugin] ${label}: +${Math.round(now - lastMark)}ms (total ${getPluginElapsedTime()}ms)`, - ); - lastMark = now; - }; const settingsSnapshot = bulkReadSettings(); - mark("bulkReadSettings"); const isEncrypted = window.roamAlphaAPI.graph.isEncrypted; - mark("encrypted check"); const isOffline = window.roamAlphaAPI.graph.type === "offline"; - mark("offline check"); const disallowDiagnostics = readPathValue(settingsSnapshot.personalSettings, [ PERSONAL_KEYS.disableProductDiagnostics, ]) as boolean | undefined; - mark("diagnostics check"); if (!isEncrypted && !isOffline && !disallowDiagnostics) { initPostHog(); } - mark("posthog init"); initFeedbackWidget(); - mark("feedback widget init"); if (window?.roamjs?.loaded?.has("query-builder")) { renderToast({ timeout: 10000, @@ -95,33 +80,21 @@ export default runExtension(async (onloadArgs) => { timeout: 500, }); } - mark("posthog + feedback widget + load check"); await initializeDiscourseNodes(settingsSnapshot); - mark("initializeDiscourseNodes"); refreshConfigTree(settingsSnapshot); - mark("refreshConfigTree"); addGraphViewNodeStyling(); - mark("graph view styling"); registerCommandPaletteCommands(onloadArgs); - mark("command palette commands"); createSettingsPanel(onloadArgs); - mark("settings panel"); registerSmartBlock(onloadArgs); - mark("registerSmartBlock"); setInitialQueryPages(onloadArgs, settingsSnapshot); - mark("setInitialQueryPages"); const style = addStyle(styles); - mark("addStyle styles"); const discourseGraphStyle = addStyle(discourseGraphStyles); - mark("addStyle discourseGraphStyles"); const settingsStyle = addStyle(settingsStyles); - mark("addStyle settingsStyles"); const discourseFloatingMenuStyle = addStyle(discourseFloatingMenuStyles); - mark("addStyle discourseFloatingMenuStyles"); // Add streamline styling only if enabled const isStreamlineStylingEnabled = readPathValue( @@ -133,13 +106,11 @@ export default runExtension(async (onloadArgs) => { streamlineStyleElement = addStyle(streamlineStyling); streamlineStyleElement.id = "streamline-styling"; } - mark("streamline style check"); const { observers, listeners, cleanups } = initObservers({ onloadArgs, settingsSnapshot, }); - mark("initObservers"); const { pageActionListener, hashChangeListener, @@ -148,20 +119,14 @@ export default runExtension(async (onloadArgs) => { nodeCreationPopoverListener, } = listeners; document.addEventListener("roamjs:query-builder:action", pageActionListener); - mark("pageActionListener addEventListener"); window.addEventListener("hashchange", hashChangeListener); - mark("hashChangeListener addEventListener"); document.addEventListener("keydown", nodeMenuTriggerListener); - mark("nodeMenuTriggerListener addEventListener"); document.addEventListener("input", discourseNodeSearchTriggerListener); - mark("discourseNodeSearchTriggerListener addEventListener"); document.addEventListener("selectionchange", nodeCreationPopoverListener); - mark("document event listeners"); if (settingsSnapshot.featureFlags["Suggestive mode enabled"]) { initializeSupabaseSync(); } - mark("suggestive supabase init"); const unsubSuggestiveMode = onSettingChange( settingKeys.suggestiveModeEnabled, @@ -173,7 +138,6 @@ export default runExtension(async (onloadArgs) => { } }, ); - mark("unsubSuggestiveMode onSettingChange"); const { extensionAPI } = onloadArgs; window.roamjs.extension.queryBuilder = { @@ -190,15 +154,12 @@ export default runExtension(async (onloadArgs) => { // @ts-expect-error - we are still using roamjs-components global definition getDiscourseNodes: getDiscourseNodes, }; - mark("roamjs.extension.queryBuilder assign"); installDiscourseFloatingMenu(onloadArgs, settingsSnapshot); - mark("installDiscourseFloatingMenu"); const leftSidebarScript = document.querySelector( 'script#roam-left-sidebar[src="https://sid597.github.io/roam-left-sidebar/js/main.js"]', ); - mark("leftSidebarScript querySelector"); if (leftSidebarScript) { renderToast({ @@ -209,7 +170,6 @@ export default runExtension(async (onloadArgs) => { "Discourse Graph detected the Roam left sidebar script. Running both sidebars may cause issues. Please remove the Roam left sidebar script from your Roam instance, and reload the graph.", }); } - mark("leftSidebarScript conflict toast"); const unsubLeftSidebarFlag = onSettingChange( settingKeys.leftSidebarFlag, @@ -233,14 +193,9 @@ export default runExtension(async (onloadArgs) => { } }, ); - mark("unsubLeftSidebarFlag onSettingChange"); const { blockUids } = await initSchema(); - mark("initSchema"); const cleanupPullWatchers = setupPullWatchOnSettingsPage(blockUids); - mark("setupPullWatchOnSettingsPage"); - - console.log(`[DG Plugin] load: done in ${getPluginElapsedTime()}ms`); return { elements: [ diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index b49e08c79..b601f79d9 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -105,15 +105,6 @@ export const initObservers = ({ }; cleanups: Array<() => void>; } => { - let markT = performance.now(); - const markPhase = (label: string) => { - const now = performance.now(); - console.log( - `[DG Plugin] initObservers.${label}: ${Math.round(now - markT)}ms`, - ); - markT = now; - }; - const pageTitleObserver = createHTMLObserver({ tag: "H1", className: "rm-title-display", @@ -156,13 +147,11 @@ export const initObservers = ({ } }, }); - markPhase("pageTitleObserver"); const queryBlockObserver = createButtonObserver({ attribute: "query-block", render: (b) => renderQueryBlock(b, onloadArgs), }); - markPhase("queryBlockObserver"); let batchedTagNodes: DiscourseNode[] | null = null; const getNodesForTagBatch = (): DiscourseNode[] => { @@ -218,7 +207,6 @@ export const initObservers = ({ } }, }); - markPhase("nodeTagPopupButtonObserver"); const pageActionListener = (( e: CustomEvent<{ @@ -257,7 +245,6 @@ export const initObservers = ({ toggleSuggestiveOverlay(Boolean(newValue)); }, ); - markPhase("pageAction/suggestive handlers"); const graphOverviewExportObserver = createHTMLObserver({ tag: "DIV", @@ -277,7 +264,6 @@ export const initObservers = ({ } }, }); - markPhase("graphOverviewExport + imageMenu observers"); if ( readPathValue(settingsSnapshot.personalSettings, [ @@ -296,17 +282,11 @@ export const initObservers = ({ } if (getPageRefObserversSize()) enablePageRefObserver(); - markPhase("pageRef observer wiring"); const configPageUid = getPageUidByPageTitle(DISCOURSE_CONFIG_PAGE_TITLE); - markPhase("configPageUid lookup"); const hashChangeListener = (e: Event) => { const evt = e as HashChangeEvent; - const hashStart = performance.now(); - console.log( - `[DG Nav] hashChange fired t=${Math.round(hashStart)} old=${evt.oldURL} new=${evt.newURL}`, - ); const navSnapshot = bulkReadSettings(); // Attempt to refresh config navigating away from config page // doesn't work if they update via sidebar @@ -317,12 +297,8 @@ export const initObservers = ({ ) ) { refreshConfigTree(navSnapshot); - console.log( - `[DG Nav] hashChange refreshConfigTree +${Math.round(performance.now() - hashStart)}ms`, - ); } }; - markPhase("hashChangeListener closure"); let globalTrigger = ( (readPathValue(settingsSnapshot.globalSettings, [GLOBAL_KEYS.trigger]) as @@ -356,7 +332,6 @@ export const initObservers = ({ personalModifiers = combo ? getModifiersFromCombo(combo) : []; }, ); - markPhase("trigger snapshot reads + onSettingChange"); const leftSidebarObserver = createHTMLObserver({ tag: "DIV", @@ -375,7 +350,6 @@ export const initObservers = ({ })(); }, }); - markPhase("leftSidebarObserver"); const handleNodeMenuRender = (target: HTMLElement, evt: KeyboardEvent) => { if ( @@ -516,7 +490,6 @@ export const initObservers = ({ removeTextSelectionPopup(); } }, 150); - markPhase("listener closures (nodeMenu/search/creationPopover)"); return { observers: [ diff --git a/apps/roam/src/utils/refreshConfigTree.ts b/apps/roam/src/utils/refreshConfigTree.ts index 5f6d7c7c0..8d23401c8 100644 --- a/apps/roam/src/utils/refreshConfigTree.ts +++ b/apps/roam/src/utils/refreshConfigTree.ts @@ -19,28 +19,13 @@ const getPagesStartingWithPrefix = (prefix: string) => })); const refreshConfigTree = (snapshot?: SettingsSnapshot) => { - let t = performance.now(); - const mark = (label: string) => { - const now = performance.now(); - console.log( - `[DG Plugin] refreshConfigTree.${label}: ${Math.round(now - t)}ms`, - ); - t = now; - }; - - const labels = getDiscourseRelationLabels(undefined, snapshot); - mark("getDiscourseRelationLabels"); - labels.forEach((key) => unregisterDatalogTranslator({ key })); - mark("unregisterDatalogTranslator loop"); - + getDiscourseRelationLabels(undefined, snapshot).forEach((key) => + unregisterDatalogTranslator({ key }), + ); discourseConfigRef.tree = getBasicTreeByParentUid( getPageUidByPageTitle(DISCOURSE_CONFIG_PAGE_TITLE), ); - mark("getBasicTreeByParentUid(DG config page)"); - const pages = getPagesStartingWithPrefix("discourse-graph/nodes"); - mark(`getPagesStartingWithPrefix (${pages.length} pages)`); - discourseConfigRef.nodes = Object.fromEntries( pages.map(({ title, uid }) => { return [ @@ -52,10 +37,7 @@ const refreshConfigTree = (snapshot?: SettingsSnapshot) => { ]; }), ); - mark(`getBasicTreeByParentUid per-page loop (${pages.length} pages)`); - registerDiscourseDatalogTranslators(snapshot); - mark("registerDiscourseDatalogTranslators"); }; export default refreshConfigTree; From b13d401b0954820e9dd05f1fba3e92b76cfdcae1 Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 6 Apr 2026 20:26:44 +0530 Subject: [PATCH 3/6] =?UTF-8?q?ENG-1616:=20Address=20review=20=E2=80=94=20?= =?UTF-8?q?typed=20indexing,=20restore=20dgDualReadLog,=20optional=20snaps?= =?UTF-8?q?hot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.ts: move initPluginTimer() back to its original position (after early-return checks) so timing isn't started for graphs that bail out. - Replace readPathValue + `as T | undefined` casts with direct typed indexing on the Zod-derived snapshot types across: - index.ts (disallowDiagnostics, isStreamlineStylingEnabled) - initializeObserversAndListeners.ts (suggestiveModeOverlay, pagePreview, discourseContextOverlay, globalTrigger, personalTriggerCombo, customTrigger) — also drops dead `?? "\\"` and `?? "@"` fallbacks since Zod defaults already populate them. - isCanvasPage.ts (canvasPageFormat) - setQueryPages.ts + QueryPagesPanel.tsx (nested [Query][Query pages]) - setQueryPages.setInitialQueryPages: snapshot is now optional with a getPersonalSetting fallback, matching the pattern used elsewhere (getQueryPages, isCanvasPage, etc.). - init.ts: restore logDualReadComparison + window.dgDualReadLog so the on-demand console helper is available again. NOT auto-called on init — invoke window.dgDualReadLog() manually to dump the comparison. --- .../components/settings/QueryPagesPanel.tsx | 8 +--- .../src/components/settings/utils/init.ts | 5 +++ apps/roam/src/index.ts | 23 +++++------ .../utils/initializeObserversAndListeners.ts | 38 ++++++------------- apps/roam/src/utils/isCanvasPage.ts | 5 +-- apps/roam/src/utils/setQueryPages.ts | 14 ++++--- 6 files changed, 38 insertions(+), 55 deletions(-) diff --git a/apps/roam/src/components/settings/QueryPagesPanel.tsx b/apps/roam/src/components/settings/QueryPagesPanel.tsx index 4966a4ef8..c6f792dca 100644 --- a/apps/roam/src/components/settings/QueryPagesPanel.tsx +++ b/apps/roam/src/components/settings/QueryPagesPanel.tsx @@ -4,7 +4,6 @@ import React, { useState } from "react"; import type { OnloadArgs } from "roamjs-components/types"; import { getPersonalSetting, - readPathValue, setPersonalSetting, type SettingsSnapshot, } from "~/components/settings/utils/accessors"; @@ -16,11 +15,8 @@ import { // Legacy extensionAPI stored query-pages as string | string[] | Record. // Coerce to string[] for backward compatibility with old stored formats. export const getQueryPages = (snapshot?: SettingsSnapshot): string[] => { - const value = snapshot - ? (readPathValue(snapshot.personalSettings, [ - PERSONAL_KEYS.query, - QUERY_KEYS.queryPages, - ]) as string[] | string | Record | undefined) + const value: string[] | string | Record | undefined = snapshot + ? snapshot.personalSettings[PERSONAL_KEYS.query][QUERY_KEYS.queryPages] : getPersonalSetting>([ PERSONAL_KEYS.query, QUERY_KEYS.queryPages, diff --git a/apps/roam/src/components/settings/utils/init.ts b/apps/roam/src/components/settings/utils/init.ts index c9be88963..3720f939e 100644 --- a/apps/roam/src/components/settings/utils/init.ts +++ b/apps/roam/src/components/settings/utils/init.ts @@ -336,6 +336,9 @@ export type InitSchemaResult = { nodePageUids: Record; }; +// On-demand dual-read comparison. Not called automatically on init — +// invoke from the console via window.dgDualReadLog() to inspect the legacy +// settings tree vs. the block-prop store. const logDualReadComparison = (): void => { if (!isNewSettingsStoreEnabled()) return; @@ -415,5 +418,7 @@ export const initSchema = async (): Promise => { await migrateGraphLevel(blockUids); const nodePageUids = await initDiscourseNodePages(); await migratePersonalSettings(blockUids); + (window as unknown as Record).dgDualReadLog = + logDualReadComparison; return { blockUids, nodePageUids }; }; diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index 4beaec802..6d17d92d3 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -34,10 +34,7 @@ import { import { initPluginTimer } from "./utils/pluginTimer"; import { initPostHog } from "./utils/posthog"; import { initSchema } from "./components/settings/utils/init"; -import { - bulkReadSettings, - readPathValue, -} from "./components/settings/utils/accessors"; +import { bulkReadSettings } from "./components/settings/utils/accessors"; import { PERSONAL_KEYS } from "./components/settings/utils/settingKeys"; import { setupPullWatchOnSettingsPage } from "./components/settings/utils/pullWatchers"; import { @@ -49,19 +46,18 @@ import { mountLeftSidebar } from "./components/LeftSidebarView"; export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; export default runExtension(async (onloadArgs) => { - initPluginTimer(); - const settingsSnapshot = bulkReadSettings(); const isEncrypted = window.roamAlphaAPI.graph.isEncrypted; const isOffline = window.roamAlphaAPI.graph.type === "offline"; - const disallowDiagnostics = readPathValue(settingsSnapshot.personalSettings, [ - PERSONAL_KEYS.disableProductDiagnostics, - ]) as boolean | undefined; + const disallowDiagnostics = + settingsSnapshot.personalSettings[PERSONAL_KEYS.disableProductDiagnostics]; if (!isEncrypted && !isOffline && !disallowDiagnostics) { initPostHog(); } + initFeedbackWidget(); + if (window?.roamjs?.loaded?.has("query-builder")) { renderToast({ timeout: 10000, @@ -80,6 +76,9 @@ export default runExtension(async (onloadArgs) => { timeout: 500, }); } + + initPluginTimer(); + await initializeDiscourseNodes(settingsSnapshot); refreshConfigTree(settingsSnapshot); @@ -97,10 +96,8 @@ export default runExtension(async (onloadArgs) => { const discourseFloatingMenuStyle = addStyle(discourseFloatingMenuStyles); // Add streamline styling only if enabled - const isStreamlineStylingEnabled = readPathValue( - settingsSnapshot.personalSettings, - [PERSONAL_KEYS.streamlineStyling], - ) as boolean | undefined; + const isStreamlineStylingEnabled = + settingsSnapshot.personalSettings[PERSONAL_KEYS.streamlineStyling]; let streamlineStyleElement: HTMLStyleElement | null = null; if (isStreamlineStylingEnabled) { streamlineStyleElement = addStyle(streamlineStyling); diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index b601f79d9..8f4fc5620 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -54,7 +54,6 @@ import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTit import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import findDiscourseNode from "./findDiscourseNode"; import { - readPathValue, bulkReadSettings, type SettingsSnapshot, } from "~/components/settings/utils/accessors"; @@ -231,11 +230,7 @@ export const initObservers = ({ const suggestiveHandler = getSuggestiveOverlayHandler(onloadArgs); const toggleSuggestiveOverlay = onPageRefObserverChange(suggestiveHandler); - if ( - readPathValue(settingsSnapshot.personalSettings, [ - PERSONAL_KEYS.suggestiveModeOverlay, - ]) - ) { + if (settingsSnapshot.personalSettings[PERSONAL_KEYS.suggestiveModeOverlay]) { addPageRefObserver(suggestiveHandler); } @@ -265,17 +260,11 @@ export const initObservers = ({ }, }); - if ( - readPathValue(settingsSnapshot.personalSettings, [ - PERSONAL_KEYS.pagePreview, - ]) - ) + if (settingsSnapshot.personalSettings[PERSONAL_KEYS.pagePreview]) addPageRefObserver(previewPageRefHandler); if ( - readPathValue(settingsSnapshot.personalSettings, [ - PERSONAL_KEYS.discourseContextOverlay, - ]) + settingsSnapshot.personalSettings[PERSONAL_KEYS.discourseContextOverlay] ) { const overlayHandler = getOverlayHandler(onloadArgs); onPageRefObserverChange(overlayHandler)(true); @@ -300,15 +289,14 @@ export const initObservers = ({ } }; - let globalTrigger = ( - (readPathValue(settingsSnapshot.globalSettings, [GLOBAL_KEYS.trigger]) as - | string - | undefined) ?? "\\" - ).trim(); - const personalTriggerCombo = readPathValue( - settingsSnapshot.personalSettings, - [PERSONAL_KEYS.personalNodeMenuTrigger], - ) as IKeyCombo | undefined; + let globalTrigger = + settingsSnapshot.globalSettings[GLOBAL_KEYS.trigger].trim(); + const personalTriggerComboRaw = + settingsSnapshot.personalSettings[PERSONAL_KEYS.personalNodeMenuTrigger]; + const personalTriggerCombo = + typeof personalTriggerComboRaw === "object" + ? (personalTriggerComboRaw as IKeyCombo) + : undefined; let personalTrigger = personalTriggerCombo?.key; let personalModifiers = personalTriggerCombo ? getModifiersFromCombo(personalTriggerCombo) @@ -393,9 +381,7 @@ export const initObservers = ({ }; let customTrigger = - (readPathValue(settingsSnapshot.personalSettings, [ - PERSONAL_KEYS.nodeSearchMenuTrigger, - ]) as string | undefined) ?? "@"; + settingsSnapshot.personalSettings[PERSONAL_KEYS.nodeSearchMenuTrigger]; const unsubSearchTrigger = onSettingChange( settingKeys.nodeSearchMenuTrigger, diff --git a/apps/roam/src/utils/isCanvasPage.ts b/apps/roam/src/utils/isCanvasPage.ts index 52e898a6c..daea2ebc8 100644 --- a/apps/roam/src/utils/isCanvasPage.ts +++ b/apps/roam/src/utils/isCanvasPage.ts @@ -1,7 +1,6 @@ import { DEFAULT_CANVAS_PAGE_FORMAT } from ".."; import { getGlobalSetting, - readPathValue, type SettingsSnapshot, } from "~/components/settings/utils/accessors"; import { GLOBAL_KEYS } from "~/components/settings/utils/settingKeys"; @@ -15,9 +14,7 @@ export const isCanvasPage = ({ }) => { const format = (snapshot - ? (readPathValue(snapshot.globalSettings, [ - GLOBAL_KEYS.canvasPageFormat, - ]) as string | undefined) + ? snapshot.globalSettings[GLOBAL_KEYS.canvasPageFormat] : getGlobalSetting([GLOBAL_KEYS.canvasPageFormat])) || DEFAULT_CANVAS_PAGE_FORMAT; const canvasRegex = new RegExp(`^${format}$`.replace(/\*/g, ".+")); diff --git a/apps/roam/src/utils/setQueryPages.ts b/apps/roam/src/utils/setQueryPages.ts index bc39d5176..a29b9bdc8 100644 --- a/apps/roam/src/utils/setQueryPages.ts +++ b/apps/roam/src/utils/setQueryPages.ts @@ -1,6 +1,6 @@ import { OnloadArgs } from "roamjs-components/types"; import { - readPathValue, + getPersonalSetting, setPersonalSetting, type SettingsSnapshot, } from "~/components/settings/utils/accessors"; @@ -11,14 +11,16 @@ import { export const setInitialQueryPages = ( onloadArgs: OnloadArgs, - settingsSnapshot: SettingsSnapshot, + snapshot?: SettingsSnapshot, ) => { // Legacy extensionAPI stored query-pages as string | string[] | Record. // Coerce to string[] for backward compatibility with old stored formats. - const raw = readPathValue(settingsSnapshot.personalSettings, [ - PERSONAL_KEYS.query, - QUERY_KEYS.queryPages, - ]) as string[] | string | Record | undefined; + const raw: string[] | string | Record | undefined = snapshot + ? snapshot.personalSettings[PERSONAL_KEYS.query][QUERY_KEYS.queryPages] + : getPersonalSetting>([ + PERSONAL_KEYS.query, + QUERY_KEYS.queryPages, + ]); const queryPageArray = Array.isArray(raw) ? raw : typeof raw === "string" && raw From 4140f5c1fc53cd0254051357c328cd36fd502a0e Mon Sep 17 00:00:00 2001 From: sid597 Date: Mon, 6 Apr 2026 20:31:54 +0530 Subject: [PATCH 4/6] ENG-1616: Log total plugin load time Capture performance.now() at the top of runExtension and log the elapsed milliseconds just before the unload handler is wired, so we have a single broad measurement of plugin init cost on each load. --- apps/roam/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index 6d17d92d3..26946798d 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -46,6 +46,8 @@ import { mountLeftSidebar } from "./components/LeftSidebarView"; export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; export default runExtension(async (onloadArgs) => { + const pluginLoadStart = performance.now(); + const settingsSnapshot = bulkReadSettings(); const isEncrypted = window.roamAlphaAPI.graph.isEncrypted; @@ -194,6 +196,10 @@ export default runExtension(async (onloadArgs) => { const { blockUids } = await initSchema(); const cleanupPullWatchers = setupPullWatchOnSettingsPage(blockUids); + console.log( + `[DG Plugin] Total load: ${Math.round(performance.now() - pluginLoadStart)}ms`, + ); + return { elements: [ style, From aea086d2620123a136e637349adb6de6c888d02d Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 8 Apr 2026 16:02:29 +0530 Subject: [PATCH 5/6] ENG-1616: Tighten init-only leaves to required snapshot, AGENTS.md compliance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make snapshot required at six init-only leaves where caller audit showed every site already passed one: installDiscourseFloatingMenu, initializeDiscourseNodes, setInitialQueryPages, isQueryPage, isCurrentPageCanvas, isSidebarCanvas. No cascade — only at the leaves. Drop dead fallback code that was reachable only via the optional path: - setQueryPages: legacy string|Record coercion ladder (snapshot is Zod-typed string[]) - DiscourseFloatingMenu: getPersonalSetting cast site - DiscourseFloatingMenu: unused props parameter (no caller ever overrode default) - initializeObserversAndListeners: !== false dead pattern (Zod boolean default) - initializeObserversAndListeners: as IKeyCombo cast (schema is structurally compatible) AGENTS.md compliance for >2-arg functions: - mountLeftSidebar: object-destructured params, both call sites updated - installDiscourseFloatingMenu: kept at 2 positional via dead-props removal posthog: collapse doInitPostHog wrapper into initPostHog (caller-side gating). accessors: revert speculative readPathValue export (no consumer). LeftSidebarView/DiscourseFloatingMenu: eslint-disable react/no-deprecated on ReactDOM.render rewritten lines, matching existing codebase convention. --- .../src/components/DiscourseFloatingMenu.tsx | 25 ++++++------------- apps/roam/src/components/LeftSidebarView.tsx | 20 ++++++++++----- .../components/settings/utils/accessors.ts | 2 +- apps/roam/src/index.ts | 2 +- .../src/utils/initializeDiscourseNodes.ts | 2 +- .../utils/initializeObserversAndListeners.ts | 13 +++++----- apps/roam/src/utils/isCanvasPage.ts | 4 +-- apps/roam/src/utils/isQueryPage.ts | 2 +- apps/roam/src/utils/posthog.ts | 8 ++---- apps/roam/src/utils/setQueryPages.ts | 18 +++---------- 10 files changed, 40 insertions(+), 56 deletions(-) diff --git a/apps/roam/src/components/DiscourseFloatingMenu.tsx b/apps/roam/src/components/DiscourseFloatingMenu.tsx index 0f6c8e52d..a96c33f1b 100644 --- a/apps/roam/src/components/DiscourseFloatingMenu.tsx +++ b/apps/roam/src/components/DiscourseFloatingMenu.tsx @@ -13,10 +13,7 @@ import { import { FeedbackWidget } from "./BirdEatsBugs"; import { render as renderSettings } from "~/components/settings/Settings"; import posthog from "posthog-js"; -import { - getPersonalSetting, - type SettingsSnapshot, -} from "./settings/utils/accessors"; +import { type SettingsSnapshot } from "./settings/utils/accessors"; import { PERSONAL_KEYS } from "./settings/utils/settingKeys"; type DiscourseFloatingMenuProps = { @@ -121,12 +118,7 @@ export const showDiscourseFloatingMenu = () => { export const installDiscourseFloatingMenu = ( onLoadArgs: OnloadArgs, - snapshot?: SettingsSnapshot, - props: DiscourseFloatingMenuProps = { - position: "bottom-right", - theme: "bp3-light", - buttonTheme: "bp3-light", - }, + snapshot: SettingsSnapshot, ) => { let floatingMenuAnchor = document.getElementById(ANCHOR_ID); if (!floatingMenuAnchor) { @@ -134,17 +126,15 @@ export const installDiscourseFloatingMenu = ( floatingMenuAnchor.id = ANCHOR_ID; document.getElementById("app")?.appendChild(floatingMenuAnchor); } - const hideFeedbackButton = snapshot - ? snapshot.personalSettings[PERSONAL_KEYS.hideFeedbackButton] - : getPersonalSetting([PERSONAL_KEYS.hideFeedbackButton]); - if (hideFeedbackButton) { + if (snapshot.personalSettings[PERSONAL_KEYS.hideFeedbackButton]) { floatingMenuAnchor.classList.add("hidden"); } + // eslint-disable-next-line react/no-deprecated ReactDOM.render( , floatingMenuAnchor, @@ -155,6 +145,7 @@ export const removeDiscourseFloatingMenu = () => { const anchor = document.getElementById(ANCHOR_ID); if (anchor) { try { + // eslint-disable-next-line react/no-deprecated ReactDOM.unmountComponentAtNode(anchor); } catch (e) { // no-op: unmount best-effort diff --git a/apps/roam/src/components/LeftSidebarView.tsx b/apps/roam/src/components/LeftSidebarView.tsx index fc6946ba7..b5793707c 100644 --- a/apps/roam/src/components/LeftSidebarView.tsx +++ b/apps/roam/src/components/LeftSidebarView.tsx @@ -619,11 +619,15 @@ const migrateFavorites = async () => { refreshConfigTree(); }; -export const mountLeftSidebar = async ( - wrapper: HTMLElement, - onloadArgs: OnloadArgs, - initialSnapshot?: SettingsSnapshot, -): Promise => { +export const mountLeftSidebar = async ({ + wrapper, + onloadArgs, + initialSnapshot, +}: { + wrapper: HTMLElement; + onloadArgs: OnloadArgs; + initialSnapshot?: SettingsSnapshot; +}): Promise => { if (!wrapper) return; const id = "dg-left-sidebar-root"; @@ -640,8 +644,12 @@ export const mountLeftSidebar = async ( } else { root.className = "starred-pages"; } + // eslint-disable-next-line react/no-deprecated ReactDOM.render( - , + , root, ); }; diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 25ccf1f9d..6be04569b 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -143,7 +143,7 @@ const getSchemaAtPath = ( const formatSettingPath = (keys: string[]): string => keys.length === 0 ? "(root)" : keys.join(" > "); -export const readPathValue = (root: unknown, keys: string[]): unknown => +const readPathValue = (root: unknown, keys: string[]): unknown => keys.reduce((current, key) => { if (Array.isArray(current)) { const index = Number(key); diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index 26946798d..daec39cc4 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -180,7 +180,7 @@ export default runExtension(async (onloadArgs) => { if (!wrapper) return; if (enabled) { wrapper.style.padding = "0"; - void mountLeftSidebar(wrapper, onloadArgs); + void mountLeftSidebar({ wrapper, onloadArgs }); } else { const root = wrapper.querySelector("#dg-left-sidebar-root"); if (root) { diff --git a/apps/roam/src/utils/initializeDiscourseNodes.ts b/apps/roam/src/utils/initializeDiscourseNodes.ts index 6ab88ddf1..847e8fdf7 100644 --- a/apps/roam/src/utils/initializeDiscourseNodes.ts +++ b/apps/roam/src/utils/initializeDiscourseNodes.ts @@ -4,7 +4,7 @@ import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes"; import getDiscourseNodes, { excludeDefaultNodes } from "./getDiscourseNodes"; import type { SettingsSnapshot } from "~/components/settings/utils/accessors"; -const initializeDiscourseNodes = async (snapshot?: SettingsSnapshot) => { +const initializeDiscourseNodes = async (snapshot: SettingsSnapshot) => { const nodes = getDiscourseNodes(undefined, snapshot).filter( excludeDefaultNodes, ); diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index 8f4fc5620..ace07fb31 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -295,7 +295,7 @@ export const initObservers = ({ settingsSnapshot.personalSettings[PERSONAL_KEYS.personalNodeMenuTrigger]; const personalTriggerCombo = typeof personalTriggerComboRaw === "object" - ? (personalTriggerComboRaw as IKeyCombo) + ? personalTriggerComboRaw : undefined; let personalTrigger = personalTriggerCombo?.key; let personalModifiers = personalTriggerCombo @@ -333,7 +333,11 @@ export const initObservers = ({ const container = el as HTMLDivElement; if (isLeftSidebarEnabled) { container.style.padding = "0"; - await mountLeftSidebar(container, onloadArgs, callbackSnapshot); + await mountLeftSidebar({ + wrapper: container, + onloadArgs, + initialSnapshot: callbackSnapshot, + }); } })(); }, @@ -442,10 +446,7 @@ export const initObservers = ({ const nodeCreationPopoverListener = debounce(() => { const snap = bulkReadSettings(); - const isTextSelectionPopupEnabled = - snap.personalSettings[PERSONAL_KEYS.textSelectionPopup] !== false; - - if (!isTextSelectionPopupEnabled) return; + if (!snap.personalSettings[PERSONAL_KEYS.textSelectionPopup]) return; const selection = window.getSelection(); diff --git a/apps/roam/src/utils/isCanvasPage.ts b/apps/roam/src/utils/isCanvasPage.ts index daea2ebc8..4c7d97331 100644 --- a/apps/roam/src/utils/isCanvasPage.ts +++ b/apps/roam/src/utils/isCanvasPage.ts @@ -28,7 +28,7 @@ export const isCurrentPageCanvas = ({ }: { title: string; h1: HTMLHeadingElement; - snapshot?: SettingsSnapshot; + snapshot: SettingsSnapshot; }) => { return isCanvasPage({ title, snapshot }) && !!h1.closest(".roam-article"); }; @@ -40,7 +40,7 @@ export const isSidebarCanvas = ({ }: { title: string; h1: HTMLHeadingElement; - snapshot?: SettingsSnapshot; + snapshot: SettingsSnapshot; }) => { return ( isCanvasPage({ title, snapshot }) && !!h1.closest(".rm-sidebar-outline") diff --git a/apps/roam/src/utils/isQueryPage.ts b/apps/roam/src/utils/isQueryPage.ts index af3a831bb..4e6109944 100644 --- a/apps/roam/src/utils/isQueryPage.ts +++ b/apps/roam/src/utils/isQueryPage.ts @@ -6,7 +6,7 @@ export const isQueryPage = ({ snapshot, }: { title: string; - snapshot?: SettingsSnapshot; + snapshot: SettingsSnapshot; }): boolean => { const queryPages = getQueryPages(snapshot); diff --git a/apps/roam/src/utils/posthog.ts b/apps/roam/src/utils/posthog.ts index 1149e319a..9b19e63cd 100644 --- a/apps/roam/src/utils/posthog.ts +++ b/apps/roam/src/utils/posthog.ts @@ -5,7 +5,7 @@ import type { CaptureResult } from "posthog-js"; let initialized = false; -const doInitPostHog = (): void => { +export const initPostHog = (): void => { if (initialized) return; const propertyDenylist = new Set([ "$ip", @@ -56,14 +56,10 @@ const doInitPostHog = (): void => { }; export const enablePostHog = (): void => { - doInitPostHog(); + initPostHog(); posthog.opt_in_capturing(); }; export const disablePostHog = (): void => { if (initialized) posthog.opt_out_capturing(); }; - -export const initPostHog = (): void => { - doInitPostHog(); -}; diff --git a/apps/roam/src/utils/setQueryPages.ts b/apps/roam/src/utils/setQueryPages.ts index a29b9bdc8..f75fb521b 100644 --- a/apps/roam/src/utils/setQueryPages.ts +++ b/apps/roam/src/utils/setQueryPages.ts @@ -1,6 +1,5 @@ import { OnloadArgs } from "roamjs-components/types"; import { - getPersonalSetting, setPersonalSetting, type SettingsSnapshot, } from "~/components/settings/utils/accessors"; @@ -11,21 +10,10 @@ import { export const setInitialQueryPages = ( onloadArgs: OnloadArgs, - snapshot?: SettingsSnapshot, + snapshot: SettingsSnapshot, ) => { - // Legacy extensionAPI stored query-pages as string | string[] | Record. - // Coerce to string[] for backward compatibility with old stored formats. - const raw: string[] | string | Record | undefined = snapshot - ? snapshot.personalSettings[PERSONAL_KEYS.query][QUERY_KEYS.queryPages] - : getPersonalSetting>([ - PERSONAL_KEYS.query, - QUERY_KEYS.queryPages, - ]); - const queryPageArray = Array.isArray(raw) - ? raw - : typeof raw === "string" && raw - ? [raw] - : []; + const queryPageArray = + snapshot.personalSettings[PERSONAL_KEYS.query][QUERY_KEYS.queryPages]; if (!queryPageArray.includes("discourse-graph/queries/*")) { const updated = [...queryPageArray, "discourse-graph/queries/*"]; void onloadArgs.extensionAPI.settings.set("query-pages", updated); From 24e521b47a8afe0dab24262fbc5bef1f51879519 Mon Sep 17 00:00:00 2001 From: sid597 Date: Fri, 10 Apr 2026 14:29:24 +0530 Subject: [PATCH 6/6] =?UTF-8?q?ENG-1616:=20Address=20review=20=E2=80=94=20?= =?UTF-8?q?rename=20snapshot=20vars,=20flag-gate=20bulkRead,=20move=20Post?= =?UTF-8?q?Hog=20guards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename settingsSnapshot/callbackSnapshot/snap/navSnapshot → settings - bulkReadSettings now checks "Use new settings store" flag and falls back to legacy reads when off, matching individual getter behavior - Move encryption/offline guards into initPostHog (diagnostics check stays at call site to avoid race with async setSetting in enablePostHog) --- .../components/settings/utils/accessors.ts | 14 ++++- apps/roam/src/index.ts | 22 ++++---- .../utils/initializeObserversAndListeners.ts | 53 +++++++++---------- apps/roam/src/utils/posthog.ts | 2 + 4 files changed, 48 insertions(+), 43 deletions(-) diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 6be04569b..b23b466b3 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -949,8 +949,20 @@ export const bulkReadSettings = (): SettingsSnapshot => { } } + const featureFlags = FeatureFlagsSchema.parse(featureFlagsProps || {}); + + if (!featureFlags["Use new settings store"]) { + return { + featureFlags, + globalSettings: GlobalSettingsSchema.parse(readAllLegacyGlobalSettings()), + personalSettings: PersonalSettingsSchema.parse( + readAllLegacyPersonalSettings(), + ), + }; + } + return { - featureFlags: FeatureFlagsSchema.parse(featureFlagsProps || {}), + featureFlags, globalSettings: GlobalSettingsSchema.parse(globalProps || {}), personalSettings: PersonalSettingsSchema.parse(personalProps || {}), }; diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts index daec39cc4..68d404ede 100644 --- a/apps/roam/src/index.ts +++ b/apps/roam/src/index.ts @@ -48,13 +48,9 @@ export const DEFAULT_CANVAS_PAGE_FORMAT = "Canvas/*"; export default runExtension(async (onloadArgs) => { const pluginLoadStart = performance.now(); - const settingsSnapshot = bulkReadSettings(); + const settings = bulkReadSettings(); - const isEncrypted = window.roamAlphaAPI.graph.isEncrypted; - const isOffline = window.roamAlphaAPI.graph.type === "offline"; - const disallowDiagnostics = - settingsSnapshot.personalSettings[PERSONAL_KEYS.disableProductDiagnostics]; - if (!isEncrypted && !isOffline && !disallowDiagnostics) { + if (!settings.personalSettings[PERSONAL_KEYS.disableProductDiagnostics]) { initPostHog(); } @@ -81,16 +77,16 @@ export default runExtension(async (onloadArgs) => { initPluginTimer(); - await initializeDiscourseNodes(settingsSnapshot); + await initializeDiscourseNodes(settings); - refreshConfigTree(settingsSnapshot); + refreshConfigTree(settings); addGraphViewNodeStyling(); registerCommandPaletteCommands(onloadArgs); createSettingsPanel(onloadArgs); registerSmartBlock(onloadArgs); - setInitialQueryPages(onloadArgs, settingsSnapshot); + setInitialQueryPages(onloadArgs, settings); const style = addStyle(styles); const discourseGraphStyle = addStyle(discourseGraphStyles); @@ -99,7 +95,7 @@ export default runExtension(async (onloadArgs) => { // Add streamline styling only if enabled const isStreamlineStylingEnabled = - settingsSnapshot.personalSettings[PERSONAL_KEYS.streamlineStyling]; + settings.personalSettings[PERSONAL_KEYS.streamlineStyling]; let streamlineStyleElement: HTMLStyleElement | null = null; if (isStreamlineStylingEnabled) { streamlineStyleElement = addStyle(streamlineStyling); @@ -108,7 +104,7 @@ export default runExtension(async (onloadArgs) => { const { observers, listeners, cleanups } = initObservers({ onloadArgs, - settingsSnapshot, + settings, }); const { pageActionListener, @@ -123,7 +119,7 @@ export default runExtension(async (onloadArgs) => { document.addEventListener("input", discourseNodeSearchTriggerListener); document.addEventListener("selectionchange", nodeCreationPopoverListener); - if (settingsSnapshot.featureFlags["Suggestive mode enabled"]) { + if (settings.featureFlags["Suggestive mode enabled"]) { initializeSupabaseSync(); } @@ -154,7 +150,7 @@ export default runExtension(async (onloadArgs) => { getDiscourseNodes: getDiscourseNodes, }; - installDiscourseFloatingMenu(onloadArgs, settingsSnapshot); + installDiscourseFloatingMenu(onloadArgs, settings); const leftSidebarScript = document.querySelector( 'script#roam-left-sidebar[src="https://sid597.github.io/roam-left-sidebar/js/main.js"]', diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index ace07fb31..27a095aba 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -89,10 +89,10 @@ const getTitleAndUidFromHeader = (h1: HTMLHeadingElement) => { export const initObservers = ({ onloadArgs, - settingsSnapshot, + settings, }: { onloadArgs: OnloadArgs; - settingsSnapshot: SettingsSnapshot; + settings: SettingsSnapshot; }): { observers: MutationObserver[]; listeners: { @@ -111,16 +111,16 @@ export const initObservers = ({ const h1 = e as HTMLHeadingElement; const { title, uid } = getTitleAndUidFromHeader(h1); - const callbackSnapshot = bulkReadSettings(); + const settings = bulkReadSettings(); const props = { title, h1, onloadArgs }; const isSuggestiveModeEnabled = - callbackSnapshot.featureFlags["Suggestive mode enabled"]; + settings.featureFlags["Suggestive mode enabled"]; const node = findDiscourseNode({ uid, title, - snapshot: callbackSnapshot, + snapshot: settings, }); const isDiscourseNode = node && node.backedBy !== "default"; if (isDiscourseNode) { @@ -135,13 +135,11 @@ export const initObservers = ({ renderCanvasReferences(linkedReferencesDiv, uid, onloadArgs); } } - if (isQueryPage({ title, snapshot: callbackSnapshot })) { + if (isQueryPage({ title, snapshot: settings })) { renderQueryPage(props); - } else if ( - isCurrentPageCanvas({ title, h1, snapshot: callbackSnapshot }) - ) { + } else if (isCurrentPageCanvas({ title, h1, snapshot: settings })) { renderTldrawCanvas(props); - } else if (isSidebarCanvas({ title, h1, snapshot: callbackSnapshot })) { + } else if (isSidebarCanvas({ title, h1, snapshot: settings })) { renderTldrawCanvasInSidebar(props); } }, @@ -155,8 +153,8 @@ export const initObservers = ({ let batchedTagNodes: DiscourseNode[] | null = null; const getNodesForTagBatch = (): DiscourseNode[] => { if (batchedTagNodes === null) { - const snap = bulkReadSettings(); - batchedTagNodes = getDiscourseNodes(undefined, snap); + const settings = bulkReadSettings(); + batchedTagNodes = getDiscourseNodes(undefined, settings); queueMicrotask(() => { batchedTagNodes = null; }); @@ -230,7 +228,7 @@ export const initObservers = ({ const suggestiveHandler = getSuggestiveOverlayHandler(onloadArgs); const toggleSuggestiveOverlay = onPageRefObserverChange(suggestiveHandler); - if (settingsSnapshot.personalSettings[PERSONAL_KEYS.suggestiveModeOverlay]) { + if (settings.personalSettings[PERSONAL_KEYS.suggestiveModeOverlay]) { addPageRefObserver(suggestiveHandler); } @@ -260,12 +258,10 @@ export const initObservers = ({ }, }); - if (settingsSnapshot.personalSettings[PERSONAL_KEYS.pagePreview]) + if (settings.personalSettings[PERSONAL_KEYS.pagePreview]) addPageRefObserver(previewPageRefHandler); - if ( - settingsSnapshot.personalSettings[PERSONAL_KEYS.discourseContextOverlay] - ) { + if (settings.personalSettings[PERSONAL_KEYS.discourseContextOverlay]) { const overlayHandler = getOverlayHandler(onloadArgs); onPageRefObserverChange(overlayHandler)(true); } @@ -276,23 +272,22 @@ export const initObservers = ({ const hashChangeListener = (e: Event) => { const evt = e as HashChangeEvent; - const navSnapshot = bulkReadSettings(); + const settings = bulkReadSettings(); // Attempt to refresh config navigating away from config page // doesn't work if they update via sidebar if ( (configPageUid && evt.oldURL.endsWith(configPageUid)) || - getDiscourseNodes(undefined, navSnapshot).some(({ type }) => + getDiscourseNodes(undefined, settings).some(({ type }) => evt.oldURL.endsWith(type), ) ) { - refreshConfigTree(navSnapshot); + refreshConfigTree(settings); } }; - let globalTrigger = - settingsSnapshot.globalSettings[GLOBAL_KEYS.trigger].trim(); + let globalTrigger = settings.globalSettings[GLOBAL_KEYS.trigger].trim(); const personalTriggerComboRaw = - settingsSnapshot.personalSettings[PERSONAL_KEYS.personalNodeMenuTrigger]; + settings.personalSettings[PERSONAL_KEYS.personalNodeMenuTrigger]; const personalTriggerCombo = typeof personalTriggerComboRaw === "object" ? personalTriggerComboRaw @@ -327,16 +322,16 @@ export const initObservers = ({ className: "starred-pages-wrapper", callback: (el) => { void (async () => { - const callbackSnapshot = bulkReadSettings(); + const settings = bulkReadSettings(); const isLeftSidebarEnabled = - callbackSnapshot.featureFlags["Enable left sidebar"]; + settings.featureFlags["Enable left sidebar"]; const container = el as HTMLDivElement; if (isLeftSidebarEnabled) { container.style.padding = "0"; await mountLeftSidebar({ wrapper: container, onloadArgs, - initialSnapshot: callbackSnapshot, + initialSnapshot: settings, }); } })(); @@ -385,7 +380,7 @@ export const initObservers = ({ }; let customTrigger = - settingsSnapshot.personalSettings[PERSONAL_KEYS.nodeSearchMenuTrigger]; + settings.personalSettings[PERSONAL_KEYS.nodeSearchMenuTrigger]; const unsubSearchTrigger = onSettingChange( settingKeys.nodeSearchMenuTrigger, @@ -445,8 +440,8 @@ export const initObservers = ({ }; const nodeCreationPopoverListener = debounce(() => { - const snap = bulkReadSettings(); - if (!snap.personalSettings[PERSONAL_KEYS.textSelectionPopup]) return; + const settings = bulkReadSettings(); + if (!settings.personalSettings[PERSONAL_KEYS.textSelectionPopup]) return; const selection = window.getSelection(); diff --git a/apps/roam/src/utils/posthog.ts b/apps/roam/src/utils/posthog.ts index 9b19e63cd..c2b4bc8a5 100644 --- a/apps/roam/src/utils/posthog.ts +++ b/apps/roam/src/utils/posthog.ts @@ -7,6 +7,8 @@ let initialized = false; export const initPostHog = (): void => { if (initialized) return; + if (window.roamAlphaAPI.graph.isEncrypted) return; + if (window.roamAlphaAPI.graph.type === "offline") return; const propertyDenylist = new Set([ "$ip", "$device_id",