Skip to content

Conversation

@alltheseas
Copy link
Collaborator

@alltheseas alltheseas commented Jan 5, 2026

Summary

Display preview cards for longform article (kind 30023) references in notes instead of abbreviated inline text (@naddr1...).

  • Detect naddr and nevent references to kind 30023 longform articles
  • Show LongformPreview cards in feed, thread, chat, and DM views
  • Suppress inline abbreviated text when preview cards are displayed
  • Support compose view with real-time preview, draft persistence, and duplicate prevention
  • Handle multiple longform references with vertical stack layout

Checklist

Standard PR Checklist

  • I have read (or I am familiar with) the Contribution Guidelines
  • I have tested the changes in this PR
  • I have profiled the changes to ensure there are no performance regressions, or I do not need to profile the changes.
    • Not needed: Changes are view-layer only with async loading, no heavy computation
  • I have opened or referred to an existing github issue related to this change: Add longform preview #2798
  • My PR is either small, or I have split it into smaller logical commits that are easier to review
  • I have added the signoff line to all my commits. See Signing off your work
  • I have added appropriate changelog entries for the changes in this PR. See Adding changelog entries
  • I have added appropriate Closes: or Fixes: tags in the commit messages wherever applicable, or made sure those are not needed. See Submitting patches

Test report

Device: iPhone 15 Pro (Simulator)

iOS: 18.2

Damus: feature/longform-naddr-preview branch (84a2e3d)

Setup: Test account with access to notes containing longform article references

Steps:

  1. View note containing single naddr reference to longform article → preview card shown, no inline text
  2. View note containing multiple naddr/nevent references → vertical stack of preview cards
  3. Compose note, paste naddr1... → preview card appears in compose view
  4. Compose note, paste nevent1... (kind 30023) → preview card appears
  5. Dismiss compose view, reopen → longform reference preserved in draft
  6. Type same naddr twice in compose → only one reference in final post
  7. View longform preview in chat bubble → card displays correctly
  8. View longform preview in DM → card displays correctly

Results:

  • PASS

Other notes

Closes #2798

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for mentioning and previewing longform articles in posts, direct messages, and event views.
    • Users can now extract, manage, and display longform article references with preview cards and carousels.
    • Longform mentions are automatically detected and displayed alongside standard content across chat, messages, and event displays.
  • Chores

    • Integrated new longform article mention components into the project build structure.

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

Add first_longform_naddr_mention() function to detect kind 30023 naddr
references in note content blocks.

Create LongformNaddrMentionView and LongformNeventMentionView components
that asynchronously load referenced longform articles and display them
as LongformPreview cards. Falls back to abbreviated link on load failure.

Signed-off-by: alltheseas <[email protected]>
Show LongformMentionView in EventShell (threaded/wide), SelectedEventView,
ChatEventView, and DMView when a note contains naddr/nevent references
to kind 30023 longform articles.

Suppress inline @naddr/@Nevent text when preview cards are displayed
to avoid showing both the abbreviated reference and the preview card.

Note: In .no_mentions contexts where preview cards aren't rendered, the
references won't be visible - same limitation as regular note mentions.

Signed-off-by: alltheseas <[email protected]>
Show preview card when user pastes naddr or nevent referencing a
longform article (kind 30023) in the compose view. Supports both
naddr1... and nevent1... formats with real-time preview updates.

Handles draft persistence by re-inserting extracted references before
saving, so references aren't lost when dismissing and reopening.

Prevents duplicates on send by removing any references that remain in
text (user may have retyped an already-extracted reference). Includes
whitespace trimming to avoid orphan blank lines.

Signed-off-by: alltheseas <[email protected]>
Add all_longform_mentions() to detect all naddr/nevent references with
kind 30023. Display multiple longform previews in a vertical stack for
viewing notes, and horizontal carousel for composing notes.

Viewing: LongformMentionsStack shows read-only vertical list
Composing: LongformCarouselView with delete buttons, extracts all
references from text and re-adds them when posting.

Changelog-Added: Display multiple longform article previews in notes

Closes: damus-io#2798

Signed-off-by: alltheseas <[email protected]>
@danieldaquino danieldaquino added the pr-in-queue This PR is waiting in a queue behind their other PRs marked with the label `pr-active-review`. label Jan 5, 2026
Fixes: 84a2e3d ("Support multiple longform article mentions with vertical stack")
Signed-off-by: William Casarin <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a complete longform article mention and preview feature across the damus app. It adds infrastructure for detecting, extracting, and displaying longform references (naddr/nevent) as interactive preview cards, integrating these views into chat, direct messages, event displays, and the post composition interface. The implementation includes new data types, extraction utilities, and SwiftUI components.

Changes

Cohort / File(s) Summary
Project Configuration
damus.xcodeproj/project.pbxproj
Adds LongformNaddrMentionView.swift file references and build targets for ShareExtension and Damus configurations.
Core Nostr Data Model
damus/Core/Nostr/NostrEvent.swift
Introduces LongformReference enum (naddr/nevent cases with bech32 property), extends ValidationResult with bad_id and bad_sig cases, and adds helper functions first_longform_naddr_mention() and all_longform_mentions() to detect and collect longform mentions.
Longform UI Components
damus/Features/Events/LongformNaddrMentionView.swift
Implements four new SwiftUI views: LongformMentionView (async-loads and displays article previews), LongformPreviewCard (adds delete button overlay), LongformCarouselView (horizontal carousel with per-item deletion), and LongformMentionsStack (vertical read-only list).
Event Display Integration
damus/Features/Events/EventShell.swift, damus/Features/Events/SelectedEventView.swift
Adds get_longform_mentions() helper and renders LongformMentionsStack in both threaded and wide layouts; integrates longform mentions into selected event view.
Chat & DM Views
damus/Features/Chat/ChatEventView.swift, damus/Features/DMs/Views/DMView.swift
Inserts LongformMentionsStack after standard mentions in chat bubbles and direct messages, with adaptive white background styling.
Content Rendering
damus/Features/Events/Models/NoteContent.swift
Adds has_longform_ref computation to detect longform references in blocks and suppresses inline rendering when preview cards are displayed.
Post Composition
damus/Features/Posting/Views/PostView.swift
Introduces @State longformReferences tracking, adds six extraction utility functions (extractLongformReference, extractAllLongformReferences, extractLongformNaddrWithRange, extractLongformNeventWithRange, extractLongformNaddr, extractLongformNevent), and integrates LongformCarouselView into editor UI; updates send/draft logic to manage longform references.

Sequence Diagram

sequenceDiagram
    actor User
    participant PostView
    participant Extraction as Extraction Utils
    participant State as App State
    participant NostrNet as Nostr Network
    participant UI as LongformMentionView
    
    User->>PostView: Enter text with naddr/nevent
    PostView->>Extraction: extractAllLongformReferences(text)
    Extraction-->>PostView: [LongformExtractionResult]
    PostView->>State: Update longformReferences state
    PostView->>UI: Render LongformCarouselView
    
    rect rgb(200, 220, 240)
    Note over UI: Async Loading Phase
    UI->>NostrNet: lookup(naddr) or lookup(noteId)
    NostrNet-->>UI: NostrEvent (article)
    end
    
    UI-->>User: Display preview (title, image, author)
    
    User->>PostView: Send post
    PostView->>Extraction: Remove naddr/nevent from text
    PostView->>State: Re-insert as nostr:… strings
    PostView->>NostrNet: Publish event with references
    NostrNet-->>User: Post confirmed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A rabbit's ode to longform love:

Through naddr tunnels deep below,
We fetch the articles that glow,
With titles, pics, and authors' names,
In carousels and preview frames—
Long-form links now bloom and show! 📰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.38% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Display longform article preview cards for naddr/nevent references' clearly and concisely describes the main change—replacing abbreviated naddr/nevent text with preview cards.
Description check ✅ Passed The description is complete with summary, standard PR checklist items marked, detailed test report with device/iOS/setup details, step-by-step testing results, and proper issue closure reference.
Linked Issues check ✅ Passed The PR fully addresses issue #2798 by implementing longform preview cards displaying article title, excerpt, image, and author instead of abbreviated @naddr/@Nevent text across feed, thread, chat, DM, and compose views.
Out of Scope Changes check ✅ Passed All code changes directly support the core objective of displaying longform article previews: new UI views, detection helpers, integration into existing views, compose support, and draft persistence are all aligned with issue #2798 requirements.
✨ Finishing touches
  • 📝 Generate docstrings

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

Comment on lines +301 to +311
func extractAndRemoveLongformReferences() {
let text = post.string
let extractions = extractAllLongformReferences(from: text)

// Filter out references we already have
let newExtractions = extractions.filter { extraction in
!longformReferences.contains(extraction.reference)
}

guard !newExtractions.isEmpty else { return }

Copy link
Collaborator

Choose a reason for hiding this comment

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

notes are already parsed in the note blocks data structure. you can just walk over that instead of doing parsing here

@jb55 jb55 added pr-active-review This PR is actively being reviewed, or is next on the list to be reviewed. and removed pr-in-queue This PR is waiting in a queue behind their other PRs marked with the label `pr-active-review`. labels Jan 6, 2026
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

🤖 Fix all issues with AI Agents
In @damus/Core/Nostr/NostrEvent.swift:
- Around line 872-898: In all_longform_mentions (function all_longform_mentions)
the closure passed to blockGroup.reduce declares an unused parameter named
`index`; replace that parameter with `_` (i.e., change `(index, refs, block)` to
`(_, refs, block)`) so SwiftLint stops flagging the unused parameter and the
closure semantics remain unchanged.

In @damus/Features/Events/Models/NoteContent.swift:
- Around line 117-138: In NoteContent.swift update the closure passed to
blocks.reduce that computes has_longform_ref to avoid unused-parameter warnings:
replace the unused closure parameters `index` and `partialResult` with
underscores (e.g., use `_` placeholders) in the reduce closure signature so it
becomes `{ _, _, item in ... }`, leaving the body and logic (mentions,
MentionRef handling, nip19 checks and loopReturn/loopContinue) unchanged.
🧹 Nitpick comments (8)
damus/Features/Chat/ChatEventView.swift (1)

165-167: Consider performance optimization and pattern consistency.

The all_longform_mentions function performs block parsing via NdbBlockGroup.borrowBlockGroup and is called directly in the view body, meaning it executes on every view render. This differs from the MentionView pattern above (lines 160-164), which uses if let to check for mentions before rendering.

Consider these improvements:

  1. Check for empty results: Wrap the view in a conditional to avoid unnecessary rendering when no longform mentions exist.
  2. Cache the result: Store the result in a @State or computed property that's only calculated once, rather than on every view update.
Suggested refactor to match MentionView pattern
-                LongformMentionsStack(damus_state: damus_state, references: all_longform_mentions(ndb: damus_state.ndb, ev: event, keypair: damus_state.keypair))
-                    .background(DamusColors.adaptableWhite)
-                    .clipShape(RoundedRectangle(cornerSize: CGSize(width: 10, height: 10)))
+                let longform_refs = all_longform_mentions(ndb: damus_state.ndb, ev: event, keypair: damus_state.keypair)
+                if !longform_refs.isEmpty {
+                    LongformMentionsStack(damus_state: damus_state, references: longform_refs)
+                        .background(DamusColors.adaptableWhite)
+                        .clipShape(RoundedRectangle(cornerSize: CGSize(width: 10, height: 10)))
+                }

Alternatively, introduce a cached computed property similar to the Mention pattern used elsewhere in the file.

damus/Features/DMs/Views/DMView.swift (1)

28-30: Match the Mention property pattern for consistency.

The LongformMention computed property calls all_longform_mentions on every access without checking if the result is empty. This differs from the Mention property (lines 18-26), which returns EmptyView() when no mentions exist.

Additionally, SwiftUI may call computed properties multiple times per render cycle, and all_longform_mentions performs potentially expensive block parsing via NdbBlockGroup.borrowBlockGroup.

Suggested refactor to match Mention pattern
 var LongformMention: some View {
-    LongformMentionsStack(damus_state: damus_state, references: all_longform_mentions(ndb: damus_state.ndb, ev: event, keypair: damus_state.keypair))
+    Group {
+        let references = all_longform_mentions(ndb: damus_state.ndb, ev: event, keypair: damus_state.keypair)
+        if !references.isEmpty {
+            LongformMentionsStack(damus_state: damus_state, references: references)
+        } else {
+            EmptyView()
+        }
+    }
 }

This ensures consistent patterns across the file and avoids rendering when no longform mentions exist.

Also applies to: 76-76

damus/Features/Events/SelectedEventView.swift (2)

94-97: Match the Mention property pattern for consistency.

The LongformMention computed property should follow the same pattern as the Mention property (lines 85-92), which uses a Group with a conditional check before rendering.

Suggested refactor to match Mention pattern
 var LongformMention: some View {
-    LongformMentionsStack(damus_state: damus, references: all_longform_mentions(ndb: damus.ndb, ev: event, keypair: damus.keypair))
-        .padding(.horizontal)
+    Group {
+        let references = all_longform_mentions(ndb: damus.ndb, ev: event, keypair: damus.keypair)
+        if !references.isEmpty {
+            LongformMentionsStack(damus_state: damus, references: references)
+                .padding(.horizontal)
+        }
+    }
 }

This ensures the view hierarchy is only created when longform mentions actually exist.


94-97: Consider extracting the repeated longform mention pattern.

The same all_longform_mentions call and LongformMentionsStack rendering pattern appears in ChatEventView.swift (lines 165-167), DMView.swift (lines 28-30), and here. This code duplication could be reduced by:

  1. Creating a reusable computed property or helper on NostrEvent that caches the longform mentions
  2. Extracting a shared view component that encapsulates the check and rendering logic

This would improve maintainability and ensure consistent behavior across all three views.

damus/Features/Events/EventShell.swift (1)

46-52: Add a docstring for the new helper method.

Per coding guidelines, docstring coverage should be ensured for new or modified code. Consider adding a brief doc comment explaining the purpose and return value.

+    /// Returns all longform article mentions (naddr/nevent with kind 30023) in the event content.
+    /// Returns an empty array if the event is nested or mentions are disabled.
     func get_longform_mentions(ndb: Ndb) -> [LongformReference] {
         if self.options.contains(.nested) || self.options.contains(.no_mentions) {
             return []
         }

         return all_longform_mentions(ndb: ndb, ev: event, keypair: state.keypair)
     }
damus/Features/Events/LongformNaddrMentionView.swift (1)

52-59: Consider reusing LongformReference.bech32 instead of duplicating the encoding logic.

The LongformReference enum already has a bech32 computed property that performs the same encoding. You can simplify this to reference.bech32.

     private var bech32: String {
-        switch reference {
-        case .naddr(let naddr):
-            return Bech32Object.encode(.naddr(naddr))
-        case .nevent(let nevent):
-            return Bech32Object.encode(.nevent(nevent))
-        }
+        reference.bech32
     }
damus/Features/Posting/Views/PostView.swift (2)

127-175: Consider extracting the whitespace-trimming deletion logic into a shared helper.

The range expansion logic (lines 140-161) to include surrounding whitespace is duplicated in extractAndRemoveLongformReferences (lines 322-343). Extracting this into a shared helper would reduce duplication and improve maintainability.

Additionally, ensure that manually typed nostr:naddr1... strings that haven't been extracted yet are handled correctly—currently they would be removed and re-added, which should be fine but worth verifying.


300-349: Whitespace handling logic is duplicated from send_post.

This function duplicates the same range expansion logic found in send_post (lines 140-161). Consider extracting into a shared helper like:

private func expandRangeToIncludeSurroundingWhitespace(
    range: NSRange, 
    in text: String
) -> NSRange

This would consolidate the logic and reduce maintenance burden.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 368f94a and ae77355.

📒 Files selected for processing (9)
  • damus.xcodeproj/project.pbxproj
  • damus/Core/Nostr/NostrEvent.swift
  • damus/Features/Chat/ChatEventView.swift
  • damus/Features/DMs/Views/DMView.swift
  • damus/Features/Events/EventShell.swift
  • damus/Features/Events/LongformNaddrMentionView.swift
  • damus/Features/Events/Models/NoteContent.swift
  • damus/Features/Events/SelectedEventView.swift
  • damus/Features/Posting/Views/PostView.swift
👮 Files not reviewed due to content moderation or server errors (1)
  • damus.xcodeproj/project.pbxproj
🧰 Additional context used
📓 Path-based instructions (1)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Maximize usage of nostrdb facilities (Ndb, NdbNote, iterators) whenever possible for persistence and queries in the Damus iOS app
Favor Swift-first solutions that lean on nostrdb types (Ndb, NdbNote, iterators) before introducing new storage mechanisms
Ensure docstring coverage for any code added, or modified
Ensure nevernesting: favor early returns and guard clauses over deeply nested conditionals; simplify control flow by exiting early instead of wrapping logic in multiple layers of if statements

Files:

  • damus/Features/Events/SelectedEventView.swift
  • damus/Features/Events/EventShell.swift
  • damus/Core/Nostr/NostrEvent.swift
  • damus/Features/DMs/Views/DMView.swift
  • damus/Features/Chat/ChatEventView.swift
  • damus/Features/Posting/Views/PostView.swift
  • damus/Features/Events/LongformNaddrMentionView.swift
  • damus/Features/Events/Models/NoteContent.swift
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: damus-io/damus PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T01:28:30.381Z
Learning: Review and follow pull_request_template.md when creating PRs for iOS Damus
📚 Learning: 2026-01-06T01:28:30.381Z
Learnt from: CR
Repo: damus-io/damus PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T01:28:30.381Z
Learning: Applies to **/*.swift : Maximize usage of nostrdb facilities (Ndb, NdbNote, iterators) whenever possible for persistence and queries in the Damus iOS app

Applied to files:

  • damus/Features/Events/LongformNaddrMentionView.swift
  • damus.xcodeproj/project.pbxproj
📚 Learning: 2026-01-06T01:28:30.381Z
Learnt from: CR
Repo: damus-io/damus PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T01:28:30.381Z
Learning: Ensure new targets or resources integrate cleanly with the damus.xcodeproj main scheme

Applied to files:

  • damus.xcodeproj/project.pbxproj
📚 Learning: 2026-01-06T01:28:30.381Z
Learnt from: CR
Repo: damus-io/damus PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T01:28:30.381Z
Learning: Applies to **/*.swift : Ensure docstring coverage for any code added, or modified

Applied to files:

  • damus.xcodeproj/project.pbxproj
📚 Learning: 2026-01-06T01:28:30.381Z
Learnt from: CR
Repo: damus-io/damus PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T01:28:30.381Z
Learning: Review and follow pull_request_template.md when creating PRs for iOS Damus

Applied to files:

  • damus.xcodeproj/project.pbxproj
📚 Learning: 2026-01-06T01:28:30.381Z
Learnt from: CR
Repo: damus-io/damus PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-06T01:28:30.381Z
Learning: Applies to **/*.swift : Favor Swift-first solutions that lean on nostrdb types (Ndb, NdbNote, iterators) before introducing new storage mechanisms

Applied to files:

  • damus.xcodeproj/project.pbxproj
🧬 Code graph analysis (8)
damus/Features/Events/SelectedEventView.swift (1)
damus/Core/Nostr/NostrEvent.swift (1)
  • all_longform_mentions (873-898)
damus/Features/Events/EventShell.swift (1)
damus/Core/Nostr/NostrEvent.swift (2)
  • first_eref_mention (828-847)
  • all_longform_mentions (873-898)
damus/Core/Nostr/NostrEvent.swift (3)
damus/Shared/Utilities/Bech32Object.swift (1)
  • encode (183-199)
nostrdb/NdbBlock.swift (2)
  • borrowBlockGroup (124-139)
  • reduce (296-298)
nostrdb/NonCopyableLinkedList.swift (1)
  • reduce (62-84)
damus/Features/DMs/Views/DMView.swift (1)
damus/Core/Nostr/NostrEvent.swift (1)
  • all_longform_mentions (873-898)
damus/Features/Chat/ChatEventView.swift (1)
damus/Core/Nostr/NostrEvent.swift (1)
  • all_longform_mentions (873-898)
damus/Features/Posting/Views/PostView.swift (1)
damus/Shared/Utilities/Bech32Object.swift (1)
  • parse (159-181)
damus/Features/Events/LongformNaddrMentionView.swift (1)
damus/Shared/Utilities/DisplayName.swift (1)
  • abbrev_identifier (88-90)
damus/Features/Events/Models/NoteContent.swift (2)
nostrdb/NdbBlock.swift (1)
  • reduce (296-298)
nostrdb/NonCopyableLinkedList.swift (1)
  • reduce (62-84)
🪛 SwiftLint (0.57.0)
damus/Core/Nostr/NostrEvent.swift

[Warning] 875-875: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

damus/Features/Events/Models/NoteContent.swift

[Warning] 118-118: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 118-118: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (17)
damus/Features/Events/EventShell.swift (2)

86-91: LGTM!

The integration of LongformMentionsStack after MentionView in the Threaded layout is correct. The guard logic is handled inside get_longform_mentions, and LongformMentionsStack renders an EmptyView when the array is empty, so no additional conditional is needed here.


118-128: LGTM!

The Wide layout correctly guards the LongformMentionsStack with !options.contains(.no_mentions) and applies horizontal padding consistently with other elements.

damus/Features/Events/Models/NoteContent.swift (1)

266-282: LGTM!

The suppression logic correctly skips inline rendering of longform naddr/nevent mentions when has_longform_ref is true, ensuring preview cards are displayed instead. The early continue prevents duplicate display of longform content.

damus/Core/Nostr/NostrEvent.swift (2)

28-42: LGTM!

The LongformReference enum is well-designed with clear cases for naddr and nevent, Equatable conformance for deduplication, and a convenient bech32 computed property that delegates to Bech32Object.encode.


849-870: LGTM!

The first_longform_naddr_mention function correctly uses nostrdb's block iteration facilities to find the first naddr mention with kind 30023. The docstring provides clear documentation.

damus/Features/Events/LongformNaddrMentionView.swift (4)

10-12: LGTM!

Good docstring coverage for the main view. The async loading pattern with LoadState enum is clean.


139-148: Using \.offset as ForEach id may cause animation issues during deletion.

When items are removed, offsets shift, which can cause SwiftUI to misidentify views and produce animation glitches. Since LongformReference is Equatable, consider making it Hashable or Identifiable to use a stable identity.

For now, since the carousel is relatively small and this is a compose view where perfect animations are less critical, this is acceptable.


61-83: LGTM!

The async loading correctly handles both naddr and nevent cases. The continuation pattern for nevent preserves the copy semantics required by the lender API. The MainActor dispatch ensures thread-safe state updates.


155-170: LGTM!

The LongformMentionsStack correctly renders an EmptyView when there are no references and a vertical stack otherwise. Using \.offset as id here is acceptable since this is a read-only view with no mutations.

damus/Features/Posting/Views/PostView.swift (8)

90-91: LGTM!

The new state variable for tracking extracted longform references is appropriately scoped as private.


185-186: LGTM!

The is_post_empty check now correctly considers longformReferences, preventing the post button from being disabled when only longform references are present.


355-388: LGTM!

The draft persistence logic correctly re-embeds longform references into the saved content so they aren't lost when the view is dismissed. The check for existing refs prevents duplication.


410-413: LGTM!

Calling extractAndRemoveLongformReferences() on text change enables real-time preview of pasted longform references.


544-552: LGTM!

The LongformCarouselView is correctly integrated into the Editor view with appropriate padding and conditional rendering.


1110-1167: LGTM!

The extraction utilities are well-structured with clear docstrings. The regex patterns correctly handle the optional nostr: prefix and the bech32 character set. The kind check ensures only longform articles (kind 30023) are extracted.


1169-1231: LGTM!

The individual extraction functions for naddr and nevent are well-implemented with proper null safety and early returns. The docstrings clearly document the expected behavior.


1233-1253: LGTM!

The convenience wrappers extractLongformNaddr and extractLongformNevent provide a cleaner API for callers that only need the reference without the match range.

Comment on lines +872 to +898
/// Returns all longform article mentions (naddr or nevent with kind 30023) in the event content.
func all_longform_mentions(ndb: Ndb, ev: NostrEvent, keypair: Keypair) -> [LongformReference] {
return (try? NdbBlockGroup.borrowBlockGroup(event: ev, using: ndb, and: keypair, borrow: { blockGroup in
let mentions: [LongformReference] = (try? blockGroup.reduce(initialResult: [LongformReference](), { index, refs, block in
switch block {
case .mention(let mention):
guard let mentionRef = MentionRef(block: mention) else { return .loopContinue }
switch mentionRef.nip19 {
case .naddr(let naddr):
if naddr.kind == NostrKind.longform.rawValue {
return .loopReturn(refs + [.naddr(naddr)])
}
case .nevent(let nevent):
if nevent.kind == NostrKind.longform.rawValue {
return .loopReturn(refs + [.nevent(nevent)])
}
default:
break
}
default:
break
}
return .loopContinue
})) ?? []
return mentions
})) ?? []
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace unused closure parameter index with _.

SwiftLint reports that the index parameter is unused. Replace it with an underscore.

     func all_longform_mentions(ndb: Ndb, ev: NostrEvent, keypair: Keypair) -> [LongformReference] {
         return (try? NdbBlockGroup.borrowBlockGroup(event: ev, using: ndb, and: keypair, borrow: { blockGroup in
-            let mentions: [LongformReference] = (try? blockGroup.reduce(initialResult: [LongformReference](), { index, refs, block in
+            let mentions: [LongformReference] = (try? blockGroup.reduce(initialResult: [LongformReference](), { _, refs, block in
                 switch block {
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 875-875: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🤖 Prompt for AI Agents
In @damus/Core/Nostr/NostrEvent.swift around lines 872 - 898, In
all_longform_mentions (function all_longform_mentions) the closure passed to
blockGroup.reduce declares an unused parameter named `index`; replace that
parameter with `_` (i.e., change `(index, refs, block)` to `(_, refs, block)`)
so SwiftLint stops flagging the unused parameter and the closure semantics
remain unchanged.

Comment on lines +117 to +138
// Check for any longform naddr/nevent mentions (kind 30023) - suppress inline when preview cards shown
let has_longform_ref: Bool = (try? blocks.reduce(initialResult: false) { index, partialResult, item in
switch item {
case .mention(let mention):
guard let mentionRef = MentionRef(block: mention) else { return .loopContinue }
switch mentionRef.nip19 {
case .naddr(let naddr):
if naddr.kind == NostrKind.longform.rawValue {
return .loopReturn(true)
}
case .nevent(let nevent):
if nevent.kind == NostrKind.longform.rawValue {
return .loopReturn(true)
}
default:
break
}
default:
break
}
return .loopContinue
}) ?? false
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace unused closure parameters with _ to fix SwiftLint warnings.

The index and partialResult parameters are unused in this closure. Per SwiftLint's unused_closure_parameter rule, replace them with underscores.

-    let has_longform_ref: Bool = (try? blocks.reduce(initialResult: false) { index, partialResult, item in
+    let has_longform_ref: Bool = (try? blocks.reduce(initialResult: false) { _, _, item in
         switch item {
         case .mention(let mention):
             guard let mentionRef = MentionRef(block: mention) else { return .loopContinue }
             switch mentionRef.nip19 {
             case .naddr(let naddr):
                 if naddr.kind == NostrKind.longform.rawValue {
                     return .loopReturn(true)
                 }
             case .nevent(let nevent):
                 if nevent.kind == NostrKind.longform.rawValue {
                     return .loopReturn(true)
                 }
             default:
                 break
             }
         default:
             break
         }
         return .loopContinue
     }) ?? false
📝 Committable suggestion

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

Suggested change
// Check for any longform naddr/nevent mentions (kind 30023) - suppress inline when preview cards shown
let has_longform_ref: Bool = (try? blocks.reduce(initialResult: false) { index, partialResult, item in
switch item {
case .mention(let mention):
guard let mentionRef = MentionRef(block: mention) else { return .loopContinue }
switch mentionRef.nip19 {
case .naddr(let naddr):
if naddr.kind == NostrKind.longform.rawValue {
return .loopReturn(true)
}
case .nevent(let nevent):
if nevent.kind == NostrKind.longform.rawValue {
return .loopReturn(true)
}
default:
break
}
default:
break
}
return .loopContinue
}) ?? false
// Check for any longform naddr/nevent mentions (kind 30023) - suppress inline when preview cards shown
let has_longform_ref: Bool = (try? blocks.reduce(initialResult: false) { _, _, item in
switch item {
case .mention(let mention):
guard let mentionRef = MentionRef(block: mention) else { return .loopContinue }
switch mentionRef.nip19 {
case .naddr(let naddr):
if naddr.kind == NostrKind.longform.rawValue {
return .loopReturn(true)
}
case .nevent(let nevent):
if nevent.kind == NostrKind.longform.rawValue {
return .loopReturn(true)
}
default:
break
}
default:
break
}
return .loopContinue
}) ?? false
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 118-118: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 118-118: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🤖 Prompt for AI Agents
In @damus/Features/Events/Models/NoteContent.swift around lines 117 - 138, In
NoteContent.swift update the closure passed to blocks.reduce that computes
has_longform_ref to avoid unused-parameter warnings: replace the unused closure
parameters `index` and `partialResult` with underscores (e.g., use `_`
placeholders) in the reduce closure signature so it becomes `{ _, _, item in ...
}`, leaving the body and logic (mentions, MentionRef handling, nip19 checks and
loopReturn/loopContinue) unchanged.

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

Labels

longform pr-active-review This PR is actively being reviewed, or is next on the list to be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add longform preview

3 participants