summaryrefslogtreecommitdiffstats
path: root/CLAUDE.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-03 12:13:41 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-03 12:13:41 +0200
commit691c6ba4ef51085c89aeddbcabd5c733861eb56b (patch)
tree4e44451043309ed50af5345c34762c5f87150d86 /CLAUDE.md
parent7d995ea52d8321495630dd95851626b9664ce133 (diff)
downloadmeshbay-691c6ba4ef51085c89aeddbcabd5c733861eb56b.tar.gz
fix(hub): route the chat ack to the request that asked for it
Typing a message froze the Chat tab: the composer stopped taking clicks and keystrokes, the message never appeared, and it was there all along on the next visit to the tab. The node answers a chat message with a bare {"type": "ack"} -- no request id, no type of its own -- so _dispatch had nothing to match it on and left it to the arrival-order guess at the end of the function. That guess is wrong the moment anything else this browser asked for is still waiting: the ack went to *that* request, and the chat send waited out _sendAndWait's own 30s timeout. Since the composer is disabled while a send is in flight, that reads as a frozen tab; the node had stored the message and answered, into somebody else's promise. An outstanding request is the ordinary case, not a rare one. The node refuses an unknown file_id with a bare `error`, which names no request either and so reaches none, leaving the Videos tab's media_meta_req in _pending for the full 30s. That is the one that was live when this was found. - `ack` is now matched by request type: chat_msg, or the keypair-bundle store and delete, which name themselves in `detail`. A node naming neither still has its reply placed rather than dropped. Every line of chat-app.js is correct and every routed message in transport.js is routed correctly -- the defect is in the seam, so tests/harness/ chat_send_probe.py drives the two together: the real ChatPanel over the real MeshBayTransport, with only the DataChannel replaced by a stand-in answering what the node answers. test_chat_send.py asserts against it, and with the fix reverted all three of its tests fail on the three visible halves of the defect -- the composer still disabled, the message absent, and the ack resolving the unrelated request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GFF4BL8VSKrghkSLzCrTVs
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md21
1 files changed, 21 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 4bf4bb5..0197d1e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -452,6 +452,26 @@ anything that assumes one key per person.
the correction once and write nothing in the steady state. And a scroll
position that must survive a gesture has to be released **by the gesture**
(`wheel`/`touchmove`/`pointerdown`), never by the `scroll` event alone
+- **A reply that names nothing is routed by luck.** The node answers a chat
+ message with a bare `{"type": "ack"}` — no request id, no type of its own —
+ so `_dispatch` had nothing to match it on and fell through to its
+ arrival-order guess, which hands a reply to whichever request happens to be
+ oldest. That is wrong the moment anything else this browser asked for is
+ still waiting, and one *is*: the node refuses an unknown `file_id` with a
+ bare `error`, which names no request either and so reaches none, leaving the
+ Videos tab's `media_meta_req` in `_pending` for the full 30 s. The ack went
+ to that, the send waited out its own timeout, and because the composer is
+ disabled while a send is in flight, **typing a message froze the Chat tab**:
+ no click, no keystroke, no message — and the message there all along on the
+ next visit to the tab, since the node had stored it and answered. Every line
+ of `chat-app.js` is correct and every routed message in `transport.js` is
+ routed correctly; the defect is in the seam, which is why
+ `tests/harness/chat_send_probe.py` drives the two together. `ack` is now
+ matched by request type (`chat_msg`, or the keypair-bundle store/delete that
+ name themselves in `detail`). Anything left to the "oldest pending" guess is
+ a latent version of this bug: a request type deserves a key, and a reply
+ deserves something to key it by
+
- **A refusal that never rejects.** Denying Chromium's `fullscreen` permission
does not make `requestFullscreen()` throw — the promise never settles. The
deny-everything handler was written from a true sentence ("nothing here needs
@@ -781,6 +801,7 @@ SFR residential Fedora 44 → meshbay.org OVH VPS:
| Resume position | `static/video-player.js` | `readResumePosition` / `writeResumePosition` — localStorage, per file, per browser. No protocol, and nothing new learns what you watch |
| Layout, measured | `tests/harness/layout_probe.py` | Renders `style.css` in Chrome at any width and returns bounding boxes. Use it for layout, not `test_layout_responsive.py`, which only pins CSS values |
| Chat scrolling, measured | `tests/harness/chat_scroll_probe.py` | Mounts the real `ChatPanel` in Chrome and reads a conversation back. Answers "can the reader scroll up" and "does the panel resize itself"; `test_chat_scroll_bottom.py` only pins the source's shape |
+| Chat sending, measured | `tests/harness/chat_send_probe.py` | Mounts the real `ChatPanel` over the real `MeshBayTransport` (only the DataChannel is a stand-in) and types a message. Answers "does the send come back" — the freeze it was written for lives in the seam between the two, so neither source shows it |
| Group landing tab, measured | `tests/harness/group_tab_probe.py` | Renders the real `GroupPage` against a stub node answering a chosen `enabled_apps`, and reads the tab bar back. The landing tab is picked from a preference at mount; the app list arrives from the handshake later, and the two can disagree |
| Session renewal (browser) | `static/hub-client.js` | `refreshAccessToken` / `ensureFreshToken` — one writer (`setAuth`), one in-flight renewal, rotated refresh token stored. `hubFetch` renews on 401 and replays. Moved out of app.js in the 2026-08-23 split |
| Token lifetimes (hub) | `meshbay_hub.config` | `[jwt] access_token_ttl` 4 h, `refresh_token_ttl` 30 days. **Production sets both in `~/.config/meshbay/hub.toml`** — changing the code default alone does nothing there |