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

14 KiB
Raw Blame History

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.