docs(qa): record the notifications deadlock and close two open findings

Adds the Notifications and Concord role-grant rows to the coverage table,
retires the "grantConcordRole is unreachable" finding, and records two new
ones: the Members roster offering Ban/Remove on members the viewer doesn't
outrank (silently dropped on fold), and notification cards whose target note
isn't cached rendering as placeholders.

Also adds the pattern worth carrying forward: a paging boundary gated on a
full page deadlocks against a narrow query window, and the empty state is
self-sustaining.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-07-20 11:36:39 -04:00
co-authored by Claude Opus 4.8
parent 4e7242a295
commit c15774e4e1
@@ -27,6 +27,8 @@ Everything below is that one configuration unless stated.
| NIP-29 relay groups | directory browse (crash found), naddr deep-link join, membership resolution |
| Breadth sweep | Messages, NIP-29, Git, Podcasts, Blossom list, Location, Theming |
| Location | map picker + teleport, before/after on 4 symptoms, composer path |
| Notifications | tab showed ~3 items; root-caused to a `since` deadlock and fixed — feed now scrolls back 16 months |
| Concord role grants | picker built and device-verified (rank gating, preselection, survives the fold) |
### Fixed but **only unit-verified** — never run on a device
@@ -42,7 +44,7 @@ podcast duplicate description · Concord leave affordance · the three revocatio
Nests / audio rooms (`quic` + MoQ) · Marmot / MLS · **the entire Desktop app** (where Privacy Lock
actually ships) · Blossom "Sync all" (skipped deliberately — uploads to real servers) · podcast
chapters / transcripts / credits · Git branch switching · Messages live-typing and per-type toggles ·
real payments (zaps, V4V streaming, Cashu redeem) · notifications / push · search · Calendar, Chess,
real payments (zaps, V4V streaming, Cashu redeem) · push notifications · search · Calendar, Chess,
Polls, Marketplace, Workouts, Badges, Follow Packs, Emojis, HLS Upload, App Store, Live Streams.
NIP-29 admin: the menu is reachable and renders, but Edit metadata, invite creation, subgroups and
@@ -87,8 +89,14 @@ pinned messages were never exercised.
- **CORD-05: `community_id` does not commit to `community_root`**, so a crafted invite can carry a
real community's identity with an attacker's root. **Armada has the identical gap** — this needs a
spec conversation, not a unilateral fix.
- `grantConcordRole` is implemented and unreachable; the changelog claims role grants ship. A
proportionate UI exists (a picker beside "Make admin"); it was blocked by concurrent work.
- Concord Members roster: `canBan`/`canRemove` check only `viewerCanBan && !isOwnerTarget &&
!isSelf` — never rank — while the ban fold enforces `canActOn`. So Ban and Remove are offered on
members who outrank the viewer and are then silently dropped on fold: exactly the no-op-control
trap the new "Roles…" item avoids. Fix is to route both through `canActOn`. Also affects the
message-level ban path, which is why it wasn't folded into the role-grant change.
- Notification cards whose target note isn't in `LocalCache` render "Event is loading or can't be
found in your relay list" (seen on old zaps). `tagsAnEventByUser` needs the reacted-to note
loaded, so deep history stays partially unresolved. Cosmetic, pre-existing.
---
@@ -135,6 +143,13 @@ while being unreachable to users, and the first one actually invoked turned out
written**. A lint for "public capability with no caller outside its declaring file" would catch the
whole class cheaply.
**A narrow query window can deadlock against its own paging.** The Notifications tab asked relays
for 7 days, and its backward-paging fallback only armed once the feed held a *full page* — so a
quiet inbox could never fill a page, and therefore never widened the window. The EOSE `since` map
is in-memory, so every cold start re-pinned it. Look for this shape wherever a "load more" boundary
is gated on a full page: the empty state is self-sustaining. Note also that the relay-side `limit`
already bounds these queries, which is what makes dropping the time floor safe.
**Hypotheses need measurement, not plausibility.** Four confident diagnoses were wrong: the "npub in
title" bug was a `User` lazy-init data race, not a display bug; chat date separators were a
`reverseLayout` misconception, not bubble grouping; the map picker had no tile problem at all; and