Skip to content

[ENG-1295 & ENG-1350 & ENG-1297] F5 import and refresh published nodes#722

Merged
trangdoan982 merged 33 commits intomainfrom
eng-1295-f5-import-and-refresh-published-nodes
Feb 11, 2026
Merged

[ENG-1295 & ENG-1350 & ENG-1297] F5 import and refresh published nodes#722
trangdoan982 merged 33 commits intomainfrom
eng-1295-f5-import-and-refresh-published-nodes

Conversation

@trangdoan982
Copy link
Collaborator

@trangdoan982 trangdoan982 commented Jan 23, 2026

https://www.loom.com/share/410fb6cdb4aa4e9ba1a6a4058a69bb19

https://www.loom.com/share/69ae6c6ba43d4856b1adc1ab8ff4061e

https://www.loom.com/share/b02693ddf5cc4a9cba0aaf97986080c3

Summary by CodeRabbit

Release Notes

  • New Features

    • Import nodes from other spaces and groups with a guided selection interface
    • Refresh imported node content individually or all at once to sync with source
    • Refresh button added to individual imported files for on-demand updates
  • Improvements

    • Imported files are now properly excluded from local sync operations

✏️ Tip: You can customize this high-level summary in your review settings.


Open with Devin

@linear
Copy link

linear bot commented Jan 23, 2026

@trangdoan982
Copy link
Collaborator Author

@CodeRabbit full review

@supabase
Copy link

supabase bot commented Jan 23, 2026

Updates to Preview Branch (eng-1295-f5-import-and-refresh-published-nodes) ↗︎

Deployments Status Updated
Database Sun, 08 Feb 2026 14:54:10 UTC
Services Sun, 08 Feb 2026 14:54:10 UTC
APIs Sun, 08 Feb 2026 14:54:10 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Sun, 08 Feb 2026 14:54:10 UTC
Migrations Sun, 08 Feb 2026 14:54:11 UTC
Seeding ⏸️ Sun, 08 Feb 2026 14:54:04 UTC
Edge Functions ⏸️ Sun, 08 Feb 2026 14:54:04 UTC

❌ Branch Error • Sun, 08 Feb 2026 14:54:11 UTC

Remote migration versions not found in local migrations directory.

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

✅ Actions performed

Full review triggered.


if (!this.settings.showIdsInFrontmatter) {
keysToHide.push("nodeTypeId");
keysToHide.push(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trangdoan982 trangdoan982 changed the title [ENG-1295] F5 import and refresh published nodes [ENG-1295 & ENG-1350 & ENG-1297] F5 import and refresh published nodes Jan 23, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

This pull request adds import and refresh functionality for nodes in Obsidian. It introduces a modal UI for importing nodes from other spaces, utilities to fetch and import remote node content, mechanisms to refresh imported files, command registration for user-initiated actions, and database policy changes enabling access through resource-based permissions.

Changes

Cohort / File(s) Summary
Import Modal UI
apps/obsidian/src/components/ImportNodesModal.tsx
New modal component with multi-step UI for selecting and importing nodes; loads importable nodes from groups, displays grouped/space-structured selection interface, validates selections, calls import utility, and shows result notices.
Refresh Button Integration
apps/obsidian/src/components/DiscourseContextView.tsx
Adds refresh button to context view for imported nodes; manages isRefreshing state; calls refreshImportedFile with error/success notices; button disabled during ongoing refresh.
Import and Refresh Core Logic
apps/obsidian/src/utils/importNodes.ts
Comprehensive utilities for fetching groups, published nodes, resolving local node types, and importing nodes into vault; provides single/batch refresh functions to re-fetch and update imported file content.
Schema Publishing
apps/obsidian/src/utils/publishNode.ts
Adds publishSchema function to validate and upsert schema ResourceAccess; integrates schema publishing into publishNode when nodeTypeId exists.
Command Registration
apps/obsidian/src/utils/registerCommands.ts
Registers two new commands: import-nodes-from-another-space (opens modal) and refresh-imported-nodes (refreshes all imported files with user notices).
Import File Filtering
apps/obsidian/src/utils/fileChangeListener.ts, apps/obsidian/src/utils/syncDgNodesToSupabase.ts
Renames isDiscourseNode to shouldSyncFile and adds early returns for markdown/imported-file checks; skips syncing files with importedFromSpaceId frontmatter key.
Types and Configuration
apps/obsidian/src/types.ts, apps/obsidian/src/index.ts
Adds ImportableNode and GroupWithNodes types; expands frontmatter keys hidden when showIdsInFrontmatter is false.
Query Utility
apps/obsidian/src/services/QueryEngine.ts
Adds findExistingImportedFile method to locate imported files via nodeInstanceId and importedFromSpaceId.
Database Policy
packages/database/supabase/migrations/20260123135938_allow_space_view_via_resource_access.sql
New RLS policy allowing Space SELECT access when user has SpaceAccess or ResourceAccess to resources within the space.

Sequence Diagram(s)

sequenceDiagram
    participant User as User / Obsidian
    participant Modal as ImportNodesModal
    participant Supabase as Supabase Client
    participant Vault as Obsidian Vault
    participant DB as Database

    User->>Modal: Open import modal
    Modal->>Supabase: Fetch available groups
    Supabase->>DB: Query groups
    DB-->>Supabase: Return groups
    Supabase-->>Modal: Return groups
    
    Modal->>Supabase: Fetch published nodes for groups
    Supabase->>DB: Query published nodes
    DB-->>Supabase: Return nodes
    Supabase-->>Modal: Return nodes
    
    User->>Modal: Select nodes and confirm
    Modal->>Supabase: Import selected nodes (batch)
    Supabase->>DB: Fetch node content & metadata
    DB-->>Supabase: Return content & metadata
    
    Supabase->>Vault: Create/update files with content
    Vault->>Vault: Write frontmatter (nodeTypeId, importedFromSpaceId)
    Vault-->>Supabase: Confirm file creation
    Supabase-->>Modal: Return import results
    
    Modal-->>User: Display success/failure notice
Loading
sequenceDiagram
    participant User as User / Obsidian
    participant View as DiscourseContextView
    participant Utils as refreshImportedFile
    participant Supabase as Supabase Client
    participant Vault as Obsidian Vault
    participant DB as Database

    User->>View: Click refresh button
    View->>View: Set isRefreshing = true
    View->>Utils: Call refreshImportedFile
    
    Utils->>Supabase: Fetch latest node content
    Supabase->>DB: Query node content
    DB-->>Supabase: Return content
    Supabase-->>Utils: Return content
    
    Utils->>Vault: Update file with new content
    Vault->>Vault: Update frontmatter
    Vault-->>Utils: Confirm update
    
    Utils-->>View: Return success/error result
    View->>View: Set isRefreshing = false
    View-->>User: Display success/failure notice
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and specifically describes the main changes: importing and refreshing published nodes from other spaces, which is the core focus of the entire changeset.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/components/ImportNodesModal.tsx`:
- Line 81: Remove the debug console.log in ImportNodesModal.tsx: locate the
stray console.log("spaceNames", spaceNames); inside the ImportNodesModal
component (or the function that builds/loads spaceNames) and delete that
statement (or replace with a proper logger call if persistent logging is needed)
so no debug output remains in production code.
- Around line 315-332: The progress bar never updates because
importProgress.current is never changed; to fix quickly, replace the progress
bar UI in renderImportingStep with a simple spinner and static "Importing..."
message (remove or stop referencing
importProgress.current/importProgress.total), or alternatively implement an
onProgress callback in importSelectedNodes and wire it to update importProgress
state; update references to renderImportingStep, importProgress, and
importSelectedNodes accordingly so the component either shows a spinner while
importSelectedNodes runs or receives progress updates via the new callback.

In `@apps/obsidian/src/utils/importNodes.ts`:
- Around line 611-622: The code reads frontmatter and blindly casts
frontmatter?.importedFromSpaceId and frontmatter?.nodeInstanceId before calling
getSpaceName and importSelectedNodes; add explicit validation for required
frontmatter fields (importedFromSpaceId and nodeInstanceId) before proceeding in
the refresh path: check that cache?.frontmatter exists and that
importedFromSpaceId is a finite number and nodeInstanceId is a non-empty string,
and if validation fails return a helpful error (e.g., { success: false, error:
"Missing frontmatter: importedFromSpaceId or nodeInstanceId" }) instead of
calling getSpaceName or importSelectedNodes; keep existing calls to getSpaceName
and importSelectedNodes (referenced here) unchanged otherwise.
- Around line 324-331: parseSchemaLiteralContent currently calls JSON.parse on
literalContent when it's a string which will throw on malformed JSON; wrap the
JSON.parse call in a try/catch inside parseSchemaLiteralContent (and when
literalContent is a string) so parse errors are caught, fallback to an empty
object (or safe default), and optionally surface/log the error for debugging;
ensure the rest of the function continues to use that safe obj when JSON parsing
fails while keeping the existing fallbackName behavior.
- Around line 284-300: The current array parsing only checks
valueStr.startsWith("-") so fields like publishedToGroups: with an empty value
are parsed incorrectly; in the importNodes parsing function update the logic
around valueStr/currentLine/nextLineIndex to also treat an empty valueStr as the
start of a YAML block array by checking the subsequent line(s) for lines that
trim-start with "-" and collecting those items into arrayValues; use the same
collection loop (currentLine, nextLineIndex, arrayValues, itemValue) to gather
each "- item" line and then set value = arrayValues (falling back to
[trimmed.slice(1).trim()] only if no block items were found) so
publishedToGroups and other multi-line arrays from processFrontMatter parse as
string[].

In
`@packages/database/supabase/migrations/20260123135938_allow_space_view_via_resource_access.sql`:
- Around line 5-10: The comment incorrectly states column-level security for
ResourceAccess users while the RLS policy's USING clause grants full-row access;
update the comment to state that the policy controls row visibility only (both
SpaceAccess and ResourceAccess can SELECT all columns) or, if you truly need
column-level restriction for ResourceAccess, replace the current approach by
creating a restricted view (e.g., space_name_view) exposing only name and grant
SELECT on that view to ResourceAccess users, or apply column-level GRANT
SELECT(name) on the base table, or expose a SECURITY DEFINER function that
returns only allowed columns; reference the RLS policy USING clause and the
ResourceAccess/SpaceAccess conditions when making the change.
🧹 Nitpick comments (3)
apps/obsidian/src/utils/importNodes.ts (2)

83-107: Consider batching Content queries to avoid N+1 performance issue.

The current implementation makes a separate database query for each node, which could be slow with many nodes. Consider batching these queries.

♻️ Suggested optimization approach
-  for (const { spaceId, sourceLocalId } of spaceIdSourceIdPairs) {
-    const { data: contentData } = await client
-      .from("Content")
-      .select("text")
-      .eq("source_local_id", sourceLocalId)
-      .eq("space_id", spaceId)
-      .eq("variant", "direct")
-      .maybeSingle();
-
-    if (contentData?.text) {
-      const relevantRAs = resourceAccessData.filter(
-        (ra) =>
-          ra.source_local_id === sourceLocalId && ra.space_id === spaceId,
-      );
-
-      for (const ra of relevantRAs) {
-        nodes.push({
-          source_local_id: sourceLocalId,
-          space_id: spaceId,
-          text: contentData.text,
-          account_uid: ra.account_uid,
-        });
-      }
-    }
-  }
+  // Batch fetch all content in a single query using compound filters
+  const { data: allContent } = await client
+    .from("Content")
+    .select("source_local_id, space_id, text")
+    .eq("variant", "direct")
+    .in("source_local_id", spaceIdSourceIdPairs.map(p => p.sourceLocalId))
+    .in("space_id", spaceIdSourceIdPairs.map(p => p.spaceId));
+
+  const contentMap = new Map<string, string>();
+  for (const c of allContent || []) {
+    if (c.text) {
+      contentMap.set(`${c.space_id}:${c.source_local_id}`, c.text);
+    }
+  }
+
+  for (const { spaceId, sourceLocalId } of spaceIdSourceIdPairs) {
+    const text = contentMap.get(`${spaceId}:${sourceLocalId}`);
+    if (text) {
+      const relevantRAs = resourceAccessData.filter(
+        (ra) => ra.source_local_id === sourceLocalId && ra.space_id === spaceId,
+      );
+      for (const ra of relevantRAs) {
+        nodes.push({ source_local_id: sourceLocalId, space_id: spaceId, text, account_uid: ra.account_uid });
+      }
+    }
+  }

Note: Verify that the Supabase query with multiple .in() clauses produces the correct compound filter behavior.


122-127: Simplify redundant conditional check.

The inner if (nodeInstanceId) check is redundant since frontmatter?.nodeInstanceId was already verified as truthy.

♻️ Proposed simplification
     if (frontmatter?.nodeInstanceId) {
-      const nodeInstanceId = frontmatter.nodeInstanceId as string;
-      if (nodeInstanceId) {
-        nodeInstanceIds.add(nodeInstanceId);
-      }
+      nodeInstanceIds.add(frontmatter.nodeInstanceId as string);
     }
apps/obsidian/src/components/ImportNodesModal.tsx (1)

68-69: Simplify redundant Set creation.

getLocalNodeInstanceIds already returns Set<string>, so creating a new Set from it is unnecessary.

♻️ Proposed simplification
       // Get local nodeInstanceIds to filter out existing nodes
       const localNodeInstanceIds = await getLocalNodeInstanceIds(plugin);
-      const localIdsSet = new Set(localNodeInstanceIds);

       // Filter out nodes that already exist locally
       const importableNodes = publishedNodes.filter(
-        (node) => !localIdsSet.has(node.source_local_id),
+        (node) => !localNodeInstanceIds.has(node.source_local_id),
       );

import type { DGSupabaseClient } from "@repo/database/lib/client";

const publishSchema = async ({
client,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View issue and 6 additional flags in Devin Review.

Open in Devin Review

@trangdoan982 trangdoan982 changed the base branch from main to eng-1353-add-resourceaccess-permissions-for-nodes-schemas-when-nodes January 25, 2026 21:29
Base automatically changed from eng-1353-add-resourceaccess-permissions-for-nodes-schemas-when-nodes to main January 26, 2026 16:16
@trangdoan982 trangdoan982 force-pushed the eng-1295-f5-import-and-refresh-published-nodes branch from b9a9410 to 3e5311d Compare January 26, 2026 16:57
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View issue and 10 additional flags in Devin Review.

Open in Devin Review

@trangdoan982 trangdoan982 force-pushed the eng-1295-f5-import-and-refresh-published-nodes branch from 3e5311d to 469ffc2 Compare January 26, 2026 22:54
@trangdoan982 trangdoan982 requested a review from maparent January 26, 2026 22:55
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View issue and 13 additional flags in Devin Review.

Open in Devin Review

@maparent maparent force-pushed the eng-1295-f5-import-and-refresh-published-nodes branch 2 times, most recently from b1a1f77 to 6bd9447 Compare January 27, 2026 19:52
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View issues and 17 additional flags in Devin Review.

Open in Devin Review

Copy link
Collaborator

@maparent maparent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not done, but I hate the idea of you having to wait for all my comments before you can work. I'll keep going.
First: Still very happy with the overall work. Behaviour of happy path is pretty much what we want.
Still looking at edge cases.

Another question I'd like for us to think about: The "imported From" and groupId are using opaque Ids. (The latter is on me!)
It would be nice to have space name and group name in the frontMatter.
Is there a way to store both in the frontmatter that mostly shows the user-friendly name?
Of course we can store both as a map, or an array of pairs, but neither would look good.
We might store something like :name , which is easy to parse, and at least will include the name. What do you think?
Some improvements we can delay to other PRs (I created a task for node templates) but I think we should not store wrong things in the frontmatter if possible (though I already did that with groups!)
(Making the group names available now.)

@maparent maparent force-pushed the eng-1295-f5-import-and-refresh-published-nodes branch from 6bd9447 to bcd7d3b Compare February 5, 2026 15:36
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 7 additional findings in Devin Review.

Open in Devin Review

@trangdoan982 trangdoan982 force-pushed the eng-1295-f5-import-and-refresh-published-nodes branch from 50b4542 to cad2844 Compare February 8, 2026 18:41
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 8 additional findings in Devin Review.

Open in Devin Review

Copy link
Collaborator

@maparent maparent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a quick look at #722 (comment)
I think it's a different scope, but I could be wrong.
Take a decision, I approve either way.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 13 additional findings in Devin Review.

Open in Devin Review

trangdoan982 and others added 2 commits February 11, 2026 14:20
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@trangdoan982 trangdoan982 merged commit 55a74c8 into main Feb 11, 2026
2 of 3 checks passed
@trangdoan982 trangdoan982 deleted the eng-1295-f5-import-and-refresh-published-nodes branch February 11, 2026 22:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants