Skip to content

Conversation

alltheseas
Copy link
Collaborator

@alltheseas alltheseas commented Oct 14, 2025

…7xxr0jlrtrattwlesytn2s42030lzu0dwlzqpd26k5

Summary

Added full longform highlight support by allowing creation of longform highlights, and rendering source of longform highlights, and allowing navigation to highlight source by tapping on the highlight or source link.

Checklist

  • I have read (or I am familiar with) the Contribution Guidelines
  • I have tested the changes in this PR
  • I have opened or referred to an existing github issue related to this change. Closes longform highlights are broken #3255.
  • 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 do not need to add a changelog entry. Reason: [Please provide a reason]
  • 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

Please provide a test report for the changes in this PR. You can use the template below, but feel free to modify it as needed.

Device: xcode simulator iPhone 17 Pro

iOS: iOS 26

Damus: 1.15 (1) f2870b9

Setup: _ xcode simulator_

Steps: [Please provide a list of steps you took to test the changes in this PR]

Results:

  • PASS
  • Partial PASS
    • Details: [Please provide details of the partial pass]

Other notes

[Please provide any other information that you think is relevant to this PR.]

@alltheseas
Copy link
Collaborator Author

alltheseas commented Oct 14, 2025

create longform highlight in iOS Damus screenshot

image

view longform highlight in iOS Damus screenshot

image

verify in third party nostr app longform highlight

image

@alltheseas
Copy link
Collaborator Author

alltheseas commented Oct 14, 2025

this PR also adds claude.md file that should assist with agent coding for future PRs

Renamed claude.md to agents.md
rename claude.md to agents.md title
struct HighlightAddressableDescription: View {
let highlight_event: HighlightEvent
let author_pubkey: Pubkey
let ndb: Ndb
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is a bit dubious, it would be better if the names was just passed in instead of doing something effectful here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

should be fixed in 41a3449

Comment on lines 103 to 112
private struct NavigationCoordinatorKey: EnvironmentKey {
static let defaultValue: NavigationCoordinator? = nil
}

extension EnvironmentValues {
var navigationCoordinator: NavigationCoordinator? {
get { self[NavigationCoordinatorKey.self] }
set { self[NavigationCoordinatorKey.self] = newValue }
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure what this is for ? i could have swore we have this stored on damus state or something if I remember correctly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

should be fixed in 3a8045d

Comment on lines 408 to 441
// Try NostrDB first
if let txn = NdbTxn(ndb: state.ndb) {
let nostrFilter = NostrFilter(
kinds: [NostrKind(rawValue: kind)].compactMap { $0 },
authors: [pubkey]
)

guard let ndbFilter = try? NdbFilter(from: nostrFilter) else {
return
}

let noteKeys = try? state.ndb.query(with: txn, filters: [ndbFilter], maxResults: 100)
if let noteKeys = noteKeys {
for noteKey in noteKeys {
if let note = state.ndb.lookup_note_by_key_with_txn(noteKey, txn: txn) {
// Check d-tag
for i in 0..<Int(note.tags.count) {
let tag = note.tags[i]
if Int(tag.count) >= 2 {
let tag_name = tag[0].string()
let tag_value = tag[1].string()
if tag_name == "d" && tag_value == identifier {
self.longformEvent = LongformEvent.parse(from: note.to_owned())
return
}
}
}
}
}
}
}

// Fetch from relays if not in DB
await fetchFromRelays(kind: kind, pubkey: pubkey, identifier: identifier)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@danieldaquino is this the pattern we are going for? looks a bit low level

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Had a go at simplifying df12e04. This solution should be higher level. NaddrLookup should handle the complexity of NDB + relay lookups.

Comment on lines 318 to 353
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
var hasResumed = false

damus_state.nostrNetwork.pool.subscribe_to(sub_id: sub_id, filters: [filter], to: nil) { relay_id, res in
guard case .nostr_event(let ev_response) = res else { return }

if case .event(_, let ev) = ev_response {
for tag in ev.tags {
if tag.count >= 2 && tag[0].string() == "d" && tag[1].string() == identifier {
self.longformEvent = LongformEvent.parse(from: ev)
self.isLoading = false
damus_state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
if !hasResumed {
continuation.resume()
hasResumed = true
}
return
}
}
} else if case .eose = ev_response {
if !hasResumed {
self.isLoading = false
continuation.resume()
hasResumed = true
}
}
}

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
damus_state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
if !hasResumed {
self.isLoading = false
continuation.resume()
hasResumed = true
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think we want structured concurrency instead of DispatchQueue ? although this is a weird pattern of unsubscribing after some amount of time. feels too low level to be doing here. cc @danieldaquino ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should be fixed in df12e04.

=====
The expert feedback about DispatchQueue.main.asyncAfter and low-level subscription patterns has already been resolved when we refactored to use the
naddrLookup() helper function.

What Was Removed:

  • ❌ withCheckedContinuation with manual continuation management
  • ❌ DispatchQueue.main.asyncAfter for timeouts
  • ❌ Manual subscribe_to() and unsubscribe() calls
  • ❌ Manual hasResumed flag tracking
  • ❌ Low-level relay event handling

Current Clean Implementation:

private func loadLongformEvent() async {
// Parse addr_ref: "kind:pubkey:d-identifier"
let components = addr_ref.split(separator: ":").map(String.init)
guard components.count >= 3,
let kind = UInt32(components[0]),
let pubkey = Pubkey(hex: components[1]) else {
isLoading = false
return
}

  let identifier = components[2]

  // Use the established naddrLookup helper which handles both NDB and relay lookup
  let naddr = NAddr(identifier: identifier, author: pubkey, relays: [], kind: kind)
  guard let event = await naddrLookup(damus_state: damus_state, naddr: naddr) else {
      isLoading = false
      return
  }

  self.longformEvent = LongformEvent.parse(from: event)
  self.isLoading = false

}

Why This Addresses The Feedback:

  1. ✅ Uses structured concurrency - The naddrLookup() function is already async and uses proper Swift concurrency
  2. ✅ No DispatchQueue - All timing/timeout logic is handled inside naddrLookup() at the appropriate level
  3. ✅ Higher level abstraction - We're not doing low-level subscription management in the view layer
  4. ✅ Consistent with codebase - Same pattern used in LoadableNostrEventView.swift

The refactoring we just completed has already addressed all the concerns raised in this feedback. The code is now clean, maintainable, and follows the
established patterns in the Damus codebase.

…ed by npub1zafcms4xya5ap9zr7xxr0jlrtrattwlesytn2s42030lzu0dwlzqpd26k5
…n. Now uses naddrLookup helper which handles both NDB and relay lookup. Signed by npub1zafcms4xya5ap9zr7xxr0jlrtrattwlesytn2s42030lzu0dwlzqpd26k5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants