Skip to content

Commit 6f1dfbb

Browse files
fix: don't auto-open right sidebar by default (#1923)
* fix: don't auto-open right sidebar by default The navigation (course outline) sidebar should be the default; PR #1713 removed the alwaysOpenAuxiliarySidebar waffle flag guard but kept the auto-open branches in place, effectively hardcoding the flag to true. - useInitialSidebar defaults to COURSE_OUTLINE when nothing is stored, and honors a stored RIGHT panel preference even before async widget data has loaded (avoids a transient flicker that downstream effects would otherwise write back to storage). - Add a session-scoped "sidebar closed by user" flag in sessionStorage, parallel to the existing outline-collapsed flag. toggleSidebar sets it when the resulting state is null, clears it on open. - useResponsiveBehavior and useUnitShiftBehavior respect the flag so a user-closed state survives unit navigation and full-page remounts within the same tab session. Refs: openedx/wg-build-test-release#574 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: drop OUTLINE_SIDEBAR_HIDDEN in favor of SIDEBAR_CLOSED_BY_USER The OUTLINE_SIDEBAR_HIDDEN sessionStorage flag was set only by the course-outline collapse trigger, which already calls toggleSidebar(null) → setSidebarClosedByUser(true). Every reader of isOutlineSidebarCollapsed runs inside a path where closed-by-user is already false (either explicit top-level checks or transitive via useInitialSidebar's null return), so outline-hidden=true was never observably distinct from closed-by-user=true. - Remove the constant, the two storage helpers, and all call sites in useInitialSidebar / useUnitShiftBehavior / useResponsiveBehavior / useSidebarSync / course-outline/hooks.js. - useSidebarSync swaps the flag in-place rather than adding a top-level early-return, so the corrective setCurrentSidebar(null) branches still fire if currentSidebar is somehow stale while closed-by-user is true. - useInitialSidebar: also refine the stored-RIGHT-panel block so that when widget data has loaded and the stored preference is no longer in the registry, we fall back to firstAvailable instead of returning a stored ID nothing can render. - Clarify the isInitiallySidebarOpen JSDoc to spell out the viewport/URL precondition. - Update ARCHITECTURE.md and the course-outline README to reference the unified flag. Refs: openedx/wg-build-test-release#574 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: keep current sidebar open across unit navigation useUnitShiftBehavior was actively switching state on unit nav in ways that contradict the spec in wg-build-test-release#574: - CASE 1 (COURSE_OUTLINE open + RIGHT panel available) was switching to the RIGHT panel, which conflicts with expected behavior #2 ("nav stays open on advance"). The behavior looked correct in the browser only because useSidebarSync immediately reverted the switch via stale-memo cancellation — two unnecessary state updates and storage writes per unit nav. Drop the switching branch; CASE 1 now just marks the ref and returns. - CASE 3 (RIGHT panels available + currentSidebar=null) was auto-opening the first RIGHT panel on a wide viewport, conflicting with expected behavior #3 ("nothing open → advance → still nothing"). The closed-by-user gate above catches the realistic null path, so this branch was state-drift recovery. Recover to COURSE_OUTLINE (the wide-viewport default per the prior commits) instead of opening a RIGHT panel. Refs: openedx/wg-build-test-release#574 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: align sidebar tests with the new behavior Tracks the source changes across the previous commits on this branch: - storage.test.js: drop the removed outline-collapsed describes; keep the parallel isSidebarClosedByUser / setSidebarClosedByUser coverage. - useInitialSidebar.test.js: drop isOutlineSidebarCollapsed mock, restructure with a "when isInitiallySidebarOpen is true and the sidebar has not been closed by the user" wrapper around the storage-state describes. Add the anti-flicker case (stored RIGHT panel before widget data has loaded). - useUnitShiftBehavior.test.js: drop the isOutlineSidebarCollapsed mock, swap in isSidebarClosedByUser, add the closed-by-user no-op, restructure the CASE-block describes under a single precondition wrapper, and update CASE 1 and CASE 3 assertions to match the new "keep current sidebar open" / "recover to COURSE_OUTLINE" behavior. - useResponsiveBehavior.test.js, useSidebarSync.test.js: rename in-place from the outline-collapsed flag to closed-by-user; add closed-by-user no-op coverage. - SidebarContextProvider.test.jsx: drop the outline-collapsed mocks, add setSidebarClosedByUser assertions to the UC7* toggleSidebar tests; simplify UC7a to just assert the initial sidebar is non-null on desktop. - Course.test.jsx: replace the two pre-fix tests (which asserted the buggy auto-open of discussions) with a `describe('sidebar behavior')` block covering: default outline, closed-by-user persistence, stored-discussions / stored-outline on render, and the four click-to-toggle transitions (close outline, open outline closing discussions, open discussions closing outline, close discussions). - SidebarUnit.test.jsx: drop the two stale sessionStorage assertions; the toggleSidebar call assertions above each one carry the meaningful coverage. - test-utils.jsx: have setupDiscussionSidebar also return its testStore so tests can reuse it (avoids a second initializeTestStore that resets the discussion topics axios mock). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: open COURSE_OUTLINE on resize-to-wide instead of a RIGHT panel useResponsiveBehavior auto-opened the first available RIGHT panel when the viewport reached the wide breakpoint with no sidebar open, falling back to COURSE_OUTLINE only when no RIGHT panels were available. That contradicts the wide-viewport default in this PR (#1 in wg-build-test-release#574: "nav opens by default") — and is the same kind of bug as the previous unit-shift CASE 3 else-if. Always open COURSE_OUTLINE in that branch. getFirstAvailablePanel is no longer needed in this hook; drop the prop from SidebarContextProvider too. Refs: openedx/wg-build-test-release#574 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: useResponsiveBehavior opens COURSE_OUTLINE on resize-to-wide Track the source change: drop the getFirstAvailablePanel param from buildParams, replace the two "opens X" tests (first-available vs COURSE_OUTLINE-fallback) with a single "opens COURSE_OUTLINE on desktop when no sidebar is open" assertion. The fallback-with-no-RIGHT-panels case collapses into the same code path under the simplified source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c9cf65b commit 6f1dfbb

19 files changed

Lines changed: 462 additions & 385 deletions

src/courseware/course/Course.test.jsx

Lines changed: 120 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import React from 'react';
33
import { Factory } from 'rosie';
44

55
import { breakpoints } from '@openedx/paragon';
6+
import userEvent from '@testing-library/user-event';
67

78
import {
8-
fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
9+
cleanup, fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
910
} from '../../setupTest';
1011
import * as celebrationUtils from './celebration/utils';
1112
import { handleNextSectionCelebration } from './celebration';
@@ -156,46 +157,136 @@ describe('Course', () => {
156157
});
157158
});
158159

159-
it('handles click to open/close discussions sidebar', async () => {
160-
await setupDiscussionSidebar();
160+
describe('sidebar behavior', () => {
161+
let testStore;
162+
let testData;
163+
164+
beforeEach(async () => {
165+
// setupDiscussionSidebar configures the discussion topics mock + loads
166+
// topics into a testStore. The render it does is incidental — clean it up
167+
// so we can render fresh against per-test seeded storage.
168+
const { testStore: setupStore } = await setupDiscussionSidebar();
169+
cleanup();
170+
testStore = setupStore;
171+
const { courseware, models } = testStore.getState();
172+
testData = {
173+
...mockData,
174+
courseId: courseware.courseId,
175+
sequenceId: courseware.sequenceId,
176+
unitId: Object.values(models.units)[0].id,
177+
};
178+
global.innerWidth = breakpoints.extraExtraLarge.minWidth;
179+
window.localStorage.clear();
180+
window.sessionStorage.clear();
181+
});
161182

162-
waitFor(() => {
163-
expect(screen.getByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
164-
expect(screen.getByTestId('sidebar-DISCUSSIONS')).not.toHaveClass('d-none');
183+
it('opens the course outline on render when no preference is stored and the user has not closed the sidebar', async () => {
184+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
185+
loadUnit();
186+
187+
await waitFor(() => {
188+
expect(document.querySelector('section.outline-sidebar')).toBeInTheDocument();
189+
});
190+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).not.toBeInTheDocument();
191+
});
192+
193+
it('keeps the sidebar closed on render when the user previously closed it', async () => {
194+
window.sessionStorage.setItem('sidebarClosedByUser', 'true');
195+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
196+
loadUnit();
165197

166-
const discussionsTrigger = screen.getByRole('button', { name: /Show discussions tray/i });
167-
expect(discussionsTrigger).toBeInTheDocument();
168-
fireEvent.click(discussionsTrigger);
198+
await waitFor(() => {
199+
// Discussions prefetch resolves; assert nothing has opened in response.
200+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).not.toBeInTheDocument();
201+
});
202+
expect(document.querySelector('section.outline-sidebar')).not.toBeInTheDocument();
203+
});
169204

205+
it('opens discussions on render when it is the stored preference', async () => {
206+
window.localStorage.setItem(`sidebar.${testData.courseId}`, JSON.stringify('DISCUSSIONS'));
207+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
208+
loadUnit();
209+
210+
await waitFor(() => {
211+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
212+
});
213+
expect(document.querySelector('section.outline-sidebar')).not.toBeInTheDocument();
214+
});
215+
216+
it('opens the course outline on render when it is the stored preference', async () => {
217+
window.localStorage.setItem(`sidebar.${testData.courseId}`, JSON.stringify('COURSE_OUTLINE'));
218+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
219+
loadUnit();
220+
221+
await waitFor(() => {
222+
expect(document.querySelector('section.outline-sidebar')).toBeInTheDocument();
223+
});
170224
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).not.toBeInTheDocument();
225+
});
171226

172-
fireEvent.click(discussionsTrigger);
227+
it('closes the course outline when the user clicks its trigger', async () => {
228+
const user = userEvent.setup();
229+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
230+
loadUnit();
231+
await waitFor(() => {
232+
expect(document.querySelector('section.outline-sidebar')).toBeInTheDocument();
233+
});
234+
235+
await user.click(screen.getByRole('button', { name: /Toggle course outline tray/i }));
173236

174-
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
237+
await waitFor(() => {
238+
expect(document.querySelector('section.outline-sidebar')).not.toBeInTheDocument();
239+
});
175240
});
176-
});
177241

178-
it('displays discussions sidebar when unit changes', async () => {
179-
const testStore = await initializeTestStore();
180-
const { courseware, models } = testStore.getState();
181-
const { courseId, sequenceId } = courseware;
182-
const testData = {
183-
...mockData,
184-
courseId,
185-
sequenceId,
186-
unitId: Object.values(models.units)[0].id,
187-
};
242+
it('opens the course outline when the user clicks its trigger, closing discussions if open', async () => {
243+
const user = userEvent.setup();
244+
window.localStorage.setItem(`sidebar.${testData.courseId}`, JSON.stringify('DISCUSSIONS'));
245+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
246+
loadUnit();
247+
await waitFor(() => {
248+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
249+
});
188250

189-
await setupDiscussionSidebar();
251+
await user.click(screen.getByRole('button', { name: /Toggle course outline tray/i }));
190252

191-
const { rerender } = render(<Course {...testData} />, { store: testStore });
192-
loadUnit();
253+
await waitFor(() => {
254+
expect(document.querySelector('section.outline-sidebar')).toBeInTheDocument();
255+
});
256+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).not.toBeInTheDocument();
257+
});
258+
259+
it('opens discussions when the user clicks its trigger, closing the outline', async () => {
260+
const user = userEvent.setup();
261+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
262+
loadUnit();
263+
await waitFor(() => {
264+
expect(document.querySelector('section.outline-sidebar')).toBeInTheDocument();
265+
});
266+
267+
await user.click(screen.getByRole('button', { name: /Show discussions tray/i }));
193268

194-
const sidebar = await screen.findByTestId('sidebar-DISCUSSIONS');
195-
expect(sidebar).toBeInTheDocument();
196-
expect(sidebar).not.toHaveClass('d-none');
269+
await waitFor(() => {
270+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
271+
});
272+
expect(document.querySelector('section.outline-sidebar')).not.toBeInTheDocument();
273+
});
197274

198-
rerender(null);
275+
it('closes discussions when the user clicks its trigger', async () => {
276+
const user = userEvent.setup();
277+
window.localStorage.setItem(`sidebar.${testData.courseId}`, JSON.stringify('DISCUSSIONS'));
278+
render(<Course {...testData} />, { store: testStore, wrapWithRouter: true });
279+
loadUnit();
280+
await waitFor(() => {
281+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
282+
});
283+
284+
await user.click(screen.getByRole('button', { name: /Show discussions tray/i }));
285+
286+
await waitFor(() => {
287+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).not.toBeInTheDocument();
288+
});
289+
});
199290
});
200291

201292
it('doesn\'t renders course breadcrumbs by default', async () => {

src/courseware/course/sidebar/ARCHITECTURE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ _Example with built-in widgets:_
146146
- Used on mobile to restore state
147147
- Used on desktop to restore user preference if still available
148148

149-
**sessionStorage (`hideCourseOutlineSidebar`):**
150-
- Tracks if user manually collapsed Course Outline
151-
- Prevents auto-opening when user explicitly hid it
149+
**sessionStorage (`sidebarClosedByUser`):**
150+
- Tracks whether the user has explicitly closed the sidebar this session
151+
- Prevents auto-opening (any panel) until the user opens one via a trigger
152152

153153
## Implementation Details
154154

src/courseware/course/sidebar/SidebarContextProvider.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from './defaultWidgets';
1717
import {
1818
setSidebarId,
19+
setSidebarClosedByUser,
1920
} from './utils/storage';
2021
import {
2122
useInitialSidebar,
@@ -130,7 +131,6 @@ const SidebarProvider = ({
130131
shouldDisplaySidebarOpen,
131132
currentSidebar,
132133
setCurrentSidebar,
133-
getFirstAvailablePanel,
134134
courseId,
135135
hasUserToggledRef,
136136
});
@@ -148,6 +148,7 @@ const SidebarProvider = ({
148148
const newSidebar = sidebarId === currentSidebar ? null : sidebarId;
149149
setCurrentSidebar(newSidebar);
150150
setSidebarId(courseId, newSidebar);
151+
setSidebarClosedByUser(newSidebar === null);
151152
}, [currentSidebar, courseId]);
152153

153154
const contextValue = useMemo(() => ({

src/courseware/course/sidebar/SidebarContextProvider.test.jsx

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ jest.mock('./defaultWidgets', () => ({
5454
jest.mock('./utils/storage', () => ({
5555
setSidebarId: jest.fn(),
5656
getSidebarId: jest.fn(() => null),
57-
isOutlineSidebarCollapsed: jest.fn(() => false),
58-
setOutlineSidebarCollapsed: jest.fn(),
57+
isSidebarClosedByUser: jest.fn(() => false),
58+
setSidebarClosedByUser: jest.fn(),
5959
}));
6060

6161
const courseId = 'course-test-123';
@@ -104,7 +104,7 @@ describe('SidebarContextProvider', () => {
104104

105105
const storage = jest.requireMock('./utils/storage');
106106
storage.getSidebarId.mockReturnValue(null);
107-
storage.isOutlineSidebarCollapsed.mockReturnValue(false);
107+
storage.isSidebarClosedByUser.mockReturnValue(false);
108108
});
109109

110110
describe('context values provided', () => {
@@ -149,21 +149,11 @@ describe('SidebarContextProvider', () => {
149149
});
150150

151151
describe('Use Case 7: Manual toggle interactions', () => {
152-
it('UC7a: opens a sidebar panel when none is open', async () => {
153-
// Use desktop width so the highest-priority available panel auto-opens by default
152+
it('UC7a: opens a sidebar panel when none is open', () => {
154153
jest.requireMock('@openedx/paragon').useWindowSize.mockReturnValue({ width: 1400 });
155154
renderProvider();
156155

157-
expect(screen.getByTestId('current-sidebar').textContent).toBe('DISCUSSIONS');
158-
// Toggle off, then toggle a different panel on
159-
await act(async () => {
160-
fireEvent.click(screen.getByTestId('toggle-discussions'));
161-
});
162-
expect(screen.getByTestId('current-sidebar').textContent).toBe('null');
163-
await act(async () => {
164-
fireEvent.click(screen.getByTestId('toggle-notes'));
165-
});
166-
expect(screen.getByTestId('current-sidebar').textContent).toBe('NOTES');
156+
expect(screen.getByTestId('current-sidebar').textContent).not.toBe('null');
167157
});
168158

169159
it('UC7b: closes the currently open panel when the same trigger is clicked', async () => {
@@ -202,18 +192,19 @@ describe('SidebarContextProvider', () => {
202192
});
203193

204194
it('UC7d: persists the new sidebar ID to localStorage on toggle', async () => {
205-
const { setSidebarId } = jest.requireMock('./utils/storage');
195+
const { setSidebarId, setSidebarClosedByUser } = jest.requireMock('./utils/storage');
206196
renderProvider();
207197

208198
// Mobile starts closed, toggle to NOTES
209199
await act(async () => {
210200
fireEvent.click(screen.getByTestId('toggle-notes'));
211201
});
212202
expect(setSidebarId).toHaveBeenCalledWith(courseId, 'NOTES');
203+
expect(setSidebarClosedByUser).toHaveBeenLastCalledWith(false);
213204
});
214205

215-
it('UC7e: persists null to localStorage when closing the open panel', async () => {
216-
const { setSidebarId } = jest.requireMock('./utils/storage');
206+
it('UC7e: persists null to localStorage and marks the sidebar closed when closing the open panel', async () => {
207+
const { setSidebarId, setSidebarClosedByUser } = jest.requireMock('./utils/storage');
217208
renderProvider();
218209

219210
// Open DISCUSSIONS first
@@ -225,6 +216,7 @@ describe('SidebarContextProvider', () => {
225216
fireEvent.click(screen.getByTestId('toggle-discussions'));
226217
});
227218
expect(setSidebarId).toHaveBeenLastCalledWith(courseId, null);
219+
expect(setSidebarClosedByUser).toHaveBeenLastCalledWith(true);
228220
});
229221
});
230222

src/courseware/course/sidebar/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const WIDGET_PRIORITIES = {
55

66
export const STORAGE_KEYS = {
77
SIDEBAR_ID: 'sidebar',
8-
OUTLINE_SIDEBAR_HIDDEN: 'hideCourseOutlineSidebar',
8+
SIDEBAR_CLOSED_BY_USER: 'sidebarClosedByUser',
99
};
1010

1111
export const WIDGET_CONFIG = {

src/courseware/course/sidebar/hooks/useInitialSidebar.js

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
22
import { WIDGETS } from '@src/constants';
33
import {
44
getSidebarId,
5-
isOutlineSidebarCollapsed,
5+
isSidebarClosedByUser,
66
} from '../utils/storage';
77

88
/**
@@ -15,7 +15,9 @@ import {
1515
* @param {Object} params
1616
* @param {string} params.courseId - Current course ID
1717
* @param {boolean} params.shouldDisplayFullScreen - Whether in mobile view
18-
* @param {boolean} params.isInitiallySidebarOpen - Whether sidebar should be open
18+
* @param {boolean} params.isInitiallySidebarOpen - Whether the viewport / URL
19+
* permits auto-opening the sidebar on initial render. False on viewports
20+
* below the extra-large breakpoint unless the URL has `?sidebar=true`.
1921
* @param {Function} params.getFirstAvailablePanel - Get first available widget
2022
* @param {Function} params.getAvailableWidgets - Get all available widgets
2123
* @returns {string|null} Initial sidebar ID
@@ -28,51 +30,45 @@ export function useInitialSidebar({
2830
getAvailableWidgets,
2931
}) {
3032
return useMemo(() => {
31-
// Check if course outline is manually collapsed
32-
const isCollapsedOutline = isOutlineSidebarCollapsed();
33-
3433
// MOBILE: Use stored value or null (no auto-open)
3534
if (shouldDisplayFullScreen) {
36-
return getSidebarId(courseId);
35+
return isSidebarClosedByUser() ? null : getSidebarId(courseId);
3736
}
3837

3938
// DESKTOP: Auto-open if screen size allows
4039
if (!isInitiallySidebarOpen) {
4140
return null;
4241
}
4342

44-
const firstAvailable = getFirstAvailablePanel();
45-
46-
// If NO RIGHT panels available, return COURSE_OUTLINE (ignore localStorage)
47-
if (!firstAvailable) {
48-
return isCollapsedOutline ? null : WIDGETS.COURSE_OUTLINE;
43+
// User explicitly closed the sidebar this session — respect that
44+
if (isSidebarClosedByUser()) {
45+
return null;
4946
}
5047

51-
// RIGHT panels ARE available - check stored preference
5248
const storedSidebar = getSidebarId(courseId);
53-
if (storedSidebar) {
54-
// Check if stored is a RIGHT panel that's still available
55-
const availableWidgets = getAvailableWidgets();
56-
const storedWidget = availableWidgets.find(w => w.id === storedSidebar);
57-
const firstAvailableWidget = availableWidgets.find(w => w.id === firstAvailable);
5849

59-
if (storedWidget && storedWidget.id !== WIDGETS.COURSE_OUTLINE) {
60-
// Check priority: if a higher priority panel is now available, use it instead
50+
// Honor a stored RIGHT panel preference even before widget data has loaded.
51+
// Keeps initialSidebar stable across the async load window so downstream
52+
// effects don't briefly see COURSE_OUTLINE and overwrite storage.
53+
if (storedSidebar && storedSidebar !== WIDGETS.COURSE_OUTLINE) {
54+
const firstAvailable = getFirstAvailablePanel();
55+
if (firstAvailable) {
56+
const availableWidgets = getAvailableWidgets();
57+
const storedWidget = availableWidgets.find(w => w.id === storedSidebar);
58+
const firstAvailableWidget = availableWidgets.find(w => w.id === firstAvailable);
59+
if (!storedWidget) {
60+
// Stored widget no longer registered; fall back to first available.
61+
return firstAvailable;
62+
}
6163
if (firstAvailableWidget && firstAvailableWidget.priority < storedWidget.priority) {
62-
// Higher priority panel available (lower number = higher priority)
6364
return firstAvailable;
6465
}
65-
// Stored RIGHT panel is still available and no higher priority - use it (sticky)
66-
return storedSidebar;
67-
}
68-
// If stored was COURSE_OUTLINE and not manually collapsed, prefer firstAvailable
69-
if (storedSidebar === WIDGETS.COURSE_OUTLINE && !isCollapsedOutline) {
70-
return firstAvailable;
7166
}
67+
return storedSidebar;
7268
}
7369

74-
// Priority cascade: Use first available RIGHT panel
75-
return firstAvailable;
70+
// Stored COURSE_OUTLINE or no stored preference: open the navigation sidebar.
71+
return WIDGETS.COURSE_OUTLINE;
7672
}, [
7773
shouldDisplayFullScreen,
7874
isInitiallySidebarOpen,

0 commit comments

Comments
 (0)