Skip to content

Feat/155 pinned article for user page#164

Open
DSanich wants to merge 4 commits intonodetec:masterfrom
DSanich:feat/155-pinned-article-for-user-page
Open

Feat/155 pinned article for user page#164
DSanich wants to merge 4 commits intonodetec:masterfrom
DSanich:feat/155-pinned-article-for-user-page

Conversation

@DSanich
Copy link
Copy Markdown

@DSanich DSanich commented Feb 11, 2026

Add pinned article feature to author profile page

Summary

Implements pinned article functionality for author profile pages using NIP-51 (Nostr Lists). Users can now pin a single article to the top of their profile, making it the first thing visitors see.

fixed #155

Summary by CodeRabbit

  • New Features
    • Added support for pinning articles to author profiles
    • Pinned articles now appear at the top of the author feed with metadata, read time, and interaction counts
    • Pin/unpin actions available in article menus for authors on their own profiles

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 11, 2026

@DSanich is attempting to deploy a commit to the NodeTec Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

This pull request introduces pinned articles functionality for user profiles. A new AuthorArticlesFeed component feature fetches and displays pinned articles with management controls. Supporting Nostr protocol functions handle fetching, publishing, and type definitions for pinned article data.

Changes

Cohort / File(s) Summary
UI Component
src/components/author/AuthorArticlesFeed.tsx
Added pinned articles display with conditional rendering, state management for concurrent pin/unpin operations, dropdown actions for pinning, article counting adjustments, and integration with existing feed UI and interaction flows.
Type Definitions
src/lib/nostr/types.ts
Introduced PinnedArticles interface and eventToPinnedArticles function to parse Nostr kind-10001 events, extracting pinned article references from 'a' tags.
Data Fetching
src/lib/nostr/fetch.ts
Added fetchPinnedArticles function to retrieve the latest pinned articles event for a given pubkey from a relay with 10-second timeout and cleanup.
Publishing
src/lib/nostr/publish.ts
Added publishPinnedArticles function to construct and publish kind-10001 events containing pinned article references to multiple relays.

Sequence Diagram

sequenceDiagram
    participant User
    participant AuthorFeed as AuthorArticlesFeed<br/>Component
    participant FetchModule as Nostr Fetch<br/>Module
    participant PublishModule as Nostr Publish<br/>Module
    participant Relay as Relay

    User->>AuthorFeed: Visit author profile
    AuthorFeed->>FetchModule: fetchPinnedArticles(pubkey, relay)
    FetchModule->>Relay: Subscribe to kind 10001 event
    Relay-->>FetchModule: EVENT (pinned articles data)
    FetchModule-->>AuthorFeed: PinnedArticles object
    AuthorFeed->>AuthorFeed: Render pinned article block<br/>above feed

    User->>AuthorFeed: Click pin/unpin button
    AuthorFeed->>AuthorFeed: Set isPinning = true<br/>(guard concurrent ops)
    AuthorFeed->>PublishModule: publishPinnedArticles(pinnedArticles, relays)
    PublishModule->>PublishModule: Build kind 10001 event<br/>with article references
    PublishModule->>Relay: Publish event to relays
    Relay-->>PublishModule: Publish confirmation
    PublishModule-->>AuthorFeed: PublishResult[]
    AuthorFeed->>FetchModule: fetchPinnedArticles (refetch)
    FetchModule->>Relay: Subscribe to updated event
    Relay-->>FetchModule: EVENT (updated data)
    FetchModule-->>AuthorFeed: Updated PinnedArticles
    AuthorFeed->>AuthorFeed: Refresh UI, set isPinning = false<br/>Show success toast
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hopping through articles with glee,
Pinning favorites where all can see!
Fresh types and queries in Nostr's delight,
The feed's reorganized just right! 📌✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/155 pinned article for user page' clearly summarizes the main change—adding pinned article functionality to user profiles.
Linked Issues check ✅ Passed The pull request implements pinned article functionality with fetching, publishing, UI components, and state management as needed for issue #155.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the pinned article feature for user profiles; no unrelated modifications detected.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/components/author/AuthorArticlesFeed.tsx`:
- Around line 157-159: filteredBlogs already filters out the current pinnedBlog
(const filteredBlogs = pinnedBlog ? blogs.filter((blog) => blog.id !==
pinnedBlog.id) : blogs), so the conditional inside filteredBlogs.map that checks
pinnedBlog?.id === blog.id and renders the "Unpin article" DropdownMenuItem is
unreachable; remove that entire branch (the conditional and the DropdownMenuItem
for "Unpin article") from the map to avoid dead code, or if you intended to keep
it as a safety net instead of removing, replace it with a short inline comment
near the filteredBlogs declaration saying it purposefully excludes the pinned
article to guard against duplicates/relay delays; target symbols: filteredBlogs,
pinnedBlog, filteredBlogs.map, and the DropdownMenuItem that renders "Unpin
article".
🧹 Nitpick comments (3)
src/lib/nostr/types.ts (1)

151-186: Duplicated 'a' tag parsing logic — consider extracting a shared helper.

The tag-parsing reduce block in eventToPinnedArticles (lines 161–178) is nearly identical to the one in eventToStack (lines 121–136), differing only by the kind === 30023 filter. Extracting a shared parseATags(tags: string[][], kindFilter?: number): StackItem[] helper would eliminate this duplication.

♻️ Example shared helper
+function parseATags(tags: string[][], kindFilter?: number): StackItem[] {
+  return tags
+    .filter((t) => t[0] === 'a')
+    .reduce<StackItem[]>((acc, t) => {
+      const parts = t[1]?.split(':');
+      if (!parts || parts.length < 3) return acc;
+      const [kindStr, pubkey, identifier] = parts;
+      const kind = parseInt(kindStr, 10);
+      if (isNaN(kind) || !pubkey || !identifier) return acc;
+      if (kindFilter !== undefined && kind !== kindFilter) return acc;
+      acc.push({ kind, pubkey, identifier, relay: t[2] });
+      return acc;
+    }, []);
+}

Then use it in both eventToStack and eventToPinnedArticles.

src/lib/nostr/publish.ts (1)

504-517: Consider reusing StackItem from types.ts instead of an inline type.

The inline type for pinnedArticles items ({ kind: number; pubkey: string; identifier: string; relay?: string }) is identical to the StackItem interface. Reusing it improves consistency and reduces surface area for drift.

♻️ Proposed change
+import type { NostrEvent, StackItem } from './types';
 // ...
 export async function publishPinnedArticles({
   pinnedArticles,
   relays,
   secretKey,
 }: {
-  pinnedArticles: Array<{
-    kind: number;
-    pubkey: string;
-    identifier: string;
-    relay?: string;
-  }>;
+  pinnedArticles: StackItem[];
   relays: string[];
   secretKey?: string;
 }): Promise<PublishResult[]> {
src/components/author/AuthorArticlesFeed.tsx (1)

371-510: Consider extracting the pinned article block into a dedicated component.

The IIFE at lines 377–508 is ~130 lines of inline rendering logic with its own computed variables. Extracting it into a PinnedArticleCard component (or similar) would improve readability and make the parent component easier to follow.

Comment on lines +157 to +159
const filteredBlogs = pinnedBlog
? blogs.filter((blog) => blog.id !== pinnedBlog.id)
: blogs;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The "Unpin article" branch in the regular feed dropdown appears unreachable.

filteredBlogs (line 157–159) already excludes blogs whose id matches pinnedBlog.id. Therefore, inside filteredBlogs.map(...) at line 513, the condition pinnedBlog?.id === blog.id on line 551 should never be true, making the "Unpin article" DropdownMenuItem (lines 552–562) dead code.

If this is intentional as a safety net for edge cases (e.g., relay propagation delays causing ID mismatches), a brief inline comment would clarify intent. Otherwise, the branch can be removed.

Also applies to: 549-578

🤖 Prompt for AI Agents
In `@src/components/author/AuthorArticlesFeed.tsx` around lines 157 - 159,
filteredBlogs already filters out the current pinnedBlog (const filteredBlogs =
pinnedBlog ? blogs.filter((blog) => blog.id !== pinnedBlog.id) : blogs), so the
conditional inside filteredBlogs.map that checks pinnedBlog?.id === blog.id and
renders the "Unpin article" DropdownMenuItem is unreachable; remove that entire
branch (the conditional and the DropdownMenuItem for "Unpin article") from the
map to avoid dead code, or if you intended to keep it as a safety net instead of
removing, replace it with a short inline comment near the filteredBlogs
declaration saying it purposefully excludes the pinned article to guard against
duplicates/relay delays; target symbols: filteredBlogs, pinnedBlog,
filteredBlogs.map, and the DropdownMenuItem that renders "Unpin article".

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.

pinned article for user page

1 participant