Information Architecture Audit oppia.org 6 Aug 2026 Rev 2 — signed-out + signed-in verified

Easy to browse into. Hard to arrive at.

Oppia's navigation holds up better than a source-only read suggests: the global menu does reach all three classrooms, and topic pages do carry breadcrumbs. The failures are at the edges — where people arrive rather than browse. Two of three classrooms aren't indexed, no search reaches classroom content, a shared lesson link loses its ancestry, and the contribution surface can't be translated at all.

3classrooms live · sitemap indexes 1
20curriculum topics
~80route templates registered
5items in the account menu, against 8 dashboards
10roles named only inside 401 error copy
0translatable strings in the Contributor Dashboard
00 — Method & corrections

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.

Source A

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.

Source B

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.

Source C

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.

Source D

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.

Corrections applied in Rev 2

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.

Scope boundary

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).

01 — Sitemap & hierarchy

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.

    Stale index

    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.

    02 — Taxonomy

    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

    Classroomurl_fragmentTopicsTopic termsDiagnostic test
    Math"Learn math through fun stories!" math11 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!" science5 Matter and Non-Matter · Basics of Energy · Scientific Principles · Hygiene and Diseases · Light enabled
    Financial Literacy"Learn about money through fun stories!" finance4 Money · Basics of Making Money · Personal Finance · Banking disabled
    Term / slug divergence

    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

    Facet 1

    Subtopic

    A revision unit grouping skills. 52 enumerated in Math alone. Served at /studyguide/:subtopic; the older /revision/:subtopic 301s to it.

    Facet 2

    Story

    A narrative chapter sequence. Canonical and additional stories are distinguished in the data but not in the UI.

    Cross-cutting

    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

    ListDefined inTermsRole
    Full category setALL_CATEGORIES44Assignable to an exploration by its author
    Search filter setSEARCH_DROPDOWN_CATEGORIES19The only categories a learner can filter by
    Translated labelsI18N_LIBRARY_CATEGORIES_*36Display strings — a third, differently-scoped list
    Shelf groupingsI18N_LIBRARY_GROUPS_*8Featured · 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

    C · Editorial

    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.

    D · Localisation

    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.

    E · Permissions

    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.

    F · System

    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.

    04 — Labeling system

    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.

    ConceptInterface label(s), as renderedURL slugCode identifierVerdict
    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 contribution surface has no labels to translate

    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.

    05 — Content hierarchy

    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.

    LevelTemplateRoutePrimary contentOrientation
    L0Home/Positioning, 3 audience CTAs, social proofFull nav
    L1Classrooms index/learn3 classroom cards + teaser textFull nav · linked from Learn menu
    L2Classroom/learn/:classroomTopic grid, course details, diagnostic prompt, library search barFull nav
    L3Topic/learn/:classroom/:topicredirects to …/storyTabs: LEARN / PRACTICE / STUDY; stories, subtopics, skillsBreadcrumb + "Back to Math"
    L4Story · Study guide · Practice…/story/:s · …/studyguide/:s · …/practice/sessionChapter chain, revision content, question setBreadcrumb
    L5Lesson player/explore/:exploration_idCard sequence, interactions, hints, checkpointsOnly if query params survive
    Where the trail breaks

    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

    L0 · Home

    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.

    L2 · Classroom

    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.

    L3 · Topic

    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.

    Account tier

    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.

    06 — Content model

    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

    ClassroomTopic StoryChapter Exploration 3 live20 live chapter arcstory node n Cards / States 1..n1..n 1..n1..1 published_story_exploration_mapping — Topic also indexes its Explorations directly

    Entities — click to trace relationships

    Four structural observations

    Exploration is load-bearing in two unrelated IA branches

    Structural

    The 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.

    ImplicationOne entity, two metadata regimes, two vocabularies, two progress models. Every labeling problem in §04 traces back here.

    Chapters are a graph used as a list

    Latent capacity

    Each 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.

    ImplicationAdaptive pathing is already modelled. It's an IA and authoring problem, not a data problem.

    Skill is the only cross-cutting axis — and it's invisible

    Missed facet

    Skill 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.

    ImplicationThe best organising principle on the site is fully populated in the data and entirely absent from the IA.

    Profile models a creator, not a learner

    Identity mismatch

    The 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.

    ImplicationThe one public representation of a person is built for the smallest user group on the platform.
    07 — Findings

    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.

    P0

    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.

    MoveGenerate from /all_classrooms_summary and classroom_data_handler instead of a static list, so new topics index on publish.
    P0

    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.

    MoveResolve the chapter's parents server-side from 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.
    P0

    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.

    MoveIndex topics, subtopics, stories and chapters into one search with a type facet, and put it in the header. Skill is the natural cross-classroom facet and is already fully populated.
    P1

    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.

    MoveExtract the strings to 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.
    P1

    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.

    MovePublish a controlled vocabulary — Lesson, Community Library, Classroom, Study — and let slugs and code identifiers follow behind redirects. Rename dashboards by job rather than by role noun.
    P1

    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.

    MoveAdd Feedback Updates to the account menu. For Learner Groups, either ship it or unregister the routes — a route that answers 200 with a 404 body is indistinguishable from a broken page to a user, a crawler and a monitor.
    P2

    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.

    MoveOne denial template: correct status, correct title, full chrome, and a link to the dashboard rather than the homepage. Document the role taxonomy somewhere a role-holder can read it.
    P2

    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.

    MoveConvert aliases to 301s with a documented sunset, collapse the five stewards paths to one canonical URL, and either add /learn/financial-literacy as a redirect or rename the slug to match the displayed term.