Skip to content

fix(ui): sync pin/unpin state across conversation tabs (#12884)#12932

Merged
hieptl merged 4 commits intoOpenHands:mainfrom
bendarte:fix/12884-pin-unpin-views
Mar 3, 2026
Merged

fix(ui): sync pin/unpin state across conversation tabs (#12884)#12932
hieptl merged 4 commits intoOpenHands:mainfrom
bendarte:fix/12884-pin-unpin-views

Conversation

@bendarte
Copy link
Copy Markdown
Contributor

Summary

Fixes tab pin/unpin behavior in the conversation tab menu so changes are reflected immediately in the tab bar.

Fixes #12884

Root cause

ConversationTabs and ConversationTabsContextMenu both use useConversationLocalStorageState, but each component had independent local hook state. Updating unpinnedTabs from the context menu wrote to localStorage but did not trigger a same-tab re-sync in ConversationTabs.

What changed

  • Added a custom browser event: conversation-state-updated.
  • Dispatch this event in setConversationState and clearConversationLocalStorage.
  • Updated useConversationLocalStorageState to listen to:
    • storage for cross-tab updates
    • conversation-state-updated for same-tab updates
  • Added a regression test that verifies unpinning a tab from the context menu hides that tab immediately.

Testing

  • npm run test -- __tests__/components/features/conversation/conversation-tabs.test.tsx __tests__/conversation-local-storage.test.ts
  • Result: 2 test files passed, 22 tests passed.

Copy link
Copy Markdown
Collaborator

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid fix for the pin/unpin synchronization issue. The custom event approach works well and the test coverage is good.

}, [conversationId]);

const updateState = (updates: Partial<ConversationState>) => {
setState((prev) => ({ ...prev, ...updates }));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: This setState call is redundant since setConversationState will immediately dispatch an event that triggers another setState via the event listener (line 137). Consider removing this line to:

  1. Avoid double state updates (both are queued synchronously)
  2. Make localStorage the single source of truth
  3. Prevent potential inconsistencies if the localStorage write fails

The event-driven update alone is sufficient for keeping all components in sync.

@bendarte
Copy link
Copy Markdown
Contributor Author

Addressed review feedback in 893eda4 by removing the redundant local setState in updateState (localStorage/event-driven sync remains the single source of truth).\n\nRe-ran targeted tests:\n- npm run test -- tests/components/features/conversation/conversation-tabs.test.tsx tests/conversation-local-storage.test.ts\n- Result: 2 test files passed, 22 tests passed.

Copy link
Copy Markdown
Collaborator

@hieptl hieptl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @bendarte,

Thank you for creating this pull request. I’ve reviewed and approved it.

Thank you very much for your contribution! 🙏

@hieptl hieptl merged commit a7a4eb2 into OpenHands:main Mar 3, 2026
17 checks passed
hieptl added a commit that referenced this pull request Mar 3, 2026
Co-authored-by: hieptl <hieptl.developer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Pinning and unpinning views does not work

3 participants