fix(messages-ui): surface silent send failures, stop scroll/render churn (+cleanup)

- ChatThread send had no onError: a 404 (recipient gone) or 429 (rate-limited)
  send failed silently (api.req shows no modal for those "caller-handled" statuses).
  Added onError → toast for 404/429 (400/409/500 already raise the global dialog);
  draft is kept for retry. New trilingual messages.sendFailed key.
- ChatThread scroll-to-bottom effect keyed on `plain` too, so every 20s poll's
  decrypt pass yanked a scrolled-up reader back to the bottom. Key on items.length.
- ChatThread + Messages decrypt effects keyed on `items` (a fresh `?? []` array
  every render) → a render storm while loading. Key on q.dataUpdatedAt.
- chatDock.ts: use LS.chatDock(userId) instead of the bare "siftlode.chatDock.*"
  literal (matches the storage LS registry).

tsc green, re-review clean, localdev boots, Messages renders (locked-state) w/o console errors.
This commit is contained in:
npeter83 2026-07-11 20:16:14 +02:00
parent 40ddaa2c92
commit 5441ad203d
6 changed files with 31 additions and 7 deletions

View file

@ -3,6 +3,7 @@
// when a message arrives) and stays open across page navigation. The dock state (which chats
// are open + their minimised state) is persisted per user so it survives a reload.
import { useSyncExternalStore } from "react";
import { LS } from "./storage";
export interface DockChat {
partnerId: number;
@ -20,7 +21,7 @@ let userId: number | null = null;
const subs = new Set<() => void>();
function storageKey(): string | null {
return userId != null ? `siftlode.chatDock.${userId}` : null;
return userId != null ? LS.chatDock(userId) : null;
}
function persist(): void {