Skip to content

Conversation

@maparent
Copy link
Collaborator

@maparent maparent commented Jan 14, 2026

https://linear.app/discourse-graphs/issue/ENG-1285/base-user-affordance-to-trigger-node-sharing

https://www.loom.com/share/74b40632fe4946d8b47f4d904424660f

This PR creates the command (affordance) to trigger node sharing, and invokes a minimal publishNode function.
Many things will be added to publishNode later: notably verifying that the schemas are shared, sending the assets.
We are considering factoring out sending the text so it is only sent for published nodes.
But we decided that would be another PR if decided.

Summary by CodeRabbit

  • New Features
    • Added "Publish current node to lab space" command to share a node with your group.
    • Requires sync mode and a previously synced node; prompts to sync if missing.
    • Shows success and error notifications during publish, and treats already-published nodes as non-error.
    • Records the group in the node’s metadata after successful publishing.

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

@linear
Copy link

linear bot commented Jan 14, 2026

@supabase
Copy link

supabase bot commented Jan 14, 2026

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

📝 Walkthrough

Walkthrough

Adds a publishNode utility and a new Obsidian command that validates a node, retrieves the logged-in Supabase client and the user's group, inserts a ContentAccess record for the group's access to the node's content (handling duplicate key), and updates the file frontmatter to record the published group.

Changes

Cohort / File(s) Summary
New Publishing Utility
apps/obsidian/src/utils/publishNode.ts
Adds publishNode(plugin, file, frontmatter): Promise<void> — validates nodeInstanceId, obtains logged-in Supabase client, fetches user group, checks existing publication, resolves content_id by source_local_id/variant, inserts ContentAccess (treats duplicate key 23505 as non-error), updates publishedToGroups in frontmatter, and persists changes. Includes error handling for missing IDs, client, group, and missing content.
Command Integration
apps/obsidian/src/utils/registerCommands.ts
Imports publishNode and MarkdownView, registers publish-discourse-node command that enforces sync mode, requires an active MarkdownView and frontmatter.nodeTypeId, ensures nodeInstanceId exists (otherwise shows Notice to sync), invokes publishNode, and displays success/error Notices.

Sequence Diagram

sequenceDiagram
    participant User
    participant Plugin as Obsidian Plugin
    participant File as File System
    participant Supabase as Supabase Client
    participant DB as Database

    User->>Plugin: Trigger publish command
    Plugin->>File: Read file & frontmatter
    Plugin->>Plugin: Validate nodeInstanceId
    Plugin->>Supabase: Get logged-in client
    Supabase-->>Plugin: Client instance
    Plugin->>DB: Query user's group
    DB-->>Plugin: Group data
    Plugin->>DB: Check existing ContentAccess / fetch content_id by source_local_id
    DB-->>Plugin: Content/Access data
    Plugin->>DB: Insert ContentAccess (handle 23505 duplicate)
    DB-->>Plugin: Insert result
    Plugin->>File: Update frontmatter.publishedToGroups and save
    Plugin->>User: Show success or error Notice
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly references the main objective (ENG-1285) and accurately describes the primary change: adding a base user affordance (command) to trigger node sharing to groups.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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: 1

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/registerCommands.ts`:
- Around line 181-182: The synchronous throw of new Error("Please sync the node
first") inside the checkCallback should be removed to avoid unhandled
exceptions; rely on publishNode's existing validation (which returns a rejected
Promise and is handled by .catch()), so delete the guard that checks
frontmatter.nodeInstanceId (or replace it with a user-facing Notice via new
Notice("Please sync the node first") if you prefer non-exception feedback) in
the checkCallback surrounding frontmatter.nodeInstanceId and keep publishNode as
the source of truth for validation.
🧹 Nitpick comments (3)
apps/obsidian/src/utils/publishNode.ts (2)

20-22: Redundant truthy check on existingPublish.

existingPublish is guaranteed to be an array (due to || [] on line 21), so the truthy check is unnecessary.

Suggested simplification
   const existingPublish =
     (frontmatter.publishedToGroups as undefined | string[]) || [];
-  if (existingPublish && existingPublish.includes(myGroup)) return; // already published
+  if (existingPublish.includes(myGroup)) return; // already published

42-48: Mutating the frontmatter-derived array.

existingPublish.push(myGroup) mutates the array that may be referenced from the original frontmatter cache. While this works, creating a new array is safer and more explicit.

Suggested change
-  existingPublish.push(myGroup);
   await plugin.app.fileManager.processFrontMatter(
     file,
     (fm: Record<string, unknown>) => {
-      fm.publishedToGroups = existingPublish;
+      fm.publishedToGroups = [...existingPublish, myGroup];
     },
   );
apps/obsidian/src/utils/registerCommands.ts (1)

170-173: Inconsistent return values in checkCallback.

When validation fails inside if (!checking), the bare return; yields undefined. For consistency with other commands (e.g., lines 140-157), consider returning true to indicate the command is still available, or false if it should be hidden when conditions aren't met.

Suggested change for consistency
         if (!activeView || !activeView.file) {
           new Notice("Apply to a discourse node");
-          return;
+          return true;
         }
         const file = activeView.file;
         const cache = plugin.app.metadataCache.getFileCache(file);
         const frontmatter = cache?.frontmatter || {};
         if (!frontmatter.nodeTypeId) {
           new Notice("Apply to a discourse node");
-          return;
+          return true;
         }

Also applies to: 177-180

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25d8e3a and 47e6b83.

📒 Files selected for processing (2)
  • apps/obsidian/src/utils/publishNode.ts
  • apps/obsidian/src/utils/registerCommands.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/main.mdc)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Prefer type over interface in TypeScript
Use explicit return types for functions
Avoid any types when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Function names should describe their purpose clearly
Prefer early returns over nested conditionals for better readability

Files:

  • apps/obsidian/src/utils/publishNode.ts
  • apps/obsidian/src/utils/registerCommands.ts
apps/obsidian/**

📄 CodeRabbit inference engine (.cursor/rules/obsidian.mdc)

apps/obsidian/**: Prefer existing dependencies from apps/obsidian/package.json when adding dependencies to the Obsidian plugin
Follow the Obsidian style guide from help.obsidian.md/style-guide and docs.obsidian.md/Developer+policies for UI and code styling
Use Lucide and custom Obsidian icons alongside detailed elements to provide visual representation of features in platform-native UI

Files:

  • apps/obsidian/src/utils/publishNode.ts
  • apps/obsidian/src/utils/registerCommands.ts
🧬 Code graph analysis (1)
apps/obsidian/src/utils/registerCommands.ts (1)
apps/obsidian/src/utils/publishNode.ts (1)
  • publishNode (5-49)
🔇 Additional comments (4)
apps/obsidian/src/utils/publishNode.ts (2)

1-3: LGTM on imports.

Imports are appropriately typed and follow the project conventions.


14-19: The code silently publishes to the first group only; consider whether users with multiple group memberships should publish to a specific group or all groups.

The query fetches all group memberships (select("group_id")), but the code uses only the first one (data[0]?.group_id). If users can belong to multiple groups, this may silently ignore other memberships when publishing. The function doesn't accept a group parameter, so there's no way to publish to a different group.

apps/obsidian/src/utils/registerCommands.ts (2)

1-1: LGTM on imports.

Clean consolidation of obsidian imports and proper addition of publishNode import.

Also applies to: 10-10


187-194: LGTM on async handling pattern.

The .then()/.catch() pattern correctly handles async execution in a synchronous callback context, consistent with the existing sync command (lines 145-154).

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 1

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/publishNode.ts`:
- Around line 14-19: The code in publishNode.ts currently takes only the first
membership by reading myGroupResponse.data[0]?.group_id, which ignores multiple
group memberships and conflicts with the publishedToGroups frontmatter; update
the logic to handle multiple groups by either filtering the query (e.g., add a
WHERE to pick a specific group based on publishedToGroups or a chosenGroup ID),
or iterate over myGroupResponse.data to collect all group_ids and use that array
when publishing (replace use of myGroup with a multi-group variable), or
explicitly document and enforce a single-group limitation if multi-group
publishing is not supported; adjust error handling around myGroupResponse and
the downstream publish calls to work with the chosen multi/single-group
approach.
🧹 Nitpick comments (2)
apps/obsidian/src/utils/publishNode.ts (1)

20-22: Potential stale data issue with existingPublish.

existingPublish is captured at line 21, but processFrontMatter on line 42 executes later after async operations. If another process modifies publishedToGroups concurrently, those changes could be overwritten.

Consider reading the current state inside processFrontMatter:

♻️ Suggested fix to read fresh state inside processFrontMatter
   await plugin.app.fileManager.processFrontMatter(
     file,
     (fm: Record<string, unknown>) => {
-      fm.publishedToGroups = [...existingPublish, myGroup];
+      const current = (fm.publishedToGroups as string[] | undefined) || [];
+      if (!current.includes(myGroup)) {
+        fm.publishedToGroups = [...current, myGroup];
+      }
     },
   );

Also applies to: 42-47

apps/obsidian/src/utils/registerCommands.ts (1)

177-196: Appropriate handling of missing nodeInstanceId.

The previous review concern about synchronous throw has been addressed—the code now shows a Notice and returns true, providing user feedback while keeping the command available.

Minor typing note: Line 190 types the error as Error, but publishNode can also throw PostgrestError from Supabase responses (lines 17, 30, 41 in publishNode.ts). Consider using unknown and checking the type:

♻️ Safer error handling
         publishNode(plugin, file, frontmatter)
           .then(() => {
             new Notice("Published");
           })
-          .catch((error: Error) => {
-            new Notice(error.message);
+          .catch((error: unknown) => {
+            const message = error instanceof Error ? error.message : String(error);
+            new Notice(message);
             console.error(error);
           });
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47e6b83 and 38094bc.

📒 Files selected for processing (2)
  • apps/obsidian/src/utils/publishNode.ts
  • apps/obsidian/src/utils/registerCommands.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/main.mdc)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Prefer type over interface in TypeScript
Use explicit return types for functions
Avoid any types when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Function names should describe their purpose clearly
Prefer early returns over nested conditionals for better readability

Files:

  • apps/obsidian/src/utils/publishNode.ts
  • apps/obsidian/src/utils/registerCommands.ts
apps/obsidian/**

📄 CodeRabbit inference engine (.cursor/rules/obsidian.mdc)

apps/obsidian/**: Prefer existing dependencies from apps/obsidian/package.json when adding dependencies to the Obsidian plugin
Follow the Obsidian style guide from help.obsidian.md/style-guide and docs.obsidian.md/Developer+policies for UI and code styling
Use Lucide and custom Obsidian icons alongside detailed elements to provide visual representation of features in platform-native UI

Files:

  • apps/obsidian/src/utils/publishNode.ts
  • apps/obsidian/src/utils/registerCommands.ts
🧠 Learnings (1)
📚 Learning: 2025-10-18T18:58:16.100Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 504
File: apps/roam/src/utils/syncDgNodesToSupabase.ts:523-531
Timestamp: 2025-10-18T18:58:16.100Z
Learning: In `apps/roam/src/utils/syncDgNodesToSupabase.ts`, partial successes from `upsertNodesToSupabaseAsContent` and `addMissingEmbeddings` (indicated by numeric return values showing the count of successful operations) should NOT trigger backoff. Only complete failures (false) should trigger the exponential backoff mechanism. This design allows the sync process to continue making progress even when some items fail.

Applied to files:

  • apps/obsidian/src/utils/registerCommands.ts
🧬 Code graph analysis (1)
apps/obsidian/src/utils/registerCommands.ts (1)
apps/obsidian/src/utils/publishNode.ts (1)
  • publishNode (5-48)
🔇 Additional comments (4)
apps/obsidian/src/utils/publishNode.ts (2)

1-9: LGTM on function structure and signature.

The function follows coding guidelines: arrow function, explicit return type, type imports, and clear parameter naming.


33-41: Good handling of duplicate key constraint.

Treating PostgreSQL error code 23505 (unique violation) as success is appropriate for idempotent publish operations.

apps/obsidian/src/utils/registerCommands.ts (2)

1-1: LGTM on imports.

Import additions are appropriate for the new command functionality.

Also applies to: 10-10


159-176: Good use of checkCallback pattern.

The early returns for sync mode, active view, and nodeTypeId checks follow the established pattern from other commands in this file.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@maparent
Copy link
Collaborator Author

@CodeRabbit: PostgrestError extends Error, so typing is correct.
As for the async comment... I do not know of any other function that will affect this variable, nor is any planned.

@maparent maparent requested a review from trangdoan982 January 14, 2026 22:19
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Tip

For best results, initiate chat on the files or code changes.

@maparent: Thank you for the clarifications!

You're right about PostgrestError extending Error — the typing is indeed correct as-is.

And I understand regarding the async concern — if no concurrent operations are planned that would affect that variable, then the current implementation is fine.

I appreciate you taking the time to explain these points!


🧠 Learnings used
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in this codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-30T17:03:26.927Z
Learning: For maparent: Prefers to handle retries downstream in Vercel functions rather than at the source level, following separation of concerns principle.

import { getLoggedInClient } from "./supabaseContext";

export const publishNode = async (
plugin: DiscourseGraphPlugin,
Copy link
Collaborator

Choose a reason for hiding this comment

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

best to have parameter function here

@trangdoan982
Copy link
Collaborator

lgtm, just a nit

@maparent maparent force-pushed the eng-1285-base-user-affordance-to-trigger-node-sharing branch from 621dc18 to 8d68f2c Compare January 15, 2026 15:44
@maparent maparent requested a review from mdroidian January 15, 2026 15:58
@maparent maparent force-pushed the eng-1285-base-user-affordance-to-trigger-node-sharing branch from d34f8dd to a45aadf Compare January 18, 2026 14:52
@maparent maparent changed the base branch from main to eng-1311-unify-conceptaccess-and-contentaccess-tables January 18, 2026 16:04
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.

3 participants