From 9b97ae1ca7b6db5091e48aaa6b9352eeb4f6aa8c Mon Sep 17 00:00:00 2001 From: sid597 Date: Sun, 5 Jul 2026 23:59:14 +0530 Subject: [PATCH] ENG-1953: Remove duplicate node alert feature Removes VectorDuplicateMatches, its observer wiring, the feature flag (settings schema, keys, admin panel), and the dead vector-only search wrapper in hyde.ts. The match_content_embeddings RPC stays: it is still used by discourseNodeSearchProviders. --- .../src/components/VectorDuplicateMatches.tsx | 172 ------------------ .../src/components/settings/AdminPanel.tsx | 22 +-- .../components/settings/utils/accessors.ts | 1 - .../components/settings/utils/settingKeys.ts | 1 - .../settings/utils/zodSchema.example.ts | 2 - .../components/settings/utils/zodSchema.ts | 1 - apps/roam/src/utils/hyde.ts | 61 ------- .../utils/initializeObserversAndListeners.ts | 7 - 8 files changed, 2 insertions(+), 265 deletions(-) delete mode 100644 apps/roam/src/components/VectorDuplicateMatches.tsx diff --git a/apps/roam/src/components/VectorDuplicateMatches.tsx b/apps/roam/src/components/VectorDuplicateMatches.tsx deleted file mode 100644 index 19f1b6e5f..000000000 --- a/apps/roam/src/components/VectorDuplicateMatches.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import React, { useEffect, useState, useMemo } from "react"; -import { Collapse, Spinner, Icon } from "@blueprintjs/core"; -import { - findSimilarNodesVectorOnly as vectorSearch, - type VectorMatch, -} from "~/utils/hyde"; -import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; -import { DiscourseNode } from "~/utils/getDiscourseNodes"; -import extractContentFromTitle from "~/utils/extractContentFromTitle"; -import { handleTitleAdditions } from "~/utils/handleTitleAdditions"; -import posthog from "posthog-js"; - -export const VectorDuplicateMatches = ({ - pageTitle, - text, - limit = 15, - node, -}: { - pageTitle?: string; - text?: string; - limit?: number; - node: DiscourseNode; -}) => { - const [debouncedText, setDebouncedText] = useState(text); - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedText(text); - }, 500); - return () => { - clearTimeout(handler); - }; - }, [text]); - - const [isOpen, setIsOpen] = useState(false); - const [suggestionsLoading, setSuggestionsLoading] = useState(false); - const [hasSearched, setHasSearched] = useState(false); - const [suggestions, setSuggestions] = useState([]); - - const searchText = extractContentFromTitle(pageTitle || "", node); - const pageUid = getPageUidByPageTitle(searchText); - const activeContext = useMemo( - () => - text !== undefined - ? { searchText: debouncedText || "", pageUid: null } - : { searchText, pageUid }, - [text, debouncedText, searchText, pageUid], - ); - - useEffect(() => { - setHasSearched(false); - }, [activeContext?.searchText]); - - useEffect(() => { - let isCancelled = false; - const fetchSuggestions = async () => { - if (!isOpen || hasSearched) return; - if (!activeContext || !activeContext.searchText.trim()) return; - - const { searchText, pageUid } = activeContext; - - setSuggestionsLoading(true); - try { - const raw = await vectorSearch({ - text: searchText, - threshold: 0.3, - limit, - }); - const results: VectorMatch[] = raw.filter((candidate) => { - const sameUid = !!pageUid && candidate.node.uid === pageUid; - return !sameUid; - }); - if (!isCancelled) { - setSuggestions(results); - setSuggestionsLoading(false); - setHasSearched(true); - } - } catch (error: unknown) { - console.error("Error fetching vector duplicates:", error); - if (!isCancelled) { - setSuggestionsLoading(false); - } - } - }; - void fetchSuggestions(); - return () => { - isCancelled = true; - }; - }, [isOpen, hasSearched, activeContext, pageTitle, limit]); - - const handleSuggestionClick = async (node: VectorMatch["node"]) => { - await window.roamAlphaAPI.ui.rightSidebar.addWindow({ - window: { - type: "outline", - // @ts-expect-error - type definition mismatch - "block-uid": node.uid, - }, - }); - }; - - if (!activeContext) { - return null; - } - - const hasSuggestions = suggestions.length > 0; - - return ( -
-
{ - setIsOpen(!isOpen); - posthog.capture("Possible Duplicates: Toggled"); - }} - > -
- -
Possible Duplicates
-
- {hasSearched && !suggestionsLoading && hasSuggestions && ( - - {suggestions.length} - - )} -
- - -
- {suggestionsLoading && ( -
- - - Searching for duplicates... - -
- )} - - {!suggestionsLoading && hasSearched && !hasSuggestions && ( -

No matches found.

- )} - - {!suggestionsLoading && hasSearched && hasSuggestions && ( - - )} -
-
-
- ); -}; - -export const renderPossibleDuplicates = ( - h1: HTMLHeadingElement, - title: string, - node: DiscourseNode, -) => { - handleTitleAdditions( - h1, - , - ); -}; diff --git a/apps/roam/src/components/settings/AdminPanel.tsx b/apps/roam/src/components/settings/AdminPanel.tsx index 8c3e97905..d05cdfe29 100644 --- a/apps/roam/src/components/settings/AdminPanel.tsx +++ b/apps/roam/src/components/settings/AdminPanel.tsx @@ -264,16 +264,13 @@ const FeatureFlagsTab = (): React.ReactElement => { const [pendingFeatureKey, setPendingFeatureKey] = useState< keyof FeatureFlags | null >(null); - const [duplicateNodeAlertValue, setDuplicateNodeAlertValue] = useState( - getFeatureFlag("Duplicate node alert enabled"), - ); const [suggestiveOverlayValue, setSuggestiveOverlayValue] = useState( getFeatureFlag("Suggestive mode overlay enabled"), ); const [advancedNodeSearchValue, setAdvancedNodeSearchValue] = useState( getFeatureFlag("Advanced node search enabled"), ); - const syncAlreadyEnabled = duplicateNodeAlertValue || suggestiveOverlayValue; + const syncAlreadyEnabled = suggestiveOverlayValue; const ensureSyncEnabled = ( featureKey: keyof FeatureFlags, @@ -338,17 +335,6 @@ const FeatureFlagsTab = (): React.ReactElement => { return (
- ensureSyncEnabled("Duplicate node alert enabled")} - onAfterChange={(checked) => - handleFeatureToggled(checked, setDuplicateNodeAlertValue) - } - /> - { onConfirm={() => { if (pendingFeatureKey) { setFeatureFlag(pendingFeatureKey, true); - if (pendingFeatureKey === "Duplicate node alert enabled") { - setDuplicateNodeAlertValue(true); - } else if ( - pendingFeatureKey === "Suggestive mode overlay enabled" - ) { + if (pendingFeatureKey === "Suggestive mode overlay enabled") { setSuggestiveOverlayValue(true); } } diff --git a/apps/roam/src/components/settings/utils/accessors.ts b/apps/roam/src/components/settings/utils/accessors.ts index 29c0d141e..c751cf32a 100644 --- a/apps/roam/src/components/settings/utils/accessors.ts +++ b/apps/roam/src/components/settings/utils/accessors.ts @@ -765,7 +765,6 @@ export const readAllLegacyDiscourseNodeSettings = ( }; export const isSyncEnabled = (): boolean => - getFeatureFlag("Duplicate node alert enabled") || getFeatureFlag("Suggestive mode overlay enabled"); export const setFeatureFlag = ( diff --git a/apps/roam/src/components/settings/utils/settingKeys.ts b/apps/roam/src/components/settings/utils/settingKeys.ts index 827f26100..fd8a37651 100644 --- a/apps/roam/src/components/settings/utils/settingKeys.ts +++ b/apps/roam/src/components/settings/utils/settingKeys.ts @@ -13,7 +13,6 @@ import type { export const FEATURE_FLAG_KEYS = { enableLeftSidebar: "Enable left sidebar", - duplicateNodeAlertEnabled: "Duplicate node alert enabled", suggestiveModeOverlayEnabled: "Suggestive mode overlay enabled", useNewSettingsStore: "Use new settings store", } as const satisfies Record; diff --git a/apps/roam/src/components/settings/utils/zodSchema.example.ts b/apps/roam/src/components/settings/utils/zodSchema.example.ts index 3b3f3cd1d..8df443e13 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.example.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.example.ts @@ -87,7 +87,6 @@ const discourseNodeSettings: DiscourseNodeSettings = { const featureFlags: FeatureFlags = { "Advanced node search enabled": true, "Enable left sidebar": true, - "Duplicate node alert enabled": true, "Suggestive mode overlay enabled": true, "Use new settings store": false, }; @@ -95,7 +94,6 @@ const featureFlags: FeatureFlags = { const defaultFeatureFlags: FeatureFlags = { "Advanced node search enabled": false, "Enable left sidebar": false, - "Duplicate node alert enabled": false, "Suggestive mode overlay enabled": false, "Use new settings store": false, }; diff --git a/apps/roam/src/components/settings/utils/zodSchema.ts b/apps/roam/src/components/settings/utils/zodSchema.ts index 992635fb1..5b08bf642 100644 --- a/apps/roam/src/components/settings/utils/zodSchema.ts +++ b/apps/roam/src/components/settings/utils/zodSchema.ts @@ -157,7 +157,6 @@ export const DiscourseRelationSchema = z.object({ export const FeatureFlagsSchema = z.object({ "Advanced node search enabled": z.boolean().default(false), "Enable left sidebar": z.boolean().default(false), - "Duplicate node alert enabled": z.boolean().default(false), "Suggestive mode overlay enabled": z.boolean().default(false), "Use new settings store": z.boolean().default(false), }); diff --git a/apps/roam/src/utils/hyde.ts b/apps/roam/src/utils/hyde.ts index 8a3a7cc5e..3dfefa64a 100644 --- a/apps/roam/src/utils/hyde.ts +++ b/apps/roam/src/utils/hyde.ts @@ -1,7 +1,6 @@ import { getLoggedInClient, getSupabaseContext } from "./supabaseContext"; import { Result } from "./types"; import normalizePageTitle from "roamjs-components/queries/normalizePageTitle"; -import { render as renderToast } from "roamjs-components/components/Toast"; import findDiscourseNode from "./findDiscourseNode"; import { nextApiRoot } from "@repo/utils/execContext"; import { DiscourseNode } from "./getDiscourseNodes"; @@ -513,63 +512,3 @@ export const performHydeSearch = async ({ } return []; }; - -export type VectorMatch = { - node: Result; - score: number; -}; - -export const findSimilarNodesVectorOnly = async ({ - text, - threshold = 0.4, - limit = 15, -}: { - text: string; - threshold?: number; - limit?: number; -}): Promise => { - if (!text.trim()) { - return []; - } - - try { - const supabase = await getLoggedInClient(); - if (!supabase) return []; - - const queryEmbedding = await createEmbedding(text); - - const { data, error } = await supabase - .rpc("match_content_embeddings", { - query_embedding: JSON.stringify(queryEmbedding), - match_threshold: threshold, - }) - .limit(limit); - - if (error) { - console.error("Vector search failed:", error); - throw error; - } - - if (!data || !Array.isArray(data)) return []; - - const results: VectorMatch[] = data.map((item) => ({ - node: { - uid: item.roam_uid, - text: item.text_content, - }, - score: item.similarity, - })); - - return results; - } catch (error) { - console.error("Error in vector-only similar nodes search:", error); - renderToast({ - content: `Error in vector-only similar nodes search: ${ - error instanceof Error ? error.message : String(error) - }`, - intent: "danger", - id: "vector-search-error", - }); - return []; - } -}; diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index 57804da57..2df70e870 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -47,7 +47,6 @@ import { renderImageToolsMenu } from "./renderImageToolsMenu"; import { mountLeftSidebar } from "~/components/LeftSidebarView"; import { getCleanTagText } from "~/components/settings/NodeConfig"; import { getNodeTagStyles } from "~/utils/getDiscourseNodeColors"; -import { renderPossibleDuplicates } from "~/components/VectorDuplicateMatches"; import { renderPublishNodeTitleButton } from "~/components/PublishNodeTitleButton"; import { renderCanvasEmbed } from "~/components/canvas/CanvasEmbed"; import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; @@ -125,7 +124,6 @@ export const initObservers = ({ const isDiscourseNode = node && node.backedBy !== "default"; if (isDiscourseNode) { const syncEnabled = - settings.featureFlags[FEATURE_FLAG_KEYS.duplicateNodeAlertEnabled] || settings.featureFlags[FEATURE_FLAG_KEYS.suggestiveModeOverlayEnabled]; if (syncEnabled && node.backedBy === "user") { renderPublishNodeTitleButton({ @@ -138,11 +136,6 @@ export const initObservers = ({ if (settings.personalSettings[PERSONAL_KEYS.discourseContextOverlay]) { renderDiscourseContext({ h1, uid }); } - if ( - settings.featureFlags[FEATURE_FLAG_KEYS.duplicateNodeAlertEnabled] - ) { - renderPossibleDuplicates(h1, title, node); - } const linkedReferencesDiv = document.querySelector( ".rm-reference-main", ) as HTMLDivElement;