---
title: Land the data-views first-paint view selection
type: task
status: completed
description: >-
  The data-views page painted the default view before selecting the remembered one. Three commits
  closed it: af6af976a moved selection to mount, d69bbe2a6 covered a view selected but never edited,
  and 1f477d99a fixed the built-in regression af6af976a introduced. Deployed at 1f477d99a;
  production confirmation remains.
base_uri: user:task/league/data-views/land-data-views-first-paint-selection.md
created_at: '2026-09-04T05:44:57.151Z'
entity_id: 99379f3e-3961-44c0-891f-147a45885488
finished_at: '2026-10-10T15:36:30.911Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-10-10T15:36:30.911Z'
visibility_analyzed_at: '2026-11-06T00:17:46.121Z'
---

## Overview

Navigating to `/data-views` used to render the DEFAULT view with no query in flight, then switch to loading the last active view once `GET /api/data-views` answered. The selection waited on the network even though every field it needed was already in `localStorage`, which is synchronous.

League `af6af976a` moves the selection to a layout effect at mount, so the first painted frame is the view the user left on and its query is already out.

## What landed (2026-09-04, league `af6af976a`, unpushed at the time of writing)

- `apply_initial_view_selection` in `app/core/data-views/sagas.js` restores the stored snapshot and selects the view before the fetch. The rule itself is `app/core/data-views/resolve-initial-view-selection.mjs`, pure and specced at `test/data-views.initial-view-selection.spec.mjs`.
- The load effect in `app/views/pages/data-views/data-views.js` is a `useLayoutEffect`, so no default-view frame is painted first.
- The default view is now selected explicitly too, so a user with nothing remembered also gets a query at mount rather than after the round trip.
- A results request may wait in the ws send queue instead of being dropped while the socket is still connecting (`app/core/ws/send-queue.js` gained `replace_key`). Firing at mount routinely beats the socket open, and a dropped frame left the reducer at `pending` with nothing to end it.
- Performance: the container was deep-converting the full result three times per render and re-running it on any of seventeen unrelated inputs. Each conversion is now memoized on the result (`app/views/pages/data-views/index.js`), and per-row class derivation moved out of the render body.

Verified at that commit: `yarn verify` (17 checks), `yarn build`, and the touched specs under `test:isolated`. It was deployed as part of `f13014d6e` and did NOT resolve the reported symptom — see the two sections below for why.

## The gap that kept the defect alive (2026-09-04, league `d69bbe2a6`)

`af6af976a` shipped and reached production in `f13014d6e`, and the transition was still there. The reason is the third plan item below, and it is not an edge case — it is the common one.

The early selection read the SNAPSHOT history, and `save_snapshot` runs only off `DATA_VIEW_CHANGED` (a debounced watcher on edits). Selecting a saved view writes `save_last_active_view` and nothing else, so a view the user selected and never modified had no local state, `resolve_initial_view_selection` returned `defer`, and the page fell back to the pre-fix behavior in full: default view painted, then a switch once `GET /api/data-views` landed.

`d69bbe2a6` puts the view's `table_state` on the last-active record itself, written at selection time in `persist_selected_view_to_browser`. The rule prefers the snapshot when there is one (it is the edited state; the cached one predates those edits) and defers only when neither is valid. Invalidation rides on `clear_view`, which a revert calls: it now drops the cached state for that view and keeps the identity, so a revert cannot be undone by the next page load.

Verified: the two new selection specs fail against the pre-change rule while the seven existing ones pass, and `yarn verify` is green (17 checks).

## The regression that made it worse, not just incomplete (2026-09-04, league `1f477d99a`)

After `d69bbe2a6` the operator reported a second, sharper symptom: the page no longer returned to the most recently visited view at all. That one was a genuine regression introduced by `af6af976a` and live in production from its deploy until `1f477d99a`.

`resolve_initial_view_selection` answered a remembered BUILT-IN view with `default_view_id` rather than with that view — two different views. The damage was not one wrong frame: the early path sets `initial_selection_applied`, which is exactly the flag `restore_last_active_view_if_default` checks before declining to run. So the wrong answer also disabled the post-fetch path that used to correct it, and the remembered view never loaded. Before `af6af976a` there was no early path and the post-fetch restore handled the case correctly.

Compounding it, `persist_selected_view_to_browser` skipped built-ins entirely, so visiting one never updated the last-active record and the user was returned to whatever they had open before it. That conflated state with identity: a built-in's STATE lives in code and must never be cached, but which view was last active is identity and is as true of a built-in as of a saved view. It is recorded now, without its `table_state`.

Verified by control: both new cases fail against the deployed rule, answering `SEASON_PROJECTIONS` where the remembered view was `SEASON_FANTASY_POINTS`.

## Deployed (2026-09-04 07:21 UTC, league `1f477d99a`)

Frontend-only — `yarn build && yarn deploy:dist && yarn deploy:sourcemaps` from a worktree pinned to the sha, because the shared tree carried three sessions' work. No `pm2` reload, so the live League 1 auction's sockets were never dropped. `yarn deploy:all` would have failed anyway: the worker host has uncommitted ssl blocks in its production config and its `git pull` aborts (bulletin #655).

Host oracle: `dist/build-manifest.json` names `1f477d99a`, `index.html` references `main.9409569f.js` and that file exists, no `*.map` in the public dist, `main.9409569f.js.map` present in the host's private sourcemaps directory. The shipped minified rule reads `...(r))return{type:"default",view_id:r}` — the remembered id, the same binding the restore branch uses — and carries the `t.table_state ?? e.table_state` cascade, so both fixes are provably in the deployed artifact rather than only in source.

The deploy also carried `e434ed6e6` (empty-state polish) and `3be36394c` (plays cold-load), both cleared by their authors; `663f991c2` is backend and did not ship.

## Plan

- [x] The uncovered case: a view the user SELECTED but never edited has no local snapshot. Closed by `d69bbe2a6` — the last-active record carries `table_state`, and `clear_view` drops it on revert. Verify: the new cases in `test/data-views.initial-view-selection.spec.mjs` and `test/data-view-storage.storage.spec.mjs` § `clear_view`, run against the pre-change rule as a control.
- [x] The built-in regression. Closed by `1f477d99a`, control-verified as above.
- [x] Deploy. Live at `1f477d99a`, verified against the host.
- [x] Confirm on production that the operator's own symptom is gone. A frontend deploy does not reach a tab that never reloads, so this needs a hard reload first. Two things to see: the remembered view is the one that loads, and it loads without a default-view frame in between. As of 2026-10-10 the rule has moved on since `1f477d99a` — `5ce8fec88`, `924372fe6`, `822e8a7b6` and `9d614e721` all touch the selection path — so this check now exercises the current build, not the 09-04 fix alone. Passed 2026-10-10 on the `league-prod` profile, signed in, against served build `9f0b24ede` (`main.298771ef.js`, league master HEAD, all four commits ancestors). Three per-animation-frame samples of a fresh load showed no data-view page mounted while the store still held `SEASON_PROJECTIONS` from boot. The first mounted frame, at 588-705ms, was already the remembered view `34efe880`. A top-level hard reload landed on the same view. Evidence is in `scratch/league/land-data-views-first-paint-check/`.
- [x] Settle whether data-views races the socket on a cold load. It does not: the request send at `app/core/data-views/sagas.js:163` passes `queue_until_open: true` with `replace_key: 'data_view_request'`, so the send queue covers it at the transport layer and no `WEBSOCKET_OPEN` watcher is needed. Confirmed by `user-base-a9`, who raised it, retracted their `[bug]` observation on `user:repository/active/league/ABOUT.md` and replaced it with a `[gotcha]` naming the two sites (the timing-frame send at line 319 and the `WEBSOCKET_RECONNECTED` replay) that argue the opposite if you stop at either.

Two cases still defer to the post-fetch path, both correctly: a first visit on a browser that has nothing stored, and the one load after this deploy for an existing user, whose last-active record predates the `table_state` field. The second self-heals on the next selection.

## Key locations

- `repository/active/league` — `CLAUDE.md`, `docs/guides/spa.md` (read before touching `app/`)
- `app/core/data-views/`, `app/core/ws/`, `app/views/pages/data-views/`
- `libs-shared/data-view-storage/storage.mjs` — the localStorage contract
