perf: memoize workspace session rows + paginate + subagent marker - #18
Open
robbe1912 wants to merge 3 commits into
Open
perf: memoize workspace session rows + paginate + subagent marker#18robbe1912 wants to merge 3 commits into
robbe1912 wants to merge 3 commits into
Conversation
renderSessionItem returned a fresh closure tree on every parent state change (typing in search box, opening any menu, renaming any session, toggling archived view), forcing every visible row to re-render even when its own state was unchanged. Extract SessionListItem component with React.memo and a display-prop comparator. Rename input state moves into the row itself so other rows don't re-render during typing. Callback identity is intentionally excluded from the comparator — they only fire on user action, so stale closures are not a correctness issue. Biggest win for sessions lists with many entries (50+); no impact on small lists.
useEffect depended on [isRenaming, session.title]; if SSE/polling pushed a title update while the user was typing in the rename box, the effect would reset renameValue to the new server title, losing the user's draft. Switch to ref-based transition detection: only sync renameValue on the false→true edge of isRenaming. session.title captured via ref so the effect reads the current value without re-firing on its changes.
Performance for large session DBs: default to displaying the last 24 chats with prev/next page controls. chats per page is configurable in Settings (5-100, step 5). Subagent sessions (session.parentID set in the OpenCode SDK schema) are now visually distinct — italic muted title prefixed with ↳, 'Subagent · <preview>' description — and can be hidden from the list via a setting. - ChatPreferences: +chatsPerPage (default 24), +hideSubagentChats (default false) - workspace.tsx: orderedSessions -> filteredSessions -> visibleSessions (sliced by current page); pagination row with prev/next IconButtons + 'Page X of Y · N chats' indicator only when totalChatsPages > 1 - SessionListItem title/description updated to show subagent marker - Settings Voice section: NumericSlider (5-100) + SettingSwitchRow for chats-per-page and hide-subagent-chats Page clamps automatically when list shrinks (delete/archive).
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.
Problem
The Workspace Chats panel renders every session row inline. Each parent state change (search typing, menu open, rename on any row) re-renders all visible rows. For large session databases (50+ chats in one workspace) the list is sluggish.
Secondary issues:
parentIDin the OpenCode SDK schema) are visually indistinguishable from primary sessions.Fix
Memoization
Extract
SessionListItemcomponent withReact.memo+ display-prop comparator. Callback identity intentionally excluded — callbacks only fire on user action, so stale closures are not a correctness issue. Renamed-input state moved into the row component so other rows don't re-render while the user types in any one row's rename field.Ref-based transition guard prevents the
useEffect [isRenaming, session.title]regression where SSE/polling title updates would clobber the user's in-progress rename draft.Pagination
Default to displaying the last 24 chats with prev/next page controls. Configurable via a new
chatsPerPageChatPreference (5–100, step 5, default 24). Page indicator (Page X of Y · N chats) only renders whentotalChatsPages > 1. Auto-clamps when the list shrinks (delete/archive) so the user never lands on an out-of-range page.Subagent marker
The OpenCode SDK exposes
Session.parentID?: string(node_modules/@opencode-ai/sdk/dist/gen/types.gen.d.ts:469). When present, the session is a child of another session. Visually:↳+ italic + muted colorSubagent · <preview>Optional
hideSubagentChatsChatPreference (default false) filters them from the list entirely. They still get created and run server-side; only the row is filtered.Files changed
app/(tabs)/workspace.tsx(+172 / -32 LOC) —SessionListItemcomponent +areSessionListItemPropsEqualcomparator + pagination state + subagent visual treatmentcomponents/settings/settings-sections.tsx(+5 LOC) —NumericSlider(5–100, step 5) +SettingSwitchRowfor chats-per-page and hide-subagent-chatsproviders/opencode-provider-utils.ts(+4 LOC) —chatsPerPage: number+hideSubagentChats: booleanChatPreferences fields + defaultsValidation
npm run typecheck✓npm run lint✓npm run test:fake-server:self✓Settings
In Settings → Voice section: