openedx / openedx/frontend-app-learning

Source the access-expiration masquerade banner from the tab query, not useModel(tab)

Open
#1,999 0 comments 0 reactions 1 assignee View on GitHub

@brian-smith-tcril is already working on this.

Since Aug 15, 2026.

Dominant language
JavaScript
Stars
70
Forks
335
Avg merge
1d 17h
Merged PRs (30d)
35

Description

Part of the Redux → React Query migration, epic #1946. Related to the model-store dissolution (#1977) — it lands as a piece of that, not as a standalone prerequisite for any single tab. See Sequencing below.

Summary

useAccessExpirationMasqueradeBanner(courseId, tab) — rendered by the shared InstructorToolbar — reads accessExpiration.masqueradingExpiredCourse via useModel(tab, courseId) to show the "this learner's access expired" masquerade banner. It's the only dynamic useModel(<var>) reader that resolves to a course-home tab's data model (a literal useModel('progress' grep misses it). Because it reads the tab model store, any converted tab has to keep its data on the transitional bridge (meta tag) just to feed this one banner.

Sequencing: with #1977 (after the tabs convert), not as a per-tab prerequisite

We tried building this as a prerequisite below the progress-tab conversion (#1998), so progress could be fully bridge-free. Attempting it surfaced that the migration is awkward at any position in the current transition — it's a data-flow problem, not a stack-ordering one. accessExpiration lives in per-tab data, its consumer is the shared, tab-generic InstructorToolbar, and only some tabs are query-backed yet, so every mid-transition shape is bad:

  • Prop-thread it (TabPageLoadedTabPageInstructorToolbar → hook): useAccessExpirationMasqueradeBanner ends up taking accessExpiration in, inverting the responsibility of a hook named for the data it ought to source.
  • Hook reads the React Query cache itself: a generic src/alerts/ hook has to import course-home/data/queryKeys and special-case progress's targetUserId — worse coupling.

Both also still need a transitional useModel(activeTabSlug) fallback for the not-yet-converted tabs (courseware/live/discussion).

It's only clean at the north star — every tab query-backed and the model store gone — when the banner sources accessExpiration from a uniform query-backed source with no fallback. So this lands with the model-store dissolution (#1977), once the tabs are converted; it's a piece of that effort, not a blocker for progress.

In the meantime: the progress tab (#1998) keeps a scoped one-reader meta bridge. The model store persists until #1977 regardless, so that one line is just one more consumer of a bridge that's staying anyway — and it lets progress ship without waiting on this.

Target (at the north star)

Source accessExpiration from a query-backed source instead of useModel(tab), with no useModel fallback. TabPage already holds the tab's query via courseStatus, so threading it from there is one option; a uniform tab-data read, or relocating access_expiration onto the courseHomeMeta query (a backend change — the metadata endpoint currently carries only access_expiration_date), are others. The exact mechanism is deliberately left to #1977, when all tabs are query-backed and the tradeoffs are clean — the mid-transition options above are all worse than waiting. (useCourseStartMasqueradeBanner is untouched by any of this — it reads courseHomeMeta and uses tab only as a === 'progress' string check, not useModel(tab).)

Scope (when it lands)

Four shared files + tests: alerts/access-expiration-alert/hooks.js, instructor-toolbar/InstructorToolbar.jsx, tab-page/LoadedTabPage.tsx, tab-page/TabPage.tsx.

[!NOTE]
This issue was authored and updated by Claude (Claude Code) and reviewed before posting.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.