From 71fb1a31ce8d5047d897d322857c0d6dfc4a97c4 Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:09:45 -0300 Subject: [PATCH] perf(app): a chat item no longer changes the bot it belongs to Every chat.item with a list line replaced the bot object, and everything that shows the bot re-rendered: the open bot's whole view with its chat, every conversation in the sidebar and every row of the crew timeline. - The list line lives in its own map, state.activity, filled from bots.list and bot.changed (which carry it) and by chat.item; the sidebar and the alerts read it with activityOf. - The sidebar's conversations are memoized, so a change to one bot re-renders only its line. - A timeline row reads only its two bots instead of all of them, and is memoized. - attentionCount and actionableDead, asked on every store change, are worked out again only when their inputs change. --- app/src/features/crews/Sidebar.tsx | 11 +++++---- .../features/messages/FailedDeliveries.tsx | 14 ++++++++++- app/src/features/messages/MessageRow.tsx | 19 ++++++++------- app/src/shell/alerts.ts | 5 ++-- app/src/shell/attention.ts | 17 ++++++++++++- app/src/store/app.test.ts | 16 ++++++++++++- app/src/store/app.ts | 24 +++++++++++++++---- app/src/store/sync.ts | 5 +++- 8 files changed, 88 insertions(+), 23 deletions(-) diff --git a/app/src/features/crews/Sidebar.tsx b/app/src/features/crews/Sidebar.tsx index 3dd85325..245255e3 100644 --- a/app/src/features/crews/Sidebar.tsx +++ b/app/src/features/crews/Sidebar.tsx @@ -1,11 +1,11 @@ import { Pause, Plus } from "lucide-react"; -import { useCallback, useState } from "react"; +import { memo, useCallback, useState } from "react"; import { createPortal } from "react-dom"; import { useShallow } from "zustand/react/shallow"; import { type Messages, useT } from "../../i18n"; import { when } from "../../lib/format"; import type { Activity, Bot, Crew } from "../../lib/protocol.gen"; -import { botsOf, crewList } from "../../store/app"; +import { activityOf, botsOf, crewList } from "../../store/app"; import { useApp } from "../../store/context"; import { Button } from "../../ui/Button"; import { ContextMenu, menuPoint, type Point } from "../../ui/ContextMenu"; @@ -84,7 +84,8 @@ function CrewEntry({ crew }: { crew: Crew }) { ); } -function Conversation({ bot, crew }: { bot: Bot; crew: Crew }) { +/** Memoized: a change to one bot re-renders only its own line. */ +const Conversation = memo(function Conversation({ bot, crew }: { bot: Bot; crew: Crew }) { const t = useT(); const selected = useApp((state) => state.selectedBotId === bot.id); const selectBot = useApp((state) => state.selectBot); @@ -92,7 +93,7 @@ function Conversation({ bot, crew }: { bot: Bot; crew: Crew }) { const actions = useBotActions(bot, crew); const [menuAt, setMenuAt] = useState(null); const closeMenu = useCallback(() => setMenuAt(null), []); - const activity = bot.lastActivity; + const activity = useApp((state) => activityOf(state, bot.id)); return (
  • APP_OPENED ? "animate-rise" : undefined}>