fix(desktop): isolate full composer state per tab (#4133)#4147
Open
JesonChou wants to merge 1 commit into
Open
fix(desktop): isolate full composer state per tab (#4133)#4147JesonChou wants to merge 1 commit into
JesonChou wants to merge 1 commit into
Conversation
Previously only text was isolated per tab. This extends the draft mechanism to
all user-facing composer state: attachments, workspace @-refs, pasted blocks,
and past:chats session refs.
Changes:
- types.ts: Add Attachment, WorkspaceReference, PastedBlock, ComposerDraft
- useController.ts: draft field is now ComposerDraft (was string);
setDraft accepts full draft object; emptyDraft constant for init;
reset action explicitly spreads emptyDraft to avoid shared refs
- Composer.tsx: import types from types.ts (remove local definitions);
restore all state from draft on mount; save all state on unmount
via draftRef synced every render; skip save when nothing changed
(reference equality against mount-time draft prop avoids wasted
set_draft → bump() → re-render cycle on no-op tab switches)
- App.tsx: pass draft={state.draft} (was draftText)
key={activeTabId} forces clean remount per tab — all state is now
properly saved before unmount and restored on the next mount for that tab.
All 190 frontend tests pass, TypeScript compiles cleanly.
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
Fixes #4133. When multiple tabs are open, the composer input box content was shared across all tabs instead of being independent. Editing text (or attaching files, adding @-refs, pasting blocks, selecting past:chats sessions) in Tab A was visible
in Tab B and vice versa.
Root cause: The
<Composer>component was a single instance without akeyprop, so React preserved its local state across tab switches. The per-tabStatemap (TabStates) did not track any composer draft state.Solution
Three-part fix:
Per-tab draft storage —
Stateinterface gains adraft: ComposerDraftfield that persists all user-facing composer state (text, attachments, workspace @-refs, pasted blocks, session refs) keyed by tab ID.Force remount on tab switch —
<Composer key={activeTabId}>ensures React unmounts the old tab's composer and mounts a fresh instance for the new tab.Save on unmount, restore on mount — A
useEffectcleanup saves the fullComposerDraftto per-tab state when the component unmounts.useStateinitial values read from thedraftprop on mount. A reference-equality guard skips the save when nothing changed, avoiding wasted re-renders.Design decisions
key={activeTabId}instead ofuseEffectontabIdComposerDraftaggregate typesetDraftis one dispatch call per unmount.cur.text === draft.text && cur.attachments === draft.attachments …— since every state is initialised fromdraft.*, untouched arrays share the same reference. Skipping the save avoidssetDraft → bump() → re-render.composerHeightalready has its own layout-preference persistence.setDraftpassed directly (not inline arrow)useCallback-wrapped → stable reference → no effect re-trigger loop.Files changed
desktop/frontend/src/lib/types.tsAttachment,WorkspaceReference,PastedBlock,ComposerDrafttypesdesktop/frontend/src/lib/useController.tsdraftfield,set_draftaction,setDraft()export,resetguarddesktop/frontend/src/components/Composer.tsxtypes.ts, restore/save full draft, skip-noop guarddesktop/frontend/src/App.tsxkey={activeTabId},draft={state.draft},onDraftChange={setDraft}Verification
tsc --noEmit)