Redirect about-sidebar pages from voices to sefaria.org#3391
Redirect about-sidebar pages from voices to sefaria.org#3391YishaiGlasner wants to merge 17 commits into
Conversation
📊 Code Quality Score: 18/100
Was this score accurate? 👍 Yes · 👎 No Scored by GitVelocity · How are scores calculated? |
There was a problem hiding this comment.
Pull request overview
This PR centralizes the “about” sidebar page definitions and enforces that these static pages are canonical on sefaria.org (library module), not on the voices subdomain. It adds both server-side redirects for direct navigation on voices and client-side behavior to open these pages in a new tab when clicked from within the voices experience.
Changes:
- Added
ABOUT_SIDEBAR_PAGESas a canonical source of truth for the about-section sidebar items (paths + EN/HE labels). - Consolidated
serve_static/serve_static_by_langinto a single view and added voices → library redirects for about-sidebar pages. - Updated sidebar rendering and navigation handling (template loop +
ReaderApp.openURLvoices new-tab behavior), plus added redirect tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
sites/sefaria/site_settings.py |
Adds canonical ABOUT_SIDEBAR_PAGES list used across templates and client code. |
sites/sefaria/urls.py |
Routes static_pages_by_lang to serve_static with by_lang=True. |
reader/views.py |
Merges static-serving views and redirects about-sidebar pages from voices to library. |
templates/_sidebar.html |
Replaces hardcoded sidebar list with a loop over SITE_SETTINGS.ABOUT_SIDEBAR_PAGES. |
templates/static/privacy-policy.html |
Updates whichPage to align with path-based IDs. |
templates/static/link-to-annual-report.html |
Updates whichPage to align with path-based IDs. |
static/js/ReaderApp.jsx |
Adds voices-specific handling to open about-sidebar links in a new tab on library. |
static/js/NavSidebar.jsx |
Switches footer links for about/terms/privacy-policy to path-only URLs (/about, etc.). |
reader/tests/test_catchall_redirects.py |
Adds tests for voices redirects on about-sidebar pages. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (Sefaria.activeModule === Sefaria.VOICES_MODULE) { | ||
| const sidebarPaths = new Set( | ||
| (Sefaria._siteSettings.ABOUT_SIDEBAR_PAGES || []).map(p => '/' + p.path) | ||
| ); | ||
| if (sidebarPaths.has(path)) { | ||
| window.open(Sefaria.util.fullURL(path, Sefaria.LIBRARY_MODULE), '_blank'); | ||
| return true; | ||
| } | ||
| } |
There was a problem hiding this comment.
there are no search params for those pages. i don't think we should now write complicated code for case we'll change it.
| @pytest.mark.django_db | ||
| @override_settings(DOMAIN_MODULES=TEST_DOMAIN_MODULES, ALLOWED_HOSTS=TEST_ALLOWED_HOSTS) | ||
| def test_library_about_sidebar_page_no_redirect(client): | ||
| """ | ||
| Test that accessing an about-sidebar page from library does NOT redirect. | ||
| """ | ||
| response = client.get("/about", HTTP_HOST="www.modularization.testing.sefaria.org") | ||
| assert response.status_code != 301 or "voices" not in response.get("Location", "") | ||
|
|
| if (sidebarPaths.has(path)) { | ||
| window.open(Sefaria.util.fullURL(path, Sefaria.LIBRARY_MODULE), '_blank'); | ||
| return true; | ||
| } |
| library_domain = reader_views.DOMAIN_MODULES.get("en", {}).get("library", "") | ||
| request = _make_request(factory, "/terms", VOICES_MODULE, {"foo": "bar"}) |
yodem
left a comment
There was a problem hiding this comment.
Multi-agent review (claude) — ran a 5-agent pass (security, code-quality, tests, architecture, frontend) over the diff. Security came back clean: the redirect target and all template values are fixed server-side settings, not user input, so no open-redirect/XSS. Solid, focused PR. One substantive gap and a few cleanups posted inline below.
Headline: /metrics is listed in ABOUT_SIDEBAR_PAGES but is routed to reader_views.metrics (urls.py:66) before the serve_static catch-all (urls.py:73), so the new voices→library redirect silently never fires for it — it's the one about-page of twelve that won't redirect on direct navigation. Two independent agents flagged this as a layering issue: the redirect lives inside one view, so any about-page with its own view bypasses it.
Verdict: comment (not blocking) — the metrics gap is worth a fix or an explicit accepted-exception note before merge.
| (Sefaria._siteSettings.ABOUT_SIDEBAR_PAGES || []).map(p => '/' + p.path) | ||
| ); | ||
| if (sidebarPaths.has(path)) { | ||
| window.open(Sefaria.util.fullURL(path, Sefaria.LIBRARY_MODULE), '_blank'); |
There was a problem hiding this comment.
suggestion (claude): Two things on this window.open:
- No
'noopener,noreferrer'— the opened library tab keeps a livewindow.openerback to the voices page (reverse-tabnabbing surface). Low practical risk since the target is first-party, but this is newly-added code; pass'noopener,noreferrer'as the third arg. - Query string is dropped —
pathhere isdecodeURI(url.pathname)(pathname only), andfullURLdoesn't forwardurl.searchParams. The server-side redirect preserves query params (you even test that). So/about?lang=he&utm_source=xclicked in-app on voices loses?lang=he&utm_source=x, diverging from the full-page-load behavior — breaks lang overrides and analytics attribution. Appendurl.searchto the opened URL.
There was a problem hiding this comment.
- this isn't low risk but no risk but it's considered best practice so i'll do it.
- i already answered copilot on this (in his first review)
| } | ||
| const path = decodeURI(url.pathname); | ||
| if (Sefaria.activeModule === Sefaria.VOICES_MODULE) { | ||
| const sidebarPaths = new Set( |
There was a problem hiding this comment.
nitpick (claude): sidebarPaths is rebuilt (new array + new Set) on every openURL call while on voices, including the common case where the path doesn't match. ABOUT_SIDEBAR_PAGES is static config — hoist this to a module-level constant (mirroring the server's _ABOUT_SIDEBAR_PATHS) so it's computed once.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ttps://github.com/Sefaria/Sefaria-Project into feature/sc-39253/-voices-please-make-links-to-about
…ommit remove a line
| def test_voices_sidebar_page_redirects_to_library(factory): | ||
| library_domain = reader_views.DOMAIN_MODULES.get("en", {}).get(LIBRARY_MODULE, "") | ||
| request = _make_request(factory, "/about", VOICES_MODULE) |
| def test_voices_sidebar_page_preserves_query_params(factory): | ||
| library_domain = reader_views.DOMAIN_MODULES.get("en", {}).get(LIBRARY_MODULE, "") | ||
| request = _make_request(factory, "/terms", VOICES_MODULE, {"foo": "bar"}) |
| {% for page in SITE_SETTINGS.ABOUT_SIDEBAR_PAGES %} | ||
| <li><a id="{{ page.path }}" href="/{{ page.path }}"> | ||
| <span class="int-en">{{ page.en }}<i class="fa fa-chevron-right"></i></span> | ||
| <span class="int-he">{{ page.he }}<i class="fa fa-chevron-left"></i></span></a></li> | ||
| {% endfor %} |
Summary
Static "about" pages (About, Team, Terms, Privacy Policy, etc.) belong only on
sefaria.organd have no context on thevoicessubdomain. Previously these pages had no cross-module behavior — they'd either render incorrectly on voices or be left out of voice-side navigation links entirely.This PR:
ABOUT_SIDEBAR_PAGESinsite_settings.py) as the source of truth for all 12 about-section pages, including their English and Hebrew display labels.voices.sefaria.org/about) tosefaria.org/about.ReaderApp.openURLso every link in the app is covered without per-component changes.SidebarFooter(domain-prefix removal for about/terms/privacy-policy) and_sidebar.html(hardcoded<li>list replaced by a template loop driven by the canonical list).serve_staticandserve_static_by_langinto a single view; theby_langdistinction is now passed as a URL-pattern kwarg.Changes
sites/sefaria/site_settings.pyABOUT_SIDEBAR_PAGES— the single source of truthsites/sefaria/urls.pyserve_static_by_langreference withserve_static, {"by_lang": True}reader/views.pyserve_static/serve_static_by_langinto one view; added voices redirecttemplates/_sidebar.html<li>elements with a{% for %}loop overSITE_SETTINGS.ABOUT_SIDEBAR_PAGEStemplates/static/privacy-policy.htmlwhichPage='privacy'→'privacy-policy'to match new path-based element IDstemplates/static/link-to-annual-report.htmlwhichPage='annualreport'→'link-to-annual-report'(same reason)static/js/ReaderApp.jsxopenURLnow opens about-sidebar paths in a new tab when on voicesstatic/js/NavSidebar.jsxgetSidebarFooterData: removed library-origin prefix from about/terms/privacy-policyreader/tests/test_catchall_redirects.pyserve_staticvoices redirect behaviorTests added
In
reader/tests/static_pages_test.py:test_voices_about_sidebar_page_redirects_to_library— core:/abouton voices → 301 to librarytest_voices_about_sidebar_page_preserves_query_params— redirect preserves query stringstest_library_about_sidebar_page_no_redirect— regression: library doesn't redirect sidebar pagesNote: client-side
openURLlogic has no unit test — there is no existing pattern for testingReaderAppmethods in this codebase. Covered by manual verification below.Test plan
/about,/terms,/team→ should redirect (same tab) tosefaria.org/<path>sefaria.org/aboutin a new tab