Skip to content

Conversation

minimaluminium
Copy link
Member

no issues

@github-actions github-actions bot added the community [triage] Community features and bugs label Oct 8, 2025
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a profile preview hover card and integrates it into feed and suggested profiles: new ProfilePreviewHoverCard component, APAvatar no longer sets title, and hover-card UI from Shade is exported. Updates routing and profile UI to support profile/:handle/:tab, normalizes tab/handle via actualHandle/activeTab props, and converts ProfilePage to controlled tabs using navigation. Extends useAccountForUser with an optional enabled flag and changes a suggested_profiles cache key. Adds a 'preview' feature flag and hardens openLinksInNewTab against javascript:/data:/vbscript: URLs. Bumps admin-x-activitypub version and adds Radix hover-card dependency.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description “no issues” provides no information about the actual changes in the pull request and is unrelated to the modifications detailed in the raw summary, making it impossible to understand the intent or scope of the updates. Please update the description to include a brief summary of the key changes, such as the addition of routing support for profile tabs, updates to Profile.tsx and ProfilePage.tsx, and any new components or hooks introduced.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary change by indicating that routing support for profile tabs was added to the ActivityPub application, which aligns directly with the modifications to Profile.tsx, ProfilePage.tsx, and related route definitions. It is clear and specific without extraneous details, enabling a quick understanding of the pull request’s main focus.
✨ 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 ap-profile-tab-routing

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.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)

143-182: Fix likes-tab handling for other profiles

When you visit a path like /profile/alice/likes, the normalization above sets activeTab to 'likes' even though ProfilePage hides that tab whenever actualHandle is defined. The tab list then renders without a matching trigger/content, leaving the main pane empty (no posts, no likes, no messaging). Please fall back to 'posts' (or redirect) whenever a non-current-user profile requests likes, so we never publish a state that can’t render.

Apply this diff to constrain likes to the current user:

-    } else if (params.tab) {
-        // We have both handle and tab
-        if (validTabs.includes(params.tab)) {
-            activeTab = params.tab;
-        }
-        // activeTab stays 'posts' if tab is invalid
-    }
+    } else if (params.tab) {
+        // We have both handle and tab
+        if (params.tab === 'likes' && params.handle) {
+            // Likes are only available for the current user; fall back to posts
+            activeTab = 'posts';
+        } else if (validTabs.includes(params.tab)) {
+            activeTab = params.tab;
+        }
+        // activeTab stays 'posts' if tab is invalid
+    }
🧹 Nitpick comments (1)
apps/admin-x-activitypub/src/utils/content-formatters.ts (1)

103-110: Consider removing or replacing sanitized links.

The security check correctly blocks dangerous protocols (javascript:, data:, vbscript:), but removing only the href attribute leaves a non-functional anchor element that still receives target="_blank" and rel="noopener noreferrer". This creates a visual link that doesn't navigate anywhere.

Consider either:

  1. Removing the entire anchor element and replacing it with plain text
  2. Replacing the link with a warning message
  3. Skipping the target/rel attributes if href was removed

Apply this diff to skip adding attributes to sanitized links:

 // Add target="_blank" and rel attributes to each link
 for (let i = 0; i < links.length; i++) {
     const href = links[i].getAttribute('href') || '';
     // Block javascript:, data:, and other dangerous protocols
     if (href.match(/^\s*(javascript|data|vbscript):/i)) {
         links[i].removeAttribute('href');
+        continue;
     }
     links[i].setAttribute('target', '_blank');
     links[i].setAttribute('rel', 'noopener noreferrer');
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c7aa1a and cf856ab.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (14)
  • apps/admin-x-activitypub/package.json (1 hunks)
  • apps/admin-x-activitypub/src/components/feed/FeedItem.tsx (3 hunks)
  • apps/admin-x-activitypub/src/components/global/APAvatar.tsx (1 hunks)
  • apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (1 hunks)
  • apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (3 hunks)
  • apps/admin-x-activitypub/src/lib/feature-flags.tsx (1 hunks)
  • apps/admin-x-activitypub/src/routes.tsx (1 hunks)
  • apps/admin-x-activitypub/src/utils/content-formatters.ts (1 hunks)
  • apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx (2 hunks)
  • apps/admin-x-activitypub/src/views/Profile/Profile.tsx (2 hunks)
  • apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (6 hunks)
  • apps/shade/package.json (1 hunks)
  • apps/shade/src/components/ui/hover-card.tsx (1 hunks)
  • apps/shade/src/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
apps/shade/src/components/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

apps/shade/src/components/ui/**/*.{ts,tsx}: Place new UI components under src/components/ui
Do not rename ShadCN-generated files purely for casing; keep kebab-case filenames while component identifiers use PascalCase

Files:

  • apps/shade/src/components/ui/hover-card.tsx
apps/shade/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

Use camelCase for functions and variables

Files:

  • apps/shade/src/components/ui/hover-card.tsx
  • apps/shade/src/index.ts
apps/shade/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

apps/shade/src/components/**/*.{ts,tsx}: Use CVA (class-variance-authority) for variants where useful
Prefer composable components and compound subcomponents (e.g., Header.Title, Header.Meta, Header.Actions) over heavy prop configs; attach parts as static properties and export as named exports

Files:

  • apps/shade/src/components/ui/hover-card.tsx
apps/shade/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

Use the @ alias for internal imports (e.g., @/lib/utils)

Files:

  • apps/shade/src/components/ui/hover-card.tsx
  • apps/shade/src/index.ts
apps/shade/src/index.ts

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

Export new UI components from src/index.ts

Files:

  • apps/shade/src/index.ts
🧠 Learnings (3)
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/components/**/*.{tsx} : Expose sensible Radix/HTML props and always include className merged via cn(...)

Applied to files:

  • apps/shade/src/components/ui/hover-card.tsx
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/index.ts : Export new UI components from src/index.ts

Applied to files:

  • apps/shade/src/index.ts
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/components/ui/**/*.{ts,tsx} : Place new UI components under src/components/ui

Applied to files:

  • apps/shade/src/index.ts
🧬 Code graph analysis (4)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
  • useAccountForUser (1619-1631)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (4)
apps/admin-x-framework/src/api/activitypub.ts (1)
  • ActorProperties (29-63)
apps/admin-x-activitypub/src/lib/feature-flags.tsx (1)
  • useFeatureFlags (84-90)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
  • useAccountForUser (1619-1631)
apps/admin-x-activitypub/src/utils/content-formatters.ts (2)
  • openLinksInNewTab (93-113)
  • stripHtml (1-52)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
apps/admin-x-activitypub/src/api/activitypub.ts (1)
  • Account (9-30)
apps/admin-x-activitypub/src/components/feed/FeedItem.tsx (2)
apps/admin-x-activitypub/src/utils/handle-profile-click.ts (1)
  • handleProfileClick (5-12)
apps/admin-x-activitypub/src/utils/render-timestamp.tsx (1)
  • renderTimestamp (15-40)
🪛 ast-grep (0.39.5)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx

[warning] 170-170: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx

[error] 171-171: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

⏰ 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). (3)
  • GitHub Check: Setup
  • GitHub Check: Build & Push
  • GitHub Check: Setup
🔇 Additional comments (8)
apps/shade/package.json (1)

78-78: LGTM!

The dependency addition for Radix UI's hover-card component is appropriate and follows the existing pattern of using caret versioning for Radix UI packages.

apps/shade/src/index.ts (1)

17-17: LGTM!

The export for the new hover-card UI component is correctly placed alphabetically and follows the established pattern for exporting UI components from the index.

Based on learnings.

apps/admin-x-activitypub/src/components/global/APAvatar.tsx (1)

190-190: LGTM!

The removal of the title attribute is appropriate since profile information is now provided through the ProfilePreviewHoverCard component, which offers a richer and more accessible user experience than browser-native tooltips.

apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (2)

1619-1624: LGTM!

The optional enabled flag addition to useAccountForUser provides fine-grained control over query execution while maintaining backward compatibility through the default value. This aligns well with the ProfilePreviewHoverCard use case where account data may need to be fetched conditionally based on user interaction.


1117-1117: LGTM!

The cache key updates from 'suggested_profiles_json' to 'suggested_profiles' align with the query key definition at line 78 and improve consistency across the codebase. Using exact: false correctly matches all suggested profile queries regardless of their handle and limit parameters.

Also applies to: 1281-1281

apps/shade/src/components/ui/hover-card.tsx (1)

1-27: LGTM!

The HoverCard component implementation follows best practices:

  • Uses forwardRef pattern for proper ref forwarding
  • Exposes sensible Radix UI props with appropriate defaults
  • Merges className via the cn utility as per coding guidelines
  • Sets displayName for better debugging experience
  • Provides comprehensive styling with animations and proper positioning

Based on coding guidelines.

apps/admin-x-activitypub/src/routes.tsx (1)

83-87: LGTM!

The new route for profile/:handle/:tab correctly extends the profile routing to support tab-based navigation. The placement after the existing profile routes and the reuse of the same Profile component and pageTitle is appropriate and maintains consistency.

apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx (1)

141-150: LGTM!

The ProfilePreviewHoverCard integration enhances the user experience by providing rich profile previews on hover. The wrapping preserves existing click navigation and only applies when profiles are loaded, maintaining the loading state skeleton behavior.

Copy link

codecov bot commented Oct 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.50%. Comparing base (f709a1a) to head (06f2fd4).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25090      +/-   ##
==========================================
- Coverage   71.50%   71.50%   -0.01%     
==========================================
  Files        1531     1531              
  Lines      115939   115939              
  Branches    13985    13986       +1     
==========================================
- Hits        82907    82903       -4     
+ Misses      32044    32028      -16     
- Partials      988     1008      +20     
Flag Coverage Δ
admin-tests 48.08% <ø> (-0.03%) ⬇️
e2e-tests 71.50% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This component will be used for the profile preview card when hovering
As we now display profile preview when avatars are hovered, we do not need to display the user info in generic title attribute
- added "enabled" option to useAccountForUser() hook, so that it supports on demand requests which is used for profile preview card
- updated follow/unfollow caches, so that they are updated correctly when the follow button is clicked from the profile preview card
- this component uses HoverCard component from shadcn under the hood
- the children of the component will be used as a trigger
- when the trigger is hovered, it first makes a request for fetching account data, and then displays the data in the preview
- if the data is not ready yet, the preview card will display skeleton loader
This was recommended by coderabbit. As the amount of change was small, including the fix right in the PR instead of creating a new issue
@minimaluminium minimaluminium force-pushed the ap-profile-tab-routing branch from cf856ab to 06f2fd4 Compare October 9, 2025 08:06
if (!params.tab && params.handle && validTabs.includes(params.handle)) {
// This means the URL is /profile/likes (current user with tab)
activeTab = params.handle;
actualHandle = undefined;
Copy link

Choose a reason for hiding this comment

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

Bug: Tab Routing Fails for Direct Navigation

The tab routing logic has a couple of issues. The validTabs array is missing 'posts', which means direct URL navigation to the posts tab won't work. Also, if a user's handle matches a tab name (like 'likes'), the system incorrectly interprets it as a tab for the current user, making those profiles inaccessible.

Fix in Cursor Fix in Web

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)

147-152: Routing conflict persists: tab names shadow user handles.

This logic still misinterprets a user handle that matches a tab name (e.g., a user named 'likes') as a tab for the current user, preventing access to that user's profile.

🧹 Nitpick comments (3)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (2)

32-38: Consider simplifying handle construction logic.

The fallback logic for constructing targetHandle has multiple paths. While functional, the try-catch block could be simplified to improve readability.

Consider this refactor:

-    let targetHandle = actor?.handle;
-    if (!targetHandle && actor?.preferredUsername && actor?.id) {
-        try {
-            targetHandle = `@${actor.preferredUsername}@${new URL(actor.id).hostname.replace(/^www\./, '')}`;
-        } catch {
-            targetHandle = null;
-        }
-    }
+    const targetHandle = actor?.handle || (
+        actor?.preferredUsername && actor?.id
+            ? (() => {
+                try {
+                    return `@${actor.preferredUsername}@${new URL(actor.id).hostname.replace(/^www\./, '')}`;
+                } catch {
+                    return null;
+                }
+            })()
+            : null
+    );

52-60: Refetch logic may cause unnecessary network calls.

The useEffect triggers a refetch whenever shouldFetch becomes true and data is incomplete. However, this could cause repeated fetches if the query keeps failing or returning incomplete data.

Consider adding a retry limit or debouncing the refetch:

+    const [retryCount, setRetryCount] = useState(0);
+    const MAX_RETRIES = 3;
+
     useEffect(() => {
-        if (!shouldFetch || !targetHandle) {
+        if (!shouldFetch || !targetHandle || retryCount >= MAX_RETRIES) {
             return;
         }
 
         if (!hasCompleteAccountData && !accountQuery.isFetching && !accountQuery.isLoading) {
             accountQuery.refetch({cancelRefetch: false});
+            setRetryCount(prev => prev + 1);
         }
-    }, [accountQuery, accountQuery.isFetching, accountQuery.isLoading, accountQuery.refetch, hasCompleteAccountData, shouldFetch, targetHandle]);
+    }, [accountQuery, accountQuery.isFetching, accountQuery.isLoading, accountQuery.refetch, hasCompleteAccountData, shouldFetch, targetHandle, retryCount]);
+
+    // Reset retry count when hover ends
+    useEffect(() => {
+        if (!shouldFetch) {
+            setRetryCount(0);
+        }
+    }, [shouldFetch]);
apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (1)

52-54: Optimize currentUser query with conditional fetching.

The currentAccountQuery always executes, even when viewing the current user's profile (actualHandle is undefined). This wastes a network request since we already know it's the current user.

Apply this diff to enable the query conditionally:

-    const currentAccountQuery = useAccountForUser('index', 'me');
-    const {data: currentUser} = actualHandle ? currentAccountQuery : {data: undefined};
+    const currentAccountQuery = useAccountForUser('index', 'me', {enabled: !!actualHandle});
+    const {data: currentUser} = currentAccountQuery;
     const isCurrentUser = actualHandle === currentUser?.handle || !actualHandle;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf856ab and 06f2fd4.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (14)
  • apps/admin-x-activitypub/package.json (1 hunks)
  • apps/admin-x-activitypub/src/components/feed/FeedItem.tsx (3 hunks)
  • apps/admin-x-activitypub/src/components/global/APAvatar.tsx (1 hunks)
  • apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (1 hunks)
  • apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (3 hunks)
  • apps/admin-x-activitypub/src/lib/feature-flags.tsx (1 hunks)
  • apps/admin-x-activitypub/src/routes.tsx (1 hunks)
  • apps/admin-x-activitypub/src/utils/content-formatters.ts (1 hunks)
  • apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx (2 hunks)
  • apps/admin-x-activitypub/src/views/Profile/Profile.tsx (2 hunks)
  • apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (6 hunks)
  • apps/shade/package.json (1 hunks)
  • apps/shade/src/components/ui/hover-card.tsx (1 hunks)
  • apps/shade/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/admin-x-activitypub/src/components/feed/FeedItem.tsx
  • apps/shade/src/components/ui/hover-card.tsx
  • apps/admin-x-activitypub/package.json
  • apps/admin-x-activitypub/src/lib/feature-flags.tsx
🧰 Additional context used
📓 Path-based instructions (3)
apps/shade/src/index.ts

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

Export new UI components from src/index.ts

Files:

  • apps/shade/src/index.ts
apps/shade/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

Use camelCase for functions and variables

Files:

  • apps/shade/src/index.ts
apps/shade/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/shade/AGENTS.md)

Use the @ alias for internal imports (e.g., @/lib/utils)

Files:

  • apps/shade/src/index.ts
🧠 Learnings (2)
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/index.ts : Export new UI components from src/index.ts

Applied to files:

  • apps/shade/src/index.ts
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/components/ui/**/*.{ts,tsx} : Place new UI components under src/components/ui

Applied to files:

  • apps/shade/src/index.ts
🧬 Code graph analysis (4)
apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (1)
apps/shade/src/components/ui/tabs.tsx (4)
  • Tabs (296-296)
  • TabsList (297-297)
  • TabsTrigger (298-298)
  • TabsContent (300-300)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
  • useAccountForUser (1619-1631)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (5)
apps/admin-x-framework/src/api/activitypub.ts (1)
  • ActorProperties (29-63)
apps/admin-x-activitypub/src/lib/feature-flags.tsx (1)
  • useFeatureFlags (84-90)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
  • useAccountForUser (1619-1631)
apps/admin-x-activitypub/src/utils/content-formatters.ts (2)
  • openLinksInNewTab (93-113)
  • stripHtml (1-52)
apps/admin-x-activitypub/src/utils/handle-profile-click.ts (1)
  • handleProfileClick (5-12)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
apps/admin-x-activitypub/src/api/activitypub.ts (1)
  • Account (9-30)
🪛 ast-grep (0.39.5)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx

[warning] 170-170: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx

[error] 171-171: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

⏰ 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). (3)
  • GitHub Check: Setup
  • GitHub Check: Setup
  • GitHub Check: Build & Push
🔇 Additional comments (13)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (2)

1619-1624: LGTM! Good pattern for conditional fetching.

The addition of an optional enabled flag to useAccountForUser allows callers to control when queries execute. This is correctly implemented and follows React Query best practices.


1117-1117: Cache key migration complete: no remaining suggested_profiles_json references detected.

apps/shade/package.json (1)

78-78: LGTM! Dependency addition aligns with new hover card feature.

The @radix-ui/react-hover-card dependency is required for the new ProfilePreviewHoverCard component. The version constraint is consistent with other Radix UI dependencies in the project.

apps/admin-x-activitypub/src/routes.tsx (1)

83-87: LGTM! Route addition enables tab-based navigation.

The new route profile/:handle/:tab correctly extends profile routing to support URL-based tab selection. The route is properly positioned after the less specific profile/:handle route.

apps/shade/src/index.ts (1)

17-17: LGTM! Export follows project conventions.

The hover-card component export is correctly placed in alphabetical order and follows the established pattern for UI component exports.

Based on coding guidelines.

apps/admin-x-activitypub/src/utils/content-formatters.ts (1)

103-107: LGTM! Security enhancement prevents XSS attacks.

The addition of protocol sanitization blocks dangerous URI schemes (javascript:, data:, vbscript:) before applying target="_blank". This is a critical security improvement that prevents XSS attacks through malicious href attributes.

apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx (1)

141-150: LGTM! ProfilePreviewHoverCard integration enhances UX.

The APAvatar is now wrapped with ProfilePreviewHoverCard, enabling hover-based profile previews for suggested profiles. The align='center' prop is appropriate for this centered layout.

apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (1)

95-177: LGTM! Component implements hover preview effectively.

The ProfilePreviewHoverCard component is well-structured and implements the hover preview functionality correctly. Key strengths:

  • Feature flag gating prevents unintended usage
  • Conditional fetching optimizes performance
  • Proper error handling for avatar load failures
  • Click event propagation is properly managed
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (3)

161-161: LGTM: Correct account lookup.

Using actualHandle || 'me' correctly handles both viewing other users and the current user.


178-181: LGTM: Consistent handle propagation to tabs.

The actualHandle is correctly passed to PostsTab, FollowingTab, and FollowersTab, while LikesTab appropriately receives no handle since likes are always for the current user.


185-186: LGTM: New props correctly wired.

activeTab and actualHandle are properly passed to ProfilePage for controlled tab navigation.

apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (2)

253-258: LGTM: Well-implemented controlled tabs with navigation.

The controlled tabs correctly:

  • Use a key to force re-render when switching profiles
  • Navigate to the appropriate URL on tab change
  • Use replace: true to prevent history pollution
  • Handle the 'posts' tab by navigating to the base path

261-287: LGTM: Correct conditional rendering of likes tab.

The likes tab is appropriately hidden when viewing other users' profiles, as likes are only available for the current user.

Comment on lines +170 to +171
) : bio ? (
<div dangerouslySetInnerHTML={{__html: bio}} className='leading-tight dark:text-gray-300' />
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify HTML sanitization is sufficient for XSS prevention.

The bio content is rendered using dangerouslySetInnerHTML. While openLinksInNewTab and stripHtml are applied, these utilities should be verified to ensure complete XSS protection.

The content pipeline is:

  1. stripHtml(displayData.bio, ['a']) - strips all HTML except <a> tags
  2. openLinksInNewTab() - removes dangerous protocols and adds target="_blank"

Run the following verification to confirm the sanitization is comprehensive:

Consider using a dedicated HTML sanitization library like DOMPurify for enhanced security:

import DOMPurify from 'dompurify';

const bio = displayData?.bio 
    ? DOMPurify.sanitize(openLinksInNewTab(stripHtml(displayData.bio, ['a'])), {
        ALLOWED_TAGS: ['a'],
        ALLOWED_ATTR: ['href', 'target', 'rel']
    })
    : undefined;

🏁 Script executed:

#!/bin/bash
# Verify the sanitization functions handle all edge cases
rg -A 10 "export (const|function) (stripHtml|openLinksInNewTab)" apps/admin-x-activitypub/src/utils/content-formatters.ts

Length of output: 981


🏁 Script executed:

#!/bin/bash
rg -n "export function stripHtml" -C30 apps/admin-x-activitypub/src/utils/content-formatters.ts
rg -n "export const openLinksInNewTab" -C30 apps/admin-x-activitypub/src/utils/content-formatters.ts

Length of output: 3714


Prevent XSS by using a robust HTML sanitizer
Current stripHtml + openLinksInNewTab preserve all original <a> attributes (e.g. onclick, style) and only sanitize href. Inline event handlers and other unsafe attributes remain, allowing XSS. Replace the regex‐based pipeline with a proven library (e.g. DOMPurify) configured to allow only <a> with href, target, rel.

🧰 Tools
🪛 ast-grep (0.39.5)

[warning] 170-170: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)

[error] 171-171: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🤖 Prompt for AI Agents
In apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
around lines 170-171, the current pipeline using stripHtml + openLinksInNewTab
leaves unsafe attributes (e.g. onclick, style) on tags and allows XSS; replace
that with a trusted sanitizer such as DOMPurify: import and run
DOMPurify.sanitize(bio, {ALLOWED_TAGS:
['a','b','i','strong','em','p','br','ul','ol','li'], ALLOWED_ATTR:
['href','target','rel'], FORCE_BODY: true, ADD_ATTR: []}) (or similar config)
and then transform all <a> to ensure target='_blank' and rel='noopener
noreferrer' (you can use DOMPurify hooks or a post-process step) before
injecting via dangerouslySetInnerHTML; ensure the sanitizer is applied wherever
bio is rendered and remove the regex-based openLinksInNewTab/stripHtml logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community [triage] Community features and bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant