Skip to content

feat(website): add version diff table on "All versions" page#6726

Open
corneliusroemer wants to merge 19 commits into
mainfrom
diff-versions-v2
Open

feat(website): add version diff table on "All versions" page#6726
corneliusroemer wants to merge 19 commits into
mainfrom
diff-versions-v2

Conversation

@corneliusroemer

@corneliusroemer corneliusroemer commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Add version diff table to /seq/[ACCESSION]/versions to allow quick identification of what changed between versions.

Preview ready for testing (until it gets wiped): https://diff-versions-v2.loculus.org/seq/LOC_0002U7N.3/versions?compare=2%2C3

Based on #5760
Resolves #2803

Key features:

  • Users can toggle between hiding unchanged fields (on by default) and showing all fields. Changed fields have yellow background to set them apart. Fields that are always going to be different (release time etc) are greyed out.
  • Users can toggle hiding shared substitutions/indels (on by default)
  • Fields are ordered and grouped under same headers same as on sequence details page
  • URL encodes compared versions to allow sharing. By default the most recent 2 versions are selected

Caveat: There are edge cases where a sequence can change and this is not visible in this view as /details.json does not include all information required to fully reconstruct a sequence. E.g. the position of Ns could change and this would not necessarily be visible (unless it causes differences in substitutions/indels). This could be worked around by fetching sequences separately from /details.json but I decided to keep it simple for now

Additional changes

  • Fix bug in withQueryProvider function to not recreate query client on each React render through lazy initialization
  • Make some sequence details page functions reusable to avoid duplication of header grouping/ordering
  • Fix mutation badge component to only show horizontal line below segment/gene name when that name is not empty

Tests

I extended existing integration tests that revise sequences to now also check basic functionality of the diff component.

Screenshot/cast

Google Chrome 2026-06-22 16 32 03 Google Chrome 2026-06-22 16 32 14
Screen.Recording.2026-06-22.at.16.29.24.mp4

🚀 Preview: https://diff-versions-v2.loculus.org

Copilot AI review requested due to automatic review settings June 22, 2026 14:44
@claude claude Bot added the website Tasks related to the web application label Jun 22, 2026
@claude

claude Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

This PR may be related to: #2803 (Diff view of Loculus versions)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an interactive “version diff” view to the sequence All versions page (/seq/[ACCESSION]/versions) so users can quickly see what metadata/sequence-derived fields changed between two released versions, with shareable URL state.

Changes:

  • Replaces the server-rendered versions list with a React VersionsWithDiff component that supports selecting two versions and toggling diff display options.
  • Introduces diffing/rendering logic for details.json table fields (including mutation-specific diffing and section ordering aligned to the details page).
  • Extends Playwright integration coverage to validate the diff UI behavior across 2-version and 3-version accessions.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
website/src/pages/seq/[accessionVersion]/versions.astro Swaps the static version list for the new React diff-enabled versions view.
website/src/components/VersionDiff/VersionsWithDiff.tsx Main UI: version history list, selection controls, and diff table rendering with react-query.
website/src/components/VersionDiff/useVersionComparison.ts Owns version pair selection, URL persistence (?compare=), and fetching/comparing details.json.
website/src/components/VersionDiff/DiffTable.tsx Renders grouped diff table with toggles for unchanged fields and shared mutation hiding.
website/src/components/VersionDiff/DiffFieldValue.tsx Renders field values in diff cells, reusing details-page renderers where appropriate.
website/src/components/VersionDiff/compareVersions.ts Compares two details.json payloads into changed/unchanged/noisy field groups.
website/src/components/VersionDiff/mutationDiff.ts Computes mutation/indel-only differences for mutation fields (diff-only mode).
website/src/components/VersionDiff/mutationDiff.spec.ts Unit tests for mutation/indel diff logic.
website/src/components/VersionDiff/types.ts Types for comparison results and per-field comparisons.
website/src/components/SequenceDetailsPage/groupTableDataByHeader.ts New shared helper to group/order fields consistently with the details page layout.
website/src/components/SequenceDetailsPage/getDataTableData.ts Refactors table grouping to use the shared groupTableDataByHeader helper.
website/src/components/SequenceDetailsPage/DataTableEntryValue.tsx Exports FileListComponent for reuse in the diff view renderer.
website/src/components/SequenceDetailsPage/MutationBadge.tsx Avoids rendering empty segment headers for mutation containers.
website/src/components/common/withQueryProvider.tsx Fixes QueryClient lifecycle by creating it once per mounted provider (stable cache).
integration-tests/tests/specs/features/sequence-version-banners.spec.ts Extends integration test to cover diff functionality and a third revision with sequence changes.
integration-tests/tests/pages/versions.page.ts Adds a page object for interacting with and asserting the versions + diff UI.
integration-tests/tests/pages/sequence-detail.page.ts Returns the new VersionsPage object from gotoAllVersions() and removes inline assertions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread website/src/components/VersionDiff/VersionsWithDiff.tsx
corneliusroemer and others added 19 commits June 22, 2026 16:56
Add interactive diff view to compare metadata changes between sequence versions.

Features:
- Smart defaults: auto-compares when only 2 versions exist
- Checkbox selection for 3+ versions
- URL persistence with ?compare=1,3 for shareable links
- Toggle to show all fields vs only changed fields
- Noisy fields (timestamps, version info) displayed greyed out
- Fields grouped by metadata headers
- Visual highlighting of changed values with amber background

Components:
- VersionsWithDiff: Main React component with version selection
- DiffTable: Table display component for field comparisons
- compareVersions: Logic to identify changed/unchanged/noisy fields
- Integrated into existing /seq/{accession}/versions page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…syUI

Replace DaisyUI classes (checkbox, toggle, loading-spinner, alert-error)
with project-standard components (Checkbox, Spinner, ErrorBox) and
plain Tailwind. Also use Checkbox component which handles hydration
internally, eliminating the need for manual useClientFlag wiring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Refactor the version diff view to fetch data the way the rest of
seqdetails does, and fix a couple of UX issues found along the way.

- Extract selection state, URL persistence (?compare=1,2) and data
  fetching out of VersionsWithDiff into a new useVersionComparison hook,
  replacing the two ad-hoc useEffects with a single react-query useQuery.
- Validate fetched details.json at runtime with detailsJsonSchema
  instead of casting.
- Keep the previous comparison rendered while a new pair loads
  (keepPreviousData) and dim it with an overlay instead of unmounting
  the table, fixing the page scroll jumping when toggling versions.
- Render the island with client:only since it relies on browser APIs
  (window.location) that aren't available during SSR.
- Fix withQueryProvider creating a new QueryClient (and empty cache) on
  every render by constructing it lazily once via useState.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ze diff table layout

Extend the existing 'can navigate to versions page' integration test (which
already produces two released versions differing in collection date) to also
assert the version diff view:
- auto-compares the two versions ("Comparing Version 1 vs Version 2")
- shows the changed Collection date field with both old and new values
- persists the ?compare=1,2 URL param
- "Show all fields" toggle reveals an unchanged field (Collection country)

Add a dedicated VersionsPage page object for the /seq/{accession}/versions
page (version list assertions + diff-view interactions), and have
SequenceDetailPage.gotoAllVersions() return it, since it is a distinct page.

Also stabilize the DiffTable layout: switch to table-fixed with a fixed-width
field column so the column widths no longer jump when toggling "Show all
fields", and wrap long values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reuse the sequence details page's header grouping/ordering in the version
diff table so fields and header groups appear in the central, config-defined
order (orderOnDetailsPage) instead of arbitrary insertion order.

- Extract groupTableDataByHeader() shared helper from getDataTableData
- Carry orderOnDetailsPage through FieldComparison
- Use the shared helper in DiffTable

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When no ?compare param is present, the diff view now defaults to comparing
the two most recent versions (e.g. 2 & 3 when three versions exist) instead
of only auto-comparing when exactly two versions exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The displayGroup branch created the group's array only when it already
existed, so the first entry of a group dereferenced an undefined array and
threw "Cannot read properties of undefined (reading 'push')". Create the
array when the group is not yet present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rsion diff

Drop hidden fields (data use terms) from the version diff entirely, filtered
once up front in compareVersionData so neither comparison pass considers them.

Order diff sections like the sequence details page: general sections first,
then alignment/QC, then mutation details. The details page forces alignment/QC
to the bottom via DataTable section partitioning rather than mean orderOnDetailsPage,
so add a shared headerSectionRank helper and apply it as a stable sort in DiffTable.

Also rename the getDataTableData displayGroup helper to groupCustomDisplayEntries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the versions-page integration test with a third revision that changes
the sequence (one SNP + one in-frame deletion), giving three released versions.
On the versions page this exercises:
- the version-selection checkboxes shown for 3+ versions
- the default comparison of the two most recent versions (sequence-derived
  Length change shown, unchanged Collection date hidden)
- flipping the selection to compare versions 1 and 2 (Collection date diff,
  ?compare=1,2)

Add VersionsPage helpers (expectVersionSelectionAvailable, toggleVersionSelection,
expectFieldRowPresent) and drop the superfluous "(selected: x/2)" counter text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mutation/insertion/deletion fields were never shown in the version diff
because they carry an empty `value` and store their content in
`customDisplay`. The diff now carries the full table entries for each
version and renders cells with a curated, lightweight custom-display
renderer (DiffFieldValue):

- mutations/indels (badge, list, generatedBadge) render as the same
  mutation badges used on the sequence details page
- links (incl. INSDC) and file lists keep their custom views
- everything else (grouped/composite, constant, or context-dependent
  displays like geoLocation, submittingGroup, variantReference,
  dataUseTerms) falls back to a plain text representation

Comparison uses a stable key per field (flattened mutation string for
mutation fields, the raw value otherwise) so changes are detected
correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mutation/insertion/deletion cells now show only the mutations that
differ between the two compared versions: each column lists just its
removals/additions, with mutations shared by both versions dropped. This
keeps the diff readable even when a sequence has hundreds of mutations;
the full list remains available on each version's sequence page.

The set difference is computed per segment from the structured
customDisplay data (badge / list). Mutation section headers carry a
caveat noting that only differing mutations are shown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Version diff now shows the full mutation lists for both versions by
default, with a single "Only show differing mutations" checkbox that
switches all mutation-like fields (substitutions, deletions, insertions,
nucleotide and amino acid) to a diff-only view at once.

The mutation diff is computed at render time from the full entries
(rather than precomputed during comparison), so the toggle is purely a
view concern. The "differences only" caveat on mutation section headers
is shown only while the toggle is active.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Label the mutation diff toggle "Hide shared substitutions/indels"
instead of "Only show differing mutations".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…parator

Move the "Hide shared substitutions/indels" checkbox from the top of the
version diff into the first mutation section header, so it sits next to
the fields it affects instead of requiring a scroll.

Also render mutation badges/strings in the diff without the per-segment
heading separator: that heading renders as a bare horizontal line for the
common single, unnamed segment and looked out of place in the diff table.
A light segment label is now shown only when there is an actual segment
name (multi-segment organisms).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When hiding shared substitutions/indels, a mutation field that has no
version-specific entries now renders blank rather than "None". "None"
would wrongly suggest the version has no mutations at all, when it really
means nothing differs. The normal full view still shows "None".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ent name

The per-segment heading in the mutation badge/string containers rendered
as a bare horizontal separator line for the common single, unnamed
segment. Render the heading only when there is an actual segment/gene
name, fixing both the sequence details page and the version diff.

With the separator fixed at the source, the version diff drops its
bespoke segment renderers and reuses SubstitutionsContainers /
MutationStringContainers directly again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@corneliusroemer corneliusroemer added the preview Triggers a deployment to argocd label Jun 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this page should be called Compare Versions instead of All Versions - I am not sure I would have found it as a normal user

Comment thread website/src/components/common/withQueryProvider.tsx
Comment thread website/src/components/VersionDiff/VersionsWithDiff.tsx
Comment thread website/src/components/common/withQueryProvider.tsx
<li key={version.version} className='mb-4'>
<div className='flex items-start gap-3'>
{showCheckboxes && (
<Checkbox

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think eventually we should change this to two columns of radio buttons - can make an issue some time - but this is fine for now

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(wikipedia is a good example)

'CAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCT' +
'ACTAATACATCAGGGGATGCACATGG';

// TEST_SEQUENCE with one point mutation (SNP) and one in-frame (3-nt) deletion, used to

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests do not appear to check that these sequence mutation differences are shown on the comparison page?

}

/** Returns the segments/strings of `a` that are not present (per segment) in `b`. */
function subtractSegmentedStrings(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are insertions and deletions right? maybe we should add to doc string

@anna-parker anna-parker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great! I revised metadata and sequences across all organisms (includings EVs and multi-seg, multi-ref) and this works great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Triggers a deployment to argocd website Tasks related to the web application

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diff view of Loculus versions

4 participants