Skip to content

Commit d70f4d1

Browse files
committed
[ENG-1855] Address shared-node discovery review feedback
1 parent 2fa228c commit d70f4d1

4 files changed

Lines changed: 208 additions & 113 deletions

File tree

apps/roam/src/components/DiscoverSharedNodesDialog.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
discoverSharedNodes,
1818
type DiscoveredSharedNode,
1919
} from "~/utils/discoverSharedNodes";
20+
import internalError from "~/utils/internalError";
2021
import { getLoggedInClient, getSupabaseContext } from "~/utils/supabaseContext";
2122

2223
const formatModifiedAt = (modifiedAt: string): string =>
@@ -80,7 +81,7 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
8081
const [nodes, setNodes] = useState<DiscoveredSharedNode[]>([]);
8182
const [loading, setLoading] = useState(true);
8283
const [error, setError] = useState("");
83-
const [search, setSearch] = useState("");
84+
const [searchTerm, setSearchTerm] = useState("");
8485

8586
const loadNodes = useCallback(async (): Promise<void> => {
8687
setLoading(true);
@@ -97,7 +98,12 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
9798
}),
9899
);
99100
} catch (loadError) {
100-
console.error("Failed to discover shared nodes:", loadError);
101+
internalError({
102+
error: loadError,
103+
type: "Shared node discovery failed",
104+
context: { operation: "load-shared-nodes" },
105+
sendEmail: false,
106+
});
101107
setError(
102108
loadError instanceof Error
103109
? loadError.message
@@ -113,7 +119,7 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
113119
}, [loadNodes]);
114120

115121
const visibleNodes = useMemo(() => {
116-
const normalizedSearch = search.trim().toLocaleLowerCase();
122+
const normalizedSearch = searchTerm.trim().toLocaleLowerCase();
117123
if (!normalizedSearch) return nodes;
118124
return nodes.filter((node) =>
119125
[
@@ -124,7 +130,7 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
124130
node.sourceNodeId,
125131
].some((value) => value?.toLocaleLowerCase().includes(normalizedSearch)),
126132
);
127-
}, [nodes, search]);
133+
}, [nodes, searchTerm]);
128134

129135
return (
130136
<Dialog
@@ -147,10 +153,10 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
147153
className="min-w-0 flex-1"
148154
leftIcon="search"
149155
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
150-
setSearch(event.target.value)
156+
setSearchTerm(event.target.value)
151157
}
152158
placeholder="Search shared nodes"
153-
value={search}
159+
value={searchTerm}
154160
/>
155161
<Tooltip content="Reload shared nodes">
156162
<Button
@@ -178,7 +184,9 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
178184
<div className="flex min-h-52 items-center justify-center">
179185
<NonIdealState
180186
icon="search"
181-
title={search ? "No matching shared nodes" : "No shared nodes"}
187+
title={
188+
searchTerm ? "No matching shared nodes" : "No shared nodes"
189+
}
182190
/>
183191
</div>
184192
) : (

apps/roam/src/utils/__tests__/discoverSharedNodes.test.ts

Lines changed: 81 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,50 +18,51 @@ const spaces: BuildArgs["spaces"] = [
1818
const concepts: BuildArgs["concepts"] = [
1919
{
2020
is_schema: false,
21-
last_modified: "2026-06-14T12:00:00.000Z",
21+
last_modified: "2026-06-14T12:00:00",
2222
schema_id: 200,
2323
source_local_id: "node-1",
2424
space_id: 20,
2525
},
2626
];
27-
const contents: BuildArgs["contents"] = [
27+
const directContents: BuildArgs["directContents"] = [
2828
{
29-
content_type: "text/plain",
30-
last_modified: "2026-06-14T13:00:00.000Z",
29+
last_modified: "2026-06-14T13:00:00",
3130
source_local_id: "node-1",
3231
space_id: 20,
3332
text: "EVD - REM sleep and recall",
34-
variant: "direct",
3533
},
34+
];
35+
const fullContentSummaries: BuildArgs["fullContentSummaries"] = [
3636
{
3737
content_type: "text/markdown",
38-
last_modified: "2026-06-14T15:00:00.000Z",
38+
last_modified: "2026-06-14T15:00:00",
3939
source_local_id: "node-1",
4040
space_id: 20,
41-
text: "# EVD - REM sleep and recall",
42-
variant: "full",
4341
},
4442
];
4543
const sourceNodeRid = "orn:obsidian.note:vault-a/node-1";
4644

4745
const build = ({
4846
conceptsOverride = concepts,
49-
contentsOverride = contents,
47+
directContentsOverride = directContents,
48+
fullContentSummariesOverride = fullContentSummaries,
5049
currentSpaceId = 10,
5150
importedSourceRids = new Set<string>(),
5251
resourcesOverride = resources,
5352
spacesOverride = spaces,
5453
}: {
5554
conceptsOverride?: typeof concepts;
56-
contentsOverride?: typeof contents;
55+
directContentsOverride?: typeof directContents;
56+
fullContentSummariesOverride?: typeof fullContentSummaries;
5757
currentSpaceId?: number;
5858
importedSourceRids?: ReadonlySet<string>;
5959
resourcesOverride?: typeof resources;
6060
spacesOverride?: typeof spaces;
6161
} = {}) =>
6262
buildDiscoveredSharedNodes({
6363
concepts: conceptsOverride,
64-
contents: contentsOverride,
64+
directContents: directContentsOverride,
65+
fullContentSummaries: fullContentSummariesOverride,
6566
currentSpaceId,
6667
importedSourceRids,
6768
resources: resourcesOverride,
@@ -100,30 +101,54 @@ describe("buildDiscoveredSharedNodes", () => {
100101
{
101102
name: "schema concept",
102103
conceptsOverride: [{ ...concepts[0], is_schema: true }],
103-
contentsOverride: contents,
104+
directContentsOverride: directContents,
105+
fullContentSummariesOverride: fullContentSummaries,
104106
},
105107
{
106108
name: "missing node type",
107109
conceptsOverride: [{ ...concepts[0], schema_id: null }],
108-
contentsOverride: contents,
110+
directContentsOverride: directContents,
111+
fullContentSummariesOverride: fullContentSummaries,
109112
},
110113
{
111114
name: "missing direct content",
112115
conceptsOverride: concepts,
113-
contentsOverride: [contents[1]],
114-
},
115-
{
116-
name: "missing full content",
117-
conceptsOverride: concepts,
118-
contentsOverride: [contents[0]],
116+
directContentsOverride: [],
117+
fullContentSummariesOverride: fullContentSummaries,
119118
},
120119
{
121120
name: "untyped full content",
122121
conceptsOverride: concepts,
123-
contentsOverride: [contents[0], { ...contents[1], content_type: null }],
122+
directContentsOverride: directContents,
123+
fullContentSummariesOverride: [
124+
{ ...fullContentSummaries[0], content_type: null },
125+
],
126+
},
127+
])(
128+
"filters a node with $name",
129+
({
130+
conceptsOverride,
131+
directContentsOverride,
132+
fullContentSummariesOverride,
133+
}) => {
134+
expect(
135+
build({
136+
conceptsOverride,
137+
directContentsOverride,
138+
fullContentSummariesOverride,
139+
}),
140+
).toEqual([]);
124141
},
125-
])("filters a node with $name", ({ conceptsOverride, contentsOverride }) => {
126-
expect(build({ conceptsOverride, contentsOverride })).toEqual([]);
142+
);
143+
144+
it("discovers a node without optional full content", () => {
145+
expect(build({ fullContentSummariesOverride: [] })).toMatchObject([
146+
{
147+
modifiedAt: "2026-06-14T13:00:00.000Z",
148+
sourceNodeRid,
149+
title: "EVD - REM sleep and recall",
150+
},
151+
]);
127152
});
128153

129154
it("matches imports by RID rather than source-local ID alone", () => {
@@ -134,25 +159,49 @@ describe("buildDiscoveredSharedNodes", () => {
134159
).toBe(false);
135160
});
136161

162+
it.each([
163+
"orn:obsidian.note:vault-a/node-1",
164+
"https://example.com/shared/node-1",
165+
])("preserves a RID-shaped source-local ID: %s", (rid) => {
166+
expect(
167+
build({
168+
conceptsOverride: [{ ...concepts[0], source_local_id: rid }],
169+
directContentsOverride: [
170+
{ ...directContents[0], source_local_id: rid },
171+
],
172+
fullContentSummariesOverride: [
173+
{ ...fullContentSummaries[0], source_local_id: rid },
174+
],
175+
resourcesOverride: [{ space_id: 20, source_local_id: rid }],
176+
})[0]?.sourceNodeRid,
177+
).toBe(rid);
178+
});
179+
137180
it("sorts newest nodes first", () => {
138181
const olderConcept = {
139182
...concepts[0],
140-
last_modified: "2026-06-10T12:00:00.000Z",
183+
last_modified: "2026-06-10T12:00:00",
184+
source_local_id: "node-2",
185+
};
186+
const olderDirectContent = {
187+
...directContents[0],
188+
last_modified: "2026-06-10T12:00:00",
141189
source_local_id: "node-2",
190+
text: "Older shared node",
142191
};
143-
const olderContents = contents.map((content) => ({
144-
...content,
145-
last_modified: "2026-06-10T12:00:00.000Z",
192+
const olderFullContentSummary = {
193+
...fullContentSummaries[0],
194+
last_modified: "2026-06-10T12:00:00",
146195
source_local_id: "node-2",
147-
text:
148-
content.variant === "direct"
149-
? "Older shared node"
150-
: "# Older shared node",
151-
}));
196+
};
152197
expect(
153198
build({
154199
conceptsOverride: [olderConcept, concepts[0]],
155-
contentsOverride: [...olderContents, ...contents],
200+
directContentsOverride: [olderDirectContent, ...directContents],
201+
fullContentSummariesOverride: [
202+
olderFullContentSummary,
203+
...fullContentSummaries,
204+
],
156205
resourcesOverride: [
157206
...resources,
158207
{ space_id: 20, source_local_id: "node-2" },

0 commit comments

Comments
 (0)