From 441b42e113c8063f0789ff40eb7115c961296cb9 Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sat, 16 May 2026 21:02:54 -0500 Subject: [PATCH 1/7] feat: hide stale DM threads from left rail (#261) Adds a purely local "Hide thread" action to the DM thread modal that filters the thread out of the left-rail "Direct Messages" section until either party sends a new DM (which auto-revives it with the existing unread badge logic). Storage piggybacks the chat-delegate `OutboundDmStore` blob (#256): a new `hidden_threads: Vec` field is added with `#[serde(default)]` so pre-#261 wire bytes still decode. Pinned by JSON + CBOR round-trip tests and an explicit "legacy decodes without hidden_threads" test pair. Filter logic lives in `river_core::chat_delegate::is_thread_hidden` (pure helper, unit-tested for hit/miss, equal-timestamp boundary, per-room/per-peer scoping, zero-state edge case). The UI wraps it in `is_thread_hidden_for` for the `(VerifyingKey, MemberId)` HashMap the rail uses. Outbound sends to a hidden peer revive the thread automatically because the new message's timestamp is strictly later than `hidden_at_ts`. Multi-device: hides ride the delegate blob, so a hide on device A is visible on device B on next reload. Delegate WASM hash changed (river-core changed); V19 migration entry added to `legacy_delegates.toml` per the delegate-migration workflow. Closes #261 [AI-assisted - Claude] Co-Authored-By: Claude Opus 4.7 --- cli/contracts/room_contract.wasm | Bin 662958 -> 663198 bytes cli/src/commands/dm.rs | 10 +- common/src/chat_delegate.rs | 234 ++++++++++++++++++ legacy_delegates.toml | 7 + ui/public/contracts/chat_delegate.wasm | Bin 704938 -> 705130 bytes ui/public/contracts/room_contract.wasm | Bin 662958 -> 663198 bytes ui/src/components/app/chat_delegate.rs | 142 ++++++++++- .../app/freenet_api/response_handler.rs | 13 +- ui/src/components/direct_messages.rs | 32 ++- .../direct_messages/dm_thread_modal.rs | 52 +++- .../components/room_list/dm_rail_section.rs | 26 +- 11 files changed, 498 insertions(+), 18 deletions(-) diff --git a/cli/contracts/room_contract.wasm b/cli/contracts/room_contract.wasm index f5e3891f378e8d0a16611125214500773d2a9e49..bd035cc8ea4eeaf29914f6e0bab7890a12ccb095 100755 GIT binary patch delta 65937 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 delta 65806 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 |I_r<~QZ5jB9=O^=OIAHbfv4W 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 diff --git a/cli/src/commands/dm.rs b/cli/src/commands/dm.rs index f1278df0..54b065ca 100644 --- a/cli/src/commands/dm.rs +++ b/cli/src/commands/dm.rs @@ -890,7 +890,10 @@ mod tests { let sender = MemberId(FastHash(11)); let recipient = MemberId(FastHash(22)); - let mut store = OutboundDmStore { entries: vec![] }; + let mut store = OutboundDmStore { + entries: vec![], + hidden_threads: vec![], + }; let over_cap = MAX_DM_MESSAGES_PER_PAIR + 5; for i in 0..over_cap { store.entries.push(OutboundDmEntry { @@ -923,7 +926,10 @@ mod tests { let alice = MemberId(FastHash(11)); let bob = MemberId(FastHash(22)); - let mut store = OutboundDmStore { entries: vec![] }; + let mut store = OutboundDmStore { + entries: vec![], + hidden_threads: vec![], + }; // Fill (me -> alice) to over-cap; (me -> bob) only one entry. for i in 0..MAX_DM_MESSAGES_PER_PAIR + 3 { store.entries.push(OutboundDmEntry { diff --git a/common/src/chat_delegate.rs b/common/src/chat_delegate.rs index 0c336358..0494cd7b 100644 --- a/common/src/chat_delegate.rs +++ b/common/src/chat_delegate.rs @@ -22,10 +22,50 @@ pub const OUTBOUND_DMS_STORAGE_KEY: &[u8] = b"outbound_dms"; /// `freenet/.claude/rules/bug-prevention-patterns.md`. Lookups are /// linear, which is fine: the store is bounded by per-pair caps /// (`MAX_DM_MESSAGES_PER_PAIR`) and pruned on purge tombstones. +/// +/// Piggybacks the `hidden_threads` list (issue freenet/river#261) — a +/// purely local "hide this DM thread from my left rail until a fresh +/// message arrives" view filter. We pack it into the same delegate +/// blob so a single chat-delegate fetch hydrates both, and so a hide +/// on device A is visible on device B without a second storage key. #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct OutboundDmStore { #[serde(default)] pub entries: Vec, + /// Per-`(room, peer)` "hidden-at" cutoff timestamps. Filter rule: + /// a thread is hidden iff `hidden_at_ts >= max(message.timestamp)` + /// for messages between the local user and `peer` in that room. + /// `#[serde(default)]` so pre-#261 wire bytes (a `Vec`-only + /// `OutboundDmStore`) keep decoding into an empty `hidden_threads`. + #[serde(default)] + pub hidden_threads: Vec, +} + +/// A single user-driven "hide this DM thread until further notice" entry. +/// +/// `Vec`-of-struct rather than `HashMap` for the same reason as +/// [`OutboundDmStore::entries`] — JSON object keys must serialize as +/// strings (see "Non-string map keys in JSON-serialized API types" in +/// `freenet/.claude/rules/bug-prevention-patterns.md`), and the +/// `(VerifyingKey, MemberId)` lookup tuple does not. The local UI hot +/// path materialises this list into a HashMap for O(1) render-time +/// lookup — see `OutboundDmsCache` in the river-ui crate. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct HiddenDmThreadEntry { + /// Room owner verifying key — disambiguates the same peer being a + /// member of multiple rooms. Raw 32 bytes to match the `RoomKey` + /// convention used elsewhere in this module and to keep the type + /// JSON-friendly. + pub room_owner_vk: [u8; 32], + /// Counterparty in the DM thread. + pub peer: MemberId, + /// Unix seconds at the moment the user clicked "Hide thread". + /// Captured from the most-recent message timestamp in the thread at + /// that moment (or `now()` if the thread had no messages yet — an + /// edge case that can happen if the user composes-and-hides from + /// the picker without ever sending) so any subsequent message + /// strictly later than this revives the thread. + pub hidden_at_ts: u64, } /// A single outbound DM the local user composed and sent. @@ -218,6 +258,32 @@ pub enum ChatDelegateResponseMsg { }, } +/// Pure helper: should a DM thread for `(room, peer)` currently be +/// hidden from the left rail? +/// +/// Returns `true` iff the user has a `HiddenDmThreadEntry` for the +/// thread AND no message in the thread has `timestamp > hidden_at_ts`. +/// The strict `>` (not `>=`) on `max_message_ts` ensures that the +/// message used to populate `hidden_at_ts` does not itself revive the +/// thread. Any newer DM (inbound or outbound) crosses the threshold +/// and revives. +/// +/// `hidden_threads` is the full slice as loaded from the delegate; +/// the lookup is linear because the list is tiny (bounded by the +/// number of distinct DM pairs the user has actually hidden, which +/// in practice is well under a hundred). Issue freenet/river#261. +pub fn is_thread_hidden( + hidden_threads: &[HiddenDmThreadEntry], + room_owner_vk: &[u8; 32], + peer: MemberId, + max_message_ts: u64, +) -> bool { + hidden_threads + .iter() + .find(|h| &h.room_owner_vk == room_owner_vk && h.peer == peer) + .is_some_and(|h| max_message_ts <= h.hidden_at_ts) +} + #[cfg(test)] mod tests { use super::*; @@ -234,6 +300,14 @@ mod tests { } } + fn sample_hidden() -> HiddenDmThreadEntry { + HiddenDmThreadEntry { + room_owner_vk: [9u8; 32], + peer: MemberId(FastHash(0x1234_5678)), + hidden_at_ts: 1_700_000_000, + } + } + /// Per the "Non-string map keys in JSON-serialized API types" rule /// in `freenet/.claude/rules/bug-prevention-patterns.md`, any /// wire-boundary type stored in the delegate that may eventually be @@ -244,6 +318,7 @@ mod tests { fn outbound_dm_store_json_round_trips() { let store = OutboundDmStore { entries: vec![sample_entry()], + hidden_threads: vec![], }; let json = serde_json::to_string(&store).expect("serialize JSON"); let parsed: OutboundDmStore = serde_json::from_str(&json).expect("parse JSON"); @@ -256,6 +331,7 @@ mod tests { fn outbound_dm_store_cbor_round_trips() { let store = OutboundDmStore { entries: vec![sample_entry(), sample_entry()], + hidden_threads: vec![], }; let mut buf = Vec::new(); ciborium::ser::into_writer(&store, &mut buf).expect("serialize CBOR"); @@ -274,4 +350,162 @@ mod tests { let parsed: OutboundDmStore = serde_json::from_str(&json).expect("parse JSON"); assert_eq!(parsed, store); } + + /// Issue freenet/river#261 — `hidden_threads` is now part of the + /// stored blob. JSON round-trip pins the load-bearing wire shape + /// (Vec of struct, not HashMap) per the "non-string map keys" + /// bug-prevention pattern. + #[test] + fn outbound_dm_store_with_hidden_threads_json_round_trips() { + let store = OutboundDmStore { + entries: vec![sample_entry()], + hidden_threads: vec![sample_hidden()], + }; + let json = serde_json::to_string(&store).expect("serialize JSON"); + let parsed: OutboundDmStore = serde_json::from_str(&json).expect("parse JSON"); + assert_eq!(parsed, store); + } + + /// CBOR is the on-the-wire encoding used by the chat delegate, so + /// `hidden_threads` must also CBOR round-trip. + #[test] + fn outbound_dm_store_with_hidden_threads_cbor_round_trips() { + let store = OutboundDmStore { + entries: vec![], + hidden_threads: vec![sample_hidden(), sample_hidden()], + }; + let mut buf = Vec::new(); + ciborium::ser::into_writer(&store, &mut buf).expect("serialize CBOR"); + let parsed: OutboundDmStore = + ciborium::de::from_reader(buf.as_slice()).expect("parse CBOR"); + assert_eq!(parsed, store); + } + + /// Issue freenet/river#261 BACKWARDS COMPAT: pre-#261 delegate + /// blobs serialized BEFORE `hidden_threads` existed must still + /// decode into an `OutboundDmStore` with an empty `hidden_threads` + /// (via `#[serde(default)]`). Without this, the first reload + /// after upgrading River would fail to hydrate the outbound-DM + /// cache for every user whose delegate already has the #256 blob. + /// + /// We pin both JSON and CBOR: JSON via a hand-written legacy + /// payload (the shape `serde_json::to_string` would have produced + /// before this PR), and CBOR by serializing a synthetic + /// "legacy" store that contains only the `entries` field via the + /// same path the delegate writes. + #[test] + fn outbound_dm_store_decodes_legacy_json_without_hidden_threads() { + let legacy_json = r#"{"entries":[]}"#; + let parsed: OutboundDmStore = + serde_json::from_str(legacy_json).expect("legacy JSON must decode"); + assert!(parsed.entries.is_empty()); + assert!(parsed.hidden_threads.is_empty()); + } + + #[test] + fn outbound_dm_store_decodes_legacy_cbor_without_hidden_threads() { + // Simulate a pre-#261 OutboundDmStore wire shape by hand-rolling + // a CBOR map with only the `entries` key. `ciborium` writes + // structs as definite-length maps keyed by field name, so we + // reproduce that here: + // { "entries": [ ] } + #[derive(Serialize)] + struct LegacyStore { + entries: Vec, + } + let legacy = LegacyStore { + entries: vec![sample_entry()], + }; + let mut buf = Vec::new(); + ciborium::ser::into_writer(&legacy, &mut buf).expect("serialize legacy CBOR"); + + let parsed: OutboundDmStore = + ciborium::de::from_reader(buf.as_slice()).expect("legacy CBOR must decode"); + assert_eq!(parsed.entries.len(), 1); + assert!(parsed.hidden_threads.is_empty()); + } + + /// `is_thread_hidden` returns false on an empty hidden list. This + /// is the common-case fast-path for users who have never hidden a + /// thread. + #[test] + fn is_thread_hidden_returns_false_for_empty_list() { + let peer = MemberId(FastHash(0x42)); + assert!(!is_thread_hidden(&[], &[0u8; 32], peer, 0)); + assert!(!is_thread_hidden(&[], &[0u8; 32], peer, 1_000)); + } + + /// `is_thread_hidden` returns true when the only message in the + /// thread is the one whose timestamp was captured as + /// `hidden_at_ts`. The strict `>` rule means equal-timestamp does + /// NOT revive — otherwise hiding a thread whose most-recent message + /// is exactly `now()` would instantly fail to hide. + #[test] + fn is_thread_hidden_equal_timestamp_stays_hidden() { + let peer = MemberId(FastHash(0x42)); + let hidden = vec![HiddenDmThreadEntry { + room_owner_vk: [9u8; 32], + peer, + hidden_at_ts: 1_000, + }]; + assert!(is_thread_hidden(&hidden, &[9u8; 32], peer, 1_000)); + } + + /// Any message strictly later than `hidden_at_ts` must revive the + /// thread. + #[test] + fn is_thread_hidden_strictly_later_message_revives() { + let peer = MemberId(FastHash(0x42)); + let hidden = vec![HiddenDmThreadEntry { + room_owner_vk: [9u8; 32], + peer, + hidden_at_ts: 1_000, + }]; + assert!(!is_thread_hidden(&hidden, &[9u8; 32], peer, 1_001)); + } + + /// A `HiddenDmThreadEntry` for the same peer in a DIFFERENT room + /// must NOT hide the thread in the current room. The lookup is + /// `(room, peer)`, not just `peer`. + #[test] + fn is_thread_hidden_is_scoped_per_room() { + let peer = MemberId(FastHash(0x42)); + let hidden = vec![HiddenDmThreadEntry { + room_owner_vk: [9u8; 32], + peer, + hidden_at_ts: 1_000, + }]; + // Different room — must be visible. + assert!(!is_thread_hidden(&hidden, &[7u8; 32], peer, 500)); + } + + /// A `HiddenDmThreadEntry` for a DIFFERENT peer in the same room + /// must NOT hide the thread. + #[test] + fn is_thread_hidden_is_scoped_per_peer() { + let peer_a = MemberId(FastHash(0x42)); + let peer_b = MemberId(FastHash(0x99)); + let hidden = vec![HiddenDmThreadEntry { + room_owner_vk: [9u8; 32], + peer: peer_a, + hidden_at_ts: 1_000, + }]; + assert!(!is_thread_hidden(&hidden, &[9u8; 32], peer_b, 500)); + } + + /// Thread with no messages at all (max_message_ts = 0) and a + /// `hidden_at_ts` of 0 stays hidden — the strict `<=` rule still + /// applies. This matches the design intent: a freshly hidden + /// empty thread should stay hidden until either party sends a + /// (necessarily later, since unix ts > 0) message. + #[test] + fn is_thread_hidden_zero_max_zero_hidden_stays_hidden() { + let peer = MemberId(FastHash(0x42)); + let hidden = vec![HiddenDmThreadEntry { + room_owner_vk: [9u8; 32], + peer, + hidden_at_ts: 0, + }]; + assert!(is_thread_hidden(&hidden, &[9u8; 32], peer, 0)); + } } diff --git a/legacy_delegates.toml b/legacy_delegates.toml index 6a8c0779..0486d5cf 100644 --- a/legacy_delegates.toml +++ b/legacy_delegates.toml @@ -134,3 +134,10 @@ description = "Before #256: outbound_dms storage key + OutboundDmStore types add date = "2026-05-16" delegate_key = "11db7236c2211dd54ab6853826915464ce429a7a044cbf2813aa5a3d74ba5b1f" code_hash = "6229c2a136b30a7868c07099efdea86bee260734339d872aa1e68177733b5fdd" + +[[entry]] +version = "V19" +description = "Before #261: HiddenDmThreadsV1 added to OutboundDmStore for hide-stale-DM-threads feature" +date = "2026-05-16" +delegate_key = "497b522ac5c1f0a6234f832d7f593ab78c2b7f91136607400e9d1839509e6839" +code_hash = "47e54c906561d394cf473e89180350ea9e4bc6e75091e1debcf7306e5b971c80" diff --git a/ui/public/contracts/chat_delegate.wasm b/ui/public/contracts/chat_delegate.wasm index 7bc0ded74c19fd83431805ee04a166bcd808ab56..23418d15cf98ce5855a6cccd2b27a4ae043e4d5c 100755 GIT binary patch delta 49848 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�& z+M4}lpB%ymQ02Aegf3Ja5MAG}doi#neb6e++vxG#C{32NMp>Ia_+z9RTpGERWR>4}K} zzh_TM;Pi2%uKI_B;mKjnaN~qBX`*raHg<{X%uJ=e5I-m6jBq*{FN`vRlY;oQU`!Ie zW$<2}1tDzI9|Vnn#HZ z>>&uxo$FVT4?HCYml%{D=`kKQ9Grq!aMeN4!=Q7lETdNg`_>z???9h@Pw!>zh3>O& z?7;5WcNd#!d&TNayMP}&DZi0k=y$W~jro+>(%;@yr;hP^AGVR9U~q5c(@@qsv^NY4 zcJJ;@F9jk5Ea^|_940dQQvsac0@KaE_oooQSW-3sLEaKuJlGOj8o}x2kp~Gsx?Wdh zc6^wgS9{Dm0y8pN8|9qJgOK$pni^->B#l?u!kj_9lp)EX3E+t-Qz%7* z4x-jcZk|nCkl@oTV?jt!lFmQnj6u%PmNIMpVe!Zy%GTaIEancP&Y24j+r@F<^FBoN zKA>qrlH|NvWmXTO=J+`8pt$)60>;9_qTOJs(JGIK_#xCbZZ|i+Xks!Mxzj&N*5|#S zgus}oese@TF@&&VJSXN2p^y5zt088zG)g&@eKpp5xGBb7KW+&(&`2_lS-FU|-@`qt z#Y;n}LUW%I-G|X9+JaN!{4m_^m7OwEhtpwv%5csMdXyf-$F6@fhdf3zRhNA7`Wow# zD^Kw=S{W67+*%K}rLo74d*Tes<`@-ctr|)_+%Cp?KQ8ap(~JdwV@b~&guY6sQFVwF zB-FF(|3b6TKopW`zRL2Aos=@)7)5_2ZT?v?W*qf%d7p4cD8g5<{=Eg%I~(xoJPnC& z(2(TcEm^MVCOY}2f=fMveQ<%fc07&Kq7QrK>CPx{B&(?r zZHlNM+CDZk70QB6bZ(|2~n%*4-Yv zOf;(fxZ8QIjb!8MVe^$qFu3@;yOMBBp?=YK_vgOsW#$xmUW3nOO@+6?XA7oMCK_rk zm`eEVA8@;K)5y?v9}(Hpse=YT8#|r8_E~v3Td=Ri(#ntdaix{dKW&wjR$l67`_jty zv9JIm*^!)C{yNT@uDjV#;_o)$@ZT-j zh)&mCJjmwt7!1XEPlE?FFzd#G0Cu++fpa``(z5T=b2Vh$og-rhtEvwRPHd=+Qa|O2 zhqvCtlfmQ818v&b@g+-fHgxfZ6&sUkq+u8<88@YVX=OJo5q3JcvsGfyLK>i*s}jo=Vr8qV z62C2>eDd5-I*c<_qQxQ-+OJjOqeawStF0187Ew`s<@txiz!Fq`^N=Vlp+3QHdCVw4 z6qZnwxK%C1Ol|QYaeXn}tCbuQ&L#AMR_%Fwtk}4O; zg&(@st!v_s|yf$+;5huD?gb9Rr9HiDgt&U%4X>N@W?gQ>a-}MxKV; zw;bUKt(~+Sr$6v3mV5ajD-dXPSW7~<7H@?Y7Z&%WYuuX10dpF?^ zw>G)WxhrYE+QKie*Dtr`l-1N)?e1Sh4J?XJS<=d+sY{;u-5MIEKJ2gJj(ELMVw9u; zX{KeKVA#rkQH7VWoLT$<-G`4&PKxy((owDUBtBokF=p>cv34!O`%<^4T1#EScHZgc zPPcHbqZz!BS-g(&@U7T6^T2xQqIUJROg6iryYM*4w)sn${WefBew^UEsQ8%TADw@m z?K!}4iO;d|{Mn9bR4UW?ttwulcnX933f*db;d)P@?q8_Y7w$VRGRx`S`uXv1ff!d# zXSMPR=CDs_7k(S^MbUB-r3WA7ejA$S^GQ*-2~JmeQ7qd;iJX4EiQ+juWJz-?FLK*M zXYw0kyrI!%;+nmlm5g&}AAG=}^~Y`>Zz@oTJ6ca^5e|8{`h~KAo3XjS@tr8&Ocx^0 zUW2maprWKEJO&v`%{8&PhW?a4fr3f`?TI8-e|2jMC#2Y5+ zlwI<#vk$ku-a7drbqo9GWifaQrN&*l%z`+d7o$<=e38fOjd^ada0}&Y`)-N@5Yzs8 zbIDemjKPlCO0$!zcT~a=yuZ3gewP!g7@IFZnen!Y-wJJR+)5+yO$`qz--Y6qFiop= zn+X-vPmL+(rPz3>C_F$>wJ`qDB?y%_4y6ik>hX6!7bcYCv4E&WN=PGyQ?ztY2MH<5V(zA`FH47z|NXHJ%I zUqH}ZpCvYNdMry+BUM|7gp1TAxnH(}zpYcAD=eN+IAx~Oqtuz@?B2sU!P(t;pF3O3 zyvS#%Y_a|#Ws65H!vqdyi)ojsy?7}^4;Oh?C^9ImgG1>fGA~hn&@5#0#K#v={{0m! zN0l8!*%itVmoCBHn|2h=%K+Q!sFgZKyjcgSW5If$_%rEE#PN&NJgB^*LovMdqeJuv zvFQ@@-vILgakP#G1P$usQ117ZFTYGtL1n-_xu}!KtV8{3V1t{IA?98|u;|p8pPwyG zT}ETv9?)JqPI{>L$2w}J?ddEQ)M35dqU$jt_9~_?R-|9Wo#4nEG3hD|j@+H&P!cdg ztw9>-L=JvM6n(C}5+fG;Ms3yEV%u+&&|y%VLwN|r7XlQeB~pnm+VB-%r>9(mT%)g= zwTbcC##Br!+sZa^^cv-=ZA9XA%1x4`pW?|}D)DC$3)gk(;u9MRD~B=q#P$X%%Ds%m zf$P+lmENFqBm-{X6+s*E<_+pHTB_9GdhApZX9KenY=iN#m5;m)#NzjFPz!2|Qb<5B`@1==3%P@X|L2gKt)xTQmR0_h4+ss*n? zKGuev7Q6xZ796vr0%pWp1ePIhRlt~|nUrVDP9kvzMk3jQ?*rzE9^6vY1nONolp;z# z2|Nmh#L@WER$lBvKF)^IJXnkj(v!9M`R1G;U8ugm5hWa?$BG9-^lXkY(cd;K6*mq1;+LjCS&1TX8oV=E0KX zPa)5Jx8i&oZpT=pM(U}qD2(xFKn^s#Q_6NY6n^9iEE4SF`%%t|L<_7wcFcA!%l`!n z5>J5@_psnwC}+Qr<+r`q?*LuR0X|A163FaJl& zkxzdQgl^~met9yViq)S~BOBRa?v2xPB4uyx>@Y=aync_iHD25ouMcjb~Y>SXu-`P z&)zJ{uVbQg8@B4Z;VpOhDlo$;a1U^zML{=U-r>pzFy^=}aW7yFxDxL~gRBiGw{?(l zG{-wx;Q|=e2QcPnU@h-t!9Szi+5pBemhy3EP?7~d18nUeV}qUTdV)wv)Z0hO2aN*L z+Y>P+QSarOq8DMy9V`m}3D`Pi6~H{>QqMQG@;KId0y6B`cpV64KS*!e$>2bX}jg`vW9Vy&)u(xF5F$~8JH z#5=-ve7A$cD%540mHT+cP7B_EJnz$FIb+K?VSvI10jmPWY_qZgkM!}*Qi&Rj(*$b$e|@)41cqIZmx?1%7NzMRUB7f+?=!(I4mwaRnAe**FO+KPEu zm3Sla+*^r%K%RHwR@}>ie?*?GK$d^g2*%C^S9E5V)_VS|NNEqhV&IY?T$&)}FVov} zD8~VE1aMCTZ9bDooNL3QeOR1WrWa|r{u=bI{?CE95jn6ze^H%n4q2(M)YK?(Wwo9g z8HJF_^I>9`*dM=)68+ZTyu0uxhw`tWwV-XFZ$MR`GoTxwke_i$2TB691Lc6`fuE0b z5NH%=GROsb1GEzKG3YbU4$!xt{h*&f=Rns$O^!R1SdjC$BUNdSOjl5U&?wM&P%+2_ zdJR+pdi%K8^uE5N6YISIN7(*MJu#Dm&`dVmIkMuCbzF3{_sC7^YnO3-(pBcNJP z^ik}eLE}L$fnEbG0xbi51lkGu0dx#>7Nj0?D0B>sO+lsus26A?XfkLP=#QXxKx;ss z920>b>N~V|fr&A{ctxi07L!~rn3#wezBr@z5cUnJR%tLOW;^MC0QWJ+f&bz z_%Akm#D@7sigif&Z8ltC!`n3kJx>KOaD(14Th_~iyYPuYV#9`cLGzT0jT>|mmvs|A z*5B996KD^Z*O3q7YeUJ<@IY&eXG;ZU>Q;SSNVD-+ rhxq=R_Z8`&@#6RwdO_MyM8n~rkswUIhxmdH?+9Mrsf32yuK)L+>|5)n diff --git a/ui/public/contracts/room_contract.wasm b/ui/public/contracts/room_contract.wasm index f5e3891f378e8d0a16611125214500773d2a9e49..bd035cc8ea4eeaf29914f6e0bab7890a12ccb095 100755 GIT binary patch delta 65937 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 delta 65806 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 |I_r<~QZ5jB9=O^=OIAHbfv4W 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 diff --git a/ui/src/components/app/chat_delegate.rs b/ui/src/components/app/chat_delegate.rs index 399fb41e..100f0c32 100644 --- a/ui/src/components/app/chat_delegate.rs +++ b/ui/src/components/app/chat_delegate.rs @@ -10,8 +10,8 @@ use freenet_stdlib::prelude::{ use futures::channel::oneshot; use futures::future::{select, Either}; use river_core::chat_delegate::{ - ChatDelegateKey, ChatDelegateRequestMsg, ChatDelegateResponseMsg, OutboundDmEntry, - OutboundDmStore, RequestId, RoomKey, + ChatDelegateKey, ChatDelegateRequestMsg, ChatDelegateResponseMsg, HiddenDmThreadEntry, + OutboundDmEntry, OutboundDmStore, RequestId, RoomKey, }; use river_core::room_state::direct_messages::{PurgeToken, MAX_DM_MESSAGES_PER_PAIR}; use river_core::room_state::member::MemberId; @@ -524,6 +524,123 @@ pub fn hydrate_outbound_dms_cache(entries: Vec) -> usize { count } +/// Hydrate the [`HIDDEN_DM_THREADS`] signal from a `Vec` +/// loaded from a delegate (issue freenet/river#261). +/// +/// **Conflict resolution.** If an entry for the same `(room, peer)` is +/// already in the signal (e.g. the current delegate's response landed +/// first, then a legacy delegate response arrives), keep whichever has +/// the larger `hidden_at_ts` — the user's most-recent hide intent +/// wins. A legacy response with an older cutoff must NOT clobber a +/// newer hide. +/// +/// Returns the number of entries observed. +pub fn hydrate_hidden_dm_threads(entries: Vec) -> usize { + use crate::components::direct_messages::HIDDEN_DM_THREADS; + let count = entries.len(); + if count == 0 { + return 0; + } + crate::util::defer(move || { + HIDDEN_DM_THREADS.with_mut(|hidden| { + for entry in entries { + let room_vk = match ed25519_dalek::VerifyingKey::from_bytes(&entry.room_owner_vk) { + Ok(vk) => vk, + Err(e) => { + warn!( + "Skipping hidden-DM-thread entry with invalid room VK: {}", + e + ); + continue; + } + }; + let key = (room_vk, entry.peer); + match hidden.get(&key) { + Some(existing) if existing.hidden_at_ts >= entry.hidden_at_ts => { + // Existing entry is at least as fresh — keep it. + } + _ => { + hidden.insert(key, entry); + } + } + } + }); + }); + count +} + +/// Hide the DM thread for `(room, peer)` from the left rail (issue +/// freenet/river#261). +/// +/// `hidden_at_ts` is the most-recent message timestamp in the thread +/// at the moment the user clicked "Hide thread"; the filter rule uses +/// `<=` so any message strictly later revives the thread. +/// +/// If the user hides a thread that already has a hide entry with an +/// EARLIER cutoff, we advance the cutoff — clicking "Hide" again after +/// new messages arrived should re-hide the thread, not no-op it. +/// Conversely, if an existing entry's cutoff is already at or above +/// the incoming `hidden_at_ts`, we keep it (no need to rewrite the +/// delegate blob for an unchanged value). +pub fn hide_dm_thread( + room_owner_vk: ed25519_dalek::VerifyingKey, + peer: MemberId, + hidden_at_ts: u64, +) { + use crate::components::direct_messages::HIDDEN_DM_THREADS; + + let entry = HiddenDmThreadEntry { + room_owner_vk: room_owner_vk.to_bytes(), + peer, + hidden_at_ts, + }; + crate::util::defer(move || { + let mut changed = false; + HIDDEN_DM_THREADS.with_mut(|hidden| { + let key = (room_owner_vk, peer); + match hidden.get(&key) { + Some(existing) if existing.hidden_at_ts >= hidden_at_ts => { + // Already hidden at this cutoff or later — no-op. + } + _ => { + hidden.insert(key, entry); + changed = true; + } + } + }); + if changed { + crate::util::safe_spawn_local(async { + if let Err(e) = save_outbound_dms_to_delegate().await { + warn!("Failed to persist hide-DM-thread update: {}", e); + } + }); + } + }); +} + +/// Un-hide the DM thread for `(room, peer)` — drops the local hide +/// entry. Used by the "Hidden conversations" admin path (a future +/// follow-up; not exposed in v1 of #261) and by tests. +#[allow(dead_code)] +pub fn unhide_dm_thread(room_owner_vk: ed25519_dalek::VerifyingKey, peer: MemberId) { + use crate::components::direct_messages::HIDDEN_DM_THREADS; + crate::util::defer(move || { + let mut removed = false; + HIDDEN_DM_THREADS.with_mut(|hidden| { + if hidden.remove(&(room_owner_vk, peer)).is_some() { + removed = true; + } + }); + if removed { + crate::util::safe_spawn_local(async { + if let Err(e) = save_outbound_dms_to_delegate().await { + warn!("Failed to persist unhide-DM-thread update: {}", e); + } + }); + } + }); +} + /// Single-flight gate for [`save_outbound_dms_to_delegate`]. Without /// serialization, two `safe_spawn_local(save…)` calls can race: each /// snapshots the cache before its async `send_delegate_request().await`, @@ -583,7 +700,7 @@ pub async fn save_outbound_dms_to_delegate() -> Result<(), String> { } async fn do_save_outbound_dms_to_delegate() -> Result<(), String> { - use crate::components::direct_messages::OUTBOUND_DMS; + use crate::components::direct_messages::{HIDDEN_DM_THREADS, OUTBOUND_DMS}; let store = { let cache = OUTBOUND_DMS.read(); @@ -597,7 +714,24 @@ async fn do_save_outbound_dms_to_delegate() -> Result<(), String> { .then_with(|| a.recipient.cmp(&b.recipient)) .then_with(|| a.purge_token.0.cmp(&b.purge_token.0)) }); - OutboundDmStore { entries } + drop(cache); + + // Snapshot the hide-list (#261) under its own guard, then sort + // for the same byte-identity rationale. + let mut hidden_threads = { + let hidden = HIDDEN_DM_THREADS.read(); + hidden.values().cloned().collect::>() + }; + hidden_threads.sort_by(|a, b| { + a.room_owner_vk + .cmp(&b.room_owner_vk) + .then_with(|| a.peer.cmp(&b.peer)) + }); + + OutboundDmStore { + entries, + hidden_threads, + } }; let mut buffer = Vec::new(); diff --git a/ui/src/components/app/freenet_api/response_handler.rs b/ui/src/components/app/freenet_api/response_handler.rs index b5f2bae9..766ec845 100644 --- a/ui/src/components/app/freenet_api/response_handler.rs +++ b/ui/src/components/app/freenet_api/response_handler.rs @@ -9,9 +9,10 @@ use super::room_synchronizer::RoomSynchronizer; use crate::components::app::chat_delegate::{ complete_pending_public_key_request, complete_pending_request, complete_pending_sign_request, complete_pending_signing_key_request, decide_legacy_migration_action, - fire_legacy_migration_request, hydrate_outbound_dms_cache, is_legacy_delegate_key, - mark_legacy_migration_done, prune_outbound_dms_for_purges, save_outbound_dms_to_delegate, - save_rooms_to_delegate, LegacyMigrationAction, OUTBOUND_DMS_STORAGE_KEY, ROOMS_STORAGE_KEY, + fire_legacy_migration_request, hydrate_hidden_dm_threads, hydrate_outbound_dms_cache, + is_legacy_delegate_key, mark_legacy_migration_done, prune_outbound_dms_for_purges, + save_outbound_dms_to_delegate, save_rooms_to_delegate, LegacyMigrationAction, + OUTBOUND_DMS_STORAGE_KEY, ROOMS_STORAGE_KEY, }; use crate::components::app::document_title::{mark_current_room_as_read, update_document_title}; use crate::components::app::notifications::mark_initial_sync_complete; @@ -826,17 +827,19 @@ fn handle_outbound_dms_get_response(value: Option>, is_legacy_delegate: match from_reader::(&bytes[..]) { Ok(store) => { + let hidden_count = hydrate_hidden_dm_threads(store.hidden_threads); let count = hydrate_outbound_dms_cache(store.entries); info!( - "Hydrated {} outbound-DM entries from {} delegate", + "Hydrated {} outbound-DM entries and {} hidden DM thread entries from {} delegate", count, + hidden_count, if is_legacy_delegate { "legacy" } else { "current" } ); - if is_legacy_delegate && count > 0 { + if is_legacy_delegate && (count > 0 || hidden_count > 0) { // Persist the merged cache under the current delegate // key so subsequent loads find the data without // re-hitting the legacy delegate. diff --git a/ui/src/components/direct_messages.rs b/ui/src/components/direct_messages.rs index aa0e165e..fe43e3c6 100644 --- a/ui/src/components/direct_messages.rs +++ b/ui/src/components/direct_messages.rs @@ -26,7 +26,7 @@ pub use invite_via_dm_picker_modal::InviteViaDmPickerModal; use dioxus::prelude::*; use ed25519_dalek::VerifyingKey; -use river_core::chat_delegate::OutboundDmEntry; +use river_core::chat_delegate::{HiddenDmThreadEntry, OutboundDmEntry}; use river_core::room_state::direct_messages::PurgeToken; use river_core::room_state::member::MemberId; use std::collections::HashMap; @@ -146,6 +146,36 @@ impl OutboundDmsCache { } } +/// Local view-state for issue freenet/river#261: per-`(room, peer)` +/// "hidden-at" cutoffs. A thread is hidden from +/// [`crate::components::room_list::dm_rail_section::DmRailSection`] +/// iff no message between the local user and `peer` in `room` is +/// strictly later than the recorded `hidden_at_ts`. +/// +/// Persisted via the chat-delegate `OutboundDmStore.hidden_threads` +/// side-channel so a hide on device A propagates to device B (and +/// survives a reload on the same device). In-memory shape is a +/// `HashMap` keyed by `(room, peer)` for O(1) render-time lookup; the +/// on-disk shape is a `Vec` for JSON safety (see the "non-string map +/// keys" bug-prevention pattern). +pub static HIDDEN_DM_THREADS: GlobalSignal> = + Global::new(HashMap::new); + +/// UI-side wrapper around `river_core::chat_delegate::is_thread_hidden` +/// that does the `VerifyingKey -> [u8; 32]` conversion at the boundary. +/// Kept as a thin shim so callers don't have to repeat the byte +/// conversion at every render site. +pub fn is_thread_hidden_for( + hidden: &HashMap<(VerifyingKey, MemberId), HiddenDmThreadEntry>, + room: &VerifyingKey, + peer: MemberId, + max_message_ts: u64, +) -> bool { + hidden + .get(&(*room, peer)) + .is_some_and(|h| max_message_ts <= h.hidden_at_ts) +} + /// Pure helper: decide how to render an outbound DM bubble given the /// loaded plaintext cache. /// diff --git a/ui/src/components/direct_messages/dm_thread_modal.rs b/ui/src/components/direct_messages/dm_thread_modal.rs index a5eaeb65..3777f1f2 100644 --- a/ui/src/components/direct_messages/dm_thread_modal.rs +++ b/ui/src/components/direct_messages/dm_thread_modal.rs @@ -3,7 +3,7 @@ //! `AuthorizedRecipientPurges` envelope tombstoning every inbound DM in the //! current view. -use crate::components::app::chat_delegate::save_outbound_dm; +use crate::components::app::chat_delegate::{hide_dm_thread, save_outbound_dm}; use crate::components::app::{mark_needs_sync, ROOMS}; use crate::components::direct_messages::{ lookup_outbound_plaintext, mark_thread_read, DM_DRAFT, OPEN_DM_THREAD, OUTBOUND_DMS, @@ -142,6 +142,7 @@ fn DmThreadModalBody(room: VerifyingKey, peer: MemberId) -> Element { let outbound_cache = OUTBOUND_DMS.try_read().ok().map(|g| g.clone()); let mut latest_inbound_ts: u64 = 0; + let mut latest_any_ts: u64 = 0; let mut rendered: Vec = Vec::new(); for msg in &room_data.room_state.direct_messages.messages { let is_self_sender = msg.message.sender == self_id; @@ -155,6 +156,7 @@ fn DmThreadModalBody(room: VerifyingKey, peer: MemberId) -> Element { if is_self_recipient { latest_inbound_ts = latest_inbound_ts.max(msg.message.timestamp); } + latest_any_ts = latest_any_ts.max(msg.message.timestamp); let (body, kind) = if is_self_recipient { match open_direct_message(&self_sk, msg) { @@ -207,6 +209,7 @@ fn DmThreadModalBody(room: VerifyingKey, peer: MemberId) -> Element { peer_still_member, messages: rendered, latest_inbound_ts, + latest_any_ts, }) } }); @@ -225,6 +228,7 @@ fn DmThreadModalBody(room: VerifyingKey, peer: MemberId) -> Element { let peer_label = view_data.peer_nickname.clone(); let peer_still_member = view_data.peer_still_member; + let latest_any_ts = view_data.latest_any_ts; let close = move |_| { crate::util::defer(move || { @@ -232,6 +236,26 @@ fn DmThreadModalBody(room: VerifyingKey, peer: MemberId) -> Element { }); }; + // Issue freenet/river#261: "Hide thread" — local-view filter that + // takes this thread off `DmRailSection` until either party sends a + // fresh DM. Distinct from `purge_thread` ("Delete their messages"), + // which is destructive across the network. We capture the + // most-recent message timestamp (or `unix_now()` if the thread is + // empty) as the hide cutoff; the rail filter compares it against + // each thread's `last_any_ts` with strict `>` so any later message + // revives the thread. + let hide_thread = move |_| { + let cutoff = if latest_any_ts > 0 { + latest_any_ts + } else { + unix_now() + }; + hide_dm_thread(room, peer, cutoff); + crate::util::defer(move || { + *OPEN_DM_THREAD.write() = None; + }); + }; + // No-arg send callback so both `onclick` and `onkeydown` (Enter) // can invoke it. `mut` because Dioxus signal `.set()` borrows the // closure as `FnMut`. @@ -472,10 +496,23 @@ fn DmThreadModalBody(room: VerifyingKey, peer: MemberId) -> Element { "Direct messages with " span { class: "text-accent", "{peer_label}" } } - button { - class: "p-1 text-text-muted hover:text-text transition-colors text-xl", - onclick: close, - "✕" + div { class: "flex items-center gap-1", + // "Hide thread" — issue freenet/river#261. Local-view + // filter; the recipient is unaffected. Sized smaller + // than the ✕ close button and with a clear hover state + // so it isn't confused with the close button (the + // issue explicitly called this out). + button { + class: "p-1 text-sm text-text-muted hover:text-yellow-400 transition-colors", + title: "Hide this conversation from your sidebar. It will come back when either of you sends a new message.", + onclick: hide_thread, + "Hide" + } + button { + class: "p-1 text-text-muted hover:text-text transition-colors text-xl", + onclick: close, + "✕" + } } } @@ -562,6 +599,11 @@ struct ViewData { peer_still_member: bool, messages: Vec, latest_inbound_ts: u64, + /// Max timestamp across both inbound and outbound DMs in this + /// thread. Used as `hidden_at_ts` for the "Hide thread" action + /// (#261) — the filter uses `<=`, so anything strictly newer + /// revives the thread. + latest_any_ts: u64, } /// Inbound DM body presentation. diff --git a/ui/src/components/room_list/dm_rail_section.rs b/ui/src/components/room_list/dm_rail_section.rs index 924580dd..7169b1c2 100644 --- a/ui/src/components/room_list/dm_rail_section.rs +++ b/ui/src/components/room_list/dm_rail_section.rs @@ -14,7 +14,9 @@ //! load. Sorts unread threads first, then by most-recent message time. use crate::components::app::ROOMS; -use crate::components::direct_messages::{open_dm_thread, DM_LAST_SEEN}; +use crate::components::direct_messages::{ + is_thread_hidden_for, open_dm_thread, DM_LAST_SEEN, HIDDEN_DM_THREADS, +}; use crate::util::ecies::unseal_bytes_with_secrets; use dioxus::prelude::*; use dioxus_free_icons::{icons::fa_solid_icons::FaEnvelope, Icon}; @@ -92,6 +94,14 @@ fn build_view() -> Option> { return Some(Vec::new()); } let last_seen = DM_LAST_SEEN.try_read().ok()?.clone(); + // Snapshot the hide-list (#261). `try_read` keeps us cooperative + // with any in-flight `defer`-scheduled mutation. On contention we + // silently treat the list as empty for THIS render — a hidden + // thread briefly re-appearing during a write storm is preferable + // to dropping the entire rail. Successful `try_read` registers + // the memo's subscription so subsequent hide/unhide writes + // re-run this build (Dioxus signal-safety semantics). + let hidden = HIDDEN_DM_THREADS.try_read().ok().map(|h| h.clone()); let mut entries: Vec = Vec::new(); for (owner_vk, room_data) in &rooms.map { @@ -162,6 +172,20 @@ fn build_view() -> Option> { } for (peer, acc) in per_peer { + // Issue freenet/river#261: skip threads the local user has + // hidden, unless a newer message has arrived since the + // hide. `is_thread_hidden_for` does the strict `<=` check + // on `hidden_at_ts`. An outbound DM the user sends to a + // hidden peer also revives the thread because the new + // message's timestamp is `> hidden_at_ts` (see + // `dm_thread_modal::DmThreadModalBody::do_send` — same + // `unix_now()` shim used for both the message and the + // hide cutoff). + if let Some(ref hidden_map) = hidden { + if is_thread_hidden_for(hidden_map, owner_vk, peer, acc.last_any_ts) { + continue; + } + } entries.push(DmRailEntry { room: *owner_vk, peer, From 55b09e9ece708f9c8ae09a8c4d3528c79adb852c Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sat, 16 May 2026 21:06:21 -0500 Subject: [PATCH 2/7] chore(cli): bump riverctl to 0.1.57 Required by CI `check-wasm-sync`: the room-contract WASM was rebuilt as a side effect of the river-core change, and riverctl embeds the WASM to compute the contract key. Without a version bump the published riverctl on crates.io would target a different contract than the UI shipping the new WASM. [AI-assisted - Claude] Co-Authored-By: Claude Opus 4.7 --- cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5b0df7fd..97bb4c63 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "riverctl" -version = "0.1.56" +version = "0.1.57" edition = "2021" authors = ["Freenet Project"] description = "Command-line interface for River decentralized chat on Freenet" From a60814e396a10fea3093be6b10356b20549c300c Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sat, 16 May 2026 21:08:20 -0500 Subject: [PATCH 3/7] fix(dm): explicit unhide on successful outbound (Codex P1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex caught a "hide, then immediately send" deadlock: both `hide_dm_thread`'s `hidden_at_ts` and the outbound DM's `message.timestamp` come from `unix_now()` which has 1-second resolution. If both calls land in the same second, the rail filter's `last_any_ts <= hidden_at_ts` predicate evaluates true and the thread stays hidden right after the user sent a message. Fix: explicitly call `unhide_dm_thread` from the successful `ApplyOutcome::Applied` arm. Idempotent — a no-op when no entry exists for the pair, so non-hidden threads are unaffected. This removes the dependency on second-resolution timestamp comparison for the outbound-revives case. Inbound revival on clock-skewed sender timestamps remains filter-only (deferred — would require a ROOMS-subscribed effect that diffs prior-vs-current `direct_messages.messages` for new inbound entries, which is more invasive than this PR warrants for the same-second corner case). [AI-assisted - Claude] Co-Authored-By: Claude Opus 4.7 --- ui/src/components/app/chat_delegate.rs | 9 ++++++--- .../components/direct_messages/dm_thread_modal.rs | 14 +++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ui/src/components/app/chat_delegate.rs b/ui/src/components/app/chat_delegate.rs index 100f0c32..11e5300c 100644 --- a/ui/src/components/app/chat_delegate.rs +++ b/ui/src/components/app/chat_delegate.rs @@ -619,9 +619,12 @@ pub fn hide_dm_thread( } /// Un-hide the DM thread for `(room, peer)` — drops the local hide -/// entry. Used by the "Hidden conversations" admin path (a future -/// follow-up; not exposed in v1 of #261) and by tests. -#[allow(dead_code)] +/// entry. Called by [`crate::components::direct_messages::dm_thread_modal`] +/// after a successful outbound DM to guarantee revival even when the +/// outbound message's `unix_now()` second matches the hide cutoff +/// (Codex P1 finding on #261). Idempotent: a no-op when no entry +/// exists for the pair. Also the API a future "Hidden conversations" +/// admin path would use. pub fn unhide_dm_thread(room_owner_vk: ed25519_dalek::VerifyingKey, peer: MemberId) { use crate::components::direct_messages::HIDDEN_DM_THREADS; crate::util::defer(move || { diff --git a/ui/src/components/direct_messages/dm_thread_modal.rs b/ui/src/components/direct_messages/dm_thread_modal.rs index 3777f1f2..17a4eef7 100644 --- a/ui/src/components/direct_messages/dm_thread_modal.rs +++ b/ui/src/components/direct_messages/dm_thread_modal.rs @@ -3,7 +3,7 @@ //! `AuthorizedRecipientPurges` envelope tombstoning every inbound DM in the //! current view. -use crate::components::app::chat_delegate::{hide_dm_thread, save_outbound_dm}; +use crate::components::app::chat_delegate::{hide_dm_thread, save_outbound_dm, unhide_dm_thread}; use crate::components::app::{mark_needs_sync, ROOMS}; use crate::components::direct_messages::{ lookup_outbound_plaintext, mark_thread_read, DM_DRAFT, OPEN_DM_THREAD, OUTBOUND_DMS, @@ -370,6 +370,18 @@ fn DmThreadModalBody(room: VerifyingKey, peer: MemberId) -> Element { // inside `save_outbound_dm` via `defer` / // `safe_spawn_local`. save_outbound_dm(room, self_id, peer, purge_token, dm_timestamp, plaintext); + // Issue freenet/river#261 (Codex P1): if the + // thread had been hidden, an outbound send must + // revive it. The filter's "max_message_ts > + // hidden_at_ts" check isn't sufficient on its + // own because both `unix_now()` calls (the one + // captured into `hidden_at_ts` and the one + // stamped onto the outbound message) can land + // in the same second — leaving the thread + // stuck-hidden right after the user sent a + // message. Explicit unhide is deterministic + // and idempotent (no-op when no entry exists). + unhide_dm_thread(room, peer); draft.set(String::new()); } ApplyOutcome::CapHit => { From 3c41af4c356e707dc7082984bbcec2ca4d34bfe0 Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sat, 16 May 2026 21:18:38 -0500 Subject: [PATCH 4/7] fix(dm): suppress late-hydration resurrection of unhidden threads (Codex P3) Codex second-pass found a remaining race in the P1 fix: `hydrate_hidden_dm_threads` can run AFTER `unhide_dm_thread`, in which case the delegate response re-inserts the just-removed entry. Combined with the unix-second-resolution `hidden_at_ts`, this can re-hide a thread immediately after a successful outbound DM if the delegate hydration was in-flight at the moment of send. Fix: a session-scoped `RECENTLY_UNHIDDEN: HashSet<(VerifyingKey, MemberId)>` tombstone set, populated by `unhide_dm_thread` BEFORE the deferred signal write. `hydrate_hidden_dm_threads` consults the set under its own lock and drops any incoming entry whose pair is suppressed. Persistence: `unhide_dm_thread` now queues an unconditional delegate save even when no in-memory entry existed at removal time. Without this, an entry sitting in the delegate from a prior session would resurrect on next reload (the tombstone is session-only by design). Tested via a new pure helper `resolve_hidden_thread_hydration` (6 new unit tests pinning insert/keep-fresher/take-fresher/suppress/ per-peer-scoping/per-room-scoping behaviours). The hot-path `hydrate_hidden_dm_threads` now goes through this helper so the suppression logic is unit-testable. [AI-assisted - Claude] Co-Authored-By: Claude Opus 4.7 --- ui/src/components/app/chat_delegate.rs | 297 ++++++++++++++++++++++--- 1 file changed, 266 insertions(+), 31 deletions(-) diff --git a/ui/src/components/app/chat_delegate.rs b/ui/src/components/app/chat_delegate.rs index 11e5300c..94302de7 100644 --- a/ui/src/components/app/chat_delegate.rs +++ b/ui/src/components/app/chat_delegate.rs @@ -260,6 +260,129 @@ mod tests { assert!(key.starts_with(LEGACY_MIGRATION_FLAG_PREFIX)); assert!(key.ends_with(&fp)); } + + // ===== resolve_hidden_thread_hydration tests (#261 Codex P3) ===== + use freenet_scaffold::util::FastHash; + + fn sk(seed: u8) -> ed25519_dalek::SigningKey { + ed25519_dalek::SigningKey::from_bytes(&[seed; 32]) + } + + fn make_hidden_entry( + room_vk: ed25519_dalek::VerifyingKey, + peer: MemberId, + ts: u64, + ) -> HiddenDmThreadEntry { + HiddenDmThreadEntry { + room_owner_vk: room_vk.to_bytes(), + peer, + hidden_at_ts: ts, + } + } + + /// Baseline: with no current entries and no suppression, every + /// incoming entry is inserted. + #[test] + fn resolve_hidden_thread_hydration_inserts_into_empty() { + let room_vk = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let incoming = vec![make_hidden_entry(room_vk, peer, 1_000)]; + let current = HashMap::new(); + let suppressed = HashSet::new(); + + let (to_insert, diagnostics) = + resolve_hidden_thread_hydration(incoming, ¤t, &suppressed); + assert_eq!(to_insert.len(), 1); + assert!(diagnostics.is_empty()); + assert_eq!(to_insert[0].0, (room_vk, peer)); + assert_eq!(to_insert[0].1.hidden_at_ts, 1_000); + } + + /// Conflict resolution: in-memory entry's `hidden_at_ts >=` + /// incoming → keep in-memory (no insert returned). + #[test] + fn resolve_hidden_thread_hydration_keeps_fresher_in_memory_entry() { + let room_vk = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let incoming = vec![make_hidden_entry(room_vk, peer, 500)]; + let mut current = HashMap::new(); + current.insert((room_vk, peer), make_hidden_entry(room_vk, peer, 1_000)); + let suppressed = HashSet::new(); + + let (to_insert, _) = resolve_hidden_thread_hydration(incoming, ¤t, &suppressed); + assert!( + to_insert.is_empty(), + "fresher in-memory entry must NOT be overwritten by older incoming" + ); + } + + /// Conflict resolution: incoming `hidden_at_ts >` in-memory → + /// overwrite. Pins the "most recent hide wins" semantics. + #[test] + fn resolve_hidden_thread_hydration_takes_fresher_incoming_entry() { + let room_vk = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let incoming = vec![make_hidden_entry(room_vk, peer, 2_000)]; + let mut current = HashMap::new(); + current.insert((room_vk, peer), make_hidden_entry(room_vk, peer, 1_000)); + let suppressed = HashSet::new(); + + let (to_insert, _) = resolve_hidden_thread_hydration(incoming, ¤t, &suppressed); + assert_eq!(to_insert.len(), 1); + assert_eq!(to_insert[0].1.hidden_at_ts, 2_000); + } + + /// Codex P3 regression: a `(room, peer)` in the suppression set + /// MUST drop the incoming hide, even if it would otherwise + /// overwrite a stale in-memory entry. This is what closes the + /// "unhide racing late-hydration" window. + #[test] + fn resolve_hidden_thread_hydration_suppresses_recently_unhidden() { + let room_vk = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let incoming = vec![make_hidden_entry(room_vk, peer, 5_000)]; + let current = HashMap::new(); + let mut suppressed = HashSet::new(); + suppressed.insert((room_vk, peer)); + + let (to_insert, diagnostics) = + resolve_hidden_thread_hydration(incoming, ¤t, &suppressed); + assert!( + to_insert.is_empty(), + "suppressed pair must not be re-inserted by hydration" + ); + assert!(diagnostics.is_empty()); + } + + /// Suppression is scoped per `(room, peer)`: an entry for a + /// DIFFERENT peer in the same room must still be applied. + #[test] + fn resolve_hidden_thread_hydration_suppression_does_not_leak_across_peers() { + let room_vk = sk(1).verifying_key(); + let suppressed_peer = MemberId(FastHash(11)); + let other_peer = MemberId(FastHash(22)); + let incoming = vec![ + make_hidden_entry(room_vk, suppressed_peer, 5_000), + make_hidden_entry(room_vk, other_peer, 5_000), + ]; + let current = HashMap::new(); + let mut suppressed = HashSet::new(); + suppressed.insert((room_vk, suppressed_peer)); + + let (to_insert, _) = resolve_hidden_thread_hydration(incoming, ¤t, &suppressed); + assert_eq!(to_insert.len(), 1); + assert_eq!(to_insert[0].0, (room_vk, other_peer)); + } + + // Note: a unit test for the "invalid room VK" defensive branch + // was attempted but `ed25519_dalek::VerifyingKey::from_bytes` does + // not validate canonical encoding at decode time (validation + // happens only when a signature is verified against the key), so + // there is no easy way to synthesize a `[u8; 32]` value the + // decoder rejects. The branch is defensive code against + // genuinely-corrupt delegate data; integration testing against a + // truncated delegate blob would be the right coverage if this + // path matters. } /// Idempotent helper: ask the chat delegate to subscribe to a room @@ -524,6 +647,43 @@ pub fn hydrate_outbound_dms_cache(entries: Vec) -> usize { count } +/// Session-scoped tombstone set for `(room, peer)` pairs the local +/// user has explicitly un-hidden in this session. Consulted by +/// [`hydrate_hidden_dm_threads`] to suppress any late-arriving +/// delegate response that would otherwise resurrect the just-removed +/// hide entry (Codex P3 finding on #261, second review pass). +/// +/// Scenario the tombstone closes: user clicks Hide, then Send. The +/// send's `Applied` arm calls `unhide_dm_thread` and the +/// `save_outbound_dms_to_delegate` writes the now-empty hidden list. +/// But if the delegate's GET response (or a legacy delegate's GET +/// response) is still in-flight when the user did all that, it will +/// arrive AFTER the unhide and re-insert the entry with its original +/// `hidden_at_ts`. The strict `<=` filter would then re-hide the +/// thread when the outbound DM's `unix_now()` happened to land in +/// the same second. +/// +/// Without persistence: the tombstone is in-memory only, scoped to +/// the current session. That's deliberate — the persistent store +/// (`OutboundDmStore.hidden_threads`) is itself the source of truth +/// across sessions; the tombstone is a transient guard against +/// hydrate-vs-unhide races within a session. A new session starts +/// from the delegate's current `hidden_threads` snapshot (which by +/// construction has the post-unhide state because +/// `save_outbound_dms_to_delegate` was queued by `unhide_dm_thread`). +static RECENTLY_UNHIDDEN: std::sync::LazyLock< + Mutex>, +> = std::sync::LazyLock::new(|| Mutex::new(HashSet::new())); + +/// Reset the recently-unhidden tombstone set. For tests only; +/// production never calls this. +#[cfg(test)] +pub(crate) fn reset_recently_unhidden() { + if let Ok(mut s) = RECENTLY_UNHIDDEN.lock() { + s.clear(); + } +} + /// Hydrate the [`HIDDEN_DM_THREADS`] signal from a `Vec` /// loaded from a delegate (issue freenet/river#261). /// @@ -534,7 +694,70 @@ pub fn hydrate_outbound_dms_cache(entries: Vec) -> usize { /// wins. A legacy response with an older cutoff must NOT clobber a /// newer hide. /// -/// Returns the number of entries observed. +/// **Recently-unhidden suppression (Codex P3).** Entries whose +/// `(room, peer)` appears in [`RECENTLY_UNHIDDEN`] are dropped — see +/// that static's doc-comment for the exact race. Without this guard, +/// a delegate response in-flight at the moment of `unhide_dm_thread` +/// would re-insert the just-removed entry, defeating the "outbound +/// always revives" guarantee from the Codex P1 fix. +/// +/// Returns the number of entries observed (NOT the number actually +/// inserted, so callers can still gate "is_legacy_delegate && count > 0" +/// on the wire-side presence rather than on post-filter retention). +/// Pure helper: decide which incoming hydration entries to apply to a +/// hidden-threads map, given the current map state and the set of +/// recently-unhidden `(room, peer)` pairs. +/// +/// Rules: +/// - Entries with an invalid `room_owner_vk` are dropped (caller logs). +/// - Entries whose `(room, peer)` is in `suppressed` are dropped — the +/// user explicitly un-hid this thread in the current session, so a +/// late-arriving delegate response must not resurrect the hide +/// (Codex P3 on #261 v2). +/// - Otherwise, take whichever entry has the larger `hidden_at_ts`: +/// the incoming entry overwrites the in-memory one when it is at +/// least as fresh; otherwise the in-memory entry is preserved. +/// +/// Returns the `(key, entry)` pairs that should be `insert`-ed into +/// the in-memory `HashMap`. Errors-by-malformed-VK are returned +/// separately as a `Vec` of human-readable diagnostics so the +/// caller can emit `warn!` log lines. +pub(crate) fn resolve_hidden_thread_hydration( + incoming: Vec, + current: &HashMap<(ed25519_dalek::VerifyingKey, MemberId), HiddenDmThreadEntry>, + suppressed: &HashSet<(ed25519_dalek::VerifyingKey, MemberId)>, +) -> ( + Vec<((ed25519_dalek::VerifyingKey, MemberId), HiddenDmThreadEntry)>, + Vec, +) { + let mut to_insert = Vec::with_capacity(incoming.len()); + let mut diagnostics = Vec::new(); + for entry in incoming { + let room_vk = match ed25519_dalek::VerifyingKey::from_bytes(&entry.room_owner_vk) { + Ok(vk) => vk, + Err(e) => { + diagnostics.push(format!( + "Skipping hidden-DM-thread entry with invalid room VK: {e}" + )); + continue; + } + }; + let key = (room_vk, entry.peer); + if suppressed.contains(&key) { + continue; + } + match current.get(&key) { + Some(existing) if existing.hidden_at_ts >= entry.hidden_at_ts => { + // Existing entry is at least as fresh — keep it. + } + _ => { + to_insert.push((key, entry)); + } + } + } + (to_insert, diagnostics) +} + pub fn hydrate_hidden_dm_threads(entries: Vec) -> usize { use crate::components::direct_messages::HIDDEN_DM_THREADS; let count = entries.len(); @@ -542,27 +765,25 @@ pub fn hydrate_hidden_dm_threads(entries: Vec) -> usize { return 0; } crate::util::defer(move || { - HIDDEN_DM_THREADS.with_mut(|hidden| { - for entry in entries { - let room_vk = match ed25519_dalek::VerifyingKey::from_bytes(&entry.room_owner_vk) { - Ok(vk) => vk, - Err(e) => { - warn!( - "Skipping hidden-DM-thread entry with invalid room VK: {}", - e - ); - continue; - } - }; - let key = (room_vk, entry.peer); - match hidden.get(&key) { - Some(existing) if existing.hidden_at_ts >= entry.hidden_at_ts => { - // Existing entry is at least as fresh — keep it. - } - _ => { - hidden.insert(key, entry); - } + // Snapshot the tombstone set under its own lock so we don't + // hold it across the signal write (the with_mut closure may + // run subscriber notifications synchronously on Drop). + let suppressed: HashSet<(ed25519_dalek::VerifyingKey, MemberId)> = + match RECENTLY_UNHIDDEN.lock() { + Ok(g) => g.clone(), + Err(e) => { + warn!("RECENTLY_UNHIDDEN lock poisoned: {}", e); + HashSet::new() } + }; + HIDDEN_DM_THREADS.with_mut(|hidden| { + let (to_insert, diagnostics) = + resolve_hidden_thread_hydration(entries, hidden, &suppressed); + for diag in diagnostics { + warn!("{}", diag); + } + for (key, entry) in to_insert { + hidden.insert(key, entry); } }); }); @@ -627,20 +848,34 @@ pub fn hide_dm_thread( /// admin path would use. pub fn unhide_dm_thread(room_owner_vk: ed25519_dalek::VerifyingKey, peer: MemberId) { use crate::components::direct_messages::HIDDEN_DM_THREADS; + + // Record the tombstone BEFORE deferring the signal mutation, so a + // delegate GetResponse landing between this call and the deferred + // signal write still sees the suppression (Codex P3 #261). + // Unconditional insert: even if the in-memory signal has no entry + // right now (because hydration hasn't completed yet), we want + // future hydrations to skip any matching entry. + if let Ok(mut s) = RECENTLY_UNHIDDEN.lock() { + s.insert((room_owner_vk, peer)); + } + crate::util::defer(move || { - let mut removed = false; HIDDEN_DM_THREADS.with_mut(|hidden| { - if hidden.remove(&(room_owner_vk, peer)).is_some() { - removed = true; + hidden.remove(&(room_owner_vk, peer)); + }); + // Always queue a save (even if no in-memory entry existed at + // the moment of removal): the recently-unhidden tombstone has + // been set, and persisting the now-current `hidden_threads` + // slice (without this pair) is what makes the unhide survive + // a session restart. Without an unconditional save, a hidden + // entry still sitting in the delegate from a prior session + // would resurrect on the next reload — the in-memory + // tombstone is session-only by design. + crate::util::safe_spawn_local(async { + if let Err(e) = save_outbound_dms_to_delegate().await { + warn!("Failed to persist unhide-DM-thread update: {}", e); } }); - if removed { - crate::util::safe_spawn_local(async { - if let Err(e) = save_outbound_dms_to_delegate().await { - warn!("Failed to persist unhide-DM-thread update: {}", e); - } - }); - } }); } From 18cc4d16a7a3c312877cd947a362845102565019 Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sat, 16 May 2026 21:21:39 -0500 Subject: [PATCH 5/7] fix(dm): dedupe duplicate (room, peer) entries within incoming Vec (Codex Low) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round-3 Low: `resolve_hidden_thread_hydration` compared each incoming entry only against the pre-existing `current` map, not against earlier entries within the same `Vec`. A malformed legacy blob containing the same `(room, peer)` twice in newer-then-older order would emit BOTH into `to_insert`, and the caller's eventual `HashMap::insert` loop would overwrite the newer cutoff with the older one. Normal UI saves go through a `HashMap` so duplicates can't arise there, but the on-disk wire shape is a `Vec` — any other writer (or a future corrupt-data scenario) could produce duplicates. Fold incoming entries through a temporary `HashMap` keyed by `(room_vk, peer)`, keeping whichever cutoff is largest, before emitting `to_insert`. The "largest hidden_at_ts wins" invariant is now actually upheld. Two new unit tests pin the dedup behaviour: newer-then-older ordering and reverse-with-tie ordering. [AI-assisted - Claude] Co-Authored-By: Claude Opus 4.7 --- ui/src/components/app/chat_delegate.rs | 80 ++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/ui/src/components/app/chat_delegate.rs b/ui/src/components/app/chat_delegate.rs index 94302de7..a53afd1c 100644 --- a/ui/src/components/app/chat_delegate.rs +++ b/ui/src/components/app/chat_delegate.rs @@ -383,6 +383,57 @@ mod tests { // genuinely-corrupt delegate data; integration testing against a // truncated delegate blob would be the right coverage if this // path matters. + + /// Codex round-3 Low: duplicate `(room, peer)` entries in the same + /// incoming `Vec` must resolve to the entry with the largest + /// `hidden_at_ts`. Without internal dedup, a newer-then-older + /// ordering would let the caller's eventual `HashMap::insert` loop + /// overwrite the newer cutoff with the older one (because both + /// would be emitted into `to_insert`). + #[test] + fn resolve_hidden_thread_hydration_dedupes_duplicate_incoming() { + let room_vk = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + // Same pair appears three times with varying timestamps. + let incoming = vec![ + make_hidden_entry(room_vk, peer, 500), + make_hidden_entry(room_vk, peer, 2_000), + make_hidden_entry(room_vk, peer, 1_000), + ]; + let current = HashMap::new(); + let suppressed = HashSet::new(); + + let (to_insert, _) = resolve_hidden_thread_hydration(incoming, ¤t, &suppressed); + assert_eq!( + to_insert.len(), + 1, + "duplicate (room, peer) entries must collapse to one" + ); + assert_eq!( + to_insert[0].1.hidden_at_ts, 2_000, + "the largest hidden_at_ts must win" + ); + } + + /// Reverse ordering of the above: the largest cutoff arriving + /// last must still win (and identical-cutoff entries must not + /// produce a duplicate emit). + #[test] + fn resolve_hidden_thread_hydration_dedup_handles_reverse_and_ties() { + let room_vk = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let incoming = vec![ + make_hidden_entry(room_vk, peer, 5_000), + make_hidden_entry(room_vk, peer, 5_000), // tie + make_hidden_entry(room_vk, peer, 100), // older, must not overwrite + ]; + let current = HashMap::new(); + let suppressed = HashSet::new(); + + let (to_insert, _) = resolve_hidden_thread_hydration(incoming, ¤t, &suppressed); + assert_eq!(to_insert.len(), 1); + assert_eq!(to_insert[0].1.hidden_at_ts, 5_000); + } } /// Idempotent helper: ask the chat delegate to subscribe to a room @@ -730,7 +781,20 @@ pub(crate) fn resolve_hidden_thread_hydration( Vec<((ed25519_dalek::VerifyingKey, MemberId), HiddenDmThreadEntry)>, Vec, ) { - let mut to_insert = Vec::with_capacity(incoming.len()); + // Fold incoming entries through a temporary map so duplicate + // `(room, peer)` keys in the same `Vec` resolve to the entry with + // the largest `hidden_at_ts` — without this, a malformed legacy + // blob that contains the same pair twice (newer-then-older order) + // would let the older cutoff overwrite the newer one when the + // caller does `for (k,e) in to_insert { hidden.insert(k,e); }` + // (Codex round-3 Low finding on #261). + // + // The normal UI write path goes through a `HashMap` snapshot so it + // can't produce duplicates; this guard is for legacy / corrupted + // delegate blobs from other writers (or pre-#261 vintages that + // accidentally accumulated duplicates). + let mut merged: HashMap<(ed25519_dalek::VerifyingKey, MemberId), HiddenDmThreadEntry> = + HashMap::new(); let mut diagnostics = Vec::new(); for entry in incoming { let room_vk = match ed25519_dalek::VerifyingKey::from_bytes(&entry.room_owner_vk) { @@ -746,15 +810,23 @@ pub(crate) fn resolve_hidden_thread_hydration( if suppressed.contains(&key) { continue; } - match current.get(&key) { - Some(existing) if existing.hidden_at_ts >= entry.hidden_at_ts => { + // Compare against in-flight merged entries first, then against + // the pre-existing `current` map. Keep whichever cutoff is + // largest. + let existing_ts = merged + .get(&key) + .map(|e| e.hidden_at_ts) + .or_else(|| current.get(&key).map(|e| e.hidden_at_ts)); + match existing_ts { + Some(ts) if ts >= entry.hidden_at_ts => { // Existing entry is at least as fresh — keep it. } _ => { - to_insert.push((key, entry)); + merged.insert(key, entry); } } } + let to_insert = merged.into_iter().collect(); (to_insert, diagnostics) } From 48a0ac32ef6208914f187f1b90933ccc14abfa2e Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sat, 16 May 2026 22:11:01 -0500 Subject: [PATCH 6/7] test(dm): pin #261 hide-filter + re-hide round-trip + outbound revive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the three regression tests called out as BLOCKING coverage gaps by the testing reviewer on PR #265 (issue freenet/river#261). 1. **Rail filter is now unit-tested.** Extracts the filter loop body from `dm_rail_section::build_view` into a pure helper `filter_rail_entries(entries, hidden) -> Vec`. Six tests pin the user-visible "thread disappears from the rail" behaviour: - omits hidden entries (strict `<=` boundary) - newer inbound DM revives - newer outbound DM revives (via `last_any_ts > hidden_at_ts`) - hide is scoped per (room, peer) — no cross-room leak - hide is scoped per peer within a room - empty hidden map is a pass-through fast-path - unhide → thread reappears 2. **Hide → revive → re-hide round-trip.** Extracts the `hide_dm_thread` decision branch into the pure helper `decide_hide_action(existing, incoming) -> HideAction` (`Insert(entry)` / `NoOp`). Four direct tests for the decision logic, plus one round-trip test (`hide_unhide_rehide_round_trip`) that exercises the full sequence: hide@1000 → message@1500 revives → re-hide@1500 must advance cutoff (NOT no-op). This closes the regression window where a second Hide click after revival would silently no-op. 3. **Outbound DM revives hidden thread.** Three tests for the Codex P1 fix in `dm_thread_modal::do_send`: an outbound send clears the `(room, peer)` entry from `HIDDEN_DM_THREADS`. Pure helper `process_outbound_send_for_hidden(hidden, room, peer)` mirrors `unhide_dm_thread`'s in-memory effect so the invariant can be tested without a Dioxus runtime. Scope test confirms the unhide does not leak across rooms or peers; idempotency test confirms calling on a not-hidden pair is a no-op. Total: 15 new tests (UI crate, native target). AGENTS.md updated with a Phase 6 note explaining that hidden_threads ride OUTBOUND_DMS_STORAGE_KEY (not a new storage key) so future agents don't add a second top-level delegate key by mistake — that would split the multi-device save path and need its own legacy-probe extension. Co-Authored-By: Claude Opus 4.7 --- AGENTS.md | 18 + ui/src/components/app/chat_delegate.rs | 307 +++++++++++++++++- .../components/room_list/dm_rail_section.rs | 250 ++++++++++++-- 3 files changed, 541 insertions(+), 34 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index aea41895..bb5e0aa2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -317,6 +317,24 @@ design. sibling JSON file `outbound_dms.json` in the riverctl data dir (consistent with `rooms.json`'s plaintext-on-disk threat model — full-disk encryption is the user's responsibility). +- Phase 6 (PR #265, issue #261) added **hide-stale-DM-threads** — + a local-only view filter that lets the user dismiss a DM thread + from the left rail. Storage piggybacks **the same** + `OUTBOUND_DMS_STORAGE_KEY = b"outbound_dms"` blob — `OutboundDmStore` + grew a `hidden_threads: Vec` field with + `#[serde(default)]` so pre-#261 bytes still decode. **Do not add a + second top-level delegate storage key for hide state**: a new key + would need its own probe in `fire_legacy_migration_request` and its + own routing in `response_handler.rs` (per the legacy-migration note + above), AND would split the multi-device save path into two writes + that can race. The decision rationale lives on the Phase 5 prune + path's "we only act on purge envelopes" comment in + `chat_delegate::prune_outbound_dms_for_purges`. Filter helper + `chat_delegate::is_thread_hidden` uses strict `<=`; the rail-side + pure helper `dm_rail_section::filter_rail_entries` is pinned by + `filter_rail_entries_*` tests, and the "click Hide again after + revival must re-hide" branch is pinned by + `hide_unhide_rehide_round_trip`. ## Private Room Support - Messages, metadata, and member nicknames are encrypted with AES-256-GCM. diff --git a/ui/src/components/app/chat_delegate.rs b/ui/src/components/app/chat_delegate.rs index a53afd1c..af5bd583 100644 --- a/ui/src/components/app/chat_delegate.rs +++ b/ui/src/components/app/chat_delegate.rs @@ -434,6 +434,251 @@ mod tests { assert_eq!(to_insert.len(), 1); assert_eq!(to_insert[0].1.hidden_at_ts, 5_000); } + + // ===== decide_hide_action + hide-unhide-rehide round-trip ===== + // Tests for the testing-reviewer's BLOCKING gap on PR #265: the + // "click Hide again should re-hide, not no-op" path through + // `hide_dm_thread` needs explicit regression coverage. We extract + // the decision logic into `decide_hide_action` and test it + // directly, then simulate the full hide → revive → re-hide + // round-trip against a `HashMap` exactly the way `hide_dm_thread` + // does at runtime. + + /// Baseline: no existing entry → `Insert` the incoming entry. + #[test] + fn decide_hide_action_no_existing_inserts() { + let room = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let incoming = make_hidden_entry(room, peer, 1_000); + match decide_hide_action(None, incoming.clone()) { + HideAction::Insert(e) => assert_eq!(e, incoming), + other => panic!("expected Insert, got {:?}", other), + } + } + + /// Existing entry's `hidden_at_ts == incoming` → `NoOp`. Avoids + /// churning the delegate blob for an unchanged value. + #[test] + fn decide_hide_action_equal_existing_is_noop() { + let room = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let existing = make_hidden_entry(room, peer, 1_000); + let incoming = make_hidden_entry(room, peer, 1_000); + assert_eq!( + decide_hide_action(Some(&existing), incoming), + HideAction::NoOp, + ); + } + + /// Existing entry's `hidden_at_ts > incoming` → `NoOp`. The + /// already-hidden state is at least as restrictive. + #[test] + fn decide_hide_action_newer_existing_is_noop() { + let room = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let existing = make_hidden_entry(room, peer, 2_000); + let incoming = make_hidden_entry(room, peer, 1_000); + assert_eq!( + decide_hide_action(Some(&existing), incoming), + HideAction::NoOp, + ); + } + + /// Existing entry's `hidden_at_ts < incoming` → `Insert` with the + /// NEWER cutoff. This is the load-bearing branch the testing + /// reviewer flagged: without it, a re-hide after a revive + /// silently no-ops. + #[test] + fn decide_hide_action_older_existing_advances_cutoff() { + let room = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let existing = make_hidden_entry(room, peer, 1_000); + let incoming = make_hidden_entry(room, peer, 1_500); + match decide_hide_action(Some(&existing), incoming.clone()) { + HideAction::Insert(e) => assert_eq!( + e.hidden_at_ts, 1_500, + "re-hide must advance cutoff to incoming value" + ), + other => panic!("expected Insert, got {:?}", other), + } + } + + /// Round-trip test (testing-reviewer BLOCKING #2 on PR #265). + /// + /// Sequence the user-visible bug would manifest as: + /// 1. User clicks Hide on a thread whose last message was at ts=1000. + /// `hide_dm_thread` writes entry { hidden_at_ts: 1000 }. + /// 2. A new message arrives at ts=1500 → rail filter + /// (`filter_rail_entries`) sees `last_any_ts=1500 > 1000` and + /// revives the thread. + /// 3. User clicks Hide AGAIN. `hide_dm_thread` is called with + /// `hidden_at_ts=1500`. The decision must `Insert(1500)` — + /// NOT `NoOp` — so the next render hides the thread. + /// + /// We simulate the HashMap mutation that `hide_dm_thread` does + /// inside `HIDDEN_DM_THREADS.with_mut`, then call + /// `filter_rail_entries` to confirm the rail-side observable + /// state at each step. + #[test] + fn hide_unhide_rehide_round_trip() { + use crate::components::direct_messages::is_thread_hidden_for; + let room = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let key = (room, peer); + + // Step 1: hide at ts=1000. + let mut hidden: HashMap<(ed25519_dalek::VerifyingKey, MemberId), HiddenDmThreadEntry> = + HashMap::new(); + let incoming_1 = make_hidden_entry(room, peer, 1_000); + let action_1 = decide_hide_action(hidden.get(&key), incoming_1.clone()); + match action_1 { + HideAction::Insert(e) => { + hidden.insert(key, e); + } + other => panic!("step 1 expected Insert, got {:?}", other), + } + // Confirm the thread is hidden when last_any_ts == cutoff. + assert!( + is_thread_hidden_for(&hidden, &room, peer, 1_000), + "after step 1, thread at ts=1000 must be hidden" + ); + + // Step 2: message arrives at ts=1500. Filter view revives — + // the map is unchanged, but the rail observable flips. + assert!( + !is_thread_hidden_for(&hidden, &room, peer, 1_500), + "after step 2, thread at ts=1500 must revive (strict `>`)" + ); + + // Step 3: user clicks Hide AGAIN with the now-latest ts=1500. + // This is the regression-pin: decide_hide_action MUST return + // Insert (not NoOp), because existing.hidden_at_ts (1000) is + // strictly less than incoming.hidden_at_ts (1500). + let incoming_2 = make_hidden_entry(room, peer, 1_500); + let action_2 = decide_hide_action(hidden.get(&key), incoming_2.clone()); + match action_2 { + HideAction::Insert(e) => { + assert_eq!( + e.hidden_at_ts, 1_500, + "re-hide must advance the cutoff to 1500" + ); + hidden.insert(key, e); + } + HideAction::NoOp => panic!( + "REGRESSION: re-hide after revival no-op'd — cutoff stuck at 1000, \ + next render would surface the thread again" + ), + } + + // Final: thread must be hidden at ts=1500 (the new cutoff). + assert!( + is_thread_hidden_for(&hidden, &room, peer, 1_500), + "after step 3, thread at ts=1500 must be hidden under new cutoff" + ); + } + + // ===== Outbound DM revives hidden thread (Codex P1 fix) ===== + // The testing-reviewer's BLOCKING #3 on PR #265. The "explicit + // unhide on outbound" path lives in `dm_thread_modal::do_send` / + // `unhide_dm_thread`. Its pure-function effect is "remove the + // entry from the map." We expose that effect through a tiny pure + // helper so the user-visible invariant — *after a successful + // outbound send, the (room, peer) pair must NOT remain in + // HIDDEN_DM_THREADS* — has a regression-pin. + + /// Simulate the in-memory effect of [`unhide_dm_thread`] (the + /// `HIDDEN_DM_THREADS.with_mut(|h| h.remove(...))` line). Returns + /// the map after the removal. Exists purely for unit-testing the + /// Codex P1 invariant on PR #265. + fn process_outbound_send_for_hidden( + mut hidden: HashMap<(ed25519_dalek::VerifyingKey, MemberId), HiddenDmThreadEntry>, + room: ed25519_dalek::VerifyingKey, + peer: MemberId, + ) -> HashMap<(ed25519_dalek::VerifyingKey, MemberId), HiddenDmThreadEntry> { + hidden.remove(&(room, peer)); + hidden + } + + /// Codex P1 invariant: starting from a hidden entry for + /// `(room, peer)`, after the outbound-send code path runs, the + /// entry MUST be gone. Closes the "both `unix_now()` calls land + /// in the same second → re-hides the thread right after the user + /// sent a message" race. + #[test] + fn outbound_send_clears_hidden_entry_for_pair() { + let room = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + let mut hidden = HashMap::new(); + hidden.insert((room, peer), make_hidden_entry(room, peer, 1_000)); + assert_eq!(hidden.len(), 1, "precondition: (room, peer) is hidden"); + + let after = process_outbound_send_for_hidden(hidden, room, peer); + assert!( + after.is_empty(), + "after outbound send, (room, peer) must be unhidden" + ); + assert!( + !after.contains_key(&(room, peer)), + "explicit (room, peer) key must be absent post-send" + ); + } + + /// Scope check on the outbound-revive path: an outbound send to + /// peer X MUST NOT unhide a different peer Y in the same room + /// (nor the same peer X in a different room — same-shape lookup + /// tuple as `filter_rail_entries`'s scope test). + #[test] + fn outbound_send_unhide_is_scoped_per_room_and_peer() { + let room_a = sk(1).verifying_key(); + let room_b = sk(2).verifying_key(); + let peer_x = MemberId(FastHash(11)); + let peer_y = MemberId(FastHash(22)); + + let mut hidden = HashMap::new(); + hidden.insert((room_a, peer_x), make_hidden_entry(room_a, peer_x, 1_000)); + hidden.insert((room_a, peer_y), make_hidden_entry(room_a, peer_y, 1_000)); + hidden.insert((room_b, peer_x), make_hidden_entry(room_b, peer_x, 1_000)); + + // User sends an outbound DM to peer_x in room_a. + let after = process_outbound_send_for_hidden(hidden, room_a, peer_x); + + assert!( + !after.contains_key(&(room_a, peer_x)), + "outbound to (room_a, peer_x) must unhide that pair" + ); + assert!( + after.contains_key(&(room_a, peer_y)), + "outbound to peer_x must NOT affect peer_y in same room" + ); + assert!( + after.contains_key(&(room_b, peer_x)), + "outbound to peer_x in room_a must NOT affect peer_x in room_b" + ); + } + + /// Idempotency: calling the outbound-unhide on a pair that was + /// not hidden is a no-op (matches `unhide_dm_thread`'s + /// "idempotent: no-op when no entry exists" contract). Pins that + /// `do_send`'s unconditional `unhide_dm_thread` call is safe + /// regardless of whether the user previously hid the thread. + #[test] + fn outbound_send_unhide_is_idempotent() { + let room = sk(1).verifying_key(); + let peer = MemberId(FastHash(11)); + // Hidden map containing only an unrelated entry. + let other_peer = MemberId(FastHash(22)); + let mut hidden = HashMap::new(); + hidden.insert( + (room, other_peer), + make_hidden_entry(room, other_peer, 1_000), + ); + + let after = process_outbound_send_for_hidden(hidden.clone(), room, peer); + assert_eq!( + after, hidden, + "outbound unhide on a not-hidden pair must be a no-op" + ); + } } /// Idempotent helper: ask the chat delegate to subscribe to a room @@ -862,6 +1107,49 @@ pub fn hydrate_hidden_dm_threads(entries: Vec) -> usize { count } +/// Outcome of [`decide_hide_action`]: should the caller (un)write the +/// hide entry, or treat the click as a no-op? +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum HideAction { + /// The incoming `hidden_at_ts` is strictly newer than what's + /// already recorded (or no entry exists). The caller must insert + /// the new entry AND queue a delegate save. This is the "re-hide + /// after revival" path: see the round-trip test + /// `hide_unhide_rehide_round_trip` for the canonical example. + Insert(HiddenDmThreadEntry), + /// The existing entry's `hidden_at_ts` is already at or above the + /// incoming value. No state change; no delegate save needed. + NoOp, +} + +/// Pure helper: decide whether a `hide_dm_thread` click should write a +/// new entry into the hidden-threads map. +/// +/// Rules (issue freenet/river#261): +/// - No existing entry → `Insert`. +/// - Existing `hidden_at_ts` strictly less than incoming → `Insert` +/// with the *new* cutoff. This is the load-bearing "re-hide after +/// revival" branch: a thread that was hidden at ts=1000, revived by +/// a message at ts=1500, and then hidden again must end up with +/// cutoff = 1500. Without this branch the second click would no-op, +/// leaving the cutoff at 1000 — and the very next render would +/// still see the message at 1500 cross the threshold and surface +/// the thread again. +/// - Existing `hidden_at_ts >= incoming` → `NoOp` (don't churn the +/// delegate blob for an unchanged value). +/// +/// Pinned by `decide_hide_action_*` and `hide_unhide_rehide_round_trip` +/// in this module's `tests`. +pub(crate) fn decide_hide_action( + existing: Option<&HiddenDmThreadEntry>, + incoming: HiddenDmThreadEntry, +) -> HideAction { + match existing { + Some(e) if e.hidden_at_ts >= incoming.hidden_at_ts => HideAction::NoOp, + _ => HideAction::Insert(incoming), + } +} + /// Hide the DM thread for `(room, peer)` from the left rail (issue /// freenet/river#261). /// @@ -869,12 +1157,9 @@ pub fn hydrate_hidden_dm_threads(entries: Vec) -> usize { /// at the moment the user clicked "Hide thread"; the filter rule uses /// `<=` so any message strictly later revives the thread. /// -/// If the user hides a thread that already has a hide entry with an -/// EARLIER cutoff, we advance the cutoff — clicking "Hide" again after -/// new messages arrived should re-hide the thread, not no-op it. -/// Conversely, if an existing entry's cutoff is already at or above -/// the incoming `hidden_at_ts`, we keep it (no need to rewrite the -/// delegate blob for an unchanged value). +/// Delegates to the pure helper [`decide_hide_action`] so the +/// "advance vs no-op" decision can be unit-tested without the Dioxus +/// runtime. pub fn hide_dm_thread( room_owner_vk: ed25519_dalek::VerifyingKey, peer: MemberId, @@ -891,14 +1176,12 @@ pub fn hide_dm_thread( let mut changed = false; HIDDEN_DM_THREADS.with_mut(|hidden| { let key = (room_owner_vk, peer); - match hidden.get(&key) { - Some(existing) if existing.hidden_at_ts >= hidden_at_ts => { - // Already hidden at this cutoff or later — no-op. - } - _ => { - hidden.insert(key, entry); + match decide_hide_action(hidden.get(&key), entry) { + HideAction::Insert(new_entry) => { + hidden.insert(key, new_entry); changed = true; } + HideAction::NoOp => {} } }); if changed { diff --git a/ui/src/components/room_list/dm_rail_section.rs b/ui/src/components/room_list/dm_rail_section.rs index 7169b1c2..1f453d1a 100644 --- a/ui/src/components/room_list/dm_rail_section.rs +++ b/ui/src/components/room_list/dm_rail_section.rs @@ -21,6 +21,7 @@ use crate::util::ecies::unseal_bytes_with_secrets; use dioxus::prelude::*; use dioxus_free_icons::{icons::fa_solid_icons::FaEnvelope, Icon}; use ed25519_dalek::VerifyingKey; +use river_core::chat_delegate::HiddenDmThreadEntry; use river_core::room_state::member::MemberId; use std::collections::HashMap; @@ -78,14 +79,41 @@ fn DmRailRow(entry: DmRailEntry) -> Element { } } -#[derive(Clone, PartialEq)] -struct DmRailEntry { - room: VerifyingKey, - peer: MemberId, - peer_nickname: String, - room_name: String, - last_any_ts: u64, - unread: usize, +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct DmRailEntry { + pub(crate) room: VerifyingKey, + pub(crate) peer: MemberId, + pub(crate) peer_nickname: String, + pub(crate) room_name: String, + pub(crate) last_any_ts: u64, + pub(crate) unread: usize, +} + +/// Pure helper: drop rail entries whose `(room, peer)` is currently +/// hidden AND whose latest message timestamp does not exceed the +/// recorded `hidden_at_ts`. Issue freenet/river#261. +/// +/// The "user-visible feature" of #261 is exactly "this thread no +/// longer appears in the rail." `build_view` collects candidate +/// entries from room state and `DM_LAST_SEEN`; this function does the +/// final filter step so it can be unit-tested without standing up a +/// full Dioxus runtime. +/// +/// Rules (matches `chat_delegate::is_thread_hidden` strict `<=`): +/// - Entry's `(room, peer)` absent from `hidden` → present. +/// - Entry's `last_any_ts > hidden_at_ts` → present (newer message +/// revived the thread, regardless of direction). +/// - Entry's `last_any_ts <= hidden_at_ts` → omitted. +/// +/// Pinned by `filter_rail_entries_*` tests in this module. +pub(crate) fn filter_rail_entries( + entries: Vec, + hidden: &HashMap<(VerifyingKey, MemberId), HiddenDmThreadEntry>, +) -> Vec { + entries + .into_iter() + .filter(|e| !is_thread_hidden_for(hidden, &e.room, e.peer, e.last_any_ts)) + .collect() } fn build_view() -> Option> { @@ -171,21 +199,13 @@ fn build_view() -> Option> { } } + // Build the candidate entries for this room. The hide-filter + // step runs once at the end (over all rooms' candidates) so it + // is pure and unit-testable via `filter_rail_entries`. See + // that helper's doc-comment for the #261 strict-`<=` semantics + // and the "outbound revives" invariant that's checked by + // `outbound_message_revives_hidden_thread` below. for (peer, acc) in per_peer { - // Issue freenet/river#261: skip threads the local user has - // hidden, unless a newer message has arrived since the - // hide. `is_thread_hidden_for` does the strict `<=` check - // on `hidden_at_ts`. An outbound DM the user sends to a - // hidden peer also revives the thread because the new - // message's timestamp is `> hidden_at_ts` (see - // `dm_thread_modal::DmThreadModalBody::do_send` — same - // `unix_now()` shim used for both the message and the - // hide cutoff). - if let Some(ref hidden_map) = hidden { - if is_thread_hidden_for(hidden_map, owner_vk, peer, acc.last_any_ts) { - continue; - } - } entries.push(DmRailEntry { room: *owner_vk, peer, @@ -200,6 +220,16 @@ fn build_view() -> Option> { } } + // Issue freenet/river#261: drop hidden entries. When the signal + // read was contended we silently treat the list as empty for THIS + // render — a hidden thread briefly re-appearing during a write + // storm is preferable to dropping the entire rail. + let filtered = match hidden { + Some(ref h) => filter_rail_entries(entries, h), + None => entries, + }; + let mut entries = filtered; + // Unread threads first, then most-recent. entries.sort_by(|a, b| { b.unread @@ -213,3 +243,179 @@ fn build_view() -> Option> { fn short_member_id(id: &MemberId) -> String { id.to_string().chars().take(8).collect() } + +#[cfg(test)] +mod tests { + //! Unit tests for the `DmRailSection` pure filter helper. Pin the + //! user-visible "hidden threads disappear from the rail" behaviour + //! of issue freenet/river#261 — and its corollary "an outbound DM + //! revives a hidden thread" from the Codex P1 fix. + //! + //! These tests exercise the pure helper extracted from `build_view`; + //! the full `build_view` requires a Dioxus runtime + signal context + //! to call, so it cannot be unit-tested directly. The extraction + //! keeps the test surface aligned with the user-visible behaviour + //! (the test reviewer's BLOCKING finding on PR #265). + use super::*; + use freenet_scaffold::util::FastHash; + + fn sk(seed: u8) -> ed25519_dalek::SigningKey { + ed25519_dalek::SigningKey::from_bytes(&[seed; 32]) + } + + fn entry(room: VerifyingKey, peer_seed: i64, last_any_ts: u64, unread: usize) -> DmRailEntry { + DmRailEntry { + room, + peer: MemberId(FastHash(peer_seed)), + peer_nickname: format!("peer-{peer_seed}"), + room_name: "room".into(), + last_any_ts, + unread, + } + } + + fn hidden_at( + room: VerifyingKey, + peer_seed: i64, + hidden_at_ts: u64, + ) -> ((VerifyingKey, MemberId), HiddenDmThreadEntry) { + let peer = MemberId(FastHash(peer_seed)); + ( + (room, peer), + HiddenDmThreadEntry { + room_owner_vk: room.to_bytes(), + peer, + hidden_at_ts, + }, + ) + } + + /// Baseline: a hidden entry whose `(room, peer)` matches and whose + /// `last_any_ts == hidden_at_ts` (strict `<=`) is omitted from the + /// rail. + #[test] + fn filter_rail_entries_omits_hidden_thread() { + let room = sk(1).verifying_key(); + let entries = vec![entry(room, 11, 1_000, 0)]; + let hidden = HashMap::from([hidden_at(room, 11, 1_000)]); + + let result = filter_rail_entries(entries, &hidden); + assert!( + result.is_empty(), + "hidden thread with equal-ts must be filtered out" + ); + } + + /// Codex P1 invariant + #261 "newer inbound message revives": + /// an inbound DM arriving after the hide MUST re-surface the + /// thread (its `last_any_ts > hidden_at_ts`). + #[test] + fn filter_rail_entries_newer_inbound_revives_hidden() { + let room = sk(1).verifying_key(); + // last_any_ts is 1500, hidden at 1000 — strictly newer revives. + let entries = vec![entry(room, 11, 1_500, 1)]; + let hidden = HashMap::from([hidden_at(room, 11, 1_000)]); + + let result = filter_rail_entries(entries, &hidden); + assert_eq!(result.len(), 1, "newer inbound DM must revive thread"); + assert_eq!(result[0].last_any_ts, 1_500); + assert_eq!( + result[0].unread, 1, + "unread accumulator must pass through filter" + ); + } + + /// #261 "outbound revives": an outbound DM (reflected purely + /// through `last_any_ts > hidden_at_ts` since outbound messages + /// also bump `acc.last_any_ts` in `build_view`) MUST re-surface + /// the thread. This is the rail-side mirror of the Codex P1 + /// explicit `unhide_dm_thread` call in `dm_thread_modal::do_send`. + /// Even if the hide map were never cleared, a strictly-newer + /// outbound timestamp must override. + #[test] + fn filter_rail_entries_newer_outbound_revives_hidden() { + let room = sk(1).verifying_key(); + // Outbound: zero unread (sender's own message), but + // last_any_ts moved past the hide cutoff. + let entries = vec![entry(room, 11, 1_500, 0)]; + let hidden = HashMap::from([hidden_at(room, 11, 1_000)]); + + let result = filter_rail_entries(entries, &hidden); + assert_eq!( + result.len(), + 1, + "newer outbound DM must revive thread (last_any_ts > hidden_at_ts)" + ); + } + + /// Scope check: hiding `(room A, peer X)` MUST NOT hide + /// `(room B, peer X)` — the same peer in a different room is a + /// different thread. + #[test] + fn filter_rail_entries_hide_is_scoped_per_room() { + let room_a = sk(1).verifying_key(); + let room_b = sk(2).verifying_key(); + let entries = vec![entry(room_a, 11, 1_000, 0), entry(room_b, 11, 1_000, 2)]; + // Hide ONLY in room A. + let hidden = HashMap::from([hidden_at(room_a, 11, 1_000)]); + + let result = filter_rail_entries(entries, &hidden); + assert_eq!(result.len(), 1, "hide in room A must not leak into room B"); + assert_eq!(result[0].room, room_b); + assert_eq!(result[0].unread, 2); + } + + /// Scope check: hiding `(room A, peer X)` MUST NOT hide + /// `(room A, peer Y)` — different peers in the same room are + /// different threads. + #[test] + fn filter_rail_entries_hide_is_scoped_per_peer() { + let room = sk(1).verifying_key(); + let entries = vec![entry(room, 11, 1_000, 0), entry(room, 22, 1_000, 0)]; + // Hide ONLY peer 11. + let hidden = HashMap::from([hidden_at(room, 11, 1_000)]); + + let result = filter_rail_entries(entries, &hidden); + assert_eq!(result.len(), 1, "hide of peer 11 must not affect peer 22"); + assert_eq!(result[0].peer, MemberId(FastHash(22))); + } + + /// Unhide: removing the hide entry from the map MUST cause the + /// thread to reappear (regardless of `last_any_ts`). This is the + /// rail-side observable for `unhide_dm_thread`. + #[test] + fn filter_rail_entries_unhide_reappears() { + let room = sk(1).verifying_key(); + let entries = vec![entry(room, 11, 1_000, 0)]; + + // First: hidden. + let hidden = HashMap::from([hidden_at(room, 11, 1_000)]); + assert!( + filter_rail_entries(entries.clone(), &hidden).is_empty(), + "precondition: thread is hidden" + ); + + // Then: unhide (empty map) — must reappear. + let unhidden: HashMap<(VerifyingKey, MemberId), HiddenDmThreadEntry> = HashMap::new(); + let result = filter_rail_entries(entries, &unhidden); + assert_eq!( + result.len(), + 1, + "after unhide, the thread must be visible again" + ); + } + + /// Empty hidden map is a no-op fast-path: every entry passes + /// through unmodified. Pins the optimisation `build_view` relies + /// on (`hidden.is_empty()` is the common case during normal app + /// operation). + #[test] + fn filter_rail_entries_empty_hidden_passes_all_through() { + let room = sk(1).verifying_key(); + let entries = vec![entry(room, 11, 1_000, 0), entry(room, 22, 2_000, 3)]; + let hidden: HashMap<(VerifyingKey, MemberId), HiddenDmThreadEntry> = HashMap::new(); + + let result = filter_rail_entries(entries.clone(), &hidden); + assert_eq!(result, entries); + } +} From 0b9718f5501e0e771ae31b65af84edb44d7bf4d2 Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Sat, 16 May 2026 22:13:17 -0500 Subject: [PATCH 7/7] docs(dm): fix dangling test-name reference in build_view comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new comment in build_view's per-peer loop pointed at a test name that doesn't exist (`outbound_message_revives_hidden_thread`). The real test is `filter_rail_entries_newer_outbound_revives_hidden` in the sibling `tests` module. Comment-only — no behaviour change. Co-Authored-By: Claude Opus 4.7 --- ui/src/components/room_list/dm_rail_section.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/components/room_list/dm_rail_section.rs b/ui/src/components/room_list/dm_rail_section.rs index 1f453d1a..52b30617 100644 --- a/ui/src/components/room_list/dm_rail_section.rs +++ b/ui/src/components/room_list/dm_rail_section.rs @@ -203,8 +203,8 @@ fn build_view() -> Option> { // step runs once at the end (over all rooms' candidates) so it // is pure and unit-testable via `filter_rail_entries`. See // that helper's doc-comment for the #261 strict-`<=` semantics - // and the "outbound revives" invariant that's checked by - // `outbound_message_revives_hidden_thread` below. + // and the `filter_rail_entries_newer_outbound_revives_hidden` + // test for the "outbound revives" invariant. for (peer, acc) in per_peer { entries.push(DmRailEntry { room: *owner_vk,