Skip to content

Feat/social introducer#565

Draft
serensandikci wants to merge 6 commits intodevfrom
feat/social-introducer
Draft

Feat/social introducer#565
serensandikci wants to merge 6 commits intodevfrom
feat/social-introducer

Conversation

@serensandikci
Copy link
Copy Markdown
Collaborator

@serensandikci serensandikci commented Mar 25, 2026

Summary

  • IntroductionRequestFeed: refactored from card layout to flat list with inline user chips (avatar+name pill), removed narrator chip, bumped section titles, renamed CTA to "Who fits"
  • OpportunityCardInChat: dual-avatar header for introducer view showing both parties as [pic Name] → [pic Name], with backward-compatible fallback parsing from headline
  • Backend: added parties field across the full data pipeline (types, tools, discover, home graph) so introducer cards carry both party identities for the frontend

Follow-up

  • IND-208: Wire IntroductionRequestFeed to real backend data (assigned to Yanki)

Summary by CodeRabbit

  • New Features

    • Introduction requests feed with “Who fits” (send) and “Skip” actions, “{n} more” expander and “All caught up” state
    • Dual-party avatar/header display on opportunity cards when viewing as an introducer
    • Dev preview page and route to test the introduction requests component
  • Improvements

    • Refined home/chat styling: icon sizing, typography, spacing, and loading placeholder adjustments
    • Feed surfaced in chat home and quiet states

Introduce a new IntroductionRequestFeed component (mocked data) that shows network intro requests, lets users 'Check my network' (clears chat, navigates home, and sends a query via useAIChat), mark requests as sent, and dismiss them. Integrate the feed into ChatContent in two places and add a dev preview page at /dev/intro-requests. Register the new dev route in routes.tsx. Feed currently uses in-file mock data and should be replaced with an API when available.
… polish

- IntroductionRequestFeed: user chip inline component (avatar+name pill),
  remove card wrappers (flat list), remove narrator chip, bump section
  titles from text-xs to text-sm, rename "Check my network" to "Who fits"
- OpportunityCardInChat: dual-avatar header for introducer view showing
  both parties as [pic Name] → [pic Name], with fallback parsing from
  headline for existing card data without parties field
- Backend: add parties field to OpportunityCardPayload, FormattedDiscoveryCandidate,
  HomeCardItem; populate in opportunity.tools.ts, opportunity.discover.ts,
  and home.graph.ts for introducer cards

Made-with: Cursor
@serensandikci serensandikci requested a review from yanekyuk March 25, 2026 16:12
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a25a5edd-dcd5-4e5b-89b3-9e16407935e3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an introduction-requests discovery feed and dev page, injects the feed into chat empty states, updates UI skeleton/typography, and propagates a new optional parties field through opportunity types and protocol pipelines to enable dual-party introducer headers.

Changes

Cohort / File(s) Summary
Introduction Requests Dev Page & Feed
frontend/src/app/dev/intro-requests/page.tsx, frontend/src/components/IntroductionRequestFeed.tsx
New dev page and default-exported feed component with local mock data, per-request sent/dismissed state, "Who fits" action that clears/chat-navigates and marks sent, skip behavior, and expand/collapse of visible requests.
Chat Integration & UI tweaks
frontend/src/components/ChatContent.tsx
Injected IntroductionRequestFeed into home and quiet empty states; adjusted skeleton placeholder sizes and home section heading/icon typography.
Opportunity Card — Chat UI
frontend/src/components/chat/OpportunityCardInChat.tsx
Added optional parties to OpportunityCardData; renders dual-avatar header for introducer view when parties present and added headline-splitting fallback to derive parties.
Routing
frontend/src/routes.tsx
Added lazy route /dev/intro-requests pointing to the new dev page.
Frontend service types
frontend/src/services/opportunities.ts
Extended HomeViewCardItem with optional parties array to carry dual-party metadata.
Protocol: state & graphs
protocol/src/lib/protocol/states/home.state.ts, protocol/src/lib/protocol/graphs/home.graph.ts
Added optional parties to HomeCardItem and derived introducerParties in home graph for introducer-view cards when applicable.
Protocol: discovery & tools
protocol/src/lib/protocol/support/opportunity.discover.ts, protocol/src/lib/protocol/tools/opportunity.tools.ts
Extended FormattedDiscoveryCandidate with parties; enrichOpportunities and opportunity creation now populate parties for introducer-mode cards (conditioned on counterpart discovery and viewer role).
Protocol: streaming types
protocol/src/types/chat-streaming.types.ts
Added optional parties to OpportunityCardPayload for downstream rendering in chat streams.
Tests
protocol/src/lib/protocol/graphs/tests/home.graph.spec.ts
Adjusted test expectation for narratorChip name to 'Index' in a specific manual-source case.

Sequence Diagram

sequenceDiagram
    participant User
    participant IntroRequestFeed
    participant ChatContent
    participant Router
    participant Backend
    participant Database

    User->>IntroRequestFeed: View feed / click actions
    IntroRequestFeed->>IntroRequestFeed: Filter dismissed, compute visibleRequests
    IntroRequestFeed->>User: Render requests (avatars, intents)

    User->>IntroRequestFeed: Click "Who fits"
    IntroRequestFeed->>ChatContent: Clear AI chat state
    IntroRequestFeed->>Router: Navigate to "/"
    IntroRequestFeed->>ChatContent: Send generated chat query
    IntroRequestFeed->>IntroRequestFeed: Mark request as sent

    ChatContent->>Backend: Send chat query
    Backend->>Database: Query opportunities (may include parties)
    Database-->>Backend: Return opportunities
    Backend->>ChatContent: Return cards with optional parties
    ChatContent->>User: Render opportunity cards (introducer dual-avatar)

    User->>IntroRequestFeed: Click "Skip"
    IntroRequestFeed->>IntroRequestFeed: Add to dismissedIds
    IntroRequestFeed->>User: Remove request from feed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • serefyarar

Poem

🐰 I hopped through code with a curious twitch,
Two avatars side-by-side — what a switch!
Requests appear in a tidy row,
I nudge "Who fits?" — and off the queries go.
Connections bloom; I wiggle my nose and twitch.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Feat/social introducer' is vague and uses a conventional prefix format that doesn't clearly describe the main change; a reader cannot understand the primary objective without reviewing the full changeset. Revise title to be more descriptive, such as 'Add introducer view with dual-avatar cards and intro request feed' to clearly convey the main features added.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/social-introducer

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
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
frontend/src/components/IntroductionRequestFeed.tsx (2)

83-88: Verify state persistence after navigation.

The handleSearchNetwork function calls navigate('/') after updating state. Since IntroductionRequestFeed is rendered on the home page, the component should remain mounted and retain sentIds state. However, if the user navigates away and returns, this ephemeral state will be lost.

This is acceptable for the mock-data phase, but ensure the backend integration (IND-208) persists sent/dismissed status server-side.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/IntroductionRequestFeed.tsx` around lines 83 - 88,
handleSearchNetwork currently updates local state via setSentIds then calls
navigate('/'), which means sentIds is ephemeral and will be lost if the user
leaves and returns; for IND-208 you should persist sent/dismissed status to the
backend instead of relying only on local state—call the appropriate API (e.g., a
new persistIntroStatus or updateIntroRequest endpoint) from handleSearchNetwork
(alongside clearChat, navigate, sendMessage, buildQuery) to save request.id and
its "sent" status, and update setSentIds only after a successful response or
reconcile on component mount by fetching persisted statuses so the UI remains
consistent across navigations and sessions.

127-128: Lowercasing the first character may break proper nouns or acronyms.

The intent text's first character is lowercased with request.intent.charAt(0).toLowerCase(). If the intent starts with a proper noun (e.g., "AI-focused investors"), it would become "aI-focused investors".

Consider checking if the first character is already lowercase or using a more nuanced approach:

♻️ Suggested fix
-              {' '}is looking for {request.intent.charAt(0).toLowerCase() + request.intent.slice(1)}
+              {' '}is looking for {/^[A-Z]/.test(request.intent) && !/^[A-Z]{2}/.test(request.intent) ? request.intent.charAt(0).toLowerCase() + request.intent.slice(1) : request.intent}

Alternatively, adjust the mock data to start with lowercase and rely on content authors to follow the convention.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/IntroductionRequestFeed.tsx` around lines 127 - 128,
The current inline transformation in IntroductionRequestFeed uses
request.intent.charAt(0).toLowerCase() which can corrupt acronyms/proper nouns;
replace that expression with a small helper (e.g., decapitalize or
decapitalizeIntent) and use it where the intent is rendered: implement
decapitalizeIntent(intent: string) to return the original string if the first
two characters are uppercase (acronym) or if the first character is already
lowercase, otherwise lower-case only the first character and concatenate the
rest, then call decapitalizeIntent(request.intent) in place of the existing
charAt(0)...slice(...) expression.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@protocol/src/lib/protocol/graphs/home.graph.ts`:
- Around line 378-384: Replace the direct use of introducerCounterparts.slice(0,
2) when building introducerParties with the existing
getUniqueCounterpartUserIds(...) helper so duplicate actor rows are collapsed
first; call getUniqueCounterpartUserIds(introducerCounterparts) or similar, take
the first two unique IDs, then map those IDs to { userId, name:
userMap.get(id)?.name ?? 'Unknown', avatar: userMap.get(id)?.avatar ?? null } to
construct introducerParties; apply the same change to the other similar blocks
that build presenter/fallback cards (the other introducer occurrences) so both
cards never render the same repeated user.

In `@protocol/src/lib/protocol/support/opportunity.discover.ts`:
- Around line 448-462: The introducer payload can contain duplicate users
because nonIntroducerActors is sliced raw; update the block where viewerRole ===
"introducer" builds parties to first deduplicate by a.userId (from
opportunity.actors) and then take the first two distinct userIds, mapping them
to the party objects using nameByUserId and avatarByUserId; specifically,
replace the current nonIntroducerActors.slice(0, 2).map(...) logic with logic
that iterates nonIntroducerActors, collects unique userIds in order, stops after
two distinct ids, and returns parties built from those unique ids.

In `@protocol/src/lib/protocol/tools/opportunity.tools.ts`:
- Around line 467-480: The new parties field is only added in the synchronous
create_introduction branch; update the other serializers/builders that build
card payloads (the continuation serializer, the regular discovery serializer
used around the card builder at the code near Line 267 and Line 649, and the
list_opportunities serializer/builder) to include parties when present by adding
the same conditional spread (e.g. ...(opp.parties ? { parties: opp.parties } :
{})) to the card payload construction so introducer cards receive the
dual-avatar/header data.

---

Nitpick comments:
In `@frontend/src/components/IntroductionRequestFeed.tsx`:
- Around line 83-88: handleSearchNetwork currently updates local state via
setSentIds then calls navigate('/'), which means sentIds is ephemeral and will
be lost if the user leaves and returns; for IND-208 you should persist
sent/dismissed status to the backend instead of relying only on local state—call
the appropriate API (e.g., a new persistIntroStatus or updateIntroRequest
endpoint) from handleSearchNetwork (alongside clearChat, navigate, sendMessage,
buildQuery) to save request.id and its "sent" status, and update setSentIds only
after a successful response or reconcile on component mount by fetching
persisted statuses so the UI remains consistent across navigations and sessions.
- Around line 127-128: The current inline transformation in
IntroductionRequestFeed uses request.intent.charAt(0).toLowerCase() which can
corrupt acronyms/proper nouns; replace that expression with a small helper
(e.g., decapitalize or decapitalizeIntent) and use it where the intent is
rendered: implement decapitalizeIntent(intent: string) to return the original
string if the first two characters are uppercase (acronym) or if the first
character is already lowercase, otherwise lower-case only the first character
and concatenate the rest, then call decapitalizeIntent(request.intent) in place
of the existing charAt(0)...slice(...) expression.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 346fd0e8-d807-4f2f-957c-a7db0bb50c7d

📥 Commits

Reviewing files that changed from the base of the PR and between a040a27 and 41ccd98.

📒 Files selected for processing (11)
  • frontend/src/app/dev/intro-requests/page.tsx
  • frontend/src/components/ChatContent.tsx
  • frontend/src/components/IntroductionRequestFeed.tsx
  • frontend/src/components/chat/OpportunityCardInChat.tsx
  • frontend/src/routes.tsx
  • frontend/src/services/opportunities.ts
  • protocol/src/lib/protocol/graphs/home.graph.ts
  • protocol/src/lib/protocol/states/home.state.ts
  • protocol/src/lib/protocol/support/opportunity.discover.ts
  • protocol/src/lib/protocol/tools/opportunity.tools.ts
  • protocol/src/types/chat-streaming.types.ts

…tent text

- home.graph.ts: Use getUniqueCounterpartUserIds() instead of raw slice
- opportunity.discover.ts: Deduplicate via Set before slicing parties
- IntroductionRequestFeed.tsx: Skip lowercasing when intent starts with acronym
@yanekyuk
Copy link
Copy Markdown
Contributor

Re: nitpick comments on IntroductionRequestFeed.tsx

  1. State persistence (lines 83-88): Acknowledged — ephemeral state is intentional for this mock-data phase. Server-side persistence will be part of IND-208 backend integration. No change needed now.

  2. Lowercase first char breaking acronyms (lines 127-128): Fixed. Now skips lowercasing when the intent starts with two consecutive uppercase letters (acronym pattern like "AI-focused").

@yanekyuk
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

✅ Actions performed

Review triggered.

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

@yanekyuk
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

✅ Actions performed

Review triggered.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
frontend/src/components/chat/OpportunityCardInChat.tsx (1)

271-312: Dual-avatar introducer header looks good overall.

The fallback parsing from headline (lines 275-280) gracefully handles cases where card.parties isn't populated. One minor note: when parsing from headline, the second party's userId is set to an empty string (line 277). While UserAvatar handles this gracefully via its name-based fallback, consider using undefined for clearer semantics:

♻️ Optional: use undefined instead of empty string
      : card.headline?.includes("→")
        ? card.headline.split("→").map((n, i) => ({
-           userId: i === 0 ? card.userId : "",
+           userId: i === 0 ? card.userId : undefined,
            name: n.trim(),
            avatar: i === 0 ? (card.avatar ?? null) : null,
          }))

This would also require updating the parties type to allow userId?: string (already compatible since the interface allows optional fields).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/chat/OpportunityCardInChat.tsx` around lines 271 -
312, In OpportunityCardInChat adjust the headline-parsing fallback so the second
party’s userId is undefined instead of an empty string (change the map branch
that sets userId: i === 0 ? card.userId : "" to use undefined for the non-first
party), and update the parties type/interface to allow userId?: string so
UserAvatar (and any other consumers) receive an absent userId rather than an
empty string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/components/chat/OpportunityCardInChat.tsx`:
- Around line 271-312: In OpportunityCardInChat adjust the headline-parsing
fallback so the second party’s userId is undefined instead of an empty string
(change the map branch that sets userId: i === 0 ? card.userId : "" to use
undefined for the non-first party), and update the parties type/interface to
allow userId?: string so UserAvatar (and any other consumers) receive an absent
userId rather than an empty string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5437f591-1d6c-4ef3-93bb-81f2bc5292c9

📥 Commits

Reviewing files that changed from the base of the PR and between 41ccd98 and 66528cd.

📒 Files selected for processing (9)
  • frontend/src/components/ChatContent.tsx
  • frontend/src/components/IntroductionRequestFeed.tsx
  • frontend/src/components/chat/OpportunityCardInChat.tsx
  • frontend/src/routes.tsx
  • protocol/src/lib/protocol/graphs/home.graph.ts
  • protocol/src/lib/protocol/states/home.state.ts
  • protocol/src/lib/protocol/support/opportunity.discover.ts
  • protocol/src/lib/protocol/tools/opportunity.tools.ts
  • protocol/src/types/chat-streaming.types.ts
✅ Files skipped from review due to trivial changes (5)
  • protocol/src/types/chat-streaming.types.ts
  • frontend/src/routes.tsx
  • protocol/src/lib/protocol/states/home.state.ts
  • protocol/src/lib/protocol/graphs/home.graph.ts
  • frontend/src/components/IntroductionRequestFeed.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/ChatContent.tsx
  • protocol/src/lib/protocol/support/opportunity.discover.ts

Fallback card now always includes an "Index" narrator chip for
non-introducer opportunities (filtered out by frontend). Update
test expectation to match.
@serefyarar serefyarar marked this pull request as draft March 27, 2026 01:15
Replace ambiguous 'Who fits' button text with 'Find matches' in frontend/src/components/IntroductionRequestFeed.tsx to improve clarity for users. No functional changes made.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants