Skip to content

Make the Tauri frontend handle workspace file links and views properly#1434

Closed
senamakel wants to merge 5 commits into
mainfrom
jules-7537295491042602194-472f8276
Closed

Make the Tauri frontend handle workspace file links and views properly#1434
senamakel wants to merge 5 commits into
mainfrom
jules-7537295491042602194-472f8276

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented May 9, 2026

This PR makes the Tauri frontend properly handle workspace file links and views. It achieves this by:

  1. Adding new Tauri commands open_workspace_path, reveal_workspace_path, and read_workspace_file_string in a new module workspace.rs.
  2. Exposing those capabilities to the frontend securely with path traversal checks via a new workspaceLinks.ts utility.
  3. Updating the markdown renderer in chat (AgentMessageBubble.tsx) to allow intentional local workspace links rather than rejecting them.
  4. Integrating these features into the Memory Workspace component and graph, replacing bespoke Obsidian-only schemes with native workspace file interactions.

PR created automatically by Jules for task 7537295491042602194 started by @senamakel

Summary by CodeRabbit

  • New Features
    • Added workspace linking functionality enabling users to open, reveal, and read files directly within the app workspace.
    • Integrated workspace link support in markdown-rendered content.
    • Added "Open Folder" button to reveal workspace directory in file explorer.
    • Summary nodes now open workspace files directly with safe file-type validation.

Review Change Stack

This commit introduces a unified workspace-link and workspace-file interaction layer for the desktop app. It adds three distinct actions for local workspace paths: open, reveal, and preview.
- It adds new Tauri commands `open_workspace_path`, `reveal_workspace_path`, and `read_workspace_file_string` in a new module `workspace.rs`.
- Extends the conversations renderer so it can recognize workspace-local link patterns instead of only `http/https/mailto`.
- Implements those capabilities into the Memory Workspace graph so summary nodes open natively without bouncing through custom links.
@senamakel senamakel requested a review from a team May 9, 2026 22:21
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

📝 Walkthrough

Walkthrough

This PR adds desktop-app workspace-file integration by implementing three Tauri commands (open, reveal, read), frontend TypeScript utilities for workspace-link detection, and integrating workspace-link handling into MemoryGraph, MemoryWorkspace, and AgentMessageBubble components with full test coverage.

Changes

Workspace Link Feature

Layer / File(s) Summary
Tauri Path Validation & Commands
app/src-tauri/src/workspace.rs
Workspace root resolution from data directory, path-traversal validation, canonicalization with fallback, and three Tauri commands that spawn OS-specific open/reveal/read behavior with error handling.
Tauri Module Wiring
app/src-tauri/src/lib.rs
Declares workspace module and registers three command handlers in invoke_handler.
Frontend Workspace Link Utilities
app/src/utils/workspaceLinks.ts
Exports async wrappers for Tauri commands when available; synchronous helpers detect workspace links (file URLs, absolute/relative paths) and extract paths.
MemoryGraph Summary Integration
app/src/components/intelligence/MemoryGraph.tsx
Summary-node clicks now open markdown files in workspace via openSummaryInWorkspace instead of Obsidian deep links; exports openSummaryInObsidian for reuse.
MemoryWorkspace Folder Button
app/src/components/intelligence/MemoryWorkspace.tsx
Adds "Open Folder" button with SVG icon alongside existing Obsidian button; calls revealVaultInWorkspace via workspace utilities.
AgentMessageBubble Markdown Links
app/src/pages/conversations/components/AgentMessageBubble.tsx
Anchor click handlers detect workspace links, extract paths, and open via workspace utilities; non-workspace links fall back to external URL allowlist.
Frontend Workspace Utilities Tests
app/src/utils/__tests__/workspaceLinks.test.ts
Mocks Tauri environment, verifies link detection, path extraction, and wrapper function dispatch.
MemoryGraph Integration Tests
app/src/components/intelligence/__tests__/MemoryGraph.test.tsx
Mocks workspace and URL openers, verifies summary-node clicks call correct opener with expected paths, covers error handling and edge cases.
MemoryWorkspace Integration Tests
app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx
Mocks workspace opener, verifies folder-button action and summary-node workspace opening with correct absolute paths, covers error handling.
AgentMessageBubble Integration Tests
app/src/pages/conversations/components/__tests__/AgentMessageBubble.test.tsx
Mocks workspace and URL openers; verifies workspace links trigger workspace opener, external links trigger URL opener, and error handling works correctly.
Documentation
commit_message.txt
Describes new workspace-link layer, Tauri commands, frontend utilities, and Memory Workspace integration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • tinyhumansai/openhuman#1402: The PR directly implements the requested workspace-link interaction layer with three new Tauri commands and frontend utilities integrated into MemoryGraph, MemoryWorkspace, and AgentMessageBubble components.

Possibly related PRs

  • tinyhumansai/openhuman#1031: Both PRs modify the MemoryWorkspace component to add UI actions and imports, so the changes may overlap or interact.

Poem

🐰 Workspace paths now leap and bound,
From Tauri roots to folders found,
No Obsidian detours needed here—
Markdown files open crystal clear!
A workspace link in every byte.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding native workspace file link and view handling to the Tauri frontend across multiple components and utilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@senamakel
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 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.

@google-labs-jules
Copy link
Copy Markdown
Contributor

@coderabbitai review

Acknowledged.

This commit introduces a unified workspace-link and workspace-file interaction layer for the desktop app. It adds three distinct actions for local workspace paths: open, reveal, and preview.
- It adds new Tauri commands `open_workspace_path`, `reveal_workspace_path`, and `read_workspace_file_string` in a new module `workspace.rs`.
- Extends the conversations renderer so it can recognize workspace-local link patterns instead of only `http/https/mailto`.
- Implements those capabilities into the Memory Workspace graph so summary nodes open natively without bouncing through custom links.
Copy link
Copy Markdown
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/pages/conversations/components/AgentMessageBubble.tsx (1)

67-83: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Please add tests for both workspace-link and external-link branches (both renderers).

These click handlers now contain new routing logic in two places. Please cover at least: (1) file/workspace link → openWorkspacePath, (2) allowed external link → openUrl, (3) disallowed link → no launch.

As per coding guidelines: “Co-locate unit tests as *.test.tsx under app/src/**” and “add tests for new/changed lines (happy path + at least one failure/edge case).”

Also applies to: 103-119

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/pages/conversations/components/AgentMessageBubble.tsx` around lines
67 - 83, Add a co-located test file AgentMessageBubble.test.tsx under
app/src/pages/conversations/components that renders the AgentMessageBubble (both
renderer variants referenced in the diff) and asserts the three behaviors:
clicking a workspace link (mock isWorkspaceLink/getWorkspacePathFromHref to
return true/path) calls openWorkspacePath with the expected path; clicking an
allowed external link (mock isAllowedExternalHref to true) calls openUrl with
the href; and clicking a disallowed link (mock isAllowedExternalHref to false
and isWorkspaceLink false) does not call openUrl or openWorkspacePath. Mock/stub
openWorkspacePath and openUrl to return rejected/resolved promises as needed and
use user-event or fireEvent to click the link node, then assert calls and that
no launcher errors are thrown.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src-tauri/src/workspace.rs`:
- Around line 9-33: Add unit tests for resolve_and_validate that exercise
traversal and boundary cases: create tests that (1) pass a relative path
containing ".." and assert Err("Path traversal denied"), (2) pass an absolute
path outside the workspace root and assert Err("Path is outside the workspace"),
(3) create a symlink inside the workspace that points outside and assert
canonicalization rejects it (Err), and (4) verify valid relative and absolute
paths inside the workspace return Ok canonical target. Use temporary directories
and filesystem operations (creating files, dirs, and symlinks) and call
resolve_and_validate and get_workspace_root to set up the workspace root stubs
as needed so both the path-normalization branch (Path::is_absolute) and the
canonicalize unwrap_or branches are covered.
- Around line 99-104: The function that reads text previews (e.g.,
read_workspace_file_string) currently calls std::fs::read_to_string(&resolved)
which can load arbitrarily large files; instead, before reading, check the file
size via std::fs::metadata(&resolved).len() and reject or truncate files above a
defined limit (e.g., 1MB) or open the file and read only the first N bytes using
a reader with take() (File::open + BufReader::new(...).take(MAX_PREVIEW_BYTES))
into a String/Vec<u8>, returning a clear error or a truncated preview; update
the match arm (extensions "md"|"txt"|"json"|"csv"|"log") to perform this bounded
read using resolved and propagate errors as strings consistent with the existing
map_err(|e| e.to_string()) behavior.

In `@app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx`:
- Around line 138-147: Add a test in MemoryWorkspace.test.tsx that renders
<MemoryWorkspace />, finds and clicks the "memory-reveal-workspace" button
(query by test id 'memory-reveal-workspace' or the rendered button), and asserts
that openWorkspacePath was called with the graph.content_root_abs value; locate
the test near the existing 'clicking a summary node opens that file in
workspace' case and reuse the mocked graph data and openWorkspacePath spy so the
new test verifies openWorkspacePath(graph.content_root_abs) is invoked when the
button is clicked.

In `@app/src/utils/workspaceLinks.ts`:
- Around line 26-34: isWorkspaceLink currently treats any string without '://'
as a workspace link, which incorrectly classifies mailto:, tel:, fragment links
(#...), and other protocols; update isWorkspaceLink to only treat absolute
file:// URLs or true filesystem/relative paths as workspace links by: keeping
the URL(file:) branch, and changing the fallback to return true only for paths
that start with '/' or './' or '../' (or otherwise look like relative filesystem
paths) while explicitly excluding strings that start with a scheme like
/^[a-zA-Z][\w+.-]*:/ (e.g., mailto:, tel:, javascript:) and fragments starting
with '#'; reference the isWorkspaceLink function and the href parameter when
making this change.

---

Outside diff comments:
In `@app/src/pages/conversations/components/AgentMessageBubble.tsx`:
- Around line 67-83: Add a co-located test file AgentMessageBubble.test.tsx
under app/src/pages/conversations/components that renders the AgentMessageBubble
(both renderer variants referenced in the diff) and asserts the three behaviors:
clicking a workspace link (mock isWorkspaceLink/getWorkspacePathFromHref to
return true/path) calls openWorkspacePath with the expected path; clicking an
allowed external link (mock isAllowedExternalHref to true) calls openUrl with
the href; and clicking a disallowed link (mock isAllowedExternalHref to false
and isWorkspaceLink false) does not call openUrl or openWorkspacePath. Mock/stub
openWorkspacePath and openUrl to return rejected/resolved promises as needed and
use user-event or fireEvent to click the link node, then assert calls and that
no launcher errors are thrown.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48c4cc16-7111-4619-b7c9-6b0c6ab075ea

📥 Commits

Reviewing files that changed from the base of the PR and between 3c2e62f and 5cb8d06.

⛔ Files ignored due to path filters (1)
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • app/src-tauri/src/lib.rs
  • app/src-tauri/src/workspace.rs
  • app/src/components/intelligence/MemoryGraph.tsx
  • app/src/components/intelligence/MemoryWorkspace.tsx
  • app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx
  • app/src/pages/conversations/components/AgentMessageBubble.tsx
  • app/src/utils/workspaceLinks.ts
  • commit_message.txt

Comment on lines +9 to +33
fn resolve_and_validate(path: &str) -> Result<PathBuf, String> {
let root = get_workspace_root();

// Support both relative paths and absolute paths that are inside the root
let target = if Path::new(path).is_absolute() {
PathBuf::from(path)
} else {
root.join(path)
};

// Prevent traversing outside workspace via ..
if path.contains("..") {
return Err("Path traversal denied".into());
}

// Attempt to canonicalize
let canonical_root = root.canonicalize().unwrap_or(root.clone());
let canonical_target = target.canonicalize().unwrap_or(target.clone());

if !canonical_target.starts_with(&canonical_root) {
return Err("Path is outside the workspace".into());
}

Ok(canonical_target)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add unit tests for resolve_and_validate boundary/security cases.

This resolver is the trust boundary for all three workspace commands, but there’s no test coverage here for traversal attempts, absolute outside-root paths, and symlink escapes.

As per coding guidelines: “PRs must meet ≥ 80% coverage on changed lines … add tests for new/changed lines” and “Ship enough unit tests and coverage for the behavior you are adding or changing before building additional features on top of it.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src-tauri/src/workspace.rs` around lines 9 - 33, Add unit tests for
resolve_and_validate that exercise traversal and boundary cases: create tests
that (1) pass a relative path containing ".." and assert Err("Path traversal
denied"), (2) pass an absolute path outside the workspace root and assert
Err("Path is outside the workspace"), (3) create a symlink inside the workspace
that points outside and assert canonicalization rejects it (Err), and (4) verify
valid relative and absolute paths inside the workspace return Ok canonical
target. Use temporary directories and filesystem operations (creating files,
dirs, and symlinks) and call resolve_and_validate and get_workspace_root to set
up the workspace root stubs as needed so both the path-normalization branch
(Path::is_absolute) and the canonicalize unwrap_or branches are covered.

Comment on lines +99 to +104
// Optionally check if file is text/markdown
let ext = resolved.extension().and_then(|s| s.to_str()).unwrap_or("");
match ext.to_lowercase().as_str() {
"md" | "txt" | "json" | "csv" | "log" => {
std::fs::read_to_string(&resolved).map_err(|e| e.to_string())
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bound the preview file size before read_to_string.

read_workspace_file_string currently reads the entire file into memory. A large .log/.csv can trigger avoidable memory pressure in the shell process.

💡 Suggested patch
 pub fn read_workspace_file_string(path: String) -> Result<String, String> {
     let resolved = resolve_and_validate(&path)?;
@@
     if !resolved.is_file() {
         return Err("Path is not a file".into());
     }
+
+    const MAX_PREVIEW_BYTES: u64 = 1024 * 1024; // 1 MiB
+    let meta = std::fs::metadata(&resolved).map_err(|e| e.to_string())?;
+    if meta.len() > MAX_PREVIEW_BYTES {
+        return Err(format!(
+            "File is too large for preview ({} bytes > {} bytes)",
+            meta.len(),
+            MAX_PREVIEW_BYTES
+        ));
+    }
@@
         "md" | "txt" | "json" | "csv" | "log" => {
             std::fs::read_to_string(&resolved).map_err(|e| e.to_string())
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src-tauri/src/workspace.rs` around lines 99 - 104, The function that
reads text previews (e.g., read_workspace_file_string) currently calls
std::fs::read_to_string(&resolved) which can load arbitrarily large files;
instead, before reading, check the file size via
std::fs::metadata(&resolved).len() and reject or truncate files above a defined
limit (e.g., 1MB) or open the file and read only the first N bytes using a
reader with take() (File::open + BufReader::new(...).take(MAX_PREVIEW_BYTES))
into a String/Vec<u8>, returning a clear error or a truncated preview; update
the match arm (extensions "md"|"txt"|"json"|"csv"|"log") to perform this bounded
read using resolved and propagate errors as strings consistent with the existing
map_err(|e| e.to_string()) behavior.

Comment thread app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx
Comment on lines +26 to +34
export function isWorkspaceLink(href: string): boolean {
try {
const url = new URL(href);
return url.protocol === 'file:';
} catch {
// If it's not a valid URL (e.g., relative path like /wiki/summaries or wiki/summaries),
// it's a potential workspace link
return href.startsWith('/') || !href.includes('://');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

isWorkspaceLink is over-broad and hijacks non-file links.

Line 33 (!href.includes('://')) classifies mailto:, tel:, and #fragment as workspace links. That breaks expected link behavior in chat/table markdown.

💡 Suggested patch
 export function isWorkspaceLink(href: string): boolean {
+  const trimmed = href.trim();
+  if (!trimmed) return false;
+
   try {
-    const url = new URL(href);
+    const url = new URL(trimmed);
     return url.protocol === 'file:';
   } catch {
-    // If it's not a valid URL (e.g., relative path like /wiki/summaries or wiki/summaries),
-    // it's a potential workspace link
-    return href.startsWith('/') || !href.includes('://');
+    // Reject non-file URI schemes like mailto:, tel:, data:, etc.
+    if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(trimmed)) return false;
+    // Accept likely local paths.
+    return (
+      trimmed.startsWith('/') ||
+      trimmed.startsWith('./') ||
+      trimmed.startsWith('../')
+    );
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isWorkspaceLink(href: string): boolean {
try {
const url = new URL(href);
return url.protocol === 'file:';
} catch {
// If it's not a valid URL (e.g., relative path like /wiki/summaries or wiki/summaries),
// it's a potential workspace link
return href.startsWith('/') || !href.includes('://');
}
export function isWorkspaceLink(href: string): boolean {
const trimmed = href.trim();
if (!trimmed) return false;
try {
const url = new URL(trimmed);
return url.protocol === 'file:';
} catch {
// Reject non-file URI schemes like mailto:, tel:, data:, etc.
if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(trimmed)) return false;
// Accept likely local paths.
return (
trimmed.startsWith('/') ||
trimmed.startsWith('./') ||
trimmed.startsWith('../')
);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/utils/workspaceLinks.ts` around lines 26 - 34, isWorkspaceLink
currently treats any string without '://' as a workspace link, which incorrectly
classifies mailto:, tel:, fragment links (#...), and other protocols; update
isWorkspaceLink to only treat absolute file:// URLs or true filesystem/relative
paths as workspace links by: keeping the URL(file:) branch, and changing the
fallback to return true only for paths that start with '/' or './' or '../' (or
otherwise look like relative filesystem paths) while explicitly excluding
strings that start with a scheme like /^[a-zA-Z][\w+.-]*:/ (e.g., mailto:, tel:,
javascript:) and fragments starting with '#'; reference the isWorkspaceLink
function and the href parameter when making this change.

This commit introduces a unified workspace-link and workspace-file interaction layer for the desktop app. It adds three distinct actions for local workspace paths: open, reveal, and preview.
- It adds new Tauri commands `open_workspace_path`, `reveal_workspace_path`, and `read_workspace_file_string` in a new module `workspace.rs`.
- Extends the conversations renderer so it can recognize workspace-local link patterns instead of only `http/https/mailto`.
- Implements those capabilities into the Memory Workspace graph so summary nodes open natively without bouncing through custom links.
@senamakel
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 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
Copy Markdown
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/src/utils/__tests__/workspaceLinks.test.ts (1)

1-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prettier check is failing for this test file.

CI already reports style-check failure here; please run Prettier on this file before merge to unblock the TypeScript check job.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/utils/__tests__/workspaceLinks.test.ts` around lines 1 - 72, Prettier
formatting is failing for this test file; run Prettier to reformat the file
containing tests for isWorkspaceLink, getWorkspacePathFromHref,
openWorkspacePath, revealWorkspacePath and readWorkspaceFileString and then
commit the changes (for example, run your project's prettier command or npx
prettier --write <this test file>), then re-run CI to ensure the
TypeScript/style check passes.
app/src/components/intelligence/__tests__/MemoryGraph.test.tsx (1)

1-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prettier check is failing for this test file.

This file is also reported by CI style checks. Please run Prettier before merge so TypeScript check passes cleanly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/intelligence/__tests__/MemoryGraph.test.tsx` around lines
1 - 41, Prettier formatting is failing for the test file that imports
MemoryGraph and openWorkspacePath; run Prettier to fix the style issues (e.g.
run prettier --write on
app/src/components/intelligence/__tests__/MemoryGraph.test.tsx or your repo-wide
prettier command), then re-run the test/CI; ensure imports, spacing, and
trailing commas match project config so the MemoryGraph test and TypeScript
checks pass and openWorkspacePath mock lines remain intact.
🧹 Nitpick comments (2)
app/src/utils/__tests__/workspaceLinks.test.ts (1)

45-70: ⚡ Quick win

Add non-Tauri edge-case tests for openWorkspacePath and revealWorkspacePath.

Only readWorkspaceFileString currently asserts the non-Tauri path. Please also verify openWorkspacePath/revealWorkspacePath no-op behavior (isTauri=false) and ensure invoke is not called.

Suggested test additions
   describe('Tauri wrappers', () => {
@@
     it('throws error for readWorkspaceFileString if isTauri is false', async () => {
       vi.mocked(isTauri).mockReturnValue(false);
       await expect(readWorkspaceFileString('/test/path')).rejects.toThrow('Workspace file preview is only supported in Tauri.');
     });
+
+    it('openWorkspacePath is a no-op when isTauri is false', async () => {
+      vi.mocked(isTauri).mockReturnValue(false);
+      await openWorkspacePath('/test/path');
+      expect(invoke).not.toHaveBeenCalled();
+    });
+
+    it('revealWorkspacePath is a no-op when isTauri is false', async () => {
+      vi.mocked(isTauri).mockReturnValue(false);
+      await revealWorkspacePath('/test/path');
+      expect(invoke).not.toHaveBeenCalled();
+    });
   });

As per coding guidelines, “PRs must meet ≥ 80% coverage on changed lines ... add tests for new/changed lines (happy path + at least one failure/edge case).”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/utils/__tests__/workspaceLinks.test.ts` around lines 45 - 70, Add
tests that cover the non-Tauri edge cases for openWorkspacePath and
revealWorkspacePath by mocking isTauri to return false, calling each function
(openWorkspacePath('/test/path') and revealWorkspacePath('/test/path')), and
asserting that invoke was not called (expect(invoke).not.toHaveBeenCalled()).
Ensure you use vi.mocked(isTauri).mockReturnValue(false) in those tests and
reset/clear the invoke mock between tests (or in a beforeEach) so previous Tauri
tests don't affect assertions.
app/src/components/intelligence/__tests__/MemoryGraph.test.tsx (1)

12-21: ⚡ Quick win

Avoid as any in the node fixture to preserve type-safety.

as any here can hide contract drift between the test fixture and MemoryGraph props. A typed fixture (e.g., via satisfies) keeps this test behavior-focused while still catching schema breaks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/intelligence/__tests__/MemoryGraph.test.tsx` around lines
12 - 21, Replace the untyped test fixture that uses "as any" with a properly
typed fixture to preserve type-safety: declare the nodes array so it satisfies
the expected MemoryGraph props type (e.g., use TypeScript's "satisfies" with the
MemoryGraphProps node type or use MemoryGraphProps['nodes']) instead of casting
to any, ensuring the shape (id, kind, tree_kind, level, file_basename, label)
matches the MemoryGraph component's prop type (reference the nodes fixture in
MemoryGraph.test.tsx and the MemoryGraph prop/interface name) so future schema
drift is caught by the compiler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/src/components/intelligence/__tests__/MemoryGraph.test.tsx`:
- Around line 1-41: Prettier formatting is failing for the test file that
imports MemoryGraph and openWorkspacePath; run Prettier to fix the style issues
(e.g. run prettier --write on
app/src/components/intelligence/__tests__/MemoryGraph.test.tsx or your repo-wide
prettier command), then re-run the test/CI; ensure imports, spacing, and
trailing commas match project config so the MemoryGraph test and TypeScript
checks pass and openWorkspacePath mock lines remain intact.

In `@app/src/utils/__tests__/workspaceLinks.test.ts`:
- Around line 1-72: Prettier formatting is failing for this test file; run
Prettier to reformat the file containing tests for isWorkspaceLink,
getWorkspacePathFromHref, openWorkspacePath, revealWorkspacePath and
readWorkspaceFileString and then commit the changes (for example, run your
project's prettier command or npx prettier --write <this test file>), then
re-run CI to ensure the TypeScript/style check passes.

---

Nitpick comments:
In `@app/src/components/intelligence/__tests__/MemoryGraph.test.tsx`:
- Around line 12-21: Replace the untyped test fixture that uses "as any" with a
properly typed fixture to preserve type-safety: declare the nodes array so it
satisfies the expected MemoryGraph props type (e.g., use TypeScript's
"satisfies" with the MemoryGraphProps node type or use
MemoryGraphProps['nodes']) instead of casting to any, ensuring the shape (id,
kind, tree_kind, level, file_basename, label) matches the MemoryGraph
component's prop type (reference the nodes fixture in MemoryGraph.test.tsx and
the MemoryGraph prop/interface name) so future schema drift is caught by the
compiler.

In `@app/src/utils/__tests__/workspaceLinks.test.ts`:
- Around line 45-70: Add tests that cover the non-Tauri edge cases for
openWorkspacePath and revealWorkspacePath by mocking isTauri to return false,
calling each function (openWorkspacePath('/test/path') and
revealWorkspacePath('/test/path')), and asserting that invoke was not called
(expect(invoke).not.toHaveBeenCalled()). Ensure you use
vi.mocked(isTauri).mockReturnValue(false) in those tests and reset/clear the
invoke mock between tests (or in a beforeEach) so previous Tauri tests don't
affect assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89045fb3-4a93-4b16-8953-4f2f2d181dbe

📥 Commits

Reviewing files that changed from the base of the PR and between 5cb8d06 and 956ad5c.

📒 Files selected for processing (6)
  • app/src-tauri/src/lib.rs
  • app/src-tauri/src/workspace.rs
  • app/src-tauri/src/workspace_tests.rs
  • app/src/components/intelligence/__tests__/MemoryGraph.test.tsx
  • app/src/pages/conversations/components/__tests__/AgentMessageBubble.test.tsx
  • app/src/utils/__tests__/workspaceLinks.test.ts
✅ Files skipped from review due to trivial changes (2)
  • app/src-tauri/src/workspace_tests.rs
  • app/src/pages/conversations/components/tests/AgentMessageBubble.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src-tauri/src/lib.rs
  • app/src-tauri/src/workspace.rs

senamakel added 2 commits May 10, 2026 00:40
- Add tests for `MemoryGraph.tsx`, `MemoryWorkspace.tsx`, `workspaceLinks.ts` and `AgentMessageBubble.tsx`.
- Cover missing `!isTauri()` checks.
- Cover promise `.catch()` handlers for missing edge cases.
- Bypass markdown anchor rewrites via mocked protocols for bubbling clicks correctly in tests.
Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
app/src/components/intelligence/MemoryGraph.tsx (1)

166-182: 💤 Low value

Consider extracting shared path-building logic to reduce duplication.

openSummaryInWorkspace and openSummaryInObsidian share identical guard clauses (lines 143-145 vs 167-169) and relative path construction (lines 152-156 vs 171-175). Extracting a helper like getSummaryRelPath(node) would reduce duplication and ensure consistency.

♻️ Optional: Extract shared path helper
+function getSummaryRelPath(node: GraphNode): string | null {
+  if (node.kind !== 'summary' || !node.tree_kind || node.level == null || !node.file_basename) {
+    return null;
+  }
+  const slug = slugify(node.tree_scope ?? '');
+  return node.tree_kind === 'global'
+    ? 'wiki/summaries'
+    : `wiki/summaries/${node.tree_kind}-${slug}/L${node.level}/${node.file_basename}.md`;
+}
+
 async function openSummaryInWorkspace(node: GraphNode, contentRootAbs: string): Promise<void> {
-  if (node.kind !== 'summary' || !node.tree_kind || node.level == null || !node.file_basename) {
-    return;
-  }
-  const slug = slugify(node.tree_scope ?? '');
-  const rel =
-    node.tree_kind === 'global'
-      ? `wiki/summaries`
-      : `wiki/summaries/${node.tree_kind}-${slug}/L${node.level}/${node.file_basename}.md`;
+  const rel = getSummaryRelPath(node);
+  if (!rel) return;
   const abs = joinPath(contentRootAbs, rel);
   // ... rest unchanged
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/intelligence/MemoryGraph.tsx` around lines 166 - 182, Both
openSummaryInWorkspace and openSummaryInObsidian repeat the same guard checks
and relative-path construction; extract a single helper (e.g.,
getSummaryRelPath(node): string | null) that performs the guard (returns null if
node is not a valid summary) and returns the relative path string like
`wiki/summaries` or
`wiki/summaries/${node.tree_kind}-${slug}/L${node.level}/${node.file_basename}.md`;
replace the duplicated guard + rel construction in openSummaryInWorkspace and
openSummaryInObsidian to call getSummaryRelPath(node) and bail out if it returns
null, then compute abs/joinPath and call openWorkspacePath/openObsidianPath as
before, ensuring you reuse slugify and maintain existing logging and error
handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src-tauri/src/workspace.rs`:
- Around line 20-22: The current path traversal check uses path.contains("..")
which can be bypassed by encoded or alternate sequences; update the validation
to either (A) percent-decode and normalize the incoming path before checking
(i.e. decode percent-encodings, collapse components, then check for any ".."
segments and reject), or (B) remove the naive contains check and rely solely on
std::fs::canonicalize + starts_with(root) (the existing canonicalize and
starts_with calls) to determine if the resolved path is inside the workspace;
implement one of these approaches and adjust the code around the
path.contains("..") check accordingly (use a decode/normalize helper or remove
the check and keep the canonicalize/starts_with logic).

---

Nitpick comments:
In `@app/src/components/intelligence/MemoryGraph.tsx`:
- Around line 166-182: Both openSummaryInWorkspace and openSummaryInObsidian
repeat the same guard checks and relative-path construction; extract a single
helper (e.g., getSummaryRelPath(node): string | null) that performs the guard
(returns null if node is not a valid summary) and returns the relative path
string like `wiki/summaries` or
`wiki/summaries/${node.tree_kind}-${slug}/L${node.level}/${node.file_basename}.md`;
replace the duplicated guard + rel construction in openSummaryInWorkspace and
openSummaryInObsidian to call getSummaryRelPath(node) and bail out if it returns
null, then compute abs/joinPath and call openWorkspacePath/openObsidianPath as
before, ensuring you reuse slugify and maintain existing logging and error
handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9b86f327-e2d2-486b-9c20-40a1c4302298

📥 Commits

Reviewing files that changed from the base of the PR and between 01cdcee and 5056c1d.

📒 Files selected for processing (8)
  • app/src-tauri/src/lib.rs
  • app/src-tauri/src/workspace.rs
  • app/src/components/intelligence/MemoryGraph.tsx
  • app/src/components/intelligence/__tests__/MemoryGraph.test.tsx
  • app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx
  • app/src/pages/conversations/components/__tests__/AgentMessageBubble.test.tsx
  • app/src/utils/__tests__/workspaceLinks.test.ts
  • commit_message.txt
✅ Files skipped from review due to trivial changes (1)
  • commit_message.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src-tauri/src/lib.rs

Comment on lines +20 to +22
if path.contains("..") {
return Err("Path traversal denied".into());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Path traversal check may be bypassable with encoded or alternate sequences.

The string-based path.contains("..") check doesn't catch URL-encoded sequences like %2e%2e or alternate traversal patterns. While the canonicalize + starts_with check on lines 28-30 provides a secondary defense, consider normalizing the path first or rejecting any non-canonical-looking inputs.

🛡️ Suggested hardening
+    // Reject obviously suspicious patterns before path normalization
+    let decoded = urlencoding::decode(path).unwrap_or(std::borrow::Cow::Borrowed(path));
+    if decoded.contains("..") || path.contains("..") {
         return Err("Path traversal denied".into());
     }

Or rely solely on the canonicalize check and remove the string check to avoid false confidence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src-tauri/src/workspace.rs` around lines 20 - 22, The current path
traversal check uses path.contains("..") which can be bypassed by encoded or
alternate sequences; update the validation to either (A) percent-decode and
normalize the incoming path before checking (i.e. decode percent-encodings,
collapse components, then check for any ".." segments and reject), or (B) remove
the naive contains check and rely solely on std::fs::canonicalize +
starts_with(root) (the existing canonicalize and starts_with calls) to determine
if the resolved path is inside the workspace; implement one of these approaches
and adjust the code around the path.contains("..") check accordingly (use a
decode/normalize helper or remove the check and keep the
canonicalize/starts_with logic).

@senamakel
Copy link
Copy Markdown
Member Author

@jules fix the typecheck issue.

@senamakel senamakel closed this May 12, 2026
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.

1 participant