Skip to content

Conversation

@lefarcen
Copy link
Contributor

@lefarcen lefarcen commented Dec 19, 2025

Summary

Fix voucher sharing permission, sanitize large tool outputs, and refactor drive file processing.

Voucher Service:

  • Fixed claimants (users who claimed voucher via invitation) unable to create sharing invitations
  • Now checks both owner and claimant permissions, consistent with validateVoucher logic

Tool Output Sanitization:

  • Added sanitization for large action results and tool call outputs to prevent UI overflow
  • Improved string byte size estimation for more accurate truncation
  • Implemented internal tool renderers for readFile and listFiles with content truncation

Drive File Processing:

  • Refactored drive.service.ts for unified file access and improved content normalization
  • Added disableTruncation prop to file preview components for shared drive files
  • Fixed file preview content handling in share pages

Cleanup:

  • Removed unused volcengine adapter (~1157 lines)

Impact Areas

  • Backend API (Voucher, Drive, Tool, Action modules)
  • Frontend (File preview, Tool renderers)

Checklist

  • I understand that this PR may be closed in case there was no previous discussion or issues.
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I ran dev/reformat(backend) and cd web && npx lint-staged(frontend) to appease the lint gods

Summary by CodeRabbit

  • New Features

    • Optional sanitization for tool outputs to redact large/sensitive tool results.
    • Unified public/private file access with optional authentication for downloads.
    • Simplified file-placeholder formats and resolved URLs for generated docs/artifacts.
    • New HTML renderer and a purePreview mode for file previews.
  • Behavior Changes

    • Voucher invitation creation now enforces owner/claimer permission; daily popup limit increased.
  • Performance

    • Memoized file decoding/sanitization for faster, more efficient previews.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Walkthrough

Adds optional auth and unified public/private drive access with per-retrieval truncation and normalization; centralizes drive file placeholder replacement; propagates optional sanitization through action/tool-call DTOs and services; and introduces purePreview plus memoization improvements for file-preview renderers.

Changes

Cohort / File(s) Summary
Drive controller & service
apps/api/src/modules/drive/drive.controller.ts, apps/api/src/modules/drive/drive.service.ts
Serve endpoint made optionally authenticated (`User
Action DTOs & sanitization
apps/api/src/modules/action/action.dto.ts, apps/api/src/modules/action/action.controller.ts, apps/api/src/modules/action/action.service.ts, apps/api/src/modules/tool-call/tool-call.service.ts
Added SanitizeOptions and sanitizeToolOutput(toolName, output); toolCallResultPO2DTO, actionStepPO2DTO, actionResultPO2DTO accept options and propagate sanitizeForDisplay; services and attachToolCallsToSteps accept/forward sanitization option and apply conditional sanitization when requested; controller now requests sanitization for display.
Agent-tools placeholder resolution
packages/agent-tools/src/builtin/index.ts
Consolidated placeholder parsing to file-content://df-<id> and file://df-<id> only; added private replaceFilePlaceholders helpers to batch-resolve file IDs via reflyService.createShareForDriveFile and replace placeholders with content/share URLs before writing.
File-preview rendering & props
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/*.tsx, .../types.ts, .../index.tsx, packages/web-core/src/pages/drive-file-share/index.tsx
Move decoding into useMemo across renderers (code/html/markdown/svg/json); add exported HtmlRenderer; add purePreview?: boolean to props and forward to HtmlRenderer; drive-file-share passes purePreview for HTML files.
Frontend small UI & memo tweaks
packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx, packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
Extended memo equality checks to include purePreview/canvasReadOnly; voucher display uses locale-aware formatting (Chinese "折" vs "% OFF").
Voucher subsystem
apps/api/src/modules/voucher/voucher.constants.ts, apps/api/src/modules/voucher/voucher.service.ts
Increased DAILY_POPUP_TRIGGER_LIMIT from 3 to 999; createInvitation now enforces permission checks (only owner or prior claimant) by fetching voucher by id and validating eligibility.
Minor DTO mapping style
apps/api/src/modules/copilot/copilot.dto.ts
Replaced .map(actionResultPO2DTO) with .map((r) => actionResultPO2DTO(r)) (no behavioral change).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant DriveController
    participant DriveService
    participant PublicOSS as Public Storage (OSS)
    participant PrivateStore as Private Storage
    participant Sanitizer as Truncation/Sanitizer

    Client->>DriveController: GET /drive/file/{id} (optional auth)
    DriveController->>DriveService: getUnifiedFileMetadata(fileId, user?)
    alt public metadata exists
        DriveService->>PublicOSS: fetch metadata
        PublicOSS-->>DriveService: metadata (isPublic: true)
    else no public metadata
        DriveService->>PrivateStore: validate ownership & fetch metadata
        PrivateStore-->>DriveService: metadata (isPublic: false)
    end
    DriveController->>DriveService: getUnifiedFileStream(fileId, user?)
    alt fetch from public
        DriveService->>PublicOSS: stream content
        PublicOSS-->>DriveService: data stream
    else fetch from private
        DriveService->>PrivateStore: stream content (owner validated)
        PrivateStore-->>DriveService: data stream
    end
    DriveService->>Sanitizer: truncate/normalize and compute wordCount (if non-public or requested)
    Sanitizer-->>DriveService: processed stream + metadata
    DriveService-->>DriveController: stream + metadata (isPublic)
    DriveController-->>Client: response (stream + metadata)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to focus on:
    • DriveService unified access semantics, ownership validation, and NotFound handling.
    • Consistency of truncation: stored vs returned content and wordCount calculation.
    • Sanitization propagation across DTO mappings and services; confirm only intended fields/tools are redacted.
    • replaceFilePlaceholders batching and error handling around reflyService.createShareForDriveFile.
    • useMemo dependency correctness in file-preview renderers to avoid stale renders.

Possibly related PRs

Poem

🐇 I hopped through streams and DTOs bright,
Replaced placeholders, trimmed for light,
Public, private — now aligned,
Sanitized outputs neatly signed,
Pure preview made the HTML right.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the three main change categories: voucher sharing permissions, tool output sanitization, and drive file processing refactoring, all of which are prominent in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/mashu/bugs2

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

@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: 5

Caution

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

⚠️ Outside diff range comments (4)
packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx (1)

98-122: Add t to the dependency array for translation updates.

The useMemo hook uses the t function from useTranslation (lines 102, 112) but has an empty dependency array. This prevents the segmented options from updating when the language changes, causing the labels to remain in the initial language until the page is reloaded.

Recommended fix
       value: 'code',
     },
   ],
-  [],
+  [t],
 );
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx (1)

20-32: Shadow DOM content won't update when sanitizedSvg changes.

The guard !el.shadowRoot prevents re-attachment, but if fileContent.data changes while the component remains mounted (e.g., previewing different files), the shadow DOM content won't update since the shadowRoot already exists.

Consider updating the innerHTML when content changes:

🔎 Proposed fix
         ref={(el) => {
-          if (el && !el.shadowRoot) {
+          if (el) {
+            let shadow = el.shadowRoot;
+            if (!shadow) {
+              shadow = el.attachShadow({ mode: 'open' });
+            }
-            const shadow = el.attachShadow({ mode: 'open' });
             // Inject styles to disable interactions on images and links
             shadow.innerHTML = `
               <style>
                 :host { display: block; }
                 image, a { pointer-events: none; cursor: default; }
               </style>
               ${sanitizedSvg}
             `;
           }
         }}
packages/web-core/src/pages/drive-file-share/index.tsx (1)

17-17: Remove debug console.log before merging.

This debug statement should be removed from production code.

🔎 Proposed fix
-  console.log('driveFileData', driveFileData);
packages/agent-tools/src/builtin/index.ts (1)

417-430: Consider surfacing URL generation failures more prominently.

The current error handling logs failures but silently falls back to the original content (lines 423-427, 460-463). This means users won't know their file references failed to resolve, resulting in broken file://df-xxx or file-content://df-xxx placeholders in the generated document.

Consider returning a partial success status or including a warning in the summary when some file URLs fail to generate, so users are aware of the issue.

💡 Suggested approach

Track failed replacements and include them in the result summary:

       const urlResults = await Promise.all(
         uniqueFileIds.map(async (fileId) => {
           try {
             const { url, contentUrl } = await reflyService.createShareForDriveFile(user, fileId);
             return { fileId, shareUrl: url, contentUrl };
           } catch (error) {
             console.error(
               `[BuiltinGenerateDoc] Failed to create share URL for fileId ${fileId}:`,
               error,
             );
             return { fileId, shareUrl: null, contentUrl: null };
           }
         }),
       );
+
+      // Track failed file IDs for user notification
+      const failedFileIds = urlResults
+        .filter(r => !r.shareUrl || !r.contentUrl)
+        .map(r => r.fileId);

Then in the _call method, append warning to summary if there are failures:

summary: `Successfully generated document: "${input.title}" with ID: ${file.fileId}${
  failedFileIds.length > 0 
    ? ` (Warning: ${failedFileIds.length} file reference(s) failed to resolve)` 
    : ''
}`

Also applies to: 460-463

🧹 Nitpick comments (6)
apps/api/src/modules/action/action.dto.ts (1)

65-66: Clarify type safety of output.data type guard.

Line 65 checks typeof output.data === 'object' before type-asserting to Record<string, unknown> on line 66. In JavaScript, both arrays and null satisfy typeof === 'object'. While the earlier check output?.data rules out null, arrays would still pass this check. Consider adding an explicit array exclusion or clarifying that this is intentional:

if (
  toolName === 'read_file' &&
  output?.data &&
  typeof output.data === 'object' &&
  !Array.isArray(output.data)
) {
packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx (1)

225-240: LGTM! Memoization comparison correctly extended.

The additions of canvasReadOnly, showActions, and purePreview to the comparison function are correct, as these props affect rendering behavior throughout the component.

Minor note: The comment at line 227 could be slightly clearer. It currently says "to ensure state updates propagate," but the actual reason for not comparing onTabChange is to avoid unnecessary re-renders when the callback reference changes. State updates propagate through value props like activeTab, not through the callback comparison.

Optional: Clarify the comment wording
-    // Note: onTabChange is intentionally not compared to ensure state updates propagate
+    // Note: Callback props like onTabChange are not compared to prevent re-renders 
+    // from reference changes. State updates propagate through value props like activeTab.
packages/web-core/src/pages/drive-file-share/index.tsx (1)

49-53: Consider extracting HTML detection to a shared utility.

This logic partially duplicates the backend's isHtmlFile function in drive.utils.ts. If the backend supports additional HTML extensions (e.g., .shtml, .xhtml), they won't trigger purePreview here.

Consider extracting this to a shared utility or ensuring consistency with the backend implementation.

packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx (1)

149-158: Pre-existing issue: Cleanup references potentially stale fileContent.

The cleanup function references fileContent but it's not in the dependency array. When fetchFileContent changes and cleanup runs, it may revoke the wrong URL or none at all.

This is pre-existing behavior, but consider addressing it in a follow-up:

🔎 Suggested pattern using a ref
+  const fileContentUrlRef = useRef<string | null>(null);
+
   useEffect(() => {
     fetchFileContent();
 
-    // Cleanup object URL on unmount
     return () => {
-      if (fileContent?.url) {
-        URL.revokeObjectURL(fileContent.url);
+      if (fileContentUrlRef.current) {
+        URL.revokeObjectURL(fileContentUrlRef.current);
       }
     };
   }, [fetchFileContent]);
+
+  // Update ref when fileContent changes
+  useEffect(() => {
+    fileContentUrlRef.current = fileContent?.url ?? null;
+  }, [fileContent?.url]);
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx (1)

15-41: Consider consolidating similar rendering blocks.

The purePreview mode (lines 15-27) and card mode (lines 30-41) have nearly identical rendering logic, differing only by the w-full class. This could be simplified to reduce duplication.

🔎 Suggested consolidation
+    // Pure preview or card mode: render HTML directly
+    if (purePreview || source === 'card') {
+      return (
+        <div className={`h-full overflow-hidden${purePreview ? ' w-full' : ''}`}>
+          <Renderer
+            content={textContent}
+            type="text/html"
+            title={file.name}
+            showActions={false}
+            purePreview={true}
+          />
+        </div>
+      );
+    }
-    // Pure preview mode: render HTML directly without CodeViewer chrome
-    if (purePreview) {
-      return (
-        <div className="h-full w-full overflow-hidden">
-          <Renderer
-            content={textContent}
-            type="text/html"
-            title={file.name}
-            showActions={false}
-            purePreview={true}
-          />
-        </div>
-      );
-    }
-
-    // Card mode: simple preview
-    if (source === 'card') {
-      return (
-        <div className="h-full overflow-hidden">
-          <Renderer
-            content={textContent}
-            type="text/html"
-            title={file.name}
-            showActions={false}
-            purePreview={true}
-          />
-        </div>
-      );
-    }
packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx (1)

54-55: Extract duplicate voucher value calculation to a shared helper.

The same calculation Math.round((100 - discountPercent) / 10) appears in two places (line 55 and line 635). Extract this to a helper function to ensure consistency and improve maintainability.

🔎 Proposed refactor

Add a helper function at the top of the file:

+// Convert discount percentage to Chinese "折" format
+// e.g., 60% off -> 4折 (40% of original price)
+const getVoucherValueInZhe = (discountPercent: number): number => {
+  return Math.round((100 - discountPercent) / 10);
+};
+
 const gridSpan = {

Then use it in both locations:

-    const voucherValue = Math.round((100 - discountPercent) / 10);
+    const voucherValue = getVoucherValueInZhe(discountPercent);
             logEvent('voucher_applied', null, {
-              voucher_value: Math.round((100 - availableVoucher.discountPercent) / 10),
+              voucher_value: getVoucherValueInZhe(availableVoucher.discountPercent),
               entry_point: 'pricing_page',

Also applies to: 635-635

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 372683b and 23e2b52.

📒 Files selected for processing (13)
  • apps/api/src/modules/action/action.dto.ts (1 hunks)
  • apps/api/src/modules/drive/drive.controller.ts (4 hunks)
  • apps/api/src/modules/drive/drive.service.ts (5 hunks)
  • packages/agent-tools/src/builtin/index.ts (6 hunks)
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx (3 hunks)
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx (1 hunks)
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx (3 hunks)
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx (2 hunks)
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx (1 hunks)
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts (1 hunks)
  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx (2 hunks)
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx (1 hunks)
  • packages/web-core/src/pages/drive-file-share/index.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{jsx,tsx}: Always use tailwind css to style the component
Always wrap pure components with React.memo to prevent unnecessary re-renders
Always use useMemo for expensive computations or complex object creation
Always use useCallback for function props to maintain referential equality
Always specify proper dependency arrays in useEffect to prevent infinite loops
Always avoid inline object/array creation in render to prevent unnecessary re-renders
Always use proper key props when rendering lists
Always split nested components with closures into separate components to avoid performance issues and improve code maintainability

**/*.{jsx,tsx}: Always wrap pure components with React.memo to prevent unnecessary re-renders
Always use useMemo for expensive computations or complex object creation in React
Always use useCallback for function props to maintain referential equality in React
Always specify proper dependency arrays in useEffect to prevent infinite loops in React
Always avoid inline object/array creation in render to prevent unnecessary re-renders in React
Always use proper key props when rendering lists in React (avoid using index when possible)
Always split nested components with closures into separate components in React
Use lazy loading for components that are not immediately needed in React
Debounce handlers for events that might fire rapidly (resize, scroll, input) in React
Implement fallback UI for components that might fail in React
Use error boundaries to catch and handle runtime errors in React

**/*.{jsx,tsx}: Place each attribute on a new line when a component has multiple attributes in JSX
Use self-closing tags for elements without children in JSX
Keep JSX expressions simple, extract complex logic to variables
Put closing brackets for multi-line JSX on a new line

**/*.{jsx,tsx}: Component file names should match the component name
Organize function components in order: imports, type definitions, constants, component function, hook calls, e...

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts,jsx,tsx}: Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Always validate object properties before destructuring
Always use single quotes for string literals in JavaScript/TypeScript code

**/*.{js,ts,jsx,tsx}: Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement

**/*.{js,ts,jsx,tsx}: Group import statements in order: React/framework libraries, third-party libraries, internal modules, relative path imports, type imports, style imports
Sort imports alphabetically within each import group
Leave a blank line between import groups
Extract complex logic into custom hooks
Use functional updates for state (e.g., setCount(prev => prev + 1))
Split complex state into multiple state variables rather than single large objects
Use useReducer for complex state logic instead of multiple useState calls

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}: All code comments MUST be written in English
All variable names, function names, class names, and other identifiers MUST use English words
Comments should be concise and explain 'why' rather than 'what'
Use proper grammar and punctuation in comments
Keep comments up-to-date when code changes
Document complex logic, edge cases, and important implementation details
Use clear, descriptive names that indicate purpose
Avoid abbreviations unless they are universally understood

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/01-code-style.mdc)

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals in TypeScript/JavaScript
Always use optional chaining (?.) when accessing object properties in TypeScript/JavaScript
Always use nullish coalescing (??) or default values for potentially undefined values in TypeScript/JavaScript
Always check array existence before using array methods in TypeScript/JavaScript
Validate object properties before destructuring in TypeScript/JavaScript
Use ES6+ features like arrow functions, destructuring, and spread operators in TypeScript/JavaScript
Avoid magic numbers and strings - use named constants in TypeScript/JavaScript
Use async/await instead of raw promises for asynchronous code in TypeScript/JavaScript

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{jsx,tsx,css}

📄 CodeRabbit inference engine (.cursor/rules/01-code-style.mdc)

**/*.{jsx,tsx,css}: Use Tailwind CSS for styling components
Follow the utility-first approach with Tailwind CSS
Group related utility classes together in Tailwind CSS
Prefer Tailwind utilities over custom CSS when possible

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - use unknown type instead with proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new types
Use TypeScript utility types (Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>) to derive new types
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{js,ts,jsx,tsx,css,json}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Maximum line length of 100 characters

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{js,ts,jsx,tsx,css,json,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Use 2 spaces for indentation, no tabs

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{js,ts,jsx,tsx,css,json,yml,yaml,md}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

No trailing whitespace at the end of lines

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{jsx,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/05-code-organization.mdc)

Each component file should contain only one main component

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/05-code-organization.mdc)

Explicitly type props with interfaces or types in React components

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{css,scss,sass,less,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,js,jsx,ts,tsx}: Primary color (#155EEF) should be used for main brand color in buttons, links, and accents
Error color (#F04438) should be used for error states and destructive actions
Success color (#12B76A) should be used for success states and confirmations
Warning color (#F79009) should be used for warnings and important notifications
Info color (#0BA5EC) should be used for informational elements

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

**/*.{tsx,ts}: Use the translation wrapper component and useTranslation hook in components
Ensure all user-facing text is translatable

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{tsx,ts,json}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

Support dynamic content with placeholders in translations

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{tsx,ts,jsx,js,vue,css,scss,less}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue,css,scss,less}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Primary buttons should be solid with the primary color
Secondary buttons should have a border with transparent or light background
Danger buttons should use the error color
Use consistent padding, border radius, and hover states for all buttons
Follow fixed button sizes based on their importance and context
Use consistent border radius (rounded-lg) for all cards
Apply light shadows (shadow-sm) for card elevation
Maintain consistent padding inside cards (p-4 or p-6)
Use subtle borders for card separation
Ensure proper spacing between card elements
Apply consistent styling to all form inputs
Use clear visual indicators for focus, hover, and error states in form elements
Apply proper spacing between elements using 8px, 16px, 24px increments
Ensure proper alignment of elements (left, center, or right)
Use responsive layouts that work across different device sizes
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{tsx,ts,jsx,js,vue}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue}: Include appropriate loading states for async actions in buttons
Group related form elements with appropriate spacing
Provide clear validation feedback for forms
Ensure proper labeling and accessibility for form elements
Ensure all interactive elements are keyboard accessible
Include appropriate ARIA attributes for complex components
Provide alternative text for images and icons
Support screen readers with semantic HTML elements

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

**/*.{ts,tsx,js,jsx}: Follow the TypeScript/JavaScript style guidelines
Ensure code is well-tested and documented

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • apps/api/src/modules/action/action.dto.ts
  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • apps/api/src/modules/drive/drive.controller.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • apps/api/src/modules/drive/drive.service.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

Use React best practices for frontend code

Files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • packages/web-core/src/pages/drive-file-share/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
**/index.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/05-code-organization.mdc)

Use index files to export multiple components from a directory

Files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • packages/agent-tools/src/builtin/index.ts
  • packages/web-core/src/pages/drive-file-share/index.tsx
apps/api/src/**/*.{controller,service}.ts

📄 CodeRabbit inference engine (.cursor/rules/06-api-structure.mdc)

Implement proper error handling in API modules

Files:

  • apps/api/src/modules/drive/drive.controller.ts
  • apps/api/src/modules/drive/drive.service.ts
apps/api/src/**/*.controller.ts

📄 CodeRabbit inference engine (.cursor/rules/06-api-structure.mdc)

Document APIs with OpenAPI specifications

Files:

  • apps/api/src/modules/drive/drive.controller.ts
🧠 Learnings (18)
📚 Learning: 2025-11-25T03:03:19.158Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/00-language-priority.mdc:0-0
Timestamp: 2025-11-25T03:03:19.158Z
Learning: Applies to **/{i18n,locales,translations,lang}/**/*.{json,ts,tsx,js,jsx,properties} : Use Chinese for all user-facing communication

Applied to files:

  • packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx
📚 Learning: 2025-11-25T03:02:45.779Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T03:02:45.779Z
Learning: Applies to **/*.{jsx,tsx} : Always wrap pure components with React.memo to prevent unnecessary re-renders

Applied to files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx
  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-25T03:04:41.334Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/09-i18n-guidelines.mdc:0-0
Timestamp: 2025-11-25T03:04:41.334Z
Learning: Applies to **/*.{tsx,ts,json} : Support dynamic content with placeholders in translations

Applied to files:

  • packages/agent-tools/src/builtin/index.ts
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
📚 Learning: 2025-11-25T03:05:07.580Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/11-ui-design-patterns.mdc:0-0
Timestamp: 2025-11-25T03:05:07.580Z
Learning: Applies to **/*.{tsx,ts,jsx,js,vue,css,scss,less} : Ensure proper spacing between card elements

Applied to files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
📚 Learning: 2025-11-25T03:05:07.580Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/11-ui-design-patterns.mdc:0-0
Timestamp: 2025-11-25T03:05:07.580Z
Learning: Applies to **/*.{tsx,ts,jsx,js,vue,css,scss,less} : Use subtle borders for card separation

Applied to files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
📚 Learning: 2025-11-25T03:02:45.779Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T03:02:45.779Z
Learning: Applies to **/*.{jsx,tsx} : Always use useMemo for expensive computations or complex object creation

Applied to files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-25T03:03:31.945Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/01-code-style.mdc:0-0
Timestamp: 2025-11-25T03:03:31.945Z
Learning: Applies to **/*.{jsx,tsx} : Always use useMemo for expensive computations or complex object creation in React

Applied to files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-25T03:04:12.836Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/06-api-structure.mdc:0-0
Timestamp: 2025-11-25T03:04:12.836Z
Learning: Applies to apps/api/src/{auth,user,project,canvas,rag,knowledge,search,skill,share,code-artifact}/**/*.{controller,service}.ts : Use dependency injection for module dependencies in NestJS

Applied to files:

  • apps/api/src/modules/drive/drive.controller.ts
📚 Learning: 2025-11-25T03:04:51.017Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/10-testing-guidelines.mdc:0-0
Timestamp: 2025-11-25T03:04:51.017Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Test component rendering, interactions, prop handling, state changes, error states and edge cases in component tests

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
📚 Learning: 2025-11-25T03:02:45.779Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T03:02:45.779Z
Learning: Applies to **/*.{jsx,tsx} : Always use useCallback for function props to maintain referential equality

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-25T03:03:31.945Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/01-code-style.mdc:0-0
Timestamp: 2025-11-25T03:03:31.945Z
Learning: Applies to **/*.{jsx,tsx} : Always use useCallback for function props to maintain referential equality in React

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-26T05:04:26.523Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/08-contributing-guidelines.mdc:0-0
Timestamp: 2025-11-26T05:04:26.523Z
Learning: Applies to **/*.{tsx,jsx} : Use React best practices for frontend code

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-25T03:03:31.945Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/01-code-style.mdc:0-0
Timestamp: 2025-11-25T03:03:31.945Z
Learning: Applies to **/*.{jsx,tsx} : Always avoid inline object/array creation in render to prevent unnecessary re-renders in React

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-25T03:02:45.779Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T03:02:45.779Z
Learning: Applies to **/*.{jsx,tsx} : Always avoid inline object/array creation in render to prevent unnecessary re-renders

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx
📚 Learning: 2025-11-25T03:04:05.715Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/05-code-organization.mdc:0-0
Timestamp: 2025-11-25T03:04:05.715Z
Learning: Applies to **/*.{jsx,tsx} : Use ternary operators or && for conditional rendering in React

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
📚 Learning: 2025-11-25T03:04:05.715Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/05-code-organization.mdc:0-0
Timestamp: 2025-11-25T03:04:05.715Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use functional updates for state (e.g., `setCount(prev => prev + 1)`)

Applied to files:

  • packages/ai-workspace-common/src/modules/artifacts/code-runner/code-viewer.tsx
📚 Learning: 2025-11-25T03:04:05.715Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/05-code-organization.mdc:0-0
Timestamp: 2025-11-25T03:04:05.715Z
Learning: Extract repeated JSX patterns into reusable components

Applied to files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
📚 Learning: 2025-11-25T03:03:31.945Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/01-code-style.mdc:0-0
Timestamp: 2025-11-25T03:03:31.945Z
Learning: Applies to **/*.{jsx,tsx} : Implement fallback UI for components that might fail in React

Applied to files:

  • packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx
🧬 Code graph analysis (5)
apps/api/src/modules/action/action.dto.ts (4)
packages/openapi-schema/src/types.gen.ts (1)
  • ToolCallResult (3445-3502)
packages/ai-workspace-common/src/requests/types.gen.ts (1)
  • ToolCallResult (3396-3453)
packages/request/src/requests/types.gen.ts (1)
  • ToolCallResult (3396-3453)
apps/api/src/modules/tool/utils/token.ts (1)
  • safeParseJSON (371-377)
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx (1)
apps/api/src/modules/drive/drive.service.ts (1)
  • truncateContent (627-641)
packages/web-core/src/pages/drive-file-share/index.tsx (2)
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx (1)
  • FilePreview (86-267)
apps/api/src/modules/drive/drive.utils.ts (1)
  • isHtmlFile (56-65)
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx (2)
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts (1)
  • SourceRendererProps (16-23)
packages/openapi-schema/src/types.gen.ts (1)
  • source (6261-6261)
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx (1)
packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts (1)
  • FileRendererProps (10-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / Build
🔇 Additional comments (24)
apps/api/src/modules/action/action.dto.ts (2)

56-78: No actionable issue found.

The sanitizeToolOutput function receives toolCall.toolName from a ToolCallResultModel (Prisma type), where toolName is a required String field. The function signature correctly expects a non-optional string parameter. The optional nature of toolName applies only to the OpenAPI return type, not the database input type.


80-98: Verify if sanitization of tool outputs impacts share page display.

The modified toolCallResultPO2DTO applies sanitizeToolOutput unconditionally to all tool results. Currently, for read_file tool outputs, the content field is replaced with '[Content omitted for display]' before being returned.

In share-creation.service.ts, the actionResultPO2DTO function (which calls toolCallResultPO2DTO via actionStepPO2DTO) is used to serialize action results for public share pages. Confirm that:

  1. Share pages do not require full file content from read_file operations, or
  2. An unsanitized data path is provided for share page generation to display full content as intended

All current callers of toolCallResultPO2DTO receive sanitized outputs with no option to bypass sanitization.

packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/types.ts (1)

22-22: LGTM!

The purePreview optional prop is properly typed and follows the existing pattern for optional boolean properties in this interface.

packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/svg.tsx (1)

6-14: LGTM! Good use of memoization.

Moving the decode and sanitization into useMemo correctly avoids recomputation on every render. The dependency array properly includes fileContent.data. Based on learnings, this aligns with best practices for expensive computations in React.

packages/web-core/src/pages/drive-file-share/index.tsx (1)

75-75: LGTM!

The purePreview prop is correctly wired to isHtmlFile, enabling full HTML preview mode for HTML files on the share page.

packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/index.tsx (1)

83-83: LGTM!

The purePreview prop is properly typed, defaulted to false, and correctly forwarded to HtmlRenderer. This follows the existing pattern for optional props in this component.

Also applies to: 92-92, 193-193

packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/markdown.tsx (2)

40-43: LGTM!

Good refactor moving the decode and truncation into useMemo. The dependency array correctly includes both fileContent.data and disableTruncation. Based on learnings, this aligns with best practices for memoizing expensive computations.


54-57: LGTM!

Consistent memoization pattern with the Card component. Both decode and truncation logic are properly memoized with correct dependencies.

apps/api/src/modules/drive/drive.controller.ts (4)

105-107: LGTM! Good implementation of optional authentication.

The change from JwtAuthGuard to OptionalJwtAuthGuard correctly enables public file access while preserving private file authentication. The User | null type accurately reflects the optional nature of the authenticated user.


116-118: LGTM!

The unified file metadata retrieval cleanly centralizes access control logic. The comment accurately describes the access check order (external/public first, then internal/private if authenticated).


139-145: LGTM! Correct conditional processing logic.

The condition user && !isPublic correctly ensures:

  • Anonymous users don't trigger private URL processing
  • Public files skip unnecessary processing regardless of auth status
  • Only authenticated users accessing private files get content processing

16-16: OptionalJwtAuthGuard is correctly implemented.

The guard properly validates tokens when present using jwtService.verifyAsync() and gracefully allows null users when tokens are absent or invalid by setting request.user = null and returning true. It supports token extraction from both Authorization headers and cookies, with appropriate error logging for invalid tokens.

packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/code.tsx (3)

54-57: LGTM! Proper memoization of content decoding.

Moving the TextDecoder operation inside useMemo correctly avoids repeated decoding on re-renders. The dependency array appropriately includes all values that affect the computation result.


106-109: LGTM! Consistent memoization pattern.

The decoding and truncation are properly memoized. Since MAX_PREVIEW_LINES and MAX_PREVIEW_CHARS are module-level constants, they don't need to be in the dependency array.


187-192: LGTM! Memoization correctly handles source-dependent truncation.

Including source in the dependency array is correct since it determines which truncation limits to apply.

packages/ai-workspace-common/src/components/canvas/canvas-resources/file-preview/html.tsx (2)

1-12: LGTM! Good use of memoization for HTML content decoding.

The useMemo correctly caches the decoded text content with fileContent.data as the dependency. Based on learnings, this follows the guideline to always use useMemo for expensive computations.


45-62: LGTM! Preview mode with appropriate defaults.

The nullish coalescing for activeTab ?? 'preview' and onTabChange ?? (() => {}) provides safe fallbacks. As per coding guidelines, this correctly uses nullish coalescing for potentially undefined values.

apps/api/src/modules/drive/drive.service.ts (4)

606-615: LGTM! HR normalization enhancement.

The regex patterns correctly normalize horizontal rules to standard markdown --- and deduplicate consecutive rules. The two-pass deduplication (before and after line trimming) handles edge cases.


659-664: Good approach: Re-apply truncation at retrieval time.

This design allows maxContentWords configuration changes to take effect without re-parsing stored content. The comment accurately describes the behavior.


748-758: Verify wordCount reflects actual returned content.

The wordCount is calculated from truncatedContent (line 757) and stored in the cache (line 769). If maxContentWords config changes later, the cached wordCount will be stale relative to newly truncated content returned at retrieval time. Is this acceptable for your use case, or should wordCount be recalculated at retrieval?


1498-1584: LGTM! Unified file stream with proper error handling.

The try-catch around externalOss.getObject correctly handles cases where the file doesn't exist in the public bucket, falling back to the internal OSS with authentication and ownership checks. The security logic properly prevents unauthorized access.

packages/agent-tools/src/builtin/index.ts (2)

317-323: LGTM! Clear documentation for placeholder formats.

The updated schema description provides clear guidance on the two supported placeholder formats with concrete examples. This will help the LLM understand when to use each format.


475-483: LGTM! Consistent documentation.

The schema description is clear and consistent with the generate_doc tool's documentation.

packages/ai-workspace-common/src/components/settings/subscribe-modal/priceContent.tsx (1)

52-68: Locale-aware voucher display implementation looks good.

The implementation correctly:

  • Uses i18n.language to detect Chinese locale with safe optional chaining
  • Conditionally formats the discount text based on locale
  • Maintains the memoized component structure to prevent unnecessary rerenders
  • Falls back to English format with translation support for other locales

The logic is clean and follows React i18n best practices, subject to the precision concern raised separately.

…result display sanitization, and configure Serena project settings.
Copy link

@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: 2

🧹 Nitpick comments (3)
apps/api/src/modules/voucher/voucher.service.ts (2)

648-660: Consider extracting the permission check into a reusable helper method.

The permission check logic (owner OR claimant) is duplicated between this method and validateVoucher (lines 527-534). This violates the DRY principle and increases maintenance burden.

🔎 Suggested refactor to eliminate duplication

Add a private helper method:

/**
 * Check if user has permission to access/use a voucher (owner or claimant)
 */
private async checkVoucherPermission(
  uid: string,
  voucherId: string
): Promise<boolean> {
  const voucher = await this.prisma.voucher.findFirst({
    where: { voucherId },
  });

  if (!voucher) {
    return false;
  }

  // Check if user is owner
  const isOwner = voucher.uid === uid;
  if (isOwner) {
    return true;
  }

  // Check if user is claimant
  const isClaimant = await this.prisma.voucherInvitation.findFirst({
    where: {
      voucherId,
      inviteeUid: uid,
      status: InvitationStatus.CLAIMED,
    },
  });

  return !!isClaimant;
}

Then simplify createInvitation:

 async createInvitation(uid: string, voucherId: string): Promise<CreateInvitationResult> {
-  // Get the voucher (without uid filter - we'll check permission separately)
-  const voucher = await this.prisma.voucher.findFirst({
-    where: { voucherId },
-  });
-
-  if (!voucher) {
-    throw new Error('Voucher not found');
-  }
-
-  // Check permission: owner OR claimant (same logic as validateVoucher)
-  const isOwner = voucher.uid === uid;
-  const isClaimant = await this.prisma.voucherInvitation.findFirst({
-    where: {
-      voucherId,
-      inviteeUid: uid,
-      status: InvitationStatus.CLAIMED,
-    },
-  });
-
-  if (!isOwner && !isClaimant) {
+  const hasPermission = await this.checkVoucherPermission(uid, voucherId);
+  if (!hasPermission) {
     throw new Error('Voucher not found');
   }
+
+  const voucher = await this.prisma.voucher.findFirst({
+    where: { voucherId },
+  });

You can apply the same refactor to validateVoucher.


658-660: Add logging for permission denial attempts.

When permission is denied (line 659), consider logging this event for security monitoring and debugging purposes. This can help identify potential abuse attempts or misconfigurations.

🔎 Suggested logging addition
   if (!isOwner && !isClaimant) {
+    this.logger.warn(
+      `Permission denied: User ${uid} attempted to create invitation for voucher ${voucherId}`
+    );
     throw new Error('Voucher not found');
   }
apps/api/src/modules/action/action.dto.ts (1)

62-80: Strengthen the type guard for output.data.

The current type check typeof output.data === 'object' will pass for arrays and (without the optional chaining) would pass for null. Consider a more explicit check to ensure data is a plain object:

🔎 Proposed refinement
 export function sanitizeToolOutput(
   toolName: string,
   output: Record<string, unknown>,
 ): Record<string, unknown> {
   // For read_file, remove the content field from data as it can be very large
-  if (toolName === 'read_file' && output?.data && typeof output.data === 'object') {
+  if (
+    toolName === 'read_file' &&
+    output?.data &&
+    typeof output.data === 'object' &&
+    !Array.isArray(output.data) &&
+    output.data !== null
+  ) {
     const data = output.data as Record<string, unknown>;
     if ('content' in data) {
       return {
         ...output,
         data: {
           ...data,
           content: '[Content omitted for display]',
         },
       };
     }
   }
   return output;
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23e2b52 and 9add405.

📒 Files selected for processing (6)
  • apps/api/src/modules/action/action.controller.ts (1 hunks)
  • apps/api/src/modules/action/action.dto.ts (4 hunks)
  • apps/api/src/modules/action/action.service.ts (7 hunks)
  • apps/api/src/modules/copilot/copilot.dto.ts (1 hunks)
  • apps/api/src/modules/voucher/voucher.constants.ts (1 hunks)
  • apps/api/src/modules/voucher/voucher.service.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts,jsx,tsx}: Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Always validate object properties before destructuring
Always use single quotes for string literals in JavaScript/TypeScript code

**/*.{js,ts,jsx,tsx}: Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement

**/*.{js,ts,jsx,tsx}: Group import statements in order: React/framework libraries, third-party libraries, internal modules, relative path imports, type imports, style imports
Sort imports alphabetically within each import group
Leave a blank line between import groups
Extract complex logic into custom hooks
Use functional updates for state (e.g., setCount(prev => prev + 1))
Split complex state into multiple state variables rather than single large objects
Use useReducer for complex state logic instead of multiple useState calls

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}: All code comments MUST be written in English
All variable names, function names, class names, and other identifiers MUST use English words
Comments should be concise and explain 'why' rather than 'what'
Use proper grammar and punctuation in comments
Keep comments up-to-date when code changes
Document complex logic, edge cases, and important implementation details
Use clear, descriptive names that indicate purpose
Avoid abbreviations unless they are universally understood

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/01-code-style.mdc)

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals in TypeScript/JavaScript
Always use optional chaining (?.) when accessing object properties in TypeScript/JavaScript
Always use nullish coalescing (??) or default values for potentially undefined values in TypeScript/JavaScript
Always check array existence before using array methods in TypeScript/JavaScript
Validate object properties before destructuring in TypeScript/JavaScript
Use ES6+ features like arrow functions, destructuring, and spread operators in TypeScript/JavaScript
Avoid magic numbers and strings - use named constants in TypeScript/JavaScript
Use async/await instead of raw promises for asynchronous code in TypeScript/JavaScript

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - use unknown type instead with proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new types
Use TypeScript utility types (Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>) to derive new types
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{js,ts,jsx,tsx,css,json}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Maximum line length of 100 characters

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{js,ts,jsx,tsx,css,json,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Use 2 spaces for indentation, no tabs

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{js,ts,jsx,tsx,css,json,yml,yaml,md}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

No trailing whitespace at the end of lines

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
apps/api/src/**/*.{controller,service}.ts

📄 CodeRabbit inference engine (.cursor/rules/06-api-structure.mdc)

Implement proper error handling in API modules

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{css,scss,sass,less,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,js,jsx,ts,tsx}: Primary color (#155EEF) should be used for main brand color in buttons, links, and accents
Error color (#F04438) should be used for error states and destructive actions
Success color (#12B76A) should be used for success states and confirmations
Warning color (#F79009) should be used for warnings and important notifications
Info color (#0BA5EC) should be used for informational elements

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

**/*.{tsx,ts}: Use the translation wrapper component and useTranslation hook in components
Ensure all user-facing text is translatable

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{tsx,ts,json}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

Support dynamic content with placeholders in translations

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{tsx,ts,jsx,js,vue,css,scss,less}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue,css,scss,less}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Primary buttons should be solid with the primary color
Secondary buttons should have a border with transparent or light background
Danger buttons should use the error color
Use consistent padding, border radius, and hover states for all buttons
Follow fixed button sizes based on their importance and context
Use consistent border radius (rounded-lg) for all cards
Apply light shadows (shadow-sm) for card elevation
Maintain consistent padding inside cards (p-4 or p-6)
Use subtle borders for card separation
Ensure proper spacing between card elements
Apply consistent styling to all form inputs
Use clear visual indicators for focus, hover, and error states in form elements
Apply proper spacing between elements using 8px, 16px, 24px increments
Ensure proper alignment of elements (left, center, or right)
Use responsive layouts that work across different device sizes
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{tsx,ts,jsx,js,vue}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue}: Include appropriate loading states for async actions in buttons
Group related form elements with appropriate spacing
Provide clear validation feedback for forms
Ensure proper labeling and accessibility for form elements
Ensure all interactive elements are keyboard accessible
Include appropriate ARIA attributes for complex components
Provide alternative text for images and icons
Support screen readers with semantic HTML elements

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

**/*.{ts,tsx,js,jsx}: Follow the TypeScript/JavaScript style guidelines
Ensure code is well-tested and documented

Files:

  • apps/api/src/modules/voucher/voucher.service.ts
  • apps/api/src/modules/action/action.controller.ts
  • apps/api/src/modules/action/action.dto.ts
  • apps/api/src/modules/voucher/voucher.constants.ts
  • apps/api/src/modules/copilot/copilot.dto.ts
  • apps/api/src/modules/action/action.service.ts
apps/api/src/**/*.controller.ts

📄 CodeRabbit inference engine (.cursor/rules/06-api-structure.mdc)

Document APIs with OpenAPI specifications

Files:

  • apps/api/src/modules/action/action.controller.ts
🧬 Code graph analysis (3)
apps/api/src/modules/voucher/voucher.service.ts (4)
packages/openapi-schema/src/types.gen.ts (2)
  • CreateInvitationResult (9312-9317)
  • InvitationStatus (9175-9175)
apps/api/src/modules/voucher/voucher.dto.ts (1)
  • CreateInvitationResult (89-91)
packages/ai-workspace-common/src/requests/types.gen.ts (2)
  • CreateInvitationResult (9251-9264)
  • InvitationStatus (9114-9114)
apps/api/src/modules/voucher/voucher.constants.ts (1)
  • InvitationStatus (42-46)
apps/api/src/modules/action/action.controller.ts (2)
apps/api/src/utils/response.ts (1)
  • buildSuccessResponse (48-53)
apps/api/src/modules/action/action.dto.ts (1)
  • actionResultPO2DTO (107-143)
apps/api/src/modules/copilot/copilot.dto.ts (1)
apps/api/src/modules/action/action.dto.ts (1)
  • actionResultPO2DTO (107-143)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / Build
🔇 Additional comments (5)
apps/api/src/modules/voucher/voucher.service.ts (1)

638-683: Permission model looks correct and consistent.

The changes correctly implement the new permission model where both the voucher owner and claimants can create invitations. The logic mirrors the permission checks in validateVoucher (lines 527-534), ensuring consistency across the service.

The intentional use of a generic "Voucher not found" error message (line 659) for permission denial is appropriate for security, preventing enumeration of valid voucher IDs.

apps/api/src/modules/voucher/voucher.constants.ts (1)

6-6: Confirm the purpose of the daily popup trigger limit value.

The DAILY_POPUP_TRIGGER_LIMIT constant is set to 999 and is actively used in the checkDailyTriggerLimit() method (line 316) to control how many times a user can trigger the voucher popup per day. A limit of 999 is unusually high and effectively removes any practical daily restriction. This change appears unrelated to the stated PR objectives (file preview truncation and canvas duplication fixes).

Clarify whether this high limit is intentional, and if so, provide justification for its inclusion in this PR.

apps/api/src/modules/action/action.controller.ts (1)

20-24: LGTM! Sanitization is consistently applied.

The controller correctly passes sanitizeForDisplay: true to both the service call and the DTO mapper, ensuring large outputs are redacted for display purposes throughout the response pipeline.

apps/api/src/modules/action/action.service.ts (2)

78-80: Sanitization propagation is correctly implemented.

The service properly forwards the sanitizeForDisplay option through the enrichment pipeline and conditionally applies sanitizeToolOutput to tool call results. The integration with the DTO layer ensures consistent sanitization across the response structure.

Also applies to: 98-154


138-144: Clarify inconsistent fallback handling for malformed tool output.

The code creates a { rawOutput: ... } wrapper when safeParseJSON fails, but toolCallResultPO2DTO in action.dto.ts handles the same conversion without this fallback. This creates structural inconsistency: parsed outputs have their original structure, while malformed JSON gets wrapped differently. The sanitizeToolOutput function may not handle this wrapped structure correctly (e.g., won't find the data field for read_file sanitization). Either remove the fallback for consistency with action.dto.ts, or document why it's necessary and ensure downstream consumers handle both structures.

… size estimation, and include Serena project configuration.
@lefarcen lefarcen changed the title fix: disable file preview truncation for shared files and update resultId mapping on canvas duplication fix: Voucher sharing for claimants, sanitize large tool outputs, refactor drive file processing Dec 19, 2025
Copy link

@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: 0

🧹 Nitpick comments (5)
apps/api/src/modules/tool-call/tool-call.service.ts (2)

238-242: Consider adding JSDoc for the new parameter.

The method signature update is correct and backward-compatible. However, the existing JSDoc comment (lines 227-237) doesn't document the new options parameter.

📝 Suggested JSDoc update

Update the JSDoc comment to include:

   * Attach tool calls to steps and merge XML content
   * This method combines grouping and content merging in one operation:
   * 1. Groups tool calls by step name (with fallback to last step)
   * 2. Generates XML content for each tool call
   * 3. Merges XML content into step.content
   *
   * @param steps - Array of steps with name and optional content
   * @param toolCalls - Array of tool calls with optional stepName
+  * @param options - Optional configuration
+  * @param options.sanitizeForDisplay - When true, sanitizes tool outputs (e.g., omits large content fields)
   * @returns Steps with attached tool calls and merged XML content

274-280: Verify type safety with sanitizeToolOutput.

The code passes rawOutput (typed as any from safeParseJSON) and potentially undefined call.toolName to sanitizeToolOutput, which expects Record<string, unknown> and string respectively.

While the current implementation is safe because:

  • sanitizeToolOutput only checks toolName === 'read_file', so undefined falls through harmlessly
  • The function handles non-matching types gracefully

Consider adding explicit type guards or using optional chaining to make the type safety more explicit and prevent future issues if sanitizeToolOutput is enhanced.

🔎 Suggested type-safe approach
 // Parse output JSON string and optionally sanitize
 const rawOutput = safeParseJSON(call.output || '{}') ?? {};
 const output = options?.sanitizeForDisplay
-  ? sanitizeToolOutput(call.toolName, rawOutput)
+  ? sanitizeToolOutput(call.toolName ?? '', rawOutput as Record<string, unknown>)
   : rawOutput;
apps/api/src/modules/action/action.service.ts (3)

95-99: Consider documenting the new parameter.

The method signature update is correct. However, consider adding a JSDoc comment block (if not already present) or updating existing documentation to describe the options parameter and its sanitizeForDisplay flag.


138-145: Inconsistent fallback logic compared to tool-call.service.ts.

In this file, when safeParseJSON returns null, the fallback wraps the raw output:

const rawOutput = safeParseJSON(toolCallResult.output || '{}') ?? {
  rawOutput: toolCallResult.output,
};

However, in tool-call.service.ts (line 276), the fallback is simply {}:

const rawOutput = safeParseJSON(call.output || '{}') ?? {};

This inconsistency could lead to different behavior in edge cases where JSON parsing fails. Consider aligning the fallback strategy across both files for consistency.

🔎 Suggested alignment

Option 1: Use simple empty object (matches tool-call.service.ts):

-const rawOutput = safeParseJSON(toolCallResult.output || '{}') ?? {
-  rawOutput: toolCallResult.output,
-};
+const rawOutput = safeParseJSON(toolCallResult.output || '{}') ?? {};

Option 2: Use wrapped fallback in both places (if the wrapping serves a purpose):
Update tool-call.service.ts line 276 to match this pattern.


138-145: Verify type safety with sanitizeToolOutput.

Similar to the issue in tool-call.service.ts, this code passes rawOutput (typed as any) and potentially undefined toolCallResult.toolName to sanitizeToolOutput. While safe in the current implementation, explicit type handling would improve code clarity and prevent future issues.

🔎 Suggested type-safe approach
 const rawOutput = safeParseJSON(toolCallResult.output || '{}') ?? {
   rawOutput: toolCallResult.output,
 };
 // Apply sanitization if needed
 const output = options?.sanitizeForDisplay
-  ? sanitizeToolOutput(toolCallResult.toolName, rawOutput)
+  ? sanitizeToolOutput(toolCallResult.toolName ?? '', rawOutput as Record<string, unknown>)
   : rawOutput;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9add405 and 051f714.

📒 Files selected for processing (2)
  • apps/api/src/modules/action/action.service.ts (8 hunks)
  • apps/api/src/modules/tool-call/tool-call.service.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts,jsx,tsx}: Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Always validate object properties before destructuring
Always use single quotes for string literals in JavaScript/TypeScript code

**/*.{js,ts,jsx,tsx}: Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement

**/*.{js,ts,jsx,tsx}: Group import statements in order: React/framework libraries, third-party libraries, internal modules, relative path imports, type imports, style imports
Sort imports alphabetically within each import group
Leave a blank line between import groups
Extract complex logic into custom hooks
Use functional updates for state (e.g., setCount(prev => prev + 1))
Split complex state into multiple state variables rather than single large objects
Use useReducer for complex state logic instead of multiple useState calls

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}: All code comments MUST be written in English
All variable names, function names, class names, and other identifiers MUST use English words
Comments should be concise and explain 'why' rather than 'what'
Use proper grammar and punctuation in comments
Keep comments up-to-date when code changes
Document complex logic, edge cases, and important implementation details
Use clear, descriptive names that indicate purpose
Avoid abbreviations unless they are universally understood

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/01-code-style.mdc)

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals in TypeScript/JavaScript
Always use optional chaining (?.) when accessing object properties in TypeScript/JavaScript
Always use nullish coalescing (??) or default values for potentially undefined values in TypeScript/JavaScript
Always check array existence before using array methods in TypeScript/JavaScript
Validate object properties before destructuring in TypeScript/JavaScript
Use ES6+ features like arrow functions, destructuring, and spread operators in TypeScript/JavaScript
Avoid magic numbers and strings - use named constants in TypeScript/JavaScript
Use async/await instead of raw promises for asynchronous code in TypeScript/JavaScript

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - use unknown type instead with proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new types
Use TypeScript utility types (Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>) to derive new types
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{js,ts,jsx,tsx,css,json}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Maximum line length of 100 characters

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{js,ts,jsx,tsx,css,json,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Use 2 spaces for indentation, no tabs

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{js,ts,jsx,tsx,css,json,yml,yaml,md}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

No trailing whitespace at the end of lines

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
apps/api/src/**/*.{controller,service}.ts

📄 CodeRabbit inference engine (.cursor/rules/06-api-structure.mdc)

Implement proper error handling in API modules

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{css,scss,sass,less,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,js,jsx,ts,tsx}: Primary color (#155EEF) should be used for main brand color in buttons, links, and accents
Error color (#F04438) should be used for error states and destructive actions
Success color (#12B76A) should be used for success states and confirmations
Warning color (#F79009) should be used for warnings and important notifications
Info color (#0BA5EC) should be used for informational elements

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

**/*.{tsx,ts}: Use the translation wrapper component and useTranslation hook in components
Ensure all user-facing text is translatable

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{tsx,ts,json}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

Support dynamic content with placeholders in translations

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{tsx,ts,jsx,js,vue,css,scss,less}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue,css,scss,less}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Primary buttons should be solid with the primary color
Secondary buttons should have a border with transparent or light background
Danger buttons should use the error color
Use consistent padding, border radius, and hover states for all buttons
Follow fixed button sizes based on their importance and context
Use consistent border radius (rounded-lg) for all cards
Apply light shadows (shadow-sm) for card elevation
Maintain consistent padding inside cards (p-4 or p-6)
Use subtle borders for card separation
Ensure proper spacing between card elements
Apply consistent styling to all form inputs
Use clear visual indicators for focus, hover, and error states in form elements
Apply proper spacing between elements using 8px, 16px, 24px increments
Ensure proper alignment of elements (left, center, or right)
Use responsive layouts that work across different device sizes
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{tsx,ts,jsx,js,vue}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue}: Include appropriate loading states for async actions in buttons
Group related form elements with appropriate spacing
Provide clear validation feedback for forms
Ensure proper labeling and accessibility for form elements
Ensure all interactive elements are keyboard accessible
Include appropriate ARIA attributes for complex components
Provide alternative text for images and icons
Support screen readers with semantic HTML elements

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

**/*.{ts,tsx,js,jsx}: Follow the TypeScript/JavaScript style guidelines
Ensure code is well-tested and documented

Files:

  • apps/api/src/modules/action/action.service.ts
  • apps/api/src/modules/tool-call/tool-call.service.ts
🧬 Code graph analysis (2)
apps/api/src/modules/action/action.service.ts (2)
apps/api/src/modules/tool/utils/token.ts (1)
  • safeParseJSON (371-377)
apps/api/src/modules/action/action.dto.ts (1)
  • sanitizeToolOutput (62-80)
apps/api/src/modules/tool-call/tool-call.service.ts (4)
packages/openapi-schema/src/types.gen.ts (2)
  • ActionStep (1709-1744)
  • ToolCallResult (3445-3502)
packages/request/src/requests/types.gen.ts (2)
  • ActionStep (1668-1703)
  • ToolCallResult (3396-3453)
apps/api/src/modules/tool/utils/token.ts (1)
  • safeParseJSON (371-377)
apps/api/src/modules/action/action.dto.ts (1)
  • sanitizeToolOutput (62-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / Build
🔇 Additional comments (6)
apps/api/src/modules/tool-call/tool-call.service.ts (1)

5-5: LGTM!

The imports for safeParseJSON and sanitizeToolOutput are properly added to support the new sanitization functionality.

Also applies to: 9-9

apps/api/src/modules/action/action.service.ts (5)

25-25: LGTM!

The import of sanitizeToolOutput is correctly added to support the sanitization feature.


36-39: LGTM!

The addition of sanitizeForDisplay to GetActionResultParams properly extends the type to support optional sanitization control.


64-64: LGTM!

The destructuring with a default value of false ensures backward compatibility and follows best practices for optional parameters.


78-80: LGTM!

The sanitizeForDisplay option is correctly propagated to the enrichment method.


171-173: LGTM!

The sanitizeForDisplay option is correctly propagated to attachToolCallsToSteps, completing the sanitization pipeline.

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
apps/api/src/modules/drive/drive.service.ts (1)

627-641: Critical bug: truncateContent removes all spaces between words.

Lines 637-638 join words with an empty string (join('')), which concatenates words without any separator. This removes all spaces between words in both the head and tail sections of truncated content, making it unreadable.

Since this method is called by the newly added truncation logic (lines 664, 754), this bug will corrupt all truncated content returned to users.

🔎 Proposed fix
-    const head = words.slice(0, headWords).join('');
-    const tail = words.slice(-tailWords).join('');
+    const head = words.slice(0, headWords).join(' ');
+    const tail = words.slice(-tailWords).join(' ');
♻️ Duplicate comments (3)
packages/agent-tools/src/builtin/index.ts (3)

526-617: Extract duplicated replaceFilePlaceholders + fix critical regex bugs.

This method is almost identical to BuiltinGenerateDoc.replaceFilePlaceholders (lines 356-441), violating DRY principles. Additionally, it contains the same critical regex bugs:

  1. Line 539: hasFile = content.includes('file://df-') incorrectly matches file-content:// URLs
  2. Line 548: Pattern lacks negative lookbehind to exclude file-content:// prefix
  3. Line 608: Replacement pattern will corrupt file-content:// URLs

Impact: Code duplication creates maintenance burden (bug fixes must be applied in multiple places), and the regex bugs cause content corruption.

🔎 Recommended refactoring + fix

Extract the common logic to a shared module-level helper:

/**
 * Replace file placeholders in content with HTTP URLs.
 * Supported formats:
 * - `file-content://df-xxx` → Direct file content URL (for images, embedded media)
 * - `file://df-xxx` → Share page URL (for links)
 */
async function replaceFilePlaceholders(
  content: string,
  reflyService: ReflyService,
  user: User,
  logContext: string,
): Promise<string> {
  if (!content) {
    return content;
  }

  const hasFileContent = content.includes('file-content://df-');
  const hasFile = /(?<!file-content:)\/\/df-/.test(content);

  if (!hasFileContent && !hasFile) {
    return content;
  }

  try {
    const contentMatchPattern = /file-content:\/\/(df-[a-z0-9]+)/gi;
    const shareMatchPattern = /(?<!file-content:)file:\/\/(df-[a-z0-9]+)/gi;

    const contentMatches = Array.from(content.matchAll(contentMatchPattern));
    const shareMatches = Array.from(content.matchAll(shareMatchPattern));

    const allFileIds = new Set<string>();
    for (const [, fileId] of contentMatches) {
      allFileIds.add(fileId);
    }
    for (const [, fileId] of shareMatches) {
      allFileIds.add(fileId);
    }

    if (allFileIds.size === 0) {
      return content;
    }

    const uniqueFileIds = Array.from(allFileIds);
    const urlResults = await Promise.all(
      uniqueFileIds.map(async (fileId) => {
        try {
          const { url, contentUrl } = await reflyService.createShareForDriveFile(user, fileId);
          return { fileId, shareUrl: url, contentUrl };
        } catch (error) {
          console.error(`[${logContext}] Failed to create share URL for ${fileId}:`, error);
          return { fileId, shareUrl: null, contentUrl: null };
        }
      }),
    );

    const shareUrlMap = new Map<string, string>();
    const contentUrlMap = new Map<string, string>();

    for (const { fileId, shareUrl, contentUrl } of urlResults) {
      if (shareUrl) {
        shareUrlMap.set(fileId, shareUrl);
      }
      if (contentUrl) {
        contentUrlMap.set(fileId, contentUrl);
      }
    }

    let result = content;
    result = result.replace(
      /file-content:\/\/(df-[a-z0-9]+)/gi,
      (match, fileId: string) => contentUrlMap.get(fileId) ?? match,
    );
    result = result.replace(
      /(?<!file-content:)file:\/\/(df-[a-z0-9]+)/gi,
      (match, fileId: string) => shareUrlMap.get(fileId) ?? match,
    );

    return result;
  } catch (error) {
    console.error(`[${logContext}] Error replacing file placeholders:`, error);
    return content;
  }
}

Then update both classes:

// In BuiltinGenerateDoc
private async replaceFilePlaceholders(content: string): Promise<string> {
  const { reflyService, user } = this.params;
  return replaceFilePlaceholders(content, reflyService, user, 'BuiltinGenerateDoc');
}

// In BuiltinGenerateCodeArtifact
private async replaceFilePlaceholders(content: string): Promise<string> {
  const { reflyService, user } = this.params;
  return replaceFilePlaceholders(content, reflyService, user, 'BuiltinGenerateCodeArtifact');
}

Note: BuiltinSendEmail has slightly different logic (calls readFile first), so it may need separate handling or optional parameters.

As per past review comments on lines 526-617 and 361-464.


706-706: Same critical regex bug in BuiltinSendEmail.

Lines 706 and 767 use patterns without negative lookbehinds, causing the same content corruption issue:

  • Line 706: Pattern will match file://df-xxx inside file-content://df-xxx
  • Line 767: Replacement will corrupt file-content:// URLs
🔎 Proposed fix
     // Match both formats: file-content://df-xxx and file://df-xxx
     const contentMatchPattern = /file-content:\/\/(df-[a-zA-Z0-9]+)/g;
-    const shareMatchPattern = /file:\/\/(df-[a-zA-Z0-9]+)/g;
+    const shareMatchPattern = /(?<!file-content:)file:\/\/(df-[a-zA-Z0-9]+)/g;

And:

     // Replace file:// with share page URLs (but not file-content://)
     result = result.replace(
-      /file:\/\/(df-[a-zA-Z0-9]+)/g,
+      /(?<!file-content:)file:\/\/(df-[a-zA-Z0-9]+)/g,
       (match, fileId: string) => shareUrlMap.get(fileId) ?? match,
     );

As per past review comments.

Also applies to: 767-767


432-434: Same regex bug at replacement stage.

Line 432 uses the same pattern without negative lookbehind, causing actual content corruption during replacement. This will replace file://df-xxx inside file-content://df-xxx URLs with share URLs, breaking the content URLs.

Apply the same fix as suggested for line 372:

       // Replace file://df-xxx with share page URLs (but not file-content://)
       result = result.replace(
-        /file:\/\/(df-[a-z0-9]+)/gi,
+        /(?<!file-content:)file:\/\/(df-[a-z0-9]+)/gi,
         (match, fileId: string) => shareUrlMap.get(fileId) ?? match,
       );
🧹 Nitpick comments (2)
apps/api/src/modules/drive/drive.service.ts (2)

1452-1474: Consider consistent error handling across unified methods.

Lines 1454-1458 log all externalOss.statObject errors at debug level without checking error codes. However, getUnifiedFileStream (lines 1556-1562) filters errors and logs warnings only for errors that are NOT "not found" errors.

For consistency and better diagnostics, consider applying the same error filtering pattern here: log expected "not found" errors at debug level and unexpected errors at warn level.

🔎 Proposed refinement for consistency
    // Step 1: Check if file exists in externalOss (public)
    let externalObjectInfo: Awaited<ReturnType<typeof this.externalOss.statObject>> | null = null;
    try {
      externalObjectInfo = await this.externalOss.statObject(storageKey);
    } catch (error) {
-     this.logger.debug(`External OSS stat failed for ${storageKey}: ${error.message}`);
+     // Log only unexpected errors; "not found" is expected and handled below
+     if (
+       error?.code !== 'NoSuchKey' &&
+       error?.code !== 'NotFound' &&
+       !error?.message?.includes('The specified key does not exist')
+     ) {
+       this.logger.warn(`Error checking externalOss for ${fileId}: ${error.message}`);
+     }
    }

606-607: Optional: horizontal rule regex accepts mixed characters.

Line 607's regex [-_=]{4,} matches 4+ of any combination of dashes, underscores, or equals, including mixed patterns like --__ or -_-_. Standard markdown horizontal rules use the same character repeated (---, ___, or ***).

If strict markdown compliance is desired, consider restricting to same-character sequences. Otherwise, this is acceptable as a more permissive normalization.

🔎 Alternative regex for strict markdown compliance
        // Normalize excessive horizontal rules (4+ dashes/underscores/equals) to standard markdown hr
-       .replace(/^[ \t]*[-_=]{4,}[ \t]*$/gm, '---')
+       .replace(/^[ \t]*(-{4,}|_{4,}|={4,})[ \t]*$/gm, '---')

This ensures each horizontal rule uses only one type of character.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ae7192 and c3e392e.

📒 Files selected for processing (2)
  • apps/api/src/modules/drive/drive.service.ts (5 hunks)
  • packages/agent-tools/src/builtin/index.ts (9 hunks)
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts,jsx,tsx}: Always use optional chaining (?.) when accessing object properties
Always use nullish coalescing (??) or default values for potentially undefined values
Always check array existence before using array methods
Always validate object properties before destructuring
Always use single quotes for string literals in JavaScript/TypeScript code

**/*.{js,ts,jsx,tsx}: Use semicolons at the end of statements
Include spaces around operators (e.g., a + b instead of a+b)
Always use curly braces for control statements
Place opening braces on the same line as their statement

**/*.{js,ts,jsx,tsx}: Group import statements in order: React/framework libraries, third-party libraries, internal modules, relative path imports, type imports, style imports
Sort imports alphabetically within each import group
Leave a blank line between import groups
Extract complex logic into custom hooks
Use functional updates for state (e.g., setCount(prev => prev + 1))
Split complex state into multiple state variables rather than single large objects
Use useReducer for complex state logic instead of multiple useState calls

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

**/*.{js,ts,tsx,jsx,py,java,cpp,c,cs,rb,go,rs,php,swift,kt,scala,r,m,mm,sql}: All code comments MUST be written in English
All variable names, function names, class names, and other identifiers MUST use English words
Comments should be concise and explain 'why' rather than 'what'
Use proper grammar and punctuation in comments
Keep comments up-to-date when code changes
Document complex logic, edge cases, and important implementation details
Use clear, descriptive names that indicate purpose
Avoid abbreviations unless they are universally understood

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/00-language-priority.mdc)

Use JSDoc style comments for functions and classes in JavaScript/TypeScript

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/01-code-style.mdc)

**/*.{js,jsx,ts,tsx}: Use single quotes for string literals in TypeScript/JavaScript
Always use optional chaining (?.) when accessing object properties in TypeScript/JavaScript
Always use nullish coalescing (??) or default values for potentially undefined values in TypeScript/JavaScript
Always check array existence before using array methods in TypeScript/JavaScript
Validate object properties before destructuring in TypeScript/JavaScript
Use ES6+ features like arrow functions, destructuring, and spread operators in TypeScript/JavaScript
Avoid magic numbers and strings - use named constants in TypeScript/JavaScript
Use async/await instead of raw promises for asynchronous code in TypeScript/JavaScript

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/03-typescript-guidelines.mdc)

**/*.{ts,tsx}: Avoid using any type whenever possible - use unknown type instead with proper type guards
Always define explicit return types for functions, especially for public APIs
Prefer extending existing types over creating entirely new types
Use TypeScript utility types (Partial<T>, Pick<T, K>, Omit<T, K>, Readonly<T>, Record<K, T>) to derive new types
Use union types and intersection types to combine existing types
Always import types explicitly using the import type syntax
Group type imports separately from value imports
Minimize creating local type aliases for imported types

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{js,ts,jsx,tsx,css,json}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Maximum line length of 100 characters

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{js,ts,jsx,tsx,css,json,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

Use 2 spaces for indentation, no tabs

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{js,ts,jsx,tsx,css,json,yml,yaml,md}

📄 CodeRabbit inference engine (.cursor/rules/04-code-formatting.mdc)

No trailing whitespace at the end of lines

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
apps/api/src/**/*.{controller,service}.ts

📄 CodeRabbit inference engine (.cursor/rules/06-api-structure.mdc)

Implement proper error handling in API modules

Files:

  • apps/api/src/modules/drive/drive.service.ts
**/*.{css,scss,sass,less,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/09-design-system.mdc)

**/*.{css,scss,sass,less,js,jsx,ts,tsx}: Primary color (#155EEF) should be used for main brand color in buttons, links, and accents
Error color (#F04438) should be used for error states and destructive actions
Success color (#12B76A) should be used for success states and confirmations
Warning color (#F79009) should be used for warnings and important notifications
Info color (#0BA5EC) should be used for informational elements

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

**/*.{tsx,ts}: Use the translation wrapper component and useTranslation hook in components
Ensure all user-facing text is translatable

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{tsx,ts,json}

📄 CodeRabbit inference engine (.cursor/rules/09-i18n-guidelines.mdc)

Support dynamic content with placeholders in translations

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{tsx,ts,jsx,js,vue,css,scss,less}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue,css,scss,less}: Use the primary blue (#155EEF) for main UI elements, CTAs, and active states
Use red (#F04438) only for errors, warnings, and destructive actions
Use green (#12B76A) for success states and confirmations
Use orange (#F79009) for warning states and important notifications
Use blue (#0BA5EC) for informational elements
Primary buttons should be solid with the primary color
Secondary buttons should have a border with transparent or light background
Danger buttons should use the error color
Use consistent padding, border radius, and hover states for all buttons
Follow fixed button sizes based on their importance and context
Use consistent border radius (rounded-lg) for all cards
Apply light shadows (shadow-sm) for card elevation
Maintain consistent padding inside cards (p-4 or p-6)
Use subtle borders for card separation
Ensure proper spacing between card elements
Apply consistent styling to all form inputs
Use clear visual indicators for focus, hover, and error states in form elements
Apply proper spacing between elements using 8px, 16px, 24px increments
Ensure proper alignment of elements (left, center, or right)
Use responsive layouts that work across different device sizes
Maintain a minimum contrast ratio of 4.5:1 for text

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{tsx,ts,jsx,js,vue}

📄 CodeRabbit inference engine (.cursor/rules/11-ui-design-patterns.mdc)

**/*.{tsx,ts,jsx,js,vue}: Include appropriate loading states for async actions in buttons
Group related form elements with appropriate spacing
Provide clear validation feedback for forms
Ensure proper labeling and accessibility for form elements
Ensure all interactive elements are keyboard accessible
Include appropriate ARIA attributes for complex components
Provide alternative text for images and icons
Support screen readers with semantic HTML elements

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/08-contributing-guidelines.mdc)

**/*.{ts,tsx,js,jsx}: Follow the TypeScript/JavaScript style guidelines
Ensure code is well-tested and documented

Files:

  • apps/api/src/modules/drive/drive.service.ts
  • packages/agent-tools/src/builtin/index.ts
**/index.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/05-code-organization.mdc)

Use index files to export multiple components from a directory

Files:

  • packages/agent-tools/src/builtin/index.ts
🧠 Learnings (3)
📚 Learning: 2025-11-25T03:04:12.836Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/06-api-structure.mdc:0-0
Timestamp: 2025-11-25T03:04:12.836Z
Learning: Applies to apps/api/src/**/*.{controller,service}.ts : Implement proper error handling in API modules

Applied to files:

  • apps/api/src/modules/drive/drive.service.ts
📚 Learning: 2025-11-25T03:04:41.334Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/09-i18n-guidelines.mdc:0-0
Timestamp: 2025-11-25T03:04:41.334Z
Learning: Applies to **/*.{tsx,ts,json} : Support dynamic content with placeholders in translations

Applied to files:

  • packages/agent-tools/src/builtin/index.ts
📚 Learning: 2025-11-26T05:04:26.523Z
Learnt from: CR
Repo: refly-ai/refly PR: 0
File: .cursor/rules/08-contributing-guidelines.mdc:0-0
Timestamp: 2025-11-26T05:04:26.523Z
Learning: Applies to **/*.{md,mdx} : Update documentation if needed

Applied to files:

  • packages/agent-tools/src/builtin/index.ts
🧬 Code graph analysis (1)
apps/api/src/modules/drive/drive.service.ts (2)
packages/openapi-schema/src/types.gen.ts (1)
  • User (474-483)
packages/utils/src/file.ts (1)
  • getSafeMimeType (90-109)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build / Build
🔇 Additional comments (5)
packages/agent-tools/src/builtin/index.ts (3)

294-300: LGTM! Clear documentation for placeholder formats.

The updated schema description clearly distinguishes between file-content:// and file:// placeholder formats with practical examples.


452-460: LGTM! Clear documentation improvements.

The updated schema descriptions and SVG notes provide clear guidance on placeholder usage and technical requirements for SVG elements.

Also applies to: 479-480


361-388: Critical regex bug: Pattern matches inside file-content:// URLs.

Lines 363 and 372 have critical correctness issues:

  1. Line 363: content.includes('file://df-') returns true for file-content://df-xxx because it contains the substring file://df-.
  2. Line 372: Pattern /file:\/\/(df-[a-z0-9]+)/gi matches file://df-xxx inside file-content://df-xxx strings.

Impact: Content like <img src="file-content://df-abc123"> will be incorrectly parsed, extracting df-abc123 as a share URL candidate and corrupting the replacement at line 432.

This issue was flagged in past review comments, which suggested using a negative lookbehind. The current changes removed the lookbehind entirely, making the problem worse.

🔎 Proposed fix using negative lookbehind
     // Check for placeholder formats
     const hasFileContent = content.includes('file-content://df-');
-    const hasFile = content.includes('file://df-');
+    const hasFile = /(?<!file-content:)\/\/df-/.test(content);
 
     if (!hasFileContent && !hasFile) {
       return content;
     }
 
     try {
       // Match both formats
       const contentMatchPattern = /file-content:\/\/(df-[a-z0-9]+)/gi;
-      const shareMatchPattern = /file:\/\/(df-[a-z0-9]+)/gi;
+      const shareMatchPattern = /(?<!file-content:)file:\/\/(df-[a-z0-9]+)/gi;

Likely an incorrect or invalid review comment.

apps/api/src/modules/drive/drive.service.ts (2)

748-750: Verify type compatibility: putObject expects Buffer.

Line 750 passes processedContent (a string) to putObject, but other calls in this file (e.g., line 475) pass a Buffer. Verify that the OSS service handles automatic string-to-Buffer conversion, or convert explicitly.

Based on coding guidelines, ensure type consistency across OSS operations.

🔎 Suggested fix if conversion is needed
      // Store normalized content to OSS (without truncation, so we can adjust limits later)
      const contentStorageKey = `drive-parsed/${user.uid}/${fileId}.txt`;
-     await this.internalOss.putObject(contentStorageKey, processedContent);
+     await this.internalOss.putObject(contentStorageKey, Buffer.from(processedContent, 'utf-8'));

1503-1589: Well-structured unified file access with proper ownership validation.

The implementation correctly handles the public-first, then private-with-ownership access pattern:

  • Attempts public access from externalOss without auth requirements
  • Falls back to private access with user ownership validation
  • Distinguishes "not found" errors from other errors (lines 1556-1562)
  • Returns isPublic flag for downstream handling

This provides a clean abstraction for unified file access across public and private buckets.

Comment on lines +756 to +757
// Calculate word count from truncated content
const wordCount = readingTime(truncatedContent).words;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify intent: word count calculated from truncated content.

Line 757 calculates wordCount from the truncated content rather than the full processedContent. This means the cached wordCount represents the truncated version's length, not the full document's length.

If the intent is to track the full document's word count for usage metrics or display purposes, this should use processedContent instead. If truncated count is intentional, consider adding a comment explaining why.

🔎 Proposed fix if full document count is needed
      // Truncate content for return (but not for storage)
      const maxWords = this.config.get<number>('drive.maxContentWords') || 3000;
      const truncatedContent = this.truncateContent(processedContent, maxWords);

-     // Calculate word count from truncated content
-     const wordCount = readingTime(truncatedContent).words;
+     // Calculate word count from full content for accurate document metrics
+     const wordCount = readingTime(processedContent).words;
🤖 Prompt for AI Agents
In apps/api/src/modules/drive/drive.service.ts around lines 756-757, the code
computes wordCount from truncatedContent which causes the cached word count to
reflect the truncated snippet rather than the full processedContent; if the
intent is to measure the full document change the call to use processedContent
(i.e., wordCount = readingTime(processedContent).words) and ensure any
cached/returned metadata uses that value, otherwise add a brief inline comment
clarifying that using truncatedContent is intentional and why (e.g., for
preview-based metrics) so future readers aren’t confused.

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