Repository navigation
Conversation
FlatSessionRow and SessionItem are memoized, but every list rebuild hands them a freshly minted row object, so the identity comparison failed for every mounted row on every socket event and the whole visible window re-rendered. Row data is flat primitives; a fast-deep-equal comparator on it (plus the primitive props) lets only the rows whose content changed render again. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Every row mounted a SessionActionsPopover with visible=false, which ran useSession, useSessionQuickActions, useWindowDimensions and the safe-area hook before returning null — a dozen store subscriptions and memos per row, all woken by every session update. Mount it only while the row holds an anchor; closing clears the anchor and unmounts it, as before. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The hook exists for the native onLongPress alert, but web rows called it too and paid a useSession subscription plus the whole quick-actions hook (useMachine, settings, a dozen callbacks) per row for a handler that is never invoked. Pick a no-op variant once at module load on web — Platform.OS is constant, so hook order stays stable — and keep the native one unchanged. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On a web tab left open for hours with dozens of streaming sessions, every socket event re-rendered every mounted row of the sessions list; an overnight heap reached 1.2 GB. Three per-row costs in
sources/components/SessionsList.tsxandsources/components/FlatSessionRow.tsxadd up to that:FlatSessionRowandSessionItemareReact.memo'd, but every list rebuild hands them a freshly mintedSessionRowData, so the default identity comparison failed for every row in the visible window on every event. Every row also mounted aSessionActionsPopoverwithvisible={false}, which ranuseSession,useSessionQuickActions,useWindowDimensionsand the safe-area hook before returningnull— a dozen store subscriptions and memos per row, all woken by every session update. And on web every row calleduseSessionActionAlert(sources/hooks/useSessionQuickActions.ts), paying auseSessionsubscription plus the whole quick-actions hook for anonLongPresshandler that only native ever invokes.This PR gives the two row memos a comparator that deep-equals the row data (flat primitives, so
fast-deep-equalis cheap) together with the primitive props, mounts the popover only while the row holds an anchor (closing clears the anchor and unmounts it, as before), and picks a no-opuseSessionActionAlertonce at module load on web —Platform.OSis constant, so hook order stays stable and the native variant is unchanged.Changes
FlatSessionRow/SessionItem: add memo comparators that compareselected/isFirst/isLast/isSingleand deep-equal the row data instead of relying on object identitySessionActionsPopoveronly whileactionsAnchoris set, on both the flat and the grouped rowuseSessionActionAlert: export a no-op variant on web, keep the native one as isTest plan
pnpm typecheck(clean)pnpm exec vitest run sources/components/HomeHeader.test.ts sources/components/sessionPresentation.test.ts sources/utils/flatSessionList.test.ts sources/utils/flatSessionRowPresentation.test.ts(the specs that cover the row presentation and list shape; all pass — the change is render-count only, no behavioural test exists for it)🤖 Generated with Claude Code