Skip to content

feat: masquerade bar#260

Merged
arbrandes merged 1 commit intoopenedx:mainfrom
arbrandes:arbrandes/masquerade-bar
May 6, 2026
Merged

feat: masquerade bar#260
arbrandes merged 1 commit intoopenedx:mainfrom
arbrandes:arbrandes/masquerade-bar

Conversation

@arbrandes
Copy link
Copy Markdown
Contributor

@arbrandes arbrandes commented May 5, 2026

Description

Introduces a course bar at the top of the course view that combines two role-aware widgets in a single header strip: the course-tabs navigation (which previously lived in its own directory) and a new masquerade widget that lets course staff view the course as a different role (Staff, a group like Audit) or as a specific learner.

Apps declare which routes get the navigation tabs by listing roles under providesCourseBarRolesId. To additionally enable the masquerade widget on those routes, they list the same roles under providesCourseBarMasqueradeRolesId. Masquerade is a refinement of the course bar: a role declared only under the masquerade key (without a matching course-bar declaration) is ignored.

When a masquerade selection causes the user's current page to no longer be visible, the bar redirects them. The course-tabs metadata is the source of truth: after a successful selection it fetches the post-masquerade tab list, and if the current path is no longer represented, navigates to the first remaining tab (in-app via react-router when possible, hard-redirect otherwise).

Testing

The easiest way to test this is to prepare this PR, openedx/frontend-app-instructor-dashboard#198, and openedx/frontend-template-site#18, then to set frontend-base and frontend-app-instructor-dashboard as workspace packages in frontend-site (like one normally would). After the usual `npm i; npm run dev:packages` with a properly up-to-date tutor@main deployment, it should be possible to compare the Instructor Dashboard's masquerade bar (at, for example, http://apps.local.openedx.io:8080/instructor-dashboard/course-v1:OpenedX+DemoX+DemoCourse/course_info) with the existing one in Learning:

Screencast.From.2026-05-05.18-23-20.mp4

LLM usage notice

Built with assistance from Claude.

Comment thread shell/header/Header.tsx
<Slot id="org.openedx.frontend.slot.header.mobile.v1" />
</nav>
</header>
<Slot id="org.openedx.frontend.slot.header.masqueradeBar.v1" />
Copy link
Copy Markdown
Contributor Author

@arbrandes arbrandes May 6, 2026

Choose a reason for hiding this comment

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

This is a course masquerade bar, innit? Like its sibling, the course navigation bar. Maybe we should rename it. Probably. Almost sure of it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This also makes me think: the right home for these two is probably-almost-100%-sure not here, but in frontend-app-learning... once that gets converted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I ran into this from the other side. I noticed that the bar requires a courseId but doesn't fully guard against one not being there.

My question was "does this need to be course-specific?" I could imagine wanting to masquerade for learner dash or something (not something the backend currently supports, but I figure from a frontend perspective it could be the same widget)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I considered the Learner Dash angle when I first reviewed the original PR. It turns out they're very different beasts: not worth unifying. In other words, this is really-really just a course masquerade bar. Not least of which because it requires a courseId.

@arbrandes
Copy link
Copy Markdown
Contributor Author

Ok @brian-smith-tcril, I'm reasonably happy with this. Once frontend-app-learning is converted, this should just drop in - with no page reloads.

Copy link
Copy Markdown
Contributor

@brian-smith-tcril brian-smith-tcril left a comment

Choose a reason for hiding this comment

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

Gave this a first pass and overall it's looking good!

Comment thread runtime/config/index.ts
Comment thread shell/header/app.tsx Outdated
Comment thread shell/header/masquerade-bar/MasqueradeBar.tsx Outdated
Comment thread shell/header/masquerade-bar/StudioLink.tsx Outdated
Comment thread shell/header/masquerade-bar/data/api.ts Outdated
Comment thread shell/header/masquerade-bar/data/api.ts Outdated
Comment thread shell/header/course-bar/masquerade/MasqueradeBar.tsx
Comment thread shell/header/course-bar/masquerade/hooks.ts
Comment thread shell/header/course-bar/masquerade/masquerade-widget/index.ts
Comment thread shell/header/masquerade-bar/MasqueradeBar.tsx Outdated
@arbrandes arbrandes force-pushed the arbrandes/masquerade-bar branch from cea4e15 to 85f3de3 Compare May 6, 2026 18:26
Copy link
Copy Markdown
Contributor

@brian-smith-tcril brian-smith-tcril left a comment

Choose a reason for hiding this comment

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

LGTM!

Left a couple comments in there, but nothing that feels like it should block this from landing.

Comment thread shell/header/course-bar/data/service.ts
Comment thread shell/header/course-bar/masquerade/hooks.ts Outdated
}
const targetPath = new URL(target.url).pathname;
if (isClientRoute(targetPath)) {
if (targetPath !== location.pathname) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will this ever be false? Wouldn't findActiveTab catch the match and early return in this scenario?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Dead code. Removed.

@arbrandes arbrandes force-pushed the arbrandes/masquerade-bar branch from 85f3de3 to 059e3a9 Compare May 6, 2026 20:19
Adds a header strip at the top of the course view that combines two
role-aware widgets: the course-tabs navigation (already in tree under
its own opt-in) and a new masquerade bar.  Both live under a unified
shell/header/course-bar/{navigation,masquerade}/ tree and share a
course_home metadata fetch.

Apps declare course-bar membership under providesCourseBarRolesId
(navigation tabs) and additionally enable the masquerade widget on
those routes by listing the same role under
providesCourseBarMasqueradeRolesId.  Masquerade is a refinement of the
course bar, not an independent feature: a role only present in the
masquerade list is ignored.

Course staff can use the masquerade widget to view a course as a
different role (Staff, a group like Audit) or as a specific learner.
When a selection causes the user's current page to no longer be
visible, the bar redirects them: the post-masquerade course-tabs list
is the source of truth, and if the current path is no longer in it
the user is sent to the first remaining tab (in-app via react-router
when possible, hard-redirect otherwise).

Co-Authored-By: Diana Villalvazo <diana.villalvazo@wgu.edu>
Co-Authored-By: Jesus Balderrama <jesus.balderrama.wgu@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@arbrandes arbrandes force-pushed the arbrandes/masquerade-bar branch from 059e3a9 to 222ce55 Compare May 6, 2026 20:22
@arbrandes arbrandes merged commit f920e64 into openedx:main May 6, 2026
1 of 5 checks passed
@arbrandes arbrandes deleted the arbrandes/masquerade-bar branch May 6, 2026 20:24
@openedx-semantic-release-bot
Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0-alpha.45 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants