What this is built from
Two passes. Rev 1 mapped the public site from the route registry and rendered snapshots. Rev 2 re-verified every navigation claim against the live DOM, signed out and signed in, and corrected four findings that the first pass got wrong.
Live DOM, signed out
Nav, footer, breadcrumb and search structure read directly from the DOM on /about, /learn/math, a topic page and the lesson player — not from an accessibility snapshot.
Live DOM, signed in
Account menu, Learner Dashboard, Preferences, Profile, Feedback Updates, Creator and Contributor dashboards, plus all eight role-gated panels probed on a real learner account.
Route & label registry
oppia/oppia@develop — routing module, PAGES_REGISTERED_WITH_FRONTEND, main.py, en.json. Authoritative for what exists; not authoritative for what ships or renders.
Live content APIs
/all_classrooms_summary, /classroom_data_handler/:c, /topic_data_handler/:c/:t. The content model in §06 is transcribed from those payloads.
1. Classrooms are in the global nav. Rev 1 claimed they weren't. They are — the Learn
menu links /learn, Math, Science and Financial Literacy. The classroom column
populates asynchronously after /all_classrooms_summary resolves, and the first
pass read the accessibility tree before it did.
2. Breadcrumbs exist. Topic pages render Home › Math › Fractions plus a
"Back to Math" link. The real defect is narrower and is now finding 03.
3. Topic tabs are LEARN / PRACTICE / STUDY, not the "Story / Revision / Practice" the string table implies.
4. Learner Groups is dark in production. /facilitator-dashboard and
/create-learner-group return HTTP 200 but render a client-side 404. HTTP status
reflects backend route registration, not feature availability — so every SPA route in Rev 1
was re-tested by rendering it.
Web only; the Android app is an external destination. The eight role-gated panels were probed from a learner account and all denied access, so their internal structure is unmapped — what they yielded instead is a verified permission taxonomy (§02E) and a set of access-denial defects (finding 07).
Eight zones on one domain
A marketing site, a structured curriculum, a legacy user-generated library, an authoring suite, a personal account tier and an operations console, sharing one header. Filter by zone, access level, or text.
What the shape tells you
Learn is the only genuinely deep branch — five levels from the domain root to a lesson
card. Every other zone is one or two levels: flat siblings hanging off /. That
asymmetry is fine in itself; the curriculum earns its depth. What matters is that three
route families sit at root level despite being pure classroom functionality:
/explore/:id (the lesson player), /diagnostic-test-player, and
/collection/:id.
The account tier is where the structure frays. Eight surfaces are called "Dashboard", five
appear in the account menu, three are selectable as a login default, and one working page
(/feedback-updates) has no entry point anywhere in the interface.
sitemap.xml lists 140 URLs, all under a single classroom. Production serves
three. Nine Science and Financial Literacy topics — and all of their subtopics and
stories — are absent, along with the newest Math topic, Length Measurements.
Six classification schemes, three of them competing
One strict hierarchy for curriculum, a flat facet set for the library, a tag set for the blog, two language taxonomies, a permission taxonomy and a system content-type vocabulary. They share no terms.
A. Curriculum taxonomy — enumerative, strictly mono-hierarchical
| Classroom | url_fragment | Topics | Topic terms | Diagnostic test |
|---|---|---|---|---|
| Math"Learn math through fun stories!" | math | 11 | Place Values · Addition and Subtraction · Multiplication · Division · Fractions · Expressions and Equations · Decimals · Ratios and Proportional Reasoning · Percentages · Geometry: Shapes, Perimeters and Areas · Length Measurements | enabled |
| Science"Learn science through engaging stories!" | science | 5 | Matter and Non-Matter · Basics of Energy · Scientific Principles · Hygiene and Diseases · Light | enabled |
| Financial Literacy"Learn about money through fun stories!" | finance | 4 | Money · Basics of Making Money · Personal Finance · Banking | disabled |
The classroom displayed as Financial Literacy lives at /learn/finance.
The guessable /learn/financial-literacy resolves to nothing. Math and Science
slugs match their display terms exactly.
Levels below topic
Subtopic
A revision unit grouping skills. 52 enumerated in Math alone. Served at /studyguide/:subtopic; the older /revision/:subtopic 301s to it.
Story
A narrative chapter sequence. Canonical and additional stories are distinguished in the data but not in the UI.
Skill
The only term linking curriculum to assessment. Attached to subtopics, acquired and required by chapters, and the key questions index on.
B. Library category taxonomy — flat, maintained in three places
| List | Defined in | Terms | Role |
|---|---|---|---|
| Full category set | ALL_CATEGORIES | 44 | Assignable to an exploration by its author |
| Search filter set | SEARCH_DROPDOWN_CATEGORIES | 19 | The only categories a learner can filter by |
| Translated labels | I18N_LIBRARY_CATEGORIES_* | 36 | Display strings — a third, differently-scoped list |
| Shelf groupings | I18N_LIBRARY_GROUPS_* | 8 | Featured · Recently Published · Top-Rated · Science · Humanities · Social Science · Languages · Computing |
The lists don't reconcile. 25 assignable categories have no filter entry, so content tagged
Calculus, Geometry, Probability or Trigonometry can't be
browsed to. The full set also carries residue: Gaulish, Welcome,
and both Maths and Mathematics as separate terms. A translated
label exists for Test.
C–F — the remaining schemes
Blog tags — 14 controlled terms
News · International · Educators · Learners · Community · Partnerships · Volunteer · Stories · Languages · New features · New lessons · Software development · Content · Teacher story
Mixes audience, content type and programme on one axis — three facets flattened into one list.
Two language taxonomies
Site UI: 18 locales in the header switcher. Content: a separate per-exploration language_code, set in Preferences as "Preferred exploration languages" and filtered independently in library search.
A learner can read the chrome in Hindi and find no lessons in Hindi; nothing explains that these are different axes.
10 roles — verified from 401 copy
- super admin · moderator
- curriculum admin · topic manager
- blog admin · blog editor
- voiceover admin
- translation admin · translation coordinator
- question admin · question coordinator
These names appear only in error messages. No page documents the role model to the people who hold the roles.
Activity types
exploration · collection · story · skill · subtopic · learntopic · classroom · questions · exp_translations · exp_voiceovers
The internal content-type vocabulary. Six of these ten never appear in the interface under those names — the origin of the labeling drift in §04.
The same object under four names
Labels drifted across a decade of renames without a migration. Interface strings, URL slugs and code identifiers each preserve a different generation of vocabulary, all in production at once. Every interface label below was read from a rendered page.
| Concept | Interface label(s), as rendered | URL slug | Code identifier | Verdict |
|---|---|---|---|---|
| The lesson unitOne playable interactive lesson | "Lesson" (homepage: 40 Curated Lessons; topic: 17 Lessons) · "Chapter" (story view) · "Exploration" (Creator Dashboard CTA: + CREATE EXPLORATION) · "Activity" | /explore/:id | ACTIVITY_TYPE_EXPLORATION | 4 names |
| Community content area | "Community Library" (menu) · "Library" (short) · "Browse the Library" (footer) · "the gallery" (Preferences body copy) | /community-library/library, /gallery → 301 | LIBRARY_INDEX | 4 names |
| Revision materialSkill-grouped study content | "STUDY" (topic tab) | /studyguide/:s/revision/:s → 301 | SUBTOPIC_VIEWERi18n strings say "Revision" | 3 names |
| Curriculum container | "Learn" (nav) · "Start with Oppia Classrooms" (menu) · "The Math Classroom" (page) | /learn | CLASSROOMS | 3 names |
| Personal work surface | 8 pages named "…Dashboard": Learner · Creator · Contributor · Contributor Admin · Blog · Facilitator · Topics and Skills · Release Coordinator. Only 5 in the menu; only 3 selectable as a login default. | 8 root-level slugs | *_DASHBOARD | Term exhausted |
| Teacher entry point | "For Parents/Teachers" (nav + footer) · "Start Teaching" (homepage CTA) | /teachCTA points to /creator-guidelines | TEACH / PLAYBOOK | Split destination |
| Partner programme | "Schools and Organizations" (nav) · "Partnerships" (page title) | /partnerships | PARTNERSHIPS | 2 names |
| Financial Literacy | "Financial Literacy" | /learn/finance | P0UyR5A4NY2M | Slug mismatch |
The Contributor Dashboard template is 17.8 KB containing zero i18n keys and zero translate pipes — every string hardcoded English ("My Contributions", "Translate Text", "Filter by Topic", "Contribution Stats", "Badges"). On a site with 18 UI locales, the one surface built for volunteer translators is the one surface that cannot be translated. The Creator Dashboard is partially done: 21 keys, with "Sort By" and others still hardcoded.
Language labels
The 18-locale switcher mixes conventions in one list. Most entries pair an English name with an endonym — Portuguese / Português (Brasil), Greek / Ελληνικά. But Dutch is listed as "Nederlands" in the English column, and Nigerian Pidgin is labelled "Nigerian" — a nationality, not a language. It's the first control a non-English learner touches.
Five levels, supported down to four
Depth is appropriate for a story-based curriculum, and orientation holds through level four. It fails at level five, and only for arrivals that skip the levels above.
| Level | Template | Route | Primary content | Orientation |
|---|---|---|---|---|
| L0 | Home | / | Positioning, 3 audience CTAs, social proof | Full nav |
| L1 | Classrooms index | /learn | 3 classroom cards + teaser text | Full nav · linked from Learn menu |
| L2 | Classroom | /learn/:classroom | Topic grid, course details, diagnostic prompt, library search bar | Full nav |
| L3 | Topic | /learn/:classroom/:topicredirects to …/story | Tabs: LEARN / PRACTICE / STUDY; stories, subtopics, skills | Breadcrumb + "Back to Math" |
| L4 | Story · Study guide · Practice | …/story/:s · …/studyguide/:s · …/practice/session | Chapter chain, revision content, question set | Breadcrumb |
| L5 | Lesson player | /explore/:exploration_id | Card sequence, interactions, hints, checkpoints | Only if query params survive |
Chapter links carry classroom_url_fragment, topic_url_fragment,
story_url_fragment and node_id as query parameters, and the player
builds its breadcrumb from them. Strip them — as a shared link, a bookmark, a search result
or the /explore/0 entry in sitemap.xml does — and the breadcrumb
collapses to the chapter title with no ancestry and no way back into the curriculum except
the global menu.
Content priority by template
Three audiences, equal weight
Students, teachers and volunteers each get a band. The primary CTA "Explore Oppia Classrooms" competes with "Access the Android app" beside it.
Sound editorial structure
Handles both real starting states — "Don't know where to start?" routes to the first topic, "Already know some Math?" to the diagnostic test.
Note: Financial Literacy has the diagnostic disabled, so one state silently disappears there.
Three parallel modes
LEARN / PRACTICE / STUDY sit as siblings — the clearest information design on the site.
Note: the practice tab is conditional on practice_tab_is_displayed, so topic pages aren't structurally uniform.
Preferences carries the load
Username, Picture, Bio, Preferred Dashboard, Subject Interests, Subscriptions, exploration languages, site language, audio language, 4 email toggles — plus the only entry points to Delete Account and Export Account.
What the objects actually are
Transcribed from live API payloads. Select an entity to highlight what it connects to.
The containment spine — L0 to the card
Entities — click to trace relationships
Four structural observations
Exploration is load-bearing in two unrelated IA branches
StructuralThe same object is a numbered Chapter inside a story arc and a standalone Exploration in the community library. It carries a category and tags from the library taxonomy that mean nothing in a classroom, and a chapter position that means nothing in the library.
Chapters are a graph used as a list
Latent capacityEach story node stores destination_node_ids[] (plural), prerequisite_skill_ids[] and acquired_skill_ids[] — the schema supports branching, prerequisite gating and adaptive routing. Every published story uses a single linear chain.
Skill is the only cross-cutting axis — and it's invisible
Missed facetSkill connects subtopics, chapters, questions, practice sessions, diagnostic tests and the learner's degrees_of_mastery. It could unify browse, search and progress across all three classrooms. It appears in no navigation, no filter and no URL.
Profile models a creator, not a learner
Identity mismatchThe public profile shows Impact, Created, Edited and a list of authored explorations. It surfaces no learning progress at all — no completed stories, no skills, no goals — despite LearnerProgress holding all of it.
Seven moves, in order
Ranked by impact on a learner reaching a lesson, then by cost to reverse. Every item below was verified on a rendered page.
Regenerate sitemap.xml from the classroom APIs
Two of three classrooms, nine topics and all of their stories and subtopics are missing from the index. Science and Financial Literacy are effectively invisible to search — the primary discovery channel for an organisation whose learners arrive by search, not by brand.
/all_classrooms_summary and classroom_data_handler instead of a static list, so new topics index on publish.Make /explore/:id self-locating
The player's breadcrumb is assembled from query parameters. Shared links, bookmarks, search results and the site's own /explore/0 sitemap entry all arrive without them, and land on a lesson with no ancestry and no route back into the curriculum. Sharing is a first-class action in the player, so this is the common case, not the edge case.
exploration_id — the mapping already exists in published_story_exploration_mapping — and render the full trail regardless of query string. Better still, give chapters a canonical hierarchical URL and make /explore/:id redirect to it.Give the site one search
Two search surfaces exist and neither indexes classroom content. There is no search box in the header at all. The search bar on the Math classroom page is labelled "Search through our Community Library" — from inside a classroom, the only available search points at a different branch of the site.
Skill is the natural cross-classroom facet and is already fully populated.Localise the Contributor Dashboard
Zero i18n keys across the whole template. The surface where volunteers translate Oppia into 18 languages is available in exactly one.
en.json and wire the translate pipe, as the Creator Dashboard partially does. Finish the Creator Dashboard's remaining hardcoded strings in the same pass.Settle one name per object
Lesson / Chapter / Exploration / Activity describe one thing. Community Library / Library / Browse the Library / the gallery describe another — and "gallery" survives in live Preferences body copy, not just as a legacy redirect. Eight pages are called "Dashboard", of which five are in the menu and three are selectable as a login default.
Resolve the orphan and the dark feature
/feedback-updates works, renders a proper empty state and a CTA — and is linked from nowhere in the interface. Meanwhile the whole Learner Groups feature (/facilitator-dashboard, /create-learner-group, /learner-group/:id) returns HTTP 200 and renders a client-side 404, with its routes still registered and its labels still shipping in en.json.
Make access denial consistent
Six role-gated panels return a clean 401 naming the required role. /release-coordinator instead sets the page title "Oppia Release Coordinator Panel" and renders a 404 body. /blog-dashboard's 401 page drops the global navigation entirely, stranding the user. And the ten role names in §02E appear only in these error messages — there is no page that documents the role model.
Retire the legacy alias layer
Eleven legacy paths still resolve, including five audience URLs — /parents, /partners, /nonprofits, /teachers, /volunteers — all serving one identical volunteer page. Separately, /learn/financial-literacy does not reach the Financial Literacy classroom, which lives at /learn/finance.
/learn/financial-literacy as a redirect or rename the slug to match the displayed term.