---
title: Build the data view catalog map as a panel and an addressable route
type: task
status: in_progress
description: >-
  Give the default data-view catalog a navigable map, rendered as the System section of the
  data-views view panel and at /data-views/catalog, organized by family and segment group.
base_uri: user:task/league/data-views/build-data-view-catalog-map.md
created_at: '2026-09-26T02:42:04.441Z'
entity_id: 49c86cee-b71a-431b-8938-e89bbe3f7e80
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
relations:
  - blocked_by [[user:task/league/build-out-default-data-view-catalog.md]]
  - relates [[user:task/league/data-views/land-data-views-first-paint-selection.md]]
started_at: '2026-09-26T15:42:56.307Z'
tags:
  - user:tag/league-xo-football.md
  - user:tag/software-task.md
updated_at: '2026-10-11T03:29:55.553Z'
visibility_analyzed_at: '2026-11-05T23:48:44.160Z'
---

## Overview

Give the default data-view catalog a navigable map: the System section of the data-views view panel, and `/data-views/catalog` as the addressable form, both rendering one component.

Split out of [[user:task/league/build-out-default-data-view-catalog.md]] during that task's review. The two share only the catalog module, which the map reads and does not write. Splitting lets the catalog ship its views when they are done rather than waiting on UI work, and lets the map wait for the first-paint task to settle rather than racing it.

## Background

### Assumptions

- **The catalog module is the single source of what the map renders.** It landed in the parent task (league `460967f27`). Everything rendered here derives from it; a hand-listed view anywhere in this task is a second registry.
- **`selected-player.js` establishes the pattern.** A drawer opened from a table the reader is working through must not cost them their place, and because the drawer and the standalone page render one component there is only ever one implementation to fix.
- **The resolver was owned by the first-paint task.** [[user:task/league/data-views/land-data-views-first-paint-selection.md]] owned `app/core/data-views/resolve-initial-view-selection.mjs` until it closed Completed on 2026-10-10. The first-load item is now free to change it.

## Design

Revised 2026-09-26 against the landed catalog (league `460967f27`), which now carries `family`, `question` and `promoted` on every view and a coverage window on every bounded segment.

### One component behind two surfaces

The map is the **System section of the data-views view panel** — the control readers already open to switch views — plus `/data-views/catalog` as the addressable form. Both render `@components/data-view-catalog`. react-table takes a `render_system_views` prop: when a host supplies it, the host's map replaces the flat system rows in the System section and closes out All, and the panel's own filter box drives it. This replaced the first build's separate "All views" `<dialog>` button (retired 2026-09-26 at operator direction), because two lists of the same 44 views was the cruft.

### Every entry is a link, so every default view is addressable

`/data-views/:view_id` resolved saved views only (it fetched from the API). A default view id now selects locally on that route, so each map entry is an ordinary `<Link>` to `/data-views/<VIEW_ID>` — new tab, copy link and share all work, and a default view gets a URL for the first time.

### Anchoring: families, then segment groups, then a filter

- **Promoted set first.** At catalog scale it is required rather than optional.
- **Five family sections** in catalog order. Each family's label and reader question live in the catalog module beside `DATA_VIEW_FAMILIES`, not in the UI.
- **Situational is sub-grouped by segment group** (field position, down and distance, clock, game script, scheme, box, personnel, target depth, coverage). Each segment declares its `group`, and each view carries its `segment` key, so the grouping is derived.
- **Coverage windows** are already in bounded views' names, the one string read at the moment of choice; the map shows the name as-is and adds nothing beside it.
- **A text filter** over name, question and segment label.
- **"You are here"** marks the currently selected view.

The four-axis filter chips from the first design were DROPPED on measurement. Across the 44 views, 43 tag `multi-position`, 42 tag `season-to-date`, subject separates only the team family, and metric domain restates the family. Chips over those axes would be controls that filter nothing.

### Two hazards inherited from the first-paint task

Paid for there, recorded here rather than rediscovered:

- **The stored history is edit-history, not visit-history.** `save_snapshot` writes off `DATA_VIEW_CHANGED`, so a view the reader merely SELECTED leaves no local state. A naive "no history" test therefore fires for a reader who has selected views all week without editing one, and would open the map over their work every morning. The no-history branch must key on the same signal the restore path uses.
- **Setting the applied flag on the early path disables the post-fetch restore.** That was the `af6af976a` regression, live in production until `1f477d99a`. A map-opens branch that marks selection applied reintroduces it exactly, because the map is not a view and the reader may still need a view selected underneath it.

## Landed (league `1dcf1cfeb`, `924372fe6`, `2c770ff47`, 2026-09-26; in the served production build `9f0b24ede` as of 2026-10-10)

Verified on `yarn dev:live`: the route renders all 44 views plus the five promoted, in every family and segment group, at desktop and phone width. Opening and closing the panel over a scrolled table left its scroll position and loaded rows untouched, and choosing an entry landed on `/data-views/<VIEW_ID>` with that view loaded.

- **Situational questions were rewritten at the source.** The generated "Where does his usage come from -- leading?" read as a formula across twenty rows. Segment descriptions now complete a clause, so each view asks "Who gets the ball ...?" and generated view descriptions stop reading as fragments.
- **Moved into the view panel** (`2c770ff47`, react-table `d9a680e`). Verified on `yarn dev:live` anonymously at desktop and 390px: the System and All sections render the map, the panel filter narrows it, the current view is marked, and choosing an entry loads it and closes the panel. Also seen signed in as admin on port 8091: the 231 saved views list above the map in All, and the System tab shows the map alone.
- **Panel refinements** (react-table `39fa770`, league `59adf2e33`). The collapsed card carries every view action, with tags in a popover and New view moved off the toolbar. The panel opens on System when the host supplies the map. All stacks Yours, System, Shared. The rail counts each view once and leaves host-rendered system views out of By author. Scroll-to-selected is deleted, and the promoted heading reads Essentials.
- **Opening seasons by source availability** (league `9d614e721`, `38232a184`). Each column line declares its availability, and every view opens on the newest season its lines answer for. Only a back-bounded segment opens earlier. At operator direction the season boundary is respected, and a thin early-season segment still opens on the current season. The population floor gate reads its events-per-row floor off the last complete season while a season is in play. It passed 44 of 44 at 2026 week 3.
- **No post-fetch guard for default-view links.** A link to the default view looked exposed to the post-fetch restore, which asks only whether the selection is still the default. A signed-in control run with the restore unguarded held the linked view anyway: selecting it rewrites the last-active record before the saved-view list lands. So no guard shipped.

## Plan

- [x] **Catalog presentation fields**
  - Files: `libs-shared/default-data-views/data-view-segments.mjs`, `libs-shared/default-data-views/default-data-view-catalog.mjs`, `test/app.default-data-view-catalog.spec.mjs`
  - Changes: a `group` on every segment plus an ordered `DATA_VIEW_SEGMENT_GROUPS` vocabulary; a `segment` key on every catalog view; an ordered `DATA_VIEW_FAMILY_DETAILS` carrying each family's label and question.
  - Verify: spec asserts every segment names a known group, every family has details, and every segmented view's `segment` resolves.

- [x] **Addressable default views**
  - Files: `app/core/data-views/sagas.js` (`load_data_view` only — not the resolver)
  - Changes: a default view id selects locally instead of fetching.
  - Verify: `/data-views/REDZONE_OPPORTUNITY` loads that view in a browser with no API 404.

- [x] **The catalog component**
  - File: `app/views/components/data-view-catalog/`
  - Changes: promoted set, family sections, situational segment groups, filter, current-view mark. The grouping is a pure sibling module with a spec.
  - Verify: `grep` for any view id literal in this directory returns nothing; the spec covers grouping and filtering.

- [x] **The map as the view panel's System section**
  - Files: react-table `src/table-view-controller/`, `src/table/table.js`; league `app/views/pages/data-views/data-views.js`, `package.json` pin
  - Changes: react-table `render_system_views` slot; league passes the catalog; the `<dialog>` panel and its trigger deleted.
  - Verify: react-table spec fails against the old controller and passes against the new; browser check above.

- [x] **The addressable route**
  - Files: `app/views/pages/data-views-catalog/`, `app/views/routes.js`, `libs-shared/page-routes.mjs`
  - Changes: page frame (title, deck, filter) rendering the catalog component; `/data-views/catalog` placed before `/data-views/:view_id`; a page-routes entry so it is not served `noindex` with home copy.
  - Verify: the route loads directly with no league context.

- [ ] **Open the map on a first load with no view history**
  - Files: `app/core/data-views/resolve-initial-view-selection.mjs`, `test/data-views.initial-view-selection.spec.mjs`
  - Purpose: a reader with nothing remembered gets a menu of questions rather than an arbitrary leaderboard; a returning reader gets the view they left on.
  - Changes: add a third outcome to the pure resolver — open the map — for the no-history case, leaving the remembered-view and default-view outcomes intact. Key the no-history test on the same signal the restore path uses. Do not set the selection-applied flag on the map branch. The view panel's open state and section live inside react-table's controller, so this item needs a way for the host to open it on the System section.
  - Verify: extend the existing pure spec with a no-history fixture asserting the map outcome, and a selected-but-never-edited fixture asserting it does NOT get the map — that second case is the regression this could reintroduce and the first fixture alone cannot catch it. Then confirm in a browser on `yarn dev:live` with localStorage cleared that the view panel opens on the map, and that after selecting a view and reloading the view returns rather than the map.
  - **Unblocked 2026-10-10:** [[user:task/league/data-views/land-data-views-first-paint-selection.md]] closed Completed, so no live task shares the resolver. As of that date the resolver has no map outcome.
