Repository navigation
Show unread from other communities in the community switcher - #3459
wpfleger96 wants to merge 10 commits into
Conversation
78ca79a to
82fb217
Compare
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Three actionable findings: browser broker starvation, native foreground socket exhaustion, and inaccurate background coverage documentation.
| const { viewer, memberships, selected } = communities.snapshot(); | ||
| if (selected) visited.add(selected); | ||
| const ids = viewer ? memberships.map((m) => m.id) : []; | ||
| const joined = new Set( |
There was a problem hiding this comment.
🤖 [P2] Reserve foreground capacity on the browser broker path
On a fresh HTTP/1.1 browser-development launch with six saved communities, background acquisition opens six persistent same-origin broker streams. Subsequent broker requests queue indefinitely: an actual-app Chromium check with the production broker and modeled upstream timed out a new identity request after 3 seconds without it reaching the broker. Two communities passed; six and eight failed. A build-only control suppressing background acquisition passed all three cases. Reserve capacity according to the active transport, or suppress browser background acquisition until streams can be multiplexed. Restore congestion coverage with real streams: the fixture's new live: !saved avoids this failure. This reproduction applies to the browser development host, not native IPC.
| for (const id of joined) { | ||
| let entry = entries.get(id); | ||
| if (!entry) { | ||
| const relay = communities.open(id); |
There was a problem hiding this comment.
🤖 [P2] Account for retained sessions before admitting background connections
With 17 reachable saved FI communities and Personal space selected, startup retains the first eight. Visiting communities 9–17 then requires 17 native sockets, so the last foreground connection hits Native sockets are busy at src-tauri/src/federated_identity/socket.rs:84. Without the new background acquisition, those nine visits need only nine sockets. Communities.open/acquire retains sessions until leave/disposal; dropping an observer only unsubscribes. The session owner needs a total budget and foreground precedence, including release of eligible background-only sessions. Lowering this observer constant alone cannot guarantee capacity. Evidence is source tracing plus candidate observer execution with modeled retention/admission and a passing no-projection control; native IPC exhaustion was not executed.
| space. Personal space | ||
| clears selection without forgetting memberships. Its **Join a community** item opens the | ||
| existing join dialog; displaying saved communities reads relay metadata but does not open sessions for them. | ||
| existing join dialog. Displaying saved communities reads relay metadata; the app |
There was a problem hiding this comment.
🤖 [P3] Document the actual background observation limit
The documentation promises connection to every saved community, but the implementation observes only the first eight plus communities selected during this launch. An unvisited ninth community can therefore show no unread badge despite having unread activity. Document the cap and visit rule so readers do not interpret missing badges as complete coverage.
Extract the Dock dot's community unread rule into observeCommunityUnread, add the sidebar's attention count, and project it for every community with an open session. Picker rows show a count or dot; the closed switcher shows a dot while another community has unread. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Open each joined community's session in the background and start its unread repair once, so communities not visited since launch are badged too. Drop this commit to badge only communities already opened. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
…is stale or unknown The picker projection now publishes no state while a community's unread is stale (its connection dropped, until the reconnect's read finishes) or no counted channel has observed evidence. The Dock dot's rule is unchanged. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
…nnection is down Evidence can stay fresh while the WebSocket is down, since HTTP reads still succeed. The picker projection now also follows the session's live status and publishes no state unless it is connected. The Dock dot is unchanged. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Journeys written for one connected community now scope their counts, holds and locks to the community under test, accept the switcher's elsewhere-unread label, and model the background sessions they start: saved congestion communities answer without a live stream, the icon journey holds the inactive session to prove icons need none, and the two-page login journey keeps one community so its streams fit the harness's single HTTP/1.1 origin. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
…ket limit The unread projection opened every joined community, and each session holds one native relay socket; the host refuses a 17th, so background sessions could starve the selected or an agent's community. It now opens at most BACKGROUND_UNREAD_LIMIT (8), plus every community selected this launch, which is retained already. The browser fixture's quotas also apply to primary only: the quota-refused roster was being spent on the secondary community's startup read, so primary's first roster revoked access early and renewed its agent observer before the recovery under test. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
…agent work Background unread sessions never take the last native socket, and when the socket limit is full the oldest badge-only session closes, without purging, before a session opened for its own work connects. A closed badge stays blank until that community is opened again. The sidebar-unread overlap test now holds the app's 10s read deadline itself, so a slow runner cannot cancel its held unread batch before release. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
32a4d9b to
6d2d2c5
Compare
Will raised the native socket limit to 32 and background unread to 24. On the browser development broker every session holds a same-origin HTTP/1.1 stream, so it gets its own limit of two sessions and badge-only sessions never take the connections ordinary requests need. The icon congestion journey gets real live streams again and runs with six and eight saved communities. The docs describe the background limit and eviction. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…iction Eviction announced the closed badge before storing the session that needed its socket, so a listener could reopen a badge into the freed slot or build a second session for the same agent community. The replacement is now stored first, then listeners are told. Badge-only sessions now keep the last slot free, so the reserved socket is never one still being released by a closed badge. The broker allows three sessions to keep one badge plus that reserve. The sidebar-unread overlap test suspends only 10s deadlines started while its read is held. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One session-admission regression found.
| closed.add(oldest); | ||
| // Close before connecting so the native socket is free first. | ||
| room = dispose(oldest); | ||
| for (const fn of listeners) fn(); |
There was a problem hiding this comment.
🤖 [P2] Reserve the replacement before notifying observers
With three saved communities and two startup sessions, selecting the third invokes this notification before the replacement is registered. The real unread listener synchronously reenters open(id, true) and creates a badge session; the outer acquisition creates another session and overwrites its map/scope entry. Actual-app checks with the production broker reproduce two distinct streams and two modeled upstream sockets for the selected community in both Chromium and WebKit (expected one). A build-only control removing this early notification passes both engines. A source-level check wiring the real communities and unread services also shows that a subsequent foreground open can evict the selected community because the reentrant badge classification survives. Reserve the replacement session/scope and foreground ownership before notifying observers, and cover admission with the real unread listener wired in.
Why
Someone in several communities can't see that another community has unread without opening the switcher and checking each row. The old community rail badged other communities; the switcher that replaced it in #1273 does not. See #2922.
What
Commits
feat(communities): badge other communities' unread in the switcher: the shared rule, the projection, the row badges and the trigger dot.feat(communities): connect every joined community for switcher unread: startup coverage.fix(communities): hide a community's switcher badge while its unread is stale or unknown: the availability rule for the switcher only. It applies on top of either commit.fix(communities): hide a community's switcher badge while its live connection is down: the switcher also follows the session's live status (session.live), because HTTP reads can keep evidence fresh while the WebSocket is down.fix(communities): paint the switcher row's unread dot: the row's ordinary-unread dot rendered 0×0; it is nowinline-block, vertically centered.test(browser): expect every joined community to connect at startup: browser journeys written for a single connected community, updated to keep what each one proves.fix(communities): cap background unread sessions under the native socket limit: the projection opens at most 8 communities in the background, plus every community selected this launch. Each session holds one native relay socket, and the host refuses a 17th (src-tauri/src/federated_identity/socket.rs), so this leaves room for the selected community and agents' communities. Communities past the cap stay unbadged until you visit them. It also scopes the browser fixture's quota refusals to primary.fix(communities): yield badge-only sessions' sockets to selected and agent work: the 8 is only a startup limit; visited and agent sessions stay retained, so with 17 communities selecting the last one could fail with "Native sockets are busy".communities.open(id, true)now marks a session as held only for its badge. A badge-only open never takes the last socket (SESSION_LIMIT = 16, mirroringsocket.rs). When a session opened for its own work (select, agentopen, join) needs a socket and all 16 are held, the oldest badge-only session is disposed first, without purging device state, and the new session connects only after that disposal finishes. The closed community's badge goes blank and stays closed until it is opened for its own work. It also makes the WebKitsidebar-unreadoverlap test hold the app's 10s read deadline itself (same init-script pattern this file already uses), so a slow runner can't cancel the held unread batch before the test releases it.fix(communities): raise session limits and bound broker badge sessions: Will's limits: the Rust socket limit (socket.rs) andSESSION_LIMITgo to 32, andBACKGROUND_UNREAD_LIMITgoes to 24. On the browser development broker, every session holds a live stream on one same-origin HTTP/1.1 connection, and browsers allow about 6 per origin. So the broker path usesBROKER_SESSION_LIMIT = 2, which is enough for the selected community plus one badge. Badge-only sessions never go past it, and selecting another community closes the badge session first. The icon-congestion journey goes back to real live streams for its saved communities (dropping commit 6'slive: !saved) and now runs with 6 and 8 saved communities, so a same-origin foreground request must still reach the server while icons are held.docs/communities.mdnow describes the 24 limit, visits, closing to make room, and the smaller broker path.fix(communities): hold the replacement's slot before announcing an eviction: eviction used to notify listeners before storing the session that needed the socket, so the unread projection could reopen a badge into the freed slot, or an agent watcher could build a second session for the same community. The replacement is now stored first, then listeners are notified, still closing the old session before connecting the new one. Badge-only sessions now leave the last slot free (>= limit - 1), so the slot work gets without eviction is never one a closed badge is still releasing.BROKER_SESSION_LIMITis 3: selected + one badge + the reserve. Thesidebar-unreadoverlap test now suspends only 10s deadlines started while its read is held.How
features/notifications/community-unread.ts:observeCommunityUnreadis the Dock dot's per-community rule moved out ofbindUnreadIndicator, with the attention count added. It takes any community's session.createCommunityUnreadsprojects it for every joined community, keyed by id.indicator-unread.ts) now usesobserveCommunityUnreadfor the selected community. Its behavior is unchanged, and its existing tests pass as they are.CommunityPickerfor the trigger dot,CommunityPickerItemfor the row badge (sharedCountBadge, or a dot).observeCommunityUnreadtakes aknownflag, and only the projection sets it. With the flag on, the observer publishesundefinedwhile any counted channel's unreadfreshnessisstale, whichsession.unread.stale()sets when the live connection drops. Recovery reads set it back toobserved. Channels whosecoverageisn'tobservedare left out of the count. The community counts as unknown only when none are observed. The fourth commit adds one subscription tosession.live, and the observer publishesundefinedunless its status isconnected. That subscription is torn down with the others. Neither commit adds a timer or poll. The Dock dot leaves the flag off, so its behavior and its tests stay as they are.communities.open(id), which already serves agent communities, and callsunread.ensure()once per session. No relay change.Foundation guidance
This touches three
FOUNDATIONfiles, for wiring only. Will approved the plan (unread badges on other communities, including connecting every joined community at startup):src/app/services.ts: creates the projection and shares the notification gates with the Dock dot.src/app/App.tsx: passes the projection to the shell (one prop).src/features/communities/service.ts: the first commit adds a read-onlyretained(id)lookup. The second commit removes it in favor ofopen(id). The eighth adds an optionalbadgeflag toopen, aSESSION_LIMITconstant, and badge-only eviction inacquire;leavenow calls the extracted non-purgingdisposeand purges separately as before.Behavior change in the second commit
The app no longer connects only the selected community. It connects every joined community, including while Personal space is selected. Two
services.test.tsassertions encoded the old behavior, and this commit updates them.Testing
community-unread.test.tscovers: count vs. dot, row quieting after a visit, manual unread, mutes and notification gates, unknown until mutes are confirmed, disposal, and a keyed projection that opens and repairs each session once and drops a community after leaving.CommunityPicker.test.tsxcase covers: the selected community is never badged, count vs. dot in row labels, the trigger dot and its label, and no badge when everything is read or nothing is known.SESSION_LIMITand frees a socket when its scope aborts. It asserts no connection is ever refused, the 33rd connects, and the closed badge session frees its socket. It adds a broker-path case forBROKER_SESSION_LIMIT.service.test.tscase against retained sessions at a fixed limit: 8 badge-only sessions, visits and an agent community fill all 16; a badge open is refused; selecting a 17th connects and closes only the oldest badge-only session, never the selected or agent one; the closed one stays closed until selected, then connects.inline-block, vertically centered. It also rewords one comment., unread in other communitieslabel suffix.relay_socket_connect) or a webview WebSocket, and its reads go throughrelay_httpIPC.Design system
The row badge reuses
CountBadge. The dots use--text-navigation-unread, as the message-list unread dot does. The trigger dot is ringed in--bg-app. No new tokens.