Virtual Scrolling 2.1 #3577

Merged
hj merged 63 commits from chat-view-update into develop 2026-09-28 12:45:52 +00:00
Member
  • added virtual scrolling support into chat view
  • refactor pleroma chat stuff into its own composable and make chat view switch between it and useConversation
  • some groundwork for supporting upside-down timelines (similarly to chat logs) as well groundwork for supporting non-window scrollers in virtual scrolling (notifications)
  • removed keyboard hint from non-chat-view forms
  • various minor chat view fixes
- added virtual scrolling support into chat view - refactor pleroma chat stuff into its own composable and make chat view switch between it and `useConversation` - some groundwork for supporting upside-down timelines (similarly to chat logs) as well groundwork for supporting non-window scrollers in virtual scrolling (notifications) - removed keyboard hint from non-chat-view forms - various minor chat view fixes
hj added 20 commits 2026-09-23 12:04:37 +00:00
fixes
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline failed
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
c9d0af7475
changelog
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
28d89b99dd
hj added 4 commits 2026-09-23 13:50:25 +00:00
fix showing new not scrolling fully
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
9fe97cbb98
hj added 4 commits 2026-09-23 17:55:01 +00:00
logic improvements
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
91a63d513d
lint & fix
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
cd1fe470e8
hj added 4 commits 2026-09-24 11:46:48 +00:00
fixes
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
d44708cd64
hj added 2 commits 2026-09-24 12:33:36 +00:00
fixes
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
2ebd575365
hj added 2 commits 2026-09-24 13:49:38 +00:00
lint
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
9a5519d9b3
i think i cracked the case finally
Some checks failed
ci/woodpecker/pr/changelog Pipeline is pending
ci/woodpecker/pr/lint Pipeline is pending
ci/woodpecker/pr/test-e2e Pipeline is pending
ci/woodpecker/pr/test Pipeline is pending
ci/woodpecker/pr/build Pipeline was canceled
455bc106c1
lint
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
2e4ea899fe
hj force-pushed chat-view-update from 2e4ea899fe
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
to 9a5519d9b3
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/test-e2e unknown status
2026-09-24 14:36:08 +00:00
Compare
hj added 3 commits 2026-09-24 17:36:55 +00:00
fix another test
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/test-e2e Pipeline was successful
a0c5505d06
context switch detection logic needs improvement
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/test-e2e Pipeline was successful
ca6a1ea430
lint
All checks were successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/test-e2e Pipeline was successful
8c5e47e804
@ -162,2 +185,4 @@
}
},
isSuspendable() {
return !this.replying && this.mediaPlaying.size === 0
Author
Member

fix

fix
hj marked this conversation as resolved
@ -12,4 +19,0 @@
getScrollPosition,
isBottomedOut,
isScrollable,
} from './chat_layout_utils.js'
Author
Member

probably remove this entire file now

probably remove this entire file now
hj marked this conversation as resolved
fix explosion
Some checks failed
ci/woodpecker/pr/test-e2e Pipeline is pending
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline was canceled
f219a60e9a
hj added 2 commits 2026-09-25 12:13:47 +00:00
fix
Some checks failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline failed
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/test-e2e Pipeline was successful
27b0dcc0c4
lint
All checks were successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/test-e2e Pipeline was successful
664260edb3
Author
Member

/oc review

/oc review
opencode-fix-bot left a comment

opencode-agent:job=87:generation=1

Automated review of develop@7e147527 → chat-view-update@664260e (28 files, +1293/−1170), triggered by /oc review on comment 118642.

Verification performed at head 664260e: full unit suite in browser mode (chromium headless): 41 files, 486 passed, 2 skipped — green; biome check clean; stylelint clean.

Overall: the decomposition is a genuine readability win (chat_view.js 725 → 256 lines) and the invertDirection/compensation comments in useVirtualScrolling.js are excellent. That said, I found one functional regression I'd treat as a blocker, one crash-on-error-path bug, and a handful of smaller issues.


Blocking / high

1. Cross-chat message contamination — usePleromaChat.js (onChatUpdate/addMessages)
The old chat_view.js had a sanity check in addMessages that dropped messages whose chat_id didn't match the current chat (if (!this.isConversation && message.chat_id !== this.chat.id) return). That check exists because the chat view registers a subscriber without a stream, so it lands in globalSubscriptions and stores/streaming.js onMessage dispatches every pleroma:chat_update (for all chats) to it. The composable port dropped the filter: onChatUpdate feeds chatUpdate.lastMessage straight into addMessages. While this chat is open, a message arriving in any other chat will be appended to this chat's list, skewing minId/maxId, newMessagesCount, and markAsRead (readChat({ lastReadId: maxId }) can be called with a foreign id). Please restore the chat_id check (in addMessages or onChatUpdate).

2. Retry path crashes — usePleromaChat.js doSendMessage
messageRetriers.set(...) is called on the ref itself, not its Map — should be messageRetriers.value.set(...). On a retryable send error (5xx), the old code scheduled an exponential-backoff retry (this.messageRetriers[key] = setTimeout(...)); the new code throws TypeError: messageRetriers.set is not a function inside the catch block, so the retry is never scheduled and the exception escapes the error handler. The new tests only cover happy paths, so this isn't caught.

3. formPlaceholder never updates — chat_view.js
The computed reads chatRecipient without .value: the ref object is always truthy, chatRecipient.screen_name_ui is always undefined, and the computed builds no reactive dependency, so it evaluates once and never again. Result: the post-form placeholder is permanently "Message " (I verified t('chats.message_user', { nickname: undefined }) interpolates to "Message ") instead of '' until the recipient loads and then "Message @nick". Fix: chatRecipient.value?.screen_name_ui (with chatRecipient.value in the condition).

Medium

4. streamingEnabled is constant false — usePleromaChat.js
mastoUserSocketStatus === WSConnectionStatus.JOINED is missing .value, and mastoUserSocketStatus is not a member of stores/streaming.js at all (the store exposes state; cf. useConversation.js:27, which at least uses .value). So the fetchChat streaming short-circuit is dead code. Practical impact is limited today because onStreamConnect stops the poller, but any future reliance on streamingEnabled will misbehave. Worth fixing both sites against state.

5. ResizeObserver never disconnected — useClientRectSize.js
onUnmounted(() => resizeObserver.disconnect) evaluates the method without calling it (missing parens), so observers are never released. Consumers: one per chat view (post form), one per embedded conversation in timelines (conversation.js previously disconnected correctly), plus the App-level body scroller. Also nothing unobserves the previous element when the element ref changes.

Small / nits

  • useVirtualScrolling.js: oldReal is computed from newVal and newReal from oldVal — swapped names. The comparison is symmetric so behavior is fine, but it's very confusing to read.
  • chat_view.js: watch(isValid) throwing TypeError routes a prop-contract violation to the global error handler in production; a console.error/report without the throw may be friendlier.
  • usePleromaChat.js: the internal error ref set on activate() failure is never returned (only fetchError is), so activate failures surface only as chatReady === false. Also neither the retry setTimeouts nor idempotencyKeyIndex are cleared in clear()/deactivate(), so a scheduled retry can fire after switching/closing a chat and re-enter doSendMessage on cleared state.
  • usePleromaChat.js deleteChatMessage: maxBy(messages, 'id').id throws when the deleted message was the last one (pre-existing behavior, but it moved here — a good moment to guard it).
  • chat_message.js: suspendableStateChange emits this.status.id while heightChange emits chatItem.id; the keys diverge if a chat message ever wraps a retweeted_status.
  • timeline.js: infinite scroll now fires on shouldLoadBottom edges only — if the user rests inside the load margin while new statuses stream in, the watcher won't re-fire (the old scroll handler re-checked continuously). Also the load margin changed from ~750px to fontSize * 25 (~350–400px) — intentional?
  • chat_view.vue/chat_view.js: ref="messageList"/ref="pendingMessageList" are unused in setup; faChevronLeft is still imported/registered after the go-back button removal; startFetching's isFirstFetch parameter is unused.
  • useWindowScroll.js: returning bare window.scrollBy/window.scrollTo happens to work because they're own properties of window (I verified detached calls don't throw in chromium), but wrapping them ((...args) => window.scrollBy(...args)) would be more defensive across engines.

Tests / coverage

chat_view.spec.js (128 lines) was deleted; usePleromaChat.spec.js covers addMessages/markAsRead/deleteChatMessage happy paths, but nothing covers the send-error path (where #2 hides) or the conversation/chat mode fork in the view. I'd love to keep some view-level coverage for that fork.

Nice touches: the fake- id prefix is now applied consistently (buildFakeMessage, fake-date-, and the context-change filter in useVirtualScrolling all line up), and the separation of pending messages into their own list instance is clean.


Published by opencode-fix-bot · marker: opencode-agent:job=87:generation=1

opencode-agent:job=87:generation=1 Automated review of `develop@7e147527` → `chat-view-update@664260e` (28 files, +1293/−1170), triggered by `/oc review` on comment 118642. **Verification performed at head `664260e`:** full unit suite in browser mode (chromium headless): 41 files, 486 passed, 2 skipped — green; `biome check` clean; `stylelint` clean. **Overall:** the decomposition is a genuine readability win (`chat_view.js` 725 → 256 lines) and the `invertDirection`/compensation comments in `useVirtualScrolling.js` are excellent. That said, I found one functional regression I'd treat as a blocker, one crash-on-error-path bug, and a handful of smaller issues. --- ### Blocking / high **1. Cross-chat message contamination — `usePleromaChat.js` (`onChatUpdate`/`addMessages`)** The old `chat_view.js` had a sanity check in `addMessages` that dropped messages whose `chat_id` didn't match the current chat (`if (!this.isConversation && message.chat_id !== this.chat.id) return`). That check exists because the chat view registers a subscriber **without** a `stream`, so it lands in `globalSubscriptions` and `stores/streaming.js` `onMessage` dispatches *every* `pleroma:chat_update` (for all chats) to it. The composable port dropped the filter: `onChatUpdate` feeds `chatUpdate.lastMessage` straight into `addMessages`. While this chat is open, a message arriving in any *other* chat will be appended to this chat's list, skewing `minId`/`maxId`, `newMessagesCount`, and `markAsRead` (`readChat({ lastReadId: maxId })` can be called with a foreign id). Please restore the `chat_id` check (in `addMessages` or `onChatUpdate`). **2. Retry path crashes — `usePleromaChat.js` `doSendMessage`** `messageRetriers.set(...)` is called on the `ref` itself, not its `Map` — should be `messageRetriers.value.set(...)`. On a retryable send error (5xx), the old code scheduled an exponential-backoff retry (`this.messageRetriers[key] = setTimeout(...)`); the new code throws `TypeError: messageRetriers.set is not a function` inside the catch block, so the retry is never scheduled and the exception escapes the error handler. The new tests only cover happy paths, so this isn't caught. **3. `formPlaceholder` never updates — `chat_view.js`** The computed reads `chatRecipient` without `.value`: the ref object is always truthy, `chatRecipient.screen_name_ui` is always `undefined`, and the computed builds no reactive dependency, so it evaluates once and never again. Result: the post-form placeholder is permanently `"Message "` (I verified `t('chats.message_user', { nickname: undefined })` interpolates to `"Message "`) instead of `''` until the recipient loads and then `"Message @nick"`. Fix: `chatRecipient.value?.screen_name_ui` (with `chatRecipient.value` in the condition). ### Medium **4. `streamingEnabled` is constant `false` — `usePleromaChat.js`** `mastoUserSocketStatus === WSConnectionStatus.JOINED` is missing `.value`, and `mastoUserSocketStatus` is not a member of `stores/streaming.js` at all (the store exposes `state`; cf. `useConversation.js:27`, which at least uses `.value`). So the `fetchChat` streaming short-circuit is dead code. Practical impact is limited today because `onStreamConnect` stops the poller, but any future reliance on `streamingEnabled` will misbehave. Worth fixing both sites against `state`. **5. `ResizeObserver` never disconnected — `useClientRectSize.js`** `onUnmounted(() => resizeObserver.disconnect)` evaluates the method without calling it (missing parens), so observers are never released. Consumers: one per chat view (post form), one per embedded conversation in timelines (`conversation.js` previously disconnected correctly), plus the App-level body scroller. Also nothing unobserves the previous element when the `element` ref changes. ### Small / nits - `useVirtualScrolling.js`: `oldReal` is computed from `newVal` and `newReal` from `oldVal` — swapped names. The comparison is symmetric so behavior is fine, but it's very confusing to read. - `chat_view.js`: `watch(isValid)` throwing `TypeError` routes a prop-contract violation to the global error handler in production; a `console.error`/report without the throw may be friendlier. - `usePleromaChat.js`: the internal `error` ref set on `activate()` failure is never returned (only `fetchError` is), so activate failures surface only as `chatReady === false`. Also neither the retry `setTimeout`s nor `idempotencyKeyIndex` are cleared in `clear()`/`deactivate()`, so a scheduled retry can fire after switching/closing a chat and re-enter `doSendMessage` on cleared state. - `usePleromaChat.js` `deleteChatMessage`: `maxBy(messages, 'id').id` throws when the deleted message was the last one (pre-existing behavior, but it moved here — a good moment to guard it). - `chat_message.js`: `suspendableStateChange` emits `this.status.id` while `heightChange` emits `chatItem.id`; the keys diverge if a chat message ever wraps a `retweeted_status`. - `timeline.js`: infinite scroll now fires on `shouldLoadBottom` *edges* only — if the user rests inside the load margin while new statuses stream in, the watcher won't re-fire (the old scroll handler re-checked continuously). Also the load margin changed from ~750px to `fontSize * 25` (~350–400px) — intentional? - `chat_view.vue`/`chat_view.js`: `ref="messageList"`/`ref="pendingMessageList"` are unused in setup; `faChevronLeft` is still imported/registered after the go-back button removal; `startFetching`'s `isFirstFetch` parameter is unused. - `useWindowScroll.js`: returning bare `window.scrollBy`/`window.scrollTo` happens to work because they're own properties of `window` (I verified detached calls don't throw in chromium), but wrapping them (`(...args) => window.scrollBy(...args)`) would be more defensive across engines. ### Tests / coverage `chat_view.spec.js` (128 lines) was deleted; `usePleromaChat.spec.js` covers `addMessages`/`markAsRead`/`deleteChatMessage` happy paths, but nothing covers the send-error path (where #2 hides) or the conversation/chat mode fork in the view. I'd love to keep some view-level coverage for that fork. Nice touches: the `fake-` id prefix is now applied consistently (`buildFakeMessage`, `fake-date-`, and the context-change filter in `useVirtualScrolling` all line up), and the separation of pending messages into their own list instance is clean. --- *Published by opencode-fix-bot · marker: `opencode-agent:job=87:generation=1`*
Author
Member

Also the load margin changed from ~750px to fontSize * 25 (~350–400px) — intentional?

Intentional. IMO old margin was way too big.

>Also the load margin changed from ~750px to fontSize * 25 (~350–400px) — intentional? Intentional. IMO old margin was way too big.
hj added 13 commits 2026-09-25 14:11:35 +00:00
Author
Member

usePleromaChat.spec.js covers addMessages/markAsRead/deleteChatMessage happy paths, but nothing covers the send-error path (where #2 hides) or the conversation/chat mode fork in the view. I'd love to keep some view-level coverage for that fork.

I'm gonna be real. I really don't want to deal with pleroma chats. Nobody does. That's why i'm doing Chats 2.0 with normal threads. I might add more tests to it on a rainy day.

>usePleromaChat.spec.js covers addMessages/markAsRead/deleteChatMessage happy paths, but nothing covers the send-error path (where #2 hides) or the conversation/chat mode fork in the view. I'd love to keep some view-level coverage for that fork. I'm gonna be real. I really don't want to deal with pleroma chats. Nobody does. That's why i'm doing Chats 2.0 with normal threads. I might add more tests to it on a rainy day.
lint
All checks were successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/test-e2e Pipeline was successful
cdf7c2a2d3
Author
Member

/oc review

/oc review
opencode-fix-bot left a comment

opencode-agent:job=88:generation=1

Automated re-review of develop@7e147527 → chat-view-update@cdf7c2a2 (29 files, +1311/−1174, 61 commits), triggered by /oc review on comment 118650. Follow-up to the earlier automated review at head 664260e: the branch gained 14 commits since, and this pass verifies those fixes against the new head and looks for anything they introduced.

Verification performed at head cdf7c2a2: full unit suite in browser mode (chromium headless): 41 files, 486 passed, 2 skipped — green; biome check clean; stylelint clean.

Previously reported findings — all fixed ✅

  1. Cross-chat contamination → the chat_id sanity check is restored in addMessages (but see new finding A below).
  2. Retry-path crash → messageRetriers.value.set(...) is correct now, and pending retry timers are cleared in clear(), so they can't fire into cleared state after switching/closing a chat.
  3. formPlaceholder reactivity → .value is in the dependency chain now (but see new finding B below).
  4. streamingEnabled → derived from state with .value in both usePleromaChat.js and useConversation.js.
  5. ResizeObserver leak → disconnect() is actually invoked; activate failures surface via fetchError; deleteChatMessage guards maxBy/minBy with ?.; suspendableStateChange now emits chatItem.id, consistent with heightChange; unused faChevronLeft, ref="messageList"/ref="pendingMessageList" and the isFirstFetch param are gone; the timeline load watcher re-checks while scrolling ([shouldLoadBottom, scrollY], debounced 100 ms), addressing the "won't re-fire while resting in the margin" concern.

New findings (introduced by the fixes)

A. addMessages guard aborts the whole batch — usePleromaChat.js line ~233

for (let message of newMessages) {
  // Chats we're getting from streaming might belong to someone else instead
  if (message.chat_id !== chat.value.id) {
    return // ← should be `continue`
  }
  ...
}

The comment says foreign messages should be skipped, but return inside the for loop drops every remaining message in the batch once one foreign message is seen. All three current call sites pass homogeneous batches (chatMessages is fetched by chat id, onChatUpdate passes a single lastMessage, send confirmation is a single message), so nothing is lost today — but this is exactly the latent-trap shape of the original #1: the first time a caller passes a mixed batch, messages silently disappear. continue makes the guard do what the comment promises.

B. formPlaceholder fix is incomplete — chat_view.js lines ~208–216

const formPlaceholder = computed(() => {
  if (chatRecipient) { // ← ref object, always truthy
    return t('chats.message_user', {
      nickname: chatRecipient.value?.screen_name_ui,
    })
  } else {
    return '' // ← unreachable
  }
})

The reactivity bug is fixed, but the condition still tests the ref object rather than chatRecipient.value, so the else branch is dead code. Consequence: while the chat is loading — or after a failed getOrCreateChat — the form placeholder renders "Message " (trailing space, nickname: undefined) instead of ''; at base it was '' until the recipient resolved. The form is visible in that state (only the submit button is disabled via chatReady). Fix: if (chatRecipient.value).

Small

  • timeline.js: the debounced watcher callback isn't cancelled on unmount, so a pending 100 ms debounce can fire fetchOlderStatuses() once after the timeline is gone. Harmless today, but debounce.cancel in onUnmounted/onScopeDispose would be tidy.
  • usePleromaChat.js: handleMessageError is invoked with { chatId, ... } but only destructures idempotencyKey/isRetry — harmless dead argument.

Not re-raised, per discussion: the load-margin change (intentional, comment 118645) and additional send-error / chat-fork test coverage (declined, comment 118647).

Overall: every blocker and medium from the previous review is properly fixed and the suite is green. Finding A (return → continue) is a two-character change worth making before merge; B is minor polish.


Published by opencode-fix-bot · marker: opencode-agent:job=88:generation=1

opencode-agent:job=88:generation=1 Automated re-review of `develop@7e147527` → `chat-view-update@cdf7c2a2` (29 files, +1311/−1174, 61 commits), triggered by `/oc review` on comment 118650. Follow-up to the earlier automated review at head `664260e`: the branch gained 14 commits since, and this pass verifies those fixes against the new head and looks for anything they introduced. **Verification performed at head `cdf7c2a2`:** full unit suite in browser mode (chromium headless): 41 files, 486 passed, 2 skipped — green; `biome check` clean; `stylelint` clean. ### Previously reported findings — all fixed ✅ 1. **Cross-chat contamination** → the `chat_id` sanity check is restored in `addMessages` (but see new finding A below). 2. **Retry-path crash** → `messageRetriers.value.set(...)` is correct now, and pending retry timers are cleared in `clear()`, so they can't fire into cleared state after switching/closing a chat. 3. **`formPlaceholder` reactivity** → `.value` is in the dependency chain now (but see new finding B below). 4. **`streamingEnabled`** → derived from `state` with `.value` in both `usePleromaChat.js` and `useConversation.js`. 5. **`ResizeObserver` leak** → `disconnect()` is actually invoked; activate failures surface via `fetchError`; `deleteChatMessage` guards `maxBy`/`minBy` with `?.`; `suspendableStateChange` now emits `chatItem.id`, consistent with `heightChange`; unused `faChevronLeft`, `ref="messageList"`/`ref="pendingMessageList"` and the `isFirstFetch` param are gone; the timeline load watcher re-checks while scrolling (`[shouldLoadBottom, scrollY]`, debounced 100 ms), addressing the "won't re-fire while resting in the margin" concern. ### New findings (introduced by the fixes) **A. `addMessages` guard aborts the whole batch — `usePleromaChat.js` line ~233** ```js for (let message of newMessages) { // Chats we're getting from streaming might belong to someone else instead if (message.chat_id !== chat.value.id) { return // ← should be `continue` } ... } ``` The comment says foreign messages should be skipped, but `return` inside the `for` loop drops **every remaining message in the batch** once one foreign message is seen. All three current call sites pass homogeneous batches (`chatMessages` is fetched by chat id, `onChatUpdate` passes a single `lastMessage`, send confirmation is a single message), so nothing is lost today — but this is exactly the latent-trap shape of the original #1: the first time a caller passes a mixed batch, messages silently disappear. `continue` makes the guard do what the comment promises. **B. `formPlaceholder` fix is incomplete — `chat_view.js` lines ~208–216** ```js const formPlaceholder = computed(() => { if (chatRecipient) { // ← ref object, always truthy return t('chats.message_user', { nickname: chatRecipient.value?.screen_name_ui, }) } else { return '' // ← unreachable } }) ``` The reactivity bug is fixed, but the condition still tests the ref object rather than `chatRecipient.value`, so the `else` branch is dead code. Consequence: while the chat is loading — or after a failed `getOrCreateChat` — the form placeholder renders `"Message "` (trailing space, `nickname: undefined`) instead of `''`; at base it was `''` until the recipient resolved. The form is visible in that state (only the submit button is disabled via `chatReady`). Fix: `if (chatRecipient.value)`. ### Small - `timeline.js`: the debounced watcher callback isn't cancelled on unmount, so a pending 100 ms debounce can fire `fetchOlderStatuses()` once after the timeline is gone. Harmless today, but `debounce.cancel` in `onUnmounted`/`onScopeDispose` would be tidy. - `usePleromaChat.js`: `handleMessageError` is invoked with `{ chatId, ... }` but only destructures `idempotencyKey`/`isRetry` — harmless dead argument. Not re-raised, per discussion: the load-margin change (intentional, comment 118645) and additional send-error / chat-fork test coverage (declined, comment 118647). **Overall:** every blocker and medium from the previous review is properly fixed and the suite is green. Finding A (`return` → `continue`) is a two-character change worth making before merge; B is minor polish. --- *Published by opencode-fix-bot · marker: `opencode-agent:job=88:generation=1`*
hj added 2 commits 2026-09-25 14:42:27 +00:00
lint
All checks were successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/changelog Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/test-e2e Pipeline was successful
804e2a9a3e
hj merged commit 865ae64061 into develop 2026-09-28 12:45:52 +00:00
hj deleted branch chat-view-update 2026-09-28 12:45:52 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pleroma/pleroma-fe!3577
No description provided.