Skip to content

feat: display direct replies below notes - #59

Open
alltheseas wants to merge 1 commit into
damus-io:masterfrom
alltheseas:fix/nprofile-mention-rendering
Open

feat: display direct replies below notes#59
alltheseas wants to merge 1 commit into
damus-io:masterfrom
alltheseas:fix/nprofile-mention-rendering

Conversation

@alltheseas

@alltheseas alltheseas commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Display direct replies below notes with author avatar, name, timestamp, and content preview
  • Resolve @nprofile/@npub mentions to display names in reply and parent previews
  • Use nevent1 (with relay hints) instead of note1 for parent note links
  • Wait for ndb writer thread to fully process reply events before extracting mention profiles

Test plan

  • Load a note with replies containing @nprofile mentions — verify display names render, not raw bech32
  • Click parent note link — verify URL uses nevent1 with relay hint, not note1
  • Load a reply note — verify parent preview shows full content, not just first mention
  • cargo clippy -- -D warnings passes
  • cargo test passes

Closes #58

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Richer note previews: mentions show display names (fallback to abbreviated IDs), hashtags rendered, and previews truncated cleanly with "..." when shortened.
  • Improvements

    • More reliable background fetching so parent notes, reply content, and profiles are retrieved before display.
    • Replies show improved preview content and avatar selection for more accurate rendering.

@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds HTML note-preview rendering with mention/hashtag resolution and bounded truncation; expands background fetch to collect and fetch missing parent and reply-content unknowns, waits for profile readiness and reply indexing before proceeding.

Changes

Cohort / File(s) Summary
Note preview & rendering
src/html.rs
Added pub fn render_note_preview_html(...) with HTML-escaped previews, centralized resolve_mention_name(), hashtag rendering, and block-aware truncation. Replaced abbreviate(...) calls in get_parent_note_info() and build_replies_html() to use the new renderer; parent event encoded via Nip19Event.
Unknowns collection & fetching logic
src/render.rs
Reworked fetch_unknowns() to compute needed profile pubkeys, subscribe to nostrdb filters before relay fetch, track only successful ingestions, and wait for profiles_ready() after ingestion. Added profiles_ready(), collect_parent_unknowns(), and collect_reply_content_unknowns(). fetch_note_stats() now subscribes before fetching replies and waits for reply indexing based on ingested count.
Background fetch pipeline
src/main.rs
Extended fetch_note_secondary_data() to call collect_parent_unknowns() and collect_reply_content_unknowns() and conditionally invoke render::fetch_unknowns() for collected IDs; errors logged as warnings and do not abort the flow.
Unknown IDs utilities
src/unknowns.rs
Added UnknownIds::profile_pubkeys() to return profile pubkeys from collected unknowns and made collect_from_blocks() public for reuse by new collectors.

Sequence Diagram(s)

sequenceDiagram
    participant Main as fetch_note_secondary_data()
    participant Render as render module
    participant RelayPool as RelayPool
    participant Ndb as Ndb
    participant Network as Relays

    Main->>Render: collect_parent_unknowns(ndb, note_rd)
    Render->>Ndb: load parent note & collect unknowns
    Render-->>Main: UnknownIds (parent)

    Main->>RelayPool: fetch_unknowns(relay_pool, ndb, unknowns)
    RelayPool->>Ndb: subscribe to filters (needed_profiles)
    RelayPool->>Network: request events
    Network-->>RelayPool: events
    RelayPool->>Ndb: ingest events

    loop Wait for Profiles
        RelayPool->>Ndb: profiles_ready(needed_profiles)?
        alt ready
            Ndb-->>RelayPool: yes
            RelayPool-->>Main: done
        else not ready
            RelayPool->>Ndb: poll subscription stream
        end
    end

    Main->>Render: collect_reply_content_unknowns(ndb, note_rd)
    Render->>Ndb: query replies & collect unknowns
    Render-->>Main: UnknownIds (replies)

    Main->>RelayPool: fetch_unknowns(relay_pool, ndb, unknowns)
    RelayPool->>Ndb: subscribe to reply filters
    RelayPool->>Network: request replies
    Network-->>RelayPool: reply events
    RelayPool->>Ndb: ingest replies

    loop Wait for Indexing
        RelayPool->>Ndb: check indexed batches vs ingested reply count
        alt indexed
            Ndb-->>RelayPool: ready
            RelayPool-->>Main: done
        else wait
            RelayPool->>Ndb: poll subscription stream
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I nibble mentions, stitch tags with care,

I trim the preview with hops to spare,
I ask the relays for profiles bright,
Wait for replies to come into sight,
A little rabbit hums — the page is light!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main feature introduced: displaying direct replies below notes with resolved mentions and improved parent/reply previews.
Linked Issues check ✅ Passed The PR successfully addresses issue #58 by implementing mention resolution for nprofile/npub identifiers, rendering display names instead of raw bech32 in both reply and parent previews, and includes relay hint support.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the objectives: reply display functionality, mention resolution, nevent1 encoding for parent links, and async waiting for profile data processing are all within scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@alltheseas
alltheseas force-pushed the fix/nprofile-mention-rendering branch 2 times, most recently from 6a02a72 to eb13ad4 Compare March 26, 2026 02:26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
src/render.rs (1)

733-748: ⚠️ Potential issue | 🟠 Major

Wait for fetched notes too, not just fetched profiles.

ingested is counted here, but the readiness gate only checks profiles_ready. If a batch contains both a missing parent/quote note and profile events, the first profile notification can satisfy this loop while the note is still not queryable, so the next render pass still misses that preview on a cold cache.

As per coding guidelines, src/render.rs: RenderData::complete pairs nostrdb subscriptions with network fetches—keep it resilient to partially missing data.

Also applies to: 752-759

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

In `@src/render.rs` around lines 733 - 748, The loop increments a single ingested
counter when events are processed (via ndb.process_event_with) but the readiness
gate only checks profiles_ready, so fetched notes (parents/quotes) can be
missed; modify the logic in RenderData::complete to track note-specific
ingestion (e.g., maintain a notes_ingested counter or a notes_ready flag when
processing events from nostr_filters/relay_pool.stream_events) and ensure the
readiness condition requires both profiles_ready and notes_ready (or
notes_ingested > 0) before marking completion; update the event processing block
around nostr_filters, IngestMetadata creation, and ndb.process_event_with to set
that notes flag/counter when the event type is a note/parent/quote so the render
pass waits for fetched notes as well as profiles.
src/html.rs (1)

1301-1306: ⚠️ Potential issue | 🟠 Major

Apply the reply cap after the is_direct check.

This query is limited to DIRECT_REPLY_LIMIT before non-direct matches are discarded, so deep thread replies can consume the first 50 rows and hide real direct replies. The same pre-filtered cap is mirrored in collect_reply_content_unknowns, so mention-prefetch will miss those replies too unless both call sites overfetch or scan until they accumulate DIRECT_REPLY_LIMIT direct replies.

Also applies to: 1326-1335

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

In `@src/html.rs` around lines 1301 - 1306, The query currently applies
DIRECT_REPLY_LIMIT before filtering by is_direct, letting deep-thread non-direct
replies crowd out true direct replies; update the logic in the blocks using
Filter::new() + .kinds([1]).event(note.id()).build() and the corresponding
app.ndb.query call (and the similar call in collect_reply_content_unknowns) to
overfetch (e.g., request more than DIRECT_REPLY_LIMIT) or loop/scan results
until you've accumulated DIRECT_REPLY_LIMIT items that pass is_direct, i.e.,
remove the pre-filter cap as the final limiting step and only enforce
DIRECT_REPLY_LIMIT after applying the is_direct check so you always return up to
DIRECT_REPLY_LIMIT direct replies for mention-prefetch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/render.rs`:
- Around line 700-707: The current logic builds relay_targets from
unknowns.relay_hints() and drops relay_pool.default_relays() whenever
relay_hints is non-empty, which causes hintless profile fetches (needed_profiles
from UnknownIds) to miss default relays; change the construction of
relay_targets so that if relay_hints is non-empty you merge
relay_pool.default_relays() with relay_hints (deduplicated) rather than
replacing them — update the code around relay_hints, needed_profiles, and
relay_targets to collect both sets (e.g., extend or union the iterators) so
kind-0/profile requests still go to default_relays() as well as any hinted
relays.
- Around line 782-795: In collect_parent_unknowns, after calling
unknowns.collect_from_blocks(ndb, &txn, &parent_note) also ensure the parent's
author profile is queued for the second-pass by adding a call to enqueue the
parent pubkey into UnknownIds (e.g. unknowns.collect_profile(ndb, &txn,
&parent_note.pubkey()) or the project-equivalent method that adds a
pubkey/profile to UnknownIds); this will ensure the parent author's profile
(used by the thread header) is fetched when the second pass runs.

---

Outside diff comments:
In `@src/html.rs`:
- Around line 1301-1306: The query currently applies DIRECT_REPLY_LIMIT before
filtering by is_direct, letting deep-thread non-direct replies crowd out true
direct replies; update the logic in the blocks using Filter::new() +
.kinds([1]).event(note.id()).build() and the corresponding app.ndb.query call
(and the similar call in collect_reply_content_unknowns) to overfetch (e.g.,
request more than DIRECT_REPLY_LIMIT) or loop/scan results until you've
accumulated DIRECT_REPLY_LIMIT items that pass is_direct, i.e., remove the
pre-filter cap as the final limiting step and only enforce DIRECT_REPLY_LIMIT
after applying the is_direct check so you always return up to DIRECT_REPLY_LIMIT
direct replies for mention-prefetch.

In `@src/render.rs`:
- Around line 733-748: The loop increments a single ingested counter when events
are processed (via ndb.process_event_with) but the readiness gate only checks
profiles_ready, so fetched notes (parents/quotes) can be missed; modify the
logic in RenderData::complete to track note-specific ingestion (e.g., maintain a
notes_ingested counter or a notes_ready flag when processing events from
nostr_filters/relay_pool.stream_events) and ensure the readiness condition
requires both profiles_ready and notes_ready (or notes_ingested > 0) before
marking completion; update the event processing block around nostr_filters,
IngestMetadata creation, and ndb.process_event_with to set that notes
flag/counter when the event type is a note/parent/quote so the render pass waits
for fetched notes as well as profiles.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9d50dff-e53c-4b35-b926-492c544bcce3

📥 Commits

Reviewing files that changed from the base of the PR and between ad35ec4 and 6a02a72.

📒 Files selected for processing (4)
  • src/html.rs
  • src/main.rs
  • src/render.rs
  • src/unknowns.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main.rs

Comment thread src/render.rs Outdated
Comment thread src/render.rs
@alltheseas
alltheseas force-pushed the fix/nprofile-mention-rendering branch from eb13ad4 to c27fb43 Compare March 26, 2026 02:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/render.rs (1)

939-953: Consider reducing notes_per_await batch size for reply waiting.

With notes_per_await(10) at line 895, the stream waits for up to 10 notes before yielding. When fewer replies are ingested (common case), this may cause unnecessary waiting until internal timeouts. A smaller batch size like 2-4 would yield faster for typical reply counts while still batching effectively.

Suggested change
         let sub_id = ndb.subscribe(&[reply_filter])?;
-        let stream = sub_id.stream(ndb).notes_per_await(10);
+        let stream = sub_id.stream(ndb).notes_per_await(4);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/render.rs` around lines 939 - 953, Reduce the batching size for
reply_stream to avoid waiting for large batches when few replies arrive: locate
where notes_per_await is set (notes_per_await currently 10) and change it to a
smaller value (2–4), keeping reply_stream, reply_ingested, and the existing
timeout loop unchanged so the waiting loop (which uses timeout(wait_for,
reply_stream.next())) yields more promptly for typical reply counts; adjust and
run tests/local runs to confirm fewer unnecessary timeouts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/render.rs`:
- Around line 939-953: Reduce the batching size for reply_stream to avoid
waiting for large batches when few replies arrive: locate where notes_per_await
is set (notes_per_await currently 10) and change it to a smaller value (2–4),
keeping reply_stream, reply_ingested, and the existing timeout loop unchanged so
the waiting loop (which uses timeout(wait_for, reply_stream.next())) yields more
promptly for typical reply counts; adjust and run tests/local runs to confirm
fewer unnecessary timeouts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: af82b765-97ff-4a4e-8018-eaf26c3a5f63

📥 Commits

Reviewing files that changed from the base of the PR and between 6a02a72 and c27fb43.

📒 Files selected for processing (4)
  • src/html.rs
  • src/main.rs
  • src/render.rs
  • src/unknowns.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main.rs
  • src/unknowns.rs

Closes damus-io#58

Signed-off-by: alltheseas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alltheseas
alltheseas force-pushed the fix/nprofile-mention-rendering branch from c27fb43 to ade0fde Compare March 26, 2026 03:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/html.rs`:
- Line 1194: The profile page is rendering recent notes via
build_note_content_html and calling render_note_preview_html without performing
the name-prefetch/readiness that the note route does, so parent/reply previews
may show bech32 instead of resolved display names; update serve_profile_html
(the handler that builds recent-note HTML) to mirror the note-route prefetch
step: after fetching the recent notes list, collect all author/prefetch keys
from those notes (including parents/replies), perform the same batched ndb
fetch/readiness call used by the note route, await its completion, then call
build_note_content_html/render_note_preview_html so names are resolved; ensure
you reference the same readiness/fetch helper used by the note route to keep
behavior consistent and batch across the recent-note query to avoid N+1 fetches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e4a944f1-348c-4378-8dae-03b4da18a946

📥 Commits

Reviewing files that changed from the base of the PR and between c27fb43 and ade0fde.

📒 Files selected for processing (4)
  • src/html.rs
  • src/main.rs
  • src/render.rs
  • src/unknowns.rs
✅ Files skipped from review due to trivial changes (1)
  • src/unknowns.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main.rs
  • src/render.rs

Comment thread src/html.rs
))
.into_owned(),
content_html: format!("{}{}", html_escape::encode_text(content), ellipsis),
content_html: render_note_preview_html(&parent_note, ndb, txn, 200),

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 | 🟠 Major

These preview call sites still miss the prefetch contract on profile pages.

Line 1194 and Line 1352 now depend on render_note_preview_html, which can only resolve names that are already in NDB. The note route satisfies that in src/main.rs (Line 412-Line 455), but serve_profile_html renders recent notes through build_note_content_html in this file (Line 2169-Line 2176) without an equivalent fetch stage. Recent notes on profile pages can therefore still fall back to abbreviated bech32 in parent/reply previews instead of resolved display names. Please mirror that prefetch/readiness step there, ideally batched across the recent-note query.

Also applies to: 1352-1352

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

In `@src/html.rs` at line 1194, The profile page is rendering recent notes via
build_note_content_html and calling render_note_preview_html without performing
the name-prefetch/readiness that the note route does, so parent/reply previews
may show bech32 instead of resolved display names; update serve_profile_html
(the handler that builds recent-note HTML) to mirror the note-route prefetch
step: after fetching the recent notes list, collect all author/prefetch keys
from those notes (including parents/replies), perform the same batched ndb
fetch/readiness call used by the note route, await its completion, then call
build_note_content_html/render_note_preview_html so names are resolved; ensure
you reference the same readiness/fetch helper used by the note route to keep
behavior consistent and batch across the recent-note query to avoid N+1 fetches.

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.

nprofile mention does not render in reply

1 participant