content-explorer/workplans/evidence/CONTENT-WP-0002-ImplementationNotes.md
tegwick 749752df4e Pass the keyboard and small-screen smoke.
CONTENT-WP-0002-T09 walks Tab, Enter, search, and Escape at 1280x800 and 390x844.

Assistant: grok
Assistant-Session: 01a1346c-fdff-7741-b1af-b7283661554b
2026-10-13 23:49:50 +02:00

156 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CONTENT-WP-0002 implementation notes
Date: 2026-10-13
## T01 — tokens, layout, header, hero, table
Pinned upstream: `b594d7433bf4e408b44215e25e5304696d6f840e`.
R4 applies. `ResultsTable.tsx` in the pin depends on translation, column filters, column resize, and infinite scroll. `SiteChrome.tsx` and `src/app/layout.tsx` depend on the OnCo mark, garden art, command palette, and medical footer. Those files were not copied whole.
What landed under `ui/`:
- Color tokens, type classes (`.display`, `.kicker`), card, chip, button, hero wash, skip link, and sticky table rules, adapted from `src/app/globals.css`. The table class is `records`.
- A sticky header and a footer. The header links to `/` and `#records` only, so it does not point at kind routes that do not exist yet.
- A hero with the upstream structure and Content Explorer copy. The sample is `examples/dataspaces/systems-thinking/snapshot.json`.
- `ResultsTable` renders columns, an empty state, sort buttons, and `aria-sort`. The parent owns the sort. URL query state stays with T04.
Deviations:
- Accent values stay the upstream pink (`#c2185b` / `#d6336c`). That color is part of the token file being adapted. It is not used as a medical claim.
- Geist font files are not fetched. The sans stack is the system UI font.
- Garden keyframes, the OnCo mark, column resize, column filters, and paging are omitted.
- Sort state is local React state.
Provenance: `ui/NOTICE` contains `Copyright (c) 2026 OnCo` and the MIT permission text. Adapted files name the pin.
Checks on 2026-10-13:
- `npm test` in `ui/`: 4 tests passed (sort order, empty state, sort button, `aria-sort`).
- `npm run typecheck` in `ui/`: `tsc --noEmit` passed.
- `python3 scripts/check_ui_boundary.py`: no oncology, cancer, drug, or target token on UI import lines, and the notice is present. This does not close T07; that gate still has to cover the later components.
- `python3 scripts/validate_fixtures.py` and `python3 -m unittest tests.test_contract tests.test_ingest` passed.
- `next dev` on `127.0.0.1:3400` returned the home page (HTTP 200, Next.js 16.3.4). The HTML contained the Content Explorer heading, the skip link, `#main`, and the fixture titles Feedback, Requisite variety, and Stock and flow model. The rendered page did not contain the words oncology or cancer. The dev server was stopped after that check. No browser pane was available, so this was an HTTP render check, not a keyboard or screenshot pass. Those remain T09.
Not done after T01: kind index, detail, BrowserRow, URL facets, keyboard states, both dataspaces, the formal import gate, or keyboard and small-screen smoke.
## T02 — registry kind index and entity detail
The kind list is the `kinds` array on each snapshot. Routes are `/{dataspace}/{kind}/` and `/{dataspace}/{kind}/{slug}/`. The slug is the part of the entity id after the kind. Both example dataspaces are loaded from `examples/dataspaces/`. An unknown kind or slug returns 404.
`EntityDetail.tsx` from the pin was not copied. The detail page is a greenfield view: title, TL;DR, summary, tags, sources, claims, and neighbouring relations with the manifest's forward and inverse labels. Search, facets, and URL sort state stay with later tasks. The neighbour list is not the CONTENT-WP-0004 graph journey.
The header kind links are the active dataspace's registry. On `/` that dataspace is systems-thinking.
Checks on 2026-10-13, after T02:
- `npm test` in `ui/`: 8 tests passed, including registry membership for both dataspaces, the Feedback neighbour, and static markup for the kind heading and the system-boundary claim.
- `npm run typecheck` in `ui/` passed.
- `python3 scripts/check_ui_boundary.py`, fixture validation, and `tests.test_contract` / `tests.test_ingest` passed.
- `next dev` on `127.0.0.1:3400` returned HTTP 200 for `/`, `/systems-thinking/concept/`, `/systems-thinking/concept/feedback/`, `/systems-thinking/concept/system-boundary/`, `/software-architecture/pattern/`, and `/software-architecture/concept/coupling/`. Unknown kind and slug returned 404. The rendered pages did not contain the words oncology or cancer. The server was stopped. No browser pane was available, so this was an HTTP render check.
Not done after T02: BrowserRow mapper, URL facets and sort, keyboard search and empty/error states, the T06 both-dataspace gate for search and filter, the formal import gate, provenance gate, or keyboard and small-screen smoke.
## T03 — generic browser row and cell renderers
`mapBrowserRow` is a pure function. It copies the OnCo row fields `id`, `name`, `tldr`, `route`, `facets`, `cols`, and `sortKeys`. It does not copy molecule, target, schematic, cancer-icon, or section-icon fields. Known cells are a name link, summary text, tag chips, and a kind link. A facet key other than `tags` is an empty list. A column key with no cell renders nothing.
Kind pages show the columns named in that kind's `kindViews` entry (`name`, `tldr`, `tags` in both fixtures). The dataspace home shows name, kind, and summary because that table mixes kinds. `renderCell` uses a renderer registry keyed by column or cell kind, so a caller can replace one cell without a domain widget.
Checks on 2026-10-13, after T03:
- `npm test` in `ui/`: 10 tests passed. The new tests cover the Feedback tag facet, an empty unknown facet, a replaced tag renderer, and the name link.
- `npm run typecheck`, `scripts/check_ui_boundary.py`, fixture validation, and the contract and ingest tests passed.
- `next dev` on `127.0.0.1:3400` returned the home table with a Kind column, and the concept and pattern indexes with Tags (`regulation`, `translation`). The server was stopped. No browser pane was available.
Not done after T03: URL facets and sort, keyboard search and empty/error states, the T06 gate for search and filter, the formal import gate, the provenance gate, or keyboard and small-screen smoke. Facet values were on the row; they did not filter the table yet.
## T04 — shareable query, facets, and sort
The query string follows the pinned table shape: `q` is the search text, a facet key repeats for each selected value (`tags=regulation&tags=structure`), and `sort` is the column key with a leading `-` when descending. The default sort is omitted. Search is written when the form is submitted. A facet button or a sort button writes immediately. Each write is a history entry.
`applyTableView` filters, then sorts. Within one facet, any selected value matches. Every selected facet key must match. Search looks at the name, the summary, and facet values.
Checks on 2026-10-13, after T04:
- `npm test` in `ui/`: 14 tests passed. The view tests round-trip a query and filter rows. The history test submits a search, selects a tag, reverses the sort, then goes back to the unfiltered table and forward to the sorted tag view.
- The same test dispatches `popstate` on `window` after `pushState` and checks that the history reader reports the new query.
- `npm run typecheck`, `scripts/check_ui_boundary.py`, fixture validation, and the contract and ingest tests passed.
- `next dev` on `127.0.0.1:3400` rendered `/systems-thinking/concept/` with the search field and the tag `regulation`. The server was stopped. No desktop browser was available, so back and forward were exercised in the history test rather than in a graphical browser.
Not done after T04: keyboard search and empty/error/loading states, the T06 gate for both dataspaces across search and filter, the formal import gate, the provenance gate, or keyboard and small-screen smoke. A facet key other than `tags` still has no values in these fixtures.
## T05 — keyboard search, empty, loading, error, and narrow layout
`/` focuses the record search when the key press is not already in a field. Escape clears the draft, and clears a submitted query. An empty catalog says "No records." A search or facet that matches nothing says "No records match this view." and offers Clear view. `status="loading"` and `status="error"` replace the table with a status or an alert. Route changes use `app/loading.tsx` and `app/error.tsx`.
The search form stacks in one column and the header wraps instead of staying on one fixed row. The table still scrolls sideways when the columns are wider than the screen.
Checks on 2026-10-13, after T05:
- `npm test` in `ui/`: 17 tests passed. The new tests cover loading, error, both empty messages, Clear view, `/` focus, and Escape.
- `npm run typecheck`, `scripts/check_ui_boundary.py`, fixture validation, and the contract and ingest tests passed.
- `next dev` on `127.0.0.1:3400` rendered `/systems-thinking/concept/` with the search hint and the narrow-layout classes. The server was stopped. No graphical browser was available, so a narrow viewport was not walked. That smoke stays with T09.
Not done after T05: the T06 gate that browse, search, filter, sort, and detail pass together for both dataspaces, the formal import gate, the provenance gate, or the keyboard and small-screen smoke. A facet key other than `tags` still has no values in these fixtures.
## T06 — both dataspaces
No new screen. The gate runs the same browse, search, filter, sort, and detail path on `systems-thinking` and `software-architecture`.
For each field the check loads the concept index, confirms every row route stays inside that field, searches for one record by its name, filters by one of its tags, sorts names descending, and renders that record's detail with its summary and sources. The home of each field also links to the other.
Checks on 2026-10-13, after T06:
- `npm test` in `ui/`: 18 tests passed, including `components/both-dataspaces.test.tsx`.
- `npm run typecheck`, `scripts/check_ui_boundary.py`, fixture validation, and the contract and ingest tests passed.
- `next dev` on `127.0.0.1:3400` returned HTTP 200 for `/systems-thinking/`, `/software-architecture/`, `/systems-thinking/concept/feedback/`, and `/software-architecture/concept/coupling/`. Each page contained the other field's name or the record's sources. The server was stopped. Search and sort on those pages were exercised in the UI test, because the filter runs in the browser after the HTML is sent.
Not done after T06: the formal import gate, the provenance gate, or the keyboard and small-screen smoke. A facet key other than `tags` still has no values in these fixtures.
## T07 — domain import gate
`scripts/check_ui_boundary.py` reads every import, `require`, dynamic `import()`, and CSS `@import` under `ui/`. It fails when the statement names oncology, cancer, drug, or target, including a name such as `CancerIcon`. A property such as `event.target` is not an import and stays allowed. The kind labels and navigation labels in both example fixtures are checked the same way.
`tests/test_ui_boundary.py` rejects sample imports for each forbidden token and accepts the shipped UI.
Checks on 2026-10-13, after T07:
- `python3 -m unittest tests.test_ui_boundary tests.test_contract tests.test_ingest` passed (15 tests).
- `python3 scripts/check_ui_boundary.py` and `python3 scripts/validate_fixtures.py` passed.
Not done: the provenance gate, or the keyboard and small-screen smoke. A facet key other than `tags` still has no values in these fixtures.
## T08 — provenance headers and notices
A file under `ui/` that says it was adapted or reduced from OnCo must name the pin in `research/baseline/ONCO_PIN`, `Copyright (c) 2026 OnCo`, `MIT License`, and `ui/NOTICE`. `ui/NOTICE` keeps the MIT permission text and that same pin. The footer keeps the MIT license and the pin in the page text. A file that says OnCo was not copied is not treated as an adapted copy.
SkipLink, SiteHeader, Hero, and PageHeader already named OnCo and the license. They did not name the pin. Those four headers now include `b594d7433bf4e408b44215e25e5304696d6f840e`. ResultsTable, globals.css, browser-row, and table-view already had the full header.
Checks on 2026-10-13, after T08:
- `python3 -m unittest tests.test_ui_provenance tests.test_ui_boundary tests.test_contract tests.test_ingest` passed (21 tests).
- `python3 scripts/check_ui_provenance.py`, `python3 scripts/check_ui_boundary.py`, and `python3 scripts/validate_fixtures.py` passed.
Not done: the keyboard and small-screen smoke. A facet key other than `tags` still has no values in these fixtures.
## T09 — keyboard and small-screen smoke
The smoke is `workplans/evidence/CONTENT-WP-0002-T09-smoke.json`. It opens the production home page in Chromium at 1280×800 and 390×844. Tab focuses the skip link. Enter lands on `/#main`. The search field receives "Feedback" and Enter. The URL becomes `/?q=Feedback` and the Feedback link is the matching record. Escape clears the query and returns the URL to `/`.
The browser job protocol accepts Tab, Enter, and Escape. It rejects `/`, so the slash shortcut stays covered by `ui/components/RecordBrowser.states.test.tsx`.
`next dev` did not hydrate when the browser service closed its websocket, because the dev client waits on the HMR socket. The smoke therefore used `npx next build` and `npx next start --hostname 0.0.0.0 --port 3400` from `ui/`. `allowedDevOrigins` includes `host.docker.internal` so a container can load the dev server. The passing run did not use the dev server.
The browser was a separate container of image `sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441` on `127.0.0.1:18084`, with only `http://host.docker.internal:3400` allowed. The fixture container on port 18083 was left running.
Passed run `99f366b8-2af2-46a6-bedf-a6342e2cb923` at 2026-10-13T21:47:49Z. Playwright 1.64.0, Chromium 156.0.8078.4. All 34 steps passed. At 390px the header wraps onto a second line, the search form stacks, and the kind links scroll sideways. Screenshot hashes:
- `desktop-3-baseline.png` `1e995d262ca6217e04bf6c17d0e1f089102db3ef3f6a4028aec34074191c0522`
- `desktop-16-keyboard.png` `825187674d958ad08295dff3a28da893a5d2acd86e7e3c19ba9e58f1dc4f128e`
- `narrow-3-baseline.png` `01844c2596ec22815f50a26331778326d4c1b5a4e6846a31c44295e5147fbbd5`
- `narrow-16-keyboard.png` `88986ac815ecda0e75536e04ce85a0ec10eefa7c5ca73274d999fa2c4fe62562`
The PNGs stayed in `/tmp/content-explorer-t09-smoke-05`. The dev server and the smoke container were stopped after the run.
A facet key other than `tags` still has no values in these fixtures.