[FF] `duo_panel_history_stack_persistence` — AI panel history stack sessionStorage persistence
## Summary
This issue is to roll out the [AI panel history stack persistence feature](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/238549) on production, which is currently behind the `duo_panel_history_stack_persistence` feature flag.
When enabled, the AI panel persists its full Vue Router (abstract mode) history stack to `sessionStorage` on every navigation, and replays it on panel init. This makes the in-panel "Go back" button continue to work after a full page reload — without the flag, the panel's navigation history lives entirely in memory and is wiped on refresh.
## Owners
- Most appropriate Slack channel to reach out to: `#g_agent_foundations`
- Best individual to reach out to: @afontaine
## Expectations
### What are we expecting to happen?
Users who open the AI panel, navigate to a session detail page, and hard-refresh the browser will see the "Go back" button stay visible and functional. Clicking it returns them to the prior in-panel route (e.g. the sessions list).
No behavioural change for users who never close/refresh the panel mid-session, or who keep navigation purely within one tab without using back.
### What can go wrong and how would we detect it?
- **Replay-failure stranding**: if a persisted route name no longer exists (e.g. a route was removed in a later release while a stale stack lives in someone's sessionStorage), `safeRouterPush` will throw during replay. The flag-on branch catches this, clears the corrupt stack, and pushes a mode-aware default route — so worst case is "user lands on the chat or closed route" rather than a broken panel.
- **Storage growth**: stack capped at 200 entries (~16 KB) with truncate-from-oldest semantics; well below sessionStorage's 5 MB limit.
- **`gon.features` exposure regression**: the flag is pushed through `ee/lib/ee/gitlab/gon_helper.rb`. If that push is dropped in a refactor, the FE check `window.gon?.features?.duoPanelHistoryStackPersistence` returns `undefined` and the feature silently disables — matches current behaviour exactly, so safe degrade.
Detection: front-end error monitoring should catch any uncaught replay exceptions (none are expected since the catch is comprehensive). Manual smoke test on staging covers the happy path.
## Rollout Steps
### Rollout on non-production environments
- [ ] Verify the MR is merged and deployed: `/chatops gitlab run auto_deploy status <merge-commit>`
- [ ] Enable globally on non-production: `/chatops gitlab run feature set duo_panel_history_stack_persistence true --dev --pre --staging --staging-ref`
- [ ] Smoke test on `staging-canary`: open AI panel → navigate Chat → Sessions → session detail → hard reload → verify Go back button works.
### Specific rollout on production
- [ ] Enable for GitLab team members first: `/chatops gitlab run feature set --feature-group=gitlab_team_members duo_panel_history_stack_persistence true`
- [ ] Monitor for one business day.
### Global rollout on production
- [ ] 5%: `/chatops gitlab run feature set duo_panel_history_stack_persistence 5 --actors`
- [ ] 25%: `/chatops gitlab run feature set duo_panel_history_stack_persistence 25 --actors`
- [ ] 50%: `/chatops gitlab run feature set duo_panel_history_stack_persistence 50 --actors`
- [ ] 100%: `/chatops gitlab run feature set duo_panel_history_stack_persistence 100 --actors`
- [ ] Globally enable: `/chatops gitlab run feature set duo_panel_history_stack_persistence true`
### Cleanup
- [ ] After one milestone at 100%, file a follow-up MR to remove the feature flag and the legacy single-route fallback in `restoreLastRoute`. The `routeState?.name === defaultRoute` short-circuit in the legacy branch is dead code once the flag is removed.
- [ ] At that point, also revisit the [blocked-state hoist regression](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/238549#note_) — the hoist made the disabled-AND-previously-closed case force-open to the blocked route; consider adding `&& routeState?.name !== defaultRoute` to the gate.
issue