Skip to content

Fix object types not being shared across tabs#55

Merged
wobsoriano merged 7 commits into
mainfrom
copilot/fix-shared-state-issue
Mar 24, 2026
Merged

Fix object types not being shared across tabs#55
wobsoriano merged 7 commits into
mainfrom
copilot/fix-shared-state-issue

Conversation

Copilot AI commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

When a store contains ref-wrapped objects (e.g., renderSettings = ref({ fps: 60 })), cross-tab updates via $patch were replacing the reactive object entirely instead of merging into it — breaking any component that held a direct reference to the old reactive object.

Root cause

The function form of $patch sets state[key] = newValue through Vue's reactive proxy, which triggers ref.value = newObject, creating a new reactive object. Components that captured the previous reactive reference (e.g. via storeToRefs or const settings = store.renderSettings) would hold a stale reference with no further updates.

Changes

  • src/index.ts: Replace $patch(fn) with $patch(plainObject) built via reduce over keysToUpdate
  • src/vanilla.ts: Replace store[key] = evt.newValue direct assignment with store.$patch({ [key]: evt.newValue })
  • src/__tests__/plugin.test.ts: Added 14 vitest unit tests for the PiniaSharedState plugin
  • src/__tests__/vanilla.test.ts: Added 9 vitest unit tests for the share() vanilla function

When $patch receives a plain object, Pinia internally calls mergeReactiveObjects(), which mutates existing reactive objects in-place rather than replacing them — preserving reactive references throughout.

// Before: replaces the reactive object reference
store.$patch((state) => {
  keysToUpdate.forEach((key) => {
    state[key] = newState.state[key]  // ref.value = newObject ← breaks captured refs
  })
})

// After: Pinia's mergeReactiveObjects mutates in-place
store.$patch(
  keysToUpdate.reduce((acc, key) => {
    acc[key] = newState.state[key]
    return acc
  }, {} as Partial<typeof store.$state>),
)

Testing

Unit tests cover:

  • Primitive and object state sharing between tabs
  • Reactive reference preservation — after a cross-tab update, store.settings === originalSettings (same object reference, mutated in-place)
  • Partial merge without clobbering untouched keys
  • omit, enable, and initialize options
  • Two-tab simulation using two independent pinia instances
  • sync() / unshare() lifecycle for the vanilla share() function
  • Custom serializer support

broadcast-channel is mocked in-memory via vi.hoisted + vi.mock, so all tests run without browser APIs.

Original prompt

This section details on the original issue you should resolve

<issue_title>Object types cannot be shared</issue_title>
<issue_description>I tried to use pinia+PiniaSharedState as the main medium for communication between pages, and wrote the following code

import { defineStore } from 'pinia';

export const useSettingStore = defineStore(
""  "setting",""
  () => {
    const isAlwaysOnTop = ref(false);
    const renderSettings = ref({"
      fps: 60
    });
    const fps = ref(60);

    return {
      isAlwaysOnTop,
      renderSettings,
      fps
    };
  },
  {
    persist: true,
    share: {
      enable: true
    },
  }
);

fps can be shared, while renderSettings.fps cannot be shared. Is this by design? I think it should be a bug</issue_description>

Comments on the Issue (you are @copilot in this section)


⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.

@weiShaoY

Copy link
Copy Markdown

貌似项目没维护了

Copilot AI and others added 2 commits March 24, 2026 16:44
Co-authored-by: wobsoriano <13049130+wobsoriano@users.noreply.github.com>
Agent-Logs-Url: https://github.com/wobsoriano/pinia-shared-state/sessions/101ffac4-35cc-4a2a-8149-ef8c9bb7fb56
Copilot AI changed the title [WIP] Fix shared state issue with renderSettings in Pinia store Fix object types not being shared across tabs Mar 24, 2026
Copilot AI requested a review from wobsoriano March 24, 2026 16:51

@wobsoriano wobsoriano left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot use pnpm instead of npm

Co-authored-by: wobsoriano <13049130+wobsoriano@users.noreply.github.com>
Agent-Logs-Url: https://github.com/wobsoriano/pinia-shared-state/sessions/e85ff73b-eb5f-413d-a465-d0d27a1597c7

Copilot AI commented Mar 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot use pnpm instead of npm

Switched to pnpm and removed the accidentally committed package-lock.json in df0050d.

Copilot AI requested a review from wobsoriano March 24, 2026 16:57
@wobsoriano

Copy link
Copy Markdown
Owner

@copilot add a changeset

Co-authored-by: wobsoriano <13049130+wobsoriano@users.noreply.github.com>
Agent-Logs-Url: https://github.com/wobsoriano/pinia-shared-state/sessions/385d369c-d7c6-4c9b-9c64-41a9c50b8bb9

Copilot AI commented Mar 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot add a changeset

Added a patch changeset in bff637f.

@wobsoriano wobsoriano marked this pull request as ready for review March 24, 2026 17:35
@wobsoriano

Copy link
Copy Markdown
Owner

@copilot can we add unit tests for this library? Use vitest

Copilot AI commented Mar 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot can we add unit tests for this library? Use vitest

Added vitest unit tests in 5679070. There are 23 tests across two files:

  • src/__tests__/plugin.test.ts (14 tests) — covers the PiniaSharedState plugin: primitive and object state sharing, the reactive-reference preservation fix, omit/enable/initialize options, two-tab simulation, and custom serializer
  • src/__tests__/vanilla.test.ts (9 tests) — covers the share() function: primitive/object key sharing, timestamp deduplication, sync(), unshare(), and custom serializer

broadcast-channel is mocked with vi.hoisted + vi.mock so tests run entirely in-memory with no browser APIs needed.

@wobsoriano

Copy link
Copy Markdown
Owner

@copilot can we add unit tests for this library? Use vitest

Added vitest unit tests in 5679070. There are 23 tests across two files:

  • src/__tests__/plugin.test.ts (14 tests) — covers the PiniaSharedState plugin: primitive and object state sharing, the reactive-reference preservation fix, omit/enable/initialize options, two-tab simulation, and custom serializer
  • src/__tests__/vanilla.test.ts (9 tests) — covers the share() function: primitive/object key sharing, timestamp deduplication, sync(), unshare(), and custom serializer

broadcast-channel is mocked with vi.hoisted + vi.mock so tests run entirely in-memory with no browser APIs needed.

Great! Does it cover the issue that this PR is trying to fix as welll?

Copilot AI commented Mar 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot can we add unit tests for this library? Use vitest

Added vitest unit tests in [5679070](5679070bf8505181e0...

Yes — the "object state sharing (bug fix)" describe block in both test files directly replicates the reported scenario (settings = ref({ fps: 60 }) updated cross-tab):

  • applies nested object updates from another tab — verifies that store.settings.fps becomes 30 after a cross-tab message (the bug: this value would stay 60 before the fix)
  • preserves the reactive object reference when merging updates — verifies store.settings === originalSettings after the update (the root cause: old code replaced the reactive object, breaking captured references)
  • synchronises object state from tab1 to tab2 — end-to-end two-pinia simulation of the same scenario

The same pair of tests exists in vanilla.test.ts for the share() function.

@wobsoriano wobsoriano merged commit 1bb8d31 into main Mar 24, 2026
1 check passed
@wobsoriano

Copy link
Copy Markdown
Owner

貌似项目没维护了

surprise!

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.

Object types cannot be shared

3 participants