internetarchive / internetarchive/openlibrary
Genre Explorer: genre/subgenre bookshelf browsing backed by Tag hierarchy
- Dominant language
- Python
- Stars
- 6.7k
- Forks
- 2k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 138
Description
## Summary
Today the [Library Explorer](https://openlibrary.org/explore) skeuomorphically recreates a physical library organized by Dewey Decimal Classification (DDC) / Library of Congress Classification (LCC). We'd like a **Genre Explorer**: the same UI shell, but the "bookcase per DDC range / shelf per subclass" structure is replaced with **bookcase per genre / shelf per subgenre**, sourced from our `genres`/`subgenres` Tag vocabulary (from [Open-Book-Genome-Project/tags](https://github.com/open-Book-Genome-Project/tags)) instead of `ddc.json`/`lcc.json`.
This does **not** replace Library Explorer — it's a sibling entry point backed by different structured JSON.
Additionally, the homepage's "categories" carousel (currently demoted to the bottom of the page) should be brought back up and re-themed to show our agreed-upon genres, linking each genre into the Genre Explorer.
## Proposed feature
- **Genre Explorer**: reuse the Library Explorer Vue component tree (`openlibrary/components/LibraryExplorer/`), swap the classification data source from `ddc.json`/`lcc.json` to a synthesized genre→subgenre JSON tree.
- **Homepage carousel**: promote/re-theme `home/categories` to show genres (not the current hardcoded `FEATURED_SUBJECTS` list); clicking a genre opens the Genre Explorer at that genre.
- **Genre Explorer top nav**: instead of Library Explorer's current prev/active/next "signage" strip, show a horizontally-scrollable list of all genres (alphabetical), similar to the homepage carousel — you can see which genre is selected, scroll through the rest, and clicking a different one jumps ("zips") to that bookcase.
## Research findings / prerequisites
### 1. Deep-linking within Library Explorer — already exists, reuse it
I assumed (per the ask) that no jump-to-bookcase mechanism existed. It does:
- `openlibrary/components/LibraryExplorer.vue:104-111` parses a `jumpTo` query param (e.g. `/explore?jumpTo=ddc:658.9`) and resolves it to a classification via `classifications[i].toQueryFormat(...)`.
- `openlibrary/components/LibraryExplorer/components/BookRoom.vue:119-146` (`findClassification`) walks the classification tree to resolve room/bookcase/shelf + breadcrumbs.
- `BookRoom.vue:217-247` (`mounted()`) does the actual navigation: finds the `[data-short="..."]` element and `scrollIntoView`s it, then fetches a Solr offset to scroll to the *exact book* within the shelf.
**Action**: Genre Explorer should extend this existing `jumpTo` mechanism (e.g. `jumpTo=genre:horror` or `jumpTo=genre:horror/subgenre:gothic-horror`) rather than building new routing/scroll logic. The new top-nav's "zip to bookcase on click" should call into the same `findClassification` + scroll path.
### 2. Tag schema has no genre↔subgenre association field today
- `Tag` model: `openlibrary/core/models.py:1228`; schema: `openlibrary/plugins/openlibrary/types/tag.type`.
- Allowed `tag_type` values incl. `genre`, `subgenre`: `openlibrary/plugins/upstream/addtag.py:26-34` (`SUBJECT_SUB_TYPES`).
- **No parent/genre reference field exists** on Tag today — confirmed via the `.type` schema and the create/edit form (`openlibrary/templates/type/tag/tag_form_inputs.html`, fields: `name`, `slugs`, `tag_description`, `body`, `tag_type`, `deputy` — nothing else).
- Good news: Infobase's query engine already supports equality filters on ref-typed properties (`vendor/infogami/infogami/infobase/dbstore.py:225,283-289`), the same mechanism used for e.g. `things({"type":"/type/edition","work": key})`. So adding a `genre` ref property to `/type/tag` would make `things({"type":"/type/tag","tag_type":"subgenre","genre":"/tags/OL12T"})` work immediately via `query.json` — **no Solr change needed for this specific lookup.**
- If this field should be user-editable, it needs adding to `openlibrary/templates/type/tag/tag_form_inputs.html` and the `web.input(...)` defaults in `openlibrary/plugins/upstream/addtag.py` (`addtag.POST` line 112; `tag_edit.POST` already passes through unlisted fields).
- No FastAPI autocomplete exists for Tags. The only autocomplete pattern in the codebase (`openlibrary/plugins/worksearch/autocomplete.py`) is legacy web.py, Solr-backed — and **Tags are not indexed in Solr at all**. A Tag autocomplete (e.g. for admin UI to pick a subgenre's parent genre) needs a new Infobase-backed endpoint (`Tag.find()`/`things()`), not a copy of the Solr pattern.
### 3. Per-genre/subgenre book listings can work today without waiting on Solr changes
- `/subjects/genre:thriller` **already** resolves against Tag objects: `openlibrary/plugins/worksearch/subjects.py:63-84` (`decorate_with_tags`) splits the `genre:` prefix, looks up the matching Tag via `Tag.find()`, and decorates the subject page with its description/disambiguations.
- However, the underlying book list still comes from Solr's **legacy subject-string field** — Tags themselves are never indexed (`openlibrary/solr/updater/work.py`'s `build_subjects()` only maps `subjects`/`subject_places`/`subject_times`/`subject_people`).
- **Implication**: the Genre Explorer's synthesized tree can compute per-genre/subgenre counts and power each "shelf" by querying Solr's subject field using each Tag's `slugs`, the same way `/subjects` pages do today. A v1 should not need a Special Deploy / new Solr schema fields.
- The long-term unlock for typed, faceted genre search is #11610 (below) — this issue's v1 doesn't need to block on it.
### 4. Synthesis + 7-day caching — established pattern to reuse
- `openlibrary/core/vendors.py:799-819` (`cached_get_amazon_metadata`) is the canonical example of `cache.memcache_memoize(fn, key_prefix, timeout=dateutil.WEEK_SECS)`.
- `WEEK_SECS` defined `openlibrary/utils/dateutil.py:16`.
- Build e.g. `get_cached_genre_tree()` following this convention. Tree shape should mirror `ddc.json`/`lcc.json` (documented as `ClassificationNode` in `openlibrary/components/LibraryExplorer/utils.js:102-123`: `name`, `short`, `query`, `count`, `children`).
- i18n caveat: tag names/descriptions aren't localized yet. Known gap — don't block v1 on it, but note it so the cache-key/invalidation design doesn't silently assume English-only forever.
### 5. Homepage carousel promotion
- Current position: `openlibrary/templates/home/index.html:41` renders `home/categories.html`, positioned second-to-last on the page (just above `home/stats`/`home/about`).
- It was demoted from the top of the page in commit `cf5bda4` ("moves onboarding after classics, demotes subjects", 2021-08-05). No inline comment records *why* the results weren't good — that context lives only in the original PR discussion (no PR number in the commit message). **Worth digging up that discussion before re-promoting**, so we don't repeat whatever made it underperform.
- Data source today: hardcoded `FEATURED_SUBJECTS` list (15 subjects) in `openlibrary/plugins/openlibrary/home.py:178-270`, cached 1hr via `get_cached_featured_subjects()`.
- A new genre carousel should follow `home/categories.html`'s shape (title/header link + one item per entry with an icon) but source from the genre Tag list/tree instead of the hardcoded list, and link each item to `/explore?jumpTo=genre:` instead of `/subjects/`.
### 6. Library Explorer's existing top-nav style bug (separate from the new Genre Explorer nav)
The "selected vs. left/right have different styles, looks funny" issue is `BookRoom.vue`'s `.lr-signs` (lines 13-45, styled via `.bookshelf-signage--lr-sign` lines 346-353/381-383) vs. the per-bookcase `.bookshelf-signage--center-sign` (lines 394-437) — two independently-maintained templates meant to look like one coherent left/current/right strip that have drifted apart (smaller max-width, hidden toolbar, dimmed classification label on the side signs vs. the center one).
## Open questions
1. **Genre/subgenre source of truth** — is the "agreed upon" genre list finalized in `Open-Book-Genome-Project/tags` (e.g. `mappings/genres.json`) enough to hardcode a v1 synthesis job, or does it need to stay live-synced from that repo?
2. **Where does `genre` (the parent-association field) get added** — purely OL-side `/type/tag` schema, or does the vocabulary repo also need a matching field so `Tag.create()` payloads round-trip it?
3. **Relationship to #11610** — should Genre Explorer v1 ship independently using the existing subject-string matching (finding #3 above), or wait for #11610's typed `Work.genres` field + Solr genre facets?
4. **BookRoom.vue nav bug** — fix as part of this issue (finding #6), or split into its own issue since it affects DDC/LCC Library Explorer today regardless of Genre Explorer?
## Possible phasing
1. Tag schema: add `genre` ref property to `/type/tag`; backfill existing subgenre tags to point at their parent genre.
2. Synthesis job: genre→subgenre JSON tree, 7-day cache (mirrors `ddc.json`/`lcc.json` shape); shelf book-counts via existing Solr subject-field matching on Tag `slugs`.
3. Library Explorer: extend `jumpTo` to accept a genre-based classification source; build the new alphabetical horizontal-scroller top nav (Genre Explorer only, v1).
4. Homepage: new/updated carousel sourced from the genre tree, promoted above the fold, linking to `/explore?jumpTo=genre:`.
5. *(Follow-up, not blocking)* `BookRoom.vue` DDC/LCC nav style fix, if scoped separately per open question #4.
## Related
- #11610 — RFC: Add `genres` field to Open Library Work records (currently `State: Blocked`) — the long-term unlock for typed/faceted genre search; this issue's v1 does not need to block on it (see finding #3).
- `docs/ai/tag-system/index.md` (this repo) — Phase 3 integration checklist for the controlled vocabulary, not yet started as of June 2026.
- [Open-Book-Genome-Project/tags](https://github.com/open-Book-Genome-Project/tags) — source of the genre/subgenre controlled vocabulary and mappings.
Contributor guide
Assessment
This issue has not been assessed yet.