You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Course teams can add custom static HTML pages to a course - things like a syllabus or a resources page - that show up as tabs in the course navigation. These are backed by the StaticTabBlock XBlock (xmodule/html_block.py, tab type static_tab). Authoring already moved to the authoring MFE; the learner-facing render is the piece that's still legacy.
Today the learning MFE shows the tab in its nav but links out to the old server-rendered page. The tab list comes from CourseTabSerializer (lms/djangoapps/course_home_api/course_metadata/serializers.py), whose url for a static tab resolves to the legacy StaticCourseTabView at /courses/{course_id}/{tab_slug}/. So clicking a custom page leaves the MFE and loads courseware/static_tab.html. This issue is about rendering those pages inside the learning MFE so the legacy view can go away.
What exists today
Legacy render:StaticCourseTabView (lms/djangoapps/courseware/views/views.py:475) at /courses/{course_id}/{tab_slug}/. It resolves the tab to a block and renders the block's STUDENT_VIEW fragment via get_static_tab_fragment (views.py:1298).
The block is addressable:get_static_tab_fragment builds the usage key as course.id.make_usage_key('static_tab', tab.url_slug), i.e. block-v1:{course}+type@static_tab+block@{url_slug}. That means it can be rendered chromelessly through the existing render_xblock endpoint at /xblock/{usage_key}.
MFE nav: the learning MFE already renders the tab list in src/course-tabs/ (CourseTabLinksList.tsx), one { url, title, slug } link per tab. For a static tab the url is the legacy page.
Iframe precedent: the MFE already embeds XBlock content through render_xblock for units - src/courseware/course/sequence/Unit/ContentIFrame.jsx and getIFrameUrl in Unit/urls.ts, which builds ${LMS_BASE_URL}/xblock/{id}. The same mechanism covers a static tab.
Proposed approach
Render the custom page as an in-MFE tab that iframes the static tab block, reusing the unit iframe machinery:
Backend (openedx-platform): give the MFE what it needs to render in place instead of linking out. Extend the tab data (CourseTabSerializer or a companion field) so a static tab carries its block usage key, and point the tab's url at an in-MFE route rather than /courses/{id}/{slug}/.
MFE route + page: add a route for custom pages and a component that iframes /xblock/{static_tab_usage_key} via the existing ContentIFrame path. Keep the tab highlighted in the nav while it's open.
Access control:StaticCourseTabView runs get_course_with_access and shows limited-access warnings. Confirm render_xblock enforces the same access for a static_tab block, or add it.
Multiple pages: a course can have several static tabs. The route should key off the tab slug and resolve the right block.
Styling / chrome: the legacy page renders inside the LMS course chrome; in the MFE it should sit inside the course tab layout with the block iframed as content. Confirm the block's own CSS/JS load correctly through render_xblock.
Height / resize: reuse useIFrameBehavior so the iframe resizes to content, same as units.
Transition: decide whether to ship the MFE route behind the existing tab data first and drop the legacy view in a follow-up, or do both together.
MFE iframe pattern: src/courseware/course/sequence/Unit/ContentIFrame.jsx, Unit/urls.ts (getIFrameUrl), Unit/hooks/useIFrameBehavior.ts; nav in src/course-tabs/.
Context
Course teams can add custom static HTML pages to a course - things like a syllabus or a resources page - that show up as tabs in the course navigation. These are backed by the
StaticTabBlockXBlock (xmodule/html_block.py, tab typestatic_tab). Authoring already moved to the authoring MFE; the learner-facing render is the piece that's still legacy.Today the learning MFE shows the tab in its nav but links out to the old server-rendered page. The tab list comes from
CourseTabSerializer(lms/djangoapps/course_home_api/course_metadata/serializers.py), whoseurlfor a static tab resolves to the legacyStaticCourseTabViewat/courses/{course_id}/{tab_slug}/. So clicking a custom page leaves the MFE and loadscourseware/static_tab.html. This issue is about rendering those pages inside the learning MFE so the legacy view can go away.What exists today
StaticCourseTabView(lms/djangoapps/courseware/views/views.py:475) at/courses/{course_id}/{tab_slug}/. It resolves the tab to a block and renders the block'sSTUDENT_VIEWfragment viaget_static_tab_fragment(views.py:1298).get_static_tab_fragmentbuilds the usage key ascourse.id.make_usage_key('static_tab', tab.url_slug), i.e.block-v1:{course}+type@static_tab+block@{url_slug}. That means it can be rendered chromelessly through the existingrender_xblockendpoint at/xblock/{usage_key}.src/course-tabs/(CourseTabLinksList.tsx), one{ url, title, slug }link per tab. For a static tab theurlis the legacy page.render_xblockfor units -src/courseware/course/sequence/Unit/ContentIFrame.jsxandgetIFrameUrlinUnit/urls.ts, which builds${LMS_BASE_URL}/xblock/{id}. The same mechanism covers a static tab.Proposed approach
Render the custom page as an in-MFE tab that iframes the static tab block, reusing the unit iframe machinery:
CourseTabSerializeror a companion field) so a static tab carries its block usage key, and point the tab'surlat an in-MFE route rather than/courses/{id}/{slug}/./xblock/{static_tab_usage_key}via the existingContentIFramepath. Keep the tab highlighted in the nav while it's open.StaticCourseTabViewrender, itsstatic_tab.htmltemplate, and the legacy taburlcan be removed. That removal is tracked back in Inventory the current state of the MFEs and Legacy Pages openedx-platform#38936.Open questions
StaticCourseTabViewrunsget_course_with_accessand shows limited-access warnings. Confirmrender_xblockenforces the same access for astatic_tabblock, or add it.render_xblock.useIFrameBehaviorso the iframe resizes to content, same as units.References
lms/djangoapps/courseware/views/views.py:475(StaticCourseTabView),:1298(get_static_tab_fragment); templatelms/templates/courseware/static_tab.html; routelms/urls.py:785-790.xmodule/html_block.py(StaticTabBlock),xmodule/tabs.py:297(StaticTab).lms/djangoapps/course_home_api/course_metadata/serializers.py(CourseTabSerializer).src/courseware/course/sequence/Unit/ContentIFrame.jsx,Unit/urls.ts(getIFrameUrl),Unit/hooks/useIFrameBehavior.ts; nav insrc/course-tabs/.