Repository navigation
feat: add the instrument hub and record provenance - #1807
Open
thomasbeaudry wants to merge 10 commits into
Open
thomasbeaudry wants to merge 10 commits into
thomasbeaudry wants to merge 10 commits into
Conversation
Second half of the datahub revamp, on top of the subject hub. Record provenance: `Session.type` reaches the client on every record, and the export carries `seriesId` beside a `seriesName` resolved to `en` by the worker, which has no request language. Every record table gains COLLECTION_METHOD and SERIES columns. The instrument hub at `/datahub/instruments` lists one row per edition of each FORM, INTERACTIVE and FILE instrument, with a detail page holding a table and a chart over the records of that one edition. Filters live in the URL and are applied inside the data hook, so the download cannot disagree with the table. `summarizeBySubject` is generalised into a private `summarizeRecords(by, …)` now shared with `summary/by-instrument` and `summary/by-series`; the subject view alone did not justify the parameter, which is why PR1 shipped it inline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correctness: - the kind filter rendered the raw enum, so the menu offered "FORM" while the cell beside it said "Form". Both now read one `useInstrumentKindLabels` map, keyed over `InstrumentKind` so a new kind fails to compile rather than reaching the screen untranslated - the method filter exempted a record carrying no method, so unchecking every method still left rows on screen. `methods` becomes optional, matching `series`: absent means every method, and a record with none survives the unfiltered view but no explicit selection - a series' member rows were all tagged FORM; the member's own kind is carried through instead - the scatter chart read its mark ring off the tooltip's background, so restyling the tooltip moved the ring. `CHART_SURFACE` names it Dead code from the WIP snapshot: an export-options ternary after the early return that made it unreachable, and a doc block attached to nothing in `tag-colors.ts`. De-duplication: `useSeriesNames` and `useInstrumentTitles` were the same four lines twice, so they collapse into `useInstrumentInfoById(params)`, which carries the kind the member table needed. The two identical summary mappers share `toInstrumentSummaries`. `COLOUR_BY_OPTIONS` is now the single source the search param derives from. `InitData` is renamed `InitInstruments`, since it names only the instruments. `useSubjectRecordSummaryQuery` and `useInstrumentRecordSummaryQuery` are deliberately left alone: the shape they share is the queryOptions-plus- hook pairing that apps/web/AGENTS.md requires of every prefetched hook. Tests: the seven new hooks and `InstrumentTable` gain unit tests. The e2e asserting the hub lists no series is replaced by one that drives what it now does, since the original passed only because the fixture group had none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`TabLink` navigated with `to` alone, so TanStack dropped the current search params and the newly mounted tab silently widened the selection — the opposite of what putting the filters in the URL was for. The e2e that exists to catch this was passing anyway: it asserted the URL matched /methods=/, and `methods` carried a default of all three collection methods, so the param was present whether or not the filter had survived. Making `methods` optional removed the default and exposed the bug underneath. Also stops the graph spec reaching the tab through `getPageModel`, whose exact-URL assertion the route's own search-param defaults fail; it clicks the tab as a user would instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: GPT-6
gdevenyi
requested changes
Oct 14, 2026
gdevenyi
left a comment
Contributor
There was a problem hiding this comment.
This branch has merge conflicts with main, so review is on hold.
Please merge or rebase on the current main, resolve the conflicts, run pnpm lint and pnpm test:coverage locally, and push. Review continues when the branch merges cleanly and CI is green.
Reviewed at commit f6d3f82.
…nt-hub # Conflicts: # apps/api/src/instrument-records/__tests__/instrument-records.service.test.ts # apps/api/src/instrument-records/instrument-records.service.ts # apps/web/src/routes/_app/datahub/subjects/$subjectId/graph.tsx
…raph `main` removed `datahub.visualization.selectMeasures` as dead code once the subject graph stopped using it. The instrument hub's graph route reads it, so the merge left the key missing and `tsc` rejecting the call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`main` now gates CI on 100% unit coverage, which this branch's new code did not meet. Adds the missing cases and removes three fallbacks no input can reach. The scatter tooltip's `formatter` compared the row name against `time`, but recharts passes the axis name, so the comparison never held and the collection date rendered as a raw epoch. Its `labelFormatter`, which did the formatting, is never called for a scatter. The date is now formatted in the row recharts actually names after the x axis. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…anches Also drops the export mapping's `?? 'N/A'` for a username the records mapping has already defaulted, which no input could reach. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers the instrument record table's filters, navigation and row actions, the listing's preview and spreadsheet export, and the remaining chart branches. Removes three things no input reaches: the `measureColumns` branch building an INSTRUMENT column for a series, whose page returns the member listing before that table is ever rendered; the toolbar's fallbacks for columns it declares itself and seeds filters for; and `html2canvas`, which `renderChartPng` replaced. Two types exported only for their own file go with them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d urls Two specs that could not run while the branch conflicted with main: - The series cell renders a translated "None" rather than an em-dash, which `f6d3f8295` changed without the spec following. - The subject's tab links now carry the chart palette, so a link click lands on `…/graph?palette=default` and never passes through a query-free url. A `**` glob cannot match that, so the wait is a regex allowing the query string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an instrument view of collected data alongside the subject hub from #1654 (merged). Users can inspect one instrument edition across subjects, or open a series to see what its members collected.
Merging
main#1654 merged, which conflicted this branch.
7b17710e4mergesmain(432dec811). Four conflicts:instrument-records.service.ts—maindeletedInitialMessageas unreachable while this branch renamedInitDatatoInitPayload/InitInstruments. Kept this branch's names without the deleted type;main's removal of themessage.successcheck on the INIT acknowledgement merged cleanly.instrument-records.service.test.ts— tookmain's deletion of the test pinning a defectmainhas since fixed.subjects/$subjectId/graph.tsx— took this branch'srenderChartPngrewrite whole.main's side was a cleanup inside thehtml2canvasblock that rewrite replaces.datahub.visualization.selectMeasures—mainremoved it as dead once the subject graph stopped using it, but the instrument graph still reads it, so it is restored in5657bbc84.This branch forked before two gates
mainhas since added, so it had never been measured against either:odc/unit-test-names-subject). Three flat specs underapps/web/src/__tests__/moved beside the files they test.pnpm test:coverage). 21 files were short, seven of them at 0%: both chart components,SelectChartPalette, and four instrument hub route files.Defects the coverage gate exposed
Three branches turned out to be unreachable because the code around them was wrong:
formattercompared the row name against'time', but recharts passes the axis name, so the comparison never held and both rows fell through to the measure label. ThelabelFormatterthat did the date formatting is never called for aScatterChartat all. The date is now formatted in the row recharts actually names after the x axis.instrument-hub.spec.tsexpected an em-dash in the series cell.f6d3f8295changed that cell to a translated "None" without updating the spec, which could not run while the branch conflicted.subject-detail.spec.tswaited on the glob**/graph. The subject tab links now carry the chart palette, so clicking one lands directly on…/graph?palette=defaultand the URL never passes through a query-free state — a**glob cannot match it, and the test timed out while sitting on the correct tab. The wait is now a regex allowing the query string.Code removed rather than covered
Each of these is unreachable, so it is deleted rather than pinned by a test:
isSeriesbranch ofmeasureColumnsininstruments/$instrumentId/table.tsx, which built an INSTRUMENT column for aDataTablethat function's own early return means is never rendered.InstrumentTabletoolbar's?./??fallbacks for the three columns it declares itself and seeds filters for ininitialState.getCategoricalColor's neutral fallback (the index is always belowMAX_CATEGORICAL_GROUPS, and every theme holds six hues),binDistribution's second?? 0(that row is pre-seeded at zero for every series key), and the export mapping's?? 'N/A'for a username the records mapping has already defaulted.html2canvas, whichrenderChartPngreplaced, together with its lockfile entries.RecordChartPointandDistributionRow, exported but used only in their own file.Validation on
e6d8421f8pnpm lint— 34/34, and green again on an immediate second pass. The second pass matters: each workspace's lint istsc && eslint --fix src, sotscruns before the fixer and a single green pass says nothing about the file left on disk.pnpm test:coverage— 4,649 passed and 4 expected failures; 100% statements, branches, functions and lines.pnpm knip— clean, apart from the pre-existingturboinignoreBinarieshint.pnpm test:e2e— 285/286 locally. The one failure isrecord-file-deletion.spec.ts, which 503s on record creation because a bare local stack starts no S3/rustfs backend. It passes in CI.e6d8421f8: every check green, including thelint-and-testjob that runs lint, the coverage gate and the full Playwright suite.Validation notes
--workers=1; it was contention on the shared database under six local workers, not a regression.apps/web/src/route-tree.tswas checked against the routes on disk and is in sync — every instrument hub route is registered. The generator was not run, per the repository rule that the user regenerates it.Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Co-Authored-By: GPT-6
🤖 Generated with Claude Code