Move duoChatGlobalState out of the sidebar state module

Why

duoChatGlobalState is a shared reactive object created with the observable helper from ~/lib/utils/observable. It holds Duo Chat panel state: chat mode, focus flag, last route per tab, and drag width.

It currently lives in app/assets/javascripts/super_sidebar/state.js, which is listed on INFECTION_BLOCKLIST in config/helpers/context_aliases_shared.js. During the Vue 3 migration, most modules that reach Vue are built twice, once per Vue runtime. A blocklisted module is the exception: the build keeps one single copy.

The observable helper keeps one canonical value in a globalThis registry and builds one reactive mirror per Vue runtime that loads the module. If the module is never duplicated, only one mirror is ever built, tied to whichever Vue version loaded it first.

Today every consumer of duoChatGlobalState runs on Vue 2, so this is safe. It stops being safe when the Duo panel renders on Vue 3 (#623999). The Vue 3 panel would read the Vue 2 mirror. Vue 2 tracks dependencies with Object.defineProperty getters, and a Vue 3 render effect reading that object does not register with them. The panel would render once and never update.

What

Move duoChatGlobalState to a new file ee/app/assets/javascripts/ai/state.js and repoint every import. Move CHAT_MODES, the enum for its chatMode field, into the same file. No change to the values, the observable key 'duo_chat_global_state', or any consumer logic.

How

The new module is not on the blocklist, so the build duplicates it like any other module. Each Vue runtime builds its own mirror, and observable syncs them through the shared registry, which is what it is designed to do.

CHAT_MODES lived in app/assets/javascripts/super_sidebar/constants.js, but no sidebar code read it. Every consumer is under ee/app/assets/javascripts/ai/, and ee/ai/tanuki_bot/constants.js re-exported it, so one value had two import paths. It now lives beside the state it describes, the re-export is gone, and every importer uses ee/ai/state.

Reviewer note: the infection scanner reports both modules as infected: true. The difference is only that super_sidebar/state.js has a blocklist entry that overrides this at build time. That asymmetry is what the fix relies on.

How to verify

This is a pure refactor, our test suite would cover these.

Note: duo_agentic_chat_state_manager_spec.js needs ee/frontend_islands/apps/duo_next/dist built locally, or Jest cannot resolve a virtual mock and the suite fails to run. This is unrelated to the change.

Not covered by this MR

Jest pins one VUE_VERSION per process (jest.config.base.js), so the "syncs across multiple mirrors" case in spec/frontend/lib/utils/observable_spec.js cannot exercise the cross-version path. Only a feature spec that renders a Vue 3 page next to the Vue 2 panel, or the reverse, can confirm cross-runtime updates. That check must happen before the Duo panel Vue 3 feature flag is enabled. It does not block this MR.

Note: Tests will have to be covered before we merge !252443 (merged)

References

  • Part of #623999 (tracking issue for moving global bundles, including the Duo panel, to Vue 3)
  • Builds on !252293 (merged) (merged; gives the Duo panel its own bundle, which is what makes it renderable on Vue 3)
  • Related, not a dependency: !252286 (merged) (open; extends vue3_migration.yml to global bundles, needed by the follow-up MR that declares the Duo panel rollout)
  • Parent epic &6252
  • Prompted the CHAT_MODES move: !252442 (comment 3820664807)
Edited by Miguel Rincon

Merge request reports

Loading
Loading