[EPAC-962]: Bill diff viewer — compare versions across readings#809
Merged
riddim-developer-bot[bot] merged 1 commit intoJun 14, 2026
Conversation
Adds an iOS "Compare versions" entry point on the bill detail page that
opens a sheet showing a clause-level diff between two published bill
versions. Pickers select the from/to version; a segmented toggle in the
toolbar switches between inline and side-by-side presentations. Added,
removed, modified, and unchanged clauses are colour-coded (green / red /
amber / neutral) with the verbatim before/after clause text rendered
inline; a Hansard anchor link surfaces the chamber speech that
introduced the change when the backend records one. When only one
version has been published the sheet shows an empty state explaining
the comparison will become available once a second version lands.
Domain: `BillVersion`, `BillVersionDiff`, `BillClauseDiff`,
`BillClauseChangeType` entities; `BillVersionsRepository`,
`BillVersionDiffRepository` ports; `LoadBillVersions`,
`LoadBillVersionDiff` use cases.
Data: `BackendBillVersionsRepository` (reuses the existing
`GET /api/v1/bills/{id}` bill-depth endpoint to read the versions list),
`BackendBillVersionDiffRepository` (calls the new
`GET /api/v1/bills/{id}/diff?from=…&to=…` endpoint). Both treat 204/404
as "absent" (hide entry point / show unavailable state); the diff
algorithm and version-text ingestion remain backend responsibilities.
Tests: use-case stubs for both loaders, network-mocked repository tests
covering decode + 204/404/error paths, and snapshot tests for the inline
mode, side-by-side mode, the "no clause-level changes" path, and the
"only one version published" empty state.
OpenAPI: documents the new `/bills/{id}/diff` endpoint and the
`BillVersionDiff`/`BillClauseDiff` schemas.
Use-case catalog: adds `LoadBillVersions` and `LoadBillVersionDiff`
entries and expands the `BillVersion` / `BillClauseDiff` entity rows.
Release-Note: Bill page — tap "Compare versions" to see exactly what
changed between two published versions of a bill, clause by clause.
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.
Summary
Adds the iOS "Compare versions" surface for EPAC-962. The bill detail page now exposes a button that opens a sheet showing a clause-level diff between two published versions of the bill. Users can pick which two versions to compare and toggle between an inline (before-then-after) and a side-by-side presentation. Added / removed / modified / unchanged clauses are colour-coded with the verbatim before/after clause text rendered inline. When the backend records the Hansard anchor for the speech that introduced a change, the row links out to it. When only one version has been published, the sheet renders an empty state. Verbatim text only — no LLM summaries anywhere on the surface.
The backend (versions ingestion + diff endpoint) was previously deployed to staging per the parent project plan; this PR is the iOS-only consumption side.
Architecture
Clean Architecture Shape on iOS:
BillVersion,BillVersionDiff,BillClauseDiff,BillClauseChangeType(ios/epac/Domain/Entities/).BillVersionsRepository,BillVersionDiffRepository(ios/epac/Domain/Ports/).LoadBillVersions,LoadBillVersionDiff(ios/epac/Application/).BackendBillVersionsRepositoryreuses the existingGET /api/v1/bills/{id}depth endpoint to pull the versions list.BackendBillVersionDiffRepositorycalls the newGET /api/v1/bills/{id}/diff?from=…&to=…endpoint. Both treat 204 / 404 as "absent" — the bill page hides the entry point when there are no versions; the diff viewer renders an unavailable state when the backend has no diff for the chosen pair.BillVersionsDiffViewowns the picker + toggle state and orchestrates the diff load;BillVersionsDiffContentis the pure-rendering surface used by snapshot tests.The clause-aware diff algorithm lives in the backend; iOS only renders the structured result. Boundary rule: no paraphrasing, no LLM summaries — only the verbatim clause text and the structured anchors the backend supplies.
API contract (new)
GET /api/v1/bills/{id}/diff?from={fromVersionID}&to={toVersionID}— added tobackend/openapi/openapi.jsonalong with theBillVersionDiffandBillClauseDiffschemas. Response shape (abridged):{ "from": { "id": "C-8-v1", "label": "First reading", ... }, "to": { "id": "C-8-v3", "label": "As passed by the House", ... }, "clauses": [ { "id": "clause-3", "label": "Clause 3", "change_type": "added", "from_text": "", "to_text": "The Minister shall publish quarterly progress reports to Parliament.", "hansard_anchor_url": "https://www.ourcommons.ca/.../hansard#clause-3" } ] }change_typeis one ofadded | removed | modified | unchanged; the iOS adapter normalises common synonyms (inserted,deleted,replaced,context, …) so the backend can stay flexible.Acceptance criteria (iOS scope)
Use-case catalog
Adds
LoadBillVersionsandLoadBillVersionDifftodocs/architecture/use-case-catalog.mdwith their Clean Architecture shape and boundary rules. ExpandedBillVersion, addedBillVersionDiffandBillClauseDiffentity rows.Test plan
xcodebuild buildon iPhone 17 Pro simulator — clean (no errors, no new warnings).swiftlint --stricton the changed files — clean.LoadBillVersionsTests,LoadBillVersionDiffTests— use-case stubs cover the success / nil / empty / error paths.BillVersionsRepositoryTests,BillVersionDiffRepositoryTests— network-mocked, asserting decode + 200 / 204 / 404 / 500.BillVersionDiffRepositoryTests.changeTypeMapsKnownSynonyms— asserts every synonym the backend may emit normalises correctly.SnapshotTests—BillVersionsDiff_inline,BillVersionsDiff_sideBySide,BillVersionsDiff_noChanges,BillVersionsDiff_emptyOnlyOneVersion, each in light / dark / accessibility-large variants (12 reference images committed).Verification evidence
Inline diff (snapshot, light mode):
The rendered diff lists clauses in order: an unchanged context clause, an added clause (green block + Hansard link), a modified clause (red strike-through on the prior text, green replacement below), and a removed clause (red block). Side-by-side mode lays the same clauses out as a two-column "Before" / "After" comparison. The empty state renders a
doc.text.magnifyingglassglyph plus the "Only one version published" message when the bill has only one version.🤖 Generated with Claude Code