Skip to content

fix(chat): keep Community and Direct delivery stable - #309

Closed
andersalm wants to merge 242 commits into
developfrom
fix/139-chat-stability
Closed

andersalm wants to merge 242 commits into
developfrom
fix/139-chat-stability

Conversation

@andersalm

@andersalm andersalm commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Chat shows the Community state supplied by Runtime and renders a received Shared row and clears an accepted draft before optional metadata completes. Runtime keeps Community and Direct delivery work independent, preserves signed Profile and message authority, and keeps contacts and local history readable through Leave and Rejoin.

The Linux Carrier change disables UDP segmentation offload for the observed Jetson Wi-Fi batching fault. Managed Runtimes inherit Carrier's single mDNS default and preserve an explicit operator choice through launch and restart. CPU repairs reuse bounded verified data and signature work while each request still checks current authority, expiry and revocation. Release and update fixtures now follow the merged packaging contract.

The published candidate is 31cc05131e5b040f6445a88c185d4e5f92e40f74, tree 76e2954d3dc77b7b8336b5e57361cc25e0be0946, with develop aa3d61e4 merged normally. It passes the full Linux slot-3 check (gate 35m34s, workspace 8m39s, capsules 4m36s), prescribed Python 3.12 Mac behavior and source gates, all 25 bundled-Chromium real-WASM scenarios, the CI precheck, formatting and diff checks. CI on 31cc051 is green (run 38284192556).

Status, proof and acceptance: #86. Installed and hardware qualification runs once on the alpha.16 train release. The Linux three-user restart/Recovery regression uses a separate bootstrap Runtime and follows publication. The latest node-switchboard Send/Sent/Waiting decision is a separate Community follow-up dependent on this PR.

Anders owns Chat. OMP owns integration, relay work in #354, and release. Selected human risk review covers account/session authority, signed delivery, provider availability and update/release boundaries on one exact candidate. #334 retains the separate Community signing and release-pin admission work; Sasha owns #116.

SashaMIT and others added 30 commits September 30, 2026 12:19
… charter

Adds scripts/home-phone-layout-smoke.mjs: loads the real Home host and GUI
against a fixture host in Playwright Chromium and WebKit at iPhone 14
portrait, landscape and an 820 px tablet, measures targets under 44 px, text
under 12 px and horizontal overflow on every shell surface and one window per
first-party app, writes report.json plus screenshots, and asserts a per-profile
baseline that later PRs may only lower. Records the source truths the phone
layer will change (viewport-fit, bare 100vh, backdrop-filter count).

Wires the smoke into just product-ui-browser, adds docs/HOME_MOBILE.md (size
classes, rubric, gesture-surface rule, baseline, real-device checks, capsule
guidance) linked from ROADMAP.md, and pins the ratchet in home-entropy-check.

No UI change.
- viewport-fit=cover and interactive-widget=resizes-visual on the Home host
  and GUI index files, so env(safe-area-inset-*) is real on iOS and the soft
  keyboard shrinks only the visual viewport on Android like it does on iOS
- shell-form-factor.js: single reader of the 640/1100 size classes and the
  media-feature pointer class; mirrors body[data-form-factor] and
  body[data-pointer]; writes --keyboard-inset from visualViewport on
  coarse-pointer hosts (a phone held sideways stays a phone; desktop
  pinch-zoom is never a keyboard); shouldOpenMaximizedByDefault() delegates
- style.css: --stage-bottom adds --keyboard-inset; overscroll-behavior none
  on the shell; touch-action manipulation and no tap highlight on controls;
  touch-callout off on shell chrome only; 100dvh twin for the last bare
  100vh; side safe-area insets on the bar and the phone window
- home-form-factor.test.mjs in verify; entropy contracts for every truth
  above, mutation-tested; phone smoke reports fallback vs bare 100vh

Co-authored-by: Cursor <cursoragent@cursor.com>
Phone size class in CSS is one media list mirroring classifyFormFactor
(width <= 640, or coarse/hover-less pointer with height <= 640), so a
landscape phone keeps the phone shell. home-entropy-check derives the list
from PHONE_MAX_WIDTH.

Bar: --toolbar-h 44px, every control a 44x44 hit box around the 22px glyph,
16px badge with 12px numerals. Wallet and Overview leave the phone bar;
Control Centre keeps both (Quick open / Overview).

Dock: thumb scroll (taskbar-item touch-action pan-x instead of none),
scroll-snap, scroll-driven edge fades, 14x4 running indicator, safe-area
sides in landscape. With a window open the Dock tucks (body.phone-dock-tucked)
and --stage-bottom shrinks to a 24px handle; tap/swipe the handle peeks the
Dock over the app (phone-dock-peek + scrim); scrim, Escape or picking a
window tucks it. New shell-phone-dock.js, synced from updateTaskbarState.

Smoke: opens Mission Control via Control Centre, probes tuck/peek/tuck on
both phone profiles, lowers phone baselines, records one boot retry for the
WebKit first-profile flake. node:test coverage for the Dock decisions.

Co-authored-by: Cursor <cursoragent@cursor.com>
The phone block set gap: 8px on .taskbar-inner. The hidden launcher and
Assistant face stay in flow (display: flex, collapsed to 0 for the face
animation), so the column gap added 16px of dead space above the icon row:
the pill was 88px with icons low. Drop the gap; the launcher popover already
carries its own spacing inside the clipped region. The phone smoke now pins
the idle pill at --taskbar-h (72px).

Co-authored-by: Cursor <cursoragent@cursor.com>
The plate-free glyphs are drawn at 58.6% of their square for the pointer
desktop; beside 48px app plates on a phone they read small. Launcher via
background-size (its .taskbar-icon transform is reserved for magnification);
Assistant via its inner icon node so the mark's layer animation is untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>
One 44px presentation of .window-head for every chrome mode: leading Close,
centred capsule icon + title, trailing Minimise drawn as a chevron (go home;
the capsule keeps running). Fullscreen hidden. The traffic-light group is
display: contents so the same buttons land in a three-column grid; chrome
modes in shell-core.js are untouched.

The head is in flow above the body in all modes, including unified
sidebar/toolbar: a phone-width capsule has content at the top of its main
column, so the desktop overlay would cover it.

A 2px accent hairline runs under the title until .window-frame.is-ready
(static under reduced motion) so the bar reads as alive while the capsule
boots. shell-window-geometry.js guards drag/resize on phone.

Smoke window baseline 5 -> 2 (only the 24px Dock handle remains).

Co-authored-by: Cursor <cursoragent@cursor.com>
Mission Control is the phone's app switcher: title-bar swipe-down opens it
(pointer capture across the capsule frame, the ending click swallowed), cards
carry the capsule icon + name and a 44 px Close on coarse pointers, portrait
Space thumbs are sized by height, copy loses mouse language. Windows rise
from the Dock in 200 ms (none under reduced motion).

Stage history keeps one entry of the shell's own, decided from history.state
and never more than one back() per close cycle; popstate closes the top layer
then goes home. Off on WebKit (buttons only): one back() after pushState took
the shell frame to about:blank there — recorded in docs/HOME_MOBILE.md.

Pure model unit-tested (scripts/home-phone-stage.test.mjs, in verify); the
phone smoke probes swipe, back, rotation and the Home frame staying alive;
entropy contract pins it all (21 mutations caught).

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ut fixtures

Library imports /apps/home/home-navigation-client.js and the Browser imports
/apps/home/home-clipboard-client.js; neither fixture served them, so both
product layout smokes failed before measuring anything (500 / stuck on
"Loading Library...").

Co-authored-by: Cursor <cursoragent@cursor.com>
The launcher and Assistant sit outside the scrolling app row, so when the
pill reached its width limit flex shrank them to 45 px and their glyphs
drew a third smaller than the app plates. They now keep full tiles and the
scrolling row gives way. The 1.5x glyph scale applies only to plate-free
art (the launcher, and the Assistant when it wears its plate-free mark);
a plate raster already fills its square.

Control Centre and Notifications span the phone stage with even margins
instead of hugging the trailing edge.

Co-authored-by: Cursor <cursoragent@cursor.com>
…push drawer

A capsule cannot tell a phone stage from a narrow desktop window by its own
width, so on phone every capsule kept the desktop traffic-light insets and
opened with dead space above and beside its content. The shell now posts
elastos:shell-layout {formFactor, pointer} to each frame on load and on a
size-class change (shell-capsule-layout.js). The shared theme runtime takes
it only from the opaque parent and only allowlisted values and sets
html[data-el-form-factor] and [data-el-pointer]; the shared sheet zeroes
--window-chrome-safe-top/-leading on phone and adds --el-touch-target and
the drawer tokens.

elastos-drawer.js is the Assistant's saved-chats pattern for any capsule
sidebar, bound by markup alone: content first, a 44 px toggle slides the
sidebar in and pushes the view aside, tapping the view (swallowed), Escape
or picking an item closes it; inert while closed. Vendored only to the
capsules whose sidebar uses it.

Unit-tested in verify; the phone smoke checks the size class reaches every
shared-theme frame on both engines; scripts/lib/phone-drawer-assert.mjs is
the shared smoke assertion for capsule drawers.

Co-authored-by: Cursor <cursoragent@cursor.com>
On phone the store opens on its content; the Discover title leads with a 44 px sidebar toggle that slides the categories in and pushes the store aside. Nav items are 44 px at 16 px and search is 16 px so iOS does not zoom. The layout smoke asserts the closed, open and picked states.

Co-authored-by: Cursor <cursoragent@cursor.com>
The 220 px list column squeezed the editor to half the phone. On phone the editor takes the full width, the title row leads with the list toggle, actions are one row of 44 px buttons, and the editor and fields are 16 px. Split has no room on a phone and reads as Write; the desktop Hide list control gives way to the drawer. The layout smoke asserts the editor size, the hidden controls and the drawer.

Co-authored-by: Cursor <cursoragent@cursor.com>
Favorites, path and search took 225 px above the first file. On phone the navigation row carries Back, Up, the current folder as its title, Search, view and More at 44 px, and Favorites sit under it as one sliding chip row; opening Search gives the field the whole row. Rows are 48 px at 16 px, the Name/Modified header is gone (only names show), and status floats above the footer as a pill. The list now starts at 104 px.

Co-authored-by: Cursor <cursoragent@cursor.com>
The menu button sat 72 px in, clearing traffic lights that do not exist on phone. It now takes the leading edge as a 44 px target, vertically centred on the page title, which is indented to share its line; the open sidebar starts below it. Phone landscape keeps the desktop sidebar. The UI/UX smoke gains a phone step and joins the browser lane.

Co-authored-by: Cursor <cursoragent@cursor.com>
At 640 px and below the list took a fixed 42% band, so with one or two requests the detail started mid-screen. The list is now as tall as its requests (capped at 40 dvh, then it scrolls) and the detail follows directly. The layout smoke fails on the old rule.

Co-authored-by: Cursor <cursoragent@cursor.com>
The toolbar kept a 96 px traffic-light inset on phone. The navigation row now spans the stage with 40-44 px controls and a 44 px, 16 px address field. The layout smoke gains a phone step and joins the browser lane.

Co-authored-by: Cursor <cursoragent@cursor.com>
Records the shell-to-capsule size class, the shared push drawer and the per-capsule phone layouts; the touch token is --el-touch-target.

Co-authored-by: Cursor <cursoragent@cursor.com>
At 1.5x on full tiles the launcher and the Assistant mark matched the plates' height exactly and read too heavy beside them; 1.3x sits them just inside the plates.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Write/Read and Save stay in one unscrolled row; Save as, Publish,
Unpublish, Copy Published Link and Delete open from a More button as
labelled 44 px rows. The menu reuses the real buttons, so their handlers
and enabled state are unchanged; desktop keeps them inline.

Co-authored-by: Cursor <cursoragent@cursor.com>
The Write/Read tabs become full 44 px targets with the pill painted inside
their padding, and the title field is 44 px tall. Documents' phone
ratchet drops from 3 to 0 on both profiles.

Co-authored-by: Cursor <cursoragent@cursor.com>
Verification: node --check scripts/chat-room-configured-layout-smoke.mjs; git diff --check; independent source review. Real-WASM execution awaits matching generated assets and the shared lease.
@irzhywau

Copy link
Copy Markdown
Collaborator

@andersalm Is this intended? Chat disables Send whenever Community is "unreachable", which has_remote_peers reports whenever no other Home is online, so someone alone in Community can't post (community_send_enabled, capsules/chat-room-ui/src/lib.rs:164; #86 "Only live allows Send").
Runtime does not need the lock: its Community send path only requires a joined Home, and the outgoing queue already retries unsent messages.
Proposal: while joined, keep Send enabled and show own messages as Sending → Expired (#86's remaining item); disable it only when Left or Not joined.

Screenshot 2026-10-12 at 15 53 47

@andersalm

Copy link
Copy Markdown
Contributor Author

@irzhywau agreed, thanks. Decision (Anders, 12 Oct): Send stays enabled whenever joined, also when nobody else is online; the Home keeps the message and delivers it when any participant or the Community node is reachable, and later joiners catch up. Messages show Sending, Sent, or Waiting, and do not expire while joined. Disabled only when Left or Not joined. Recorded in #86.

@andersalm

Copy link
Copy Markdown
Contributor Author

Cross-family review of 31cc051: FAIL with 1 P1 (unchecked Chat payloads can fill the incoming slots) and 3 P2 (receive-limit race, delivery outcome lost after pruning, poll cursor skips a peer message). Sent to the Chat lane; merge after the fixes and a re-review.

@andersalm

Copy link
Copy Markdown
Contributor Author

Cross-family review r2 on 04c6c69: FAIL (P1 49-character names pass admission but fail display; P2 stale poll overwrites Accepted; P2 shipped WASM lacked the cursor fix). r3 on 913568c: FAIL (P1 malformed Presence fills the 32 slots; P2 recovery with Chat closed loses the outcome; P2 retry restore exceeds caps).
Shared cause: admission checked less than the readers do. Round 4 fixes the class (one validator per type, nothing unconsumable kept, generative test); re-review on push.

@irzhywau

irzhywau commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Note, non-blocking (known issue): a Home left alone while the Community node is down stays "Community unreachable" after the node returns, until its Runtime restarts. Reproduced locally with debug logs.
Cause is iroh-gossip 0.101.0 (our pin, latest on crates.io): after a failed dial the peer stays pending and is never redialed. Upstream n0-computer/iroh-gossip#161 (peer lifecycle fixes) is merged on main (9 Oct) but not released yet; not yet verified against this case.
Proposal: bump iroh-gossip when a release includes #161 (or pin main's commit for 0.8.0, your call); meanwhile a docs/TROUBLESHOOTING.md entry comes with our visual-review fixes.

- Composer: after a send, a draft restore or an emoji insert, Chat sets
  the text itself, which raises no input event, so the composer kept its
  multiline height. set_composer_text now refits it through the Chat
  chrome's elastosChatSyncComposer.
- Message from People: Chat's hybrid window policy reused a window only
  for a launch without a query, so each conversation request opened a
  new Chat window. Home now sends elastos.chat.navigate/v1 to the open
  Chat window; Chat checks the sender, token and shape, then opens the
  conversation through open_direct_conversation.
- Open Inbox from People: People posts a home:open-target message for
  inbox, but the Home shell only let People open Chat and System, so it
  dropped the request. The People smoke now checks every target People
  opens against the shell's source policy.
- Troubleshooting: a Home that is the network's bootstrap shows
  Community Live but exchanges no Chat messages; how to confirm it and
  move the bootstrap to a separate Runtime.
- Troubleshooting: a Home left alone while the Community node was down
  stays unreachable after the node returns, until its Runtime restarts.
  Known iroh-gossip 0.101.0 issue (upstream #161 merged, unreleased);
  not worked around here.
@irzhywau

Copy link
Copy Markdown
Collaborator

Visual review of Chat on 31cc0513 (4 local Homes: 3 participants + a separate Community node): 21 checks pass, 1 struck as non-blocking (iroh-gossip, see earlier note).
Fixes pushed on top as a64fed8: People can open Inbox; the composer refits after send; People's Message reuses the open Chat window; two TROUBLESHOOTING entries.
Heads-up for the Chat lane: a64fed8 sits on 31cc051 and touches chat-room-ui/src/lib.rs and the Chat WASM; please merge it in with your review fixes and rebuild the WASM once. Local pre-push gate: all green except 15 update_controller tests that crash on macOS only (Network.framework atfork handler after fork; pass alone and on Linux), unrelated to this commit. Leave/Rejoin left to #264. Checklist below.

Visual review checklist

Basics

  • Desktop Enter sends; Shift+Enter adds a new line. Finding 1: the composer kept its multiline height after a send; fixed in a64fed8.
  • Message times and day separators use the local calendar.
  • Local Community history appears when Chat opens; a Home that joins later catches up on earlier messages.
  • A Community message appears once on every Home, in order, within about 3 s.
  • Unread badges show on Community and Direct conversations, and clear when opened.
  • An incoming Direct message raises a notification.

Profile card

  • A participant name opens a Profile card with Message and Add contact.
  • Message opens the Direct conversation in the same Chat window. Finding 2: from People it opened a second Chat window; fixed in a64fed8.

Community status

  • With no other Home online, one status line says Community unreachable; Send is disabled.
  • When another Home comes online, it changes to Live without a reload. Struck, non-blocking: a Home left alone does not redial the Community node (iroh-gossip 0.101.0; see the known-issue note above).
  • When the other Homes go offline, it returns to Community unreachable.

While unreachable

  • Direct conversations still open with their history.
  • The Community draft is kept, also when switching chats.
  • A Direct draft is kept when switching chats.
  • After a gateway restart, Chat offers Reconnect and keeps drafts.

Delivery and retry

  • A Direct message to an offline person shows Sending, then Retry.
  • Send again resends once, with no duplicate bubble.
  • A Direct message to an online person arrives; the sender sees Sent.
  • An own Community message shows Sending until accepted, then the label clears.

Spam limits

  • 6 Community messages within 10 s show Slow down with a countdown; sending works after it.

Notes

@andersalm

Copy link
Copy Markdown
Contributor Author

Merged into develop through the 0.8.0 release train #372 (merge 3f8676a). #309 landed at f173210 (bcb8174 + rebuilt UI assets + 3 test fixes), which contains #116's commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants