Skip to content

Align a comparison on what did not change, and refuse an identical version - #93

Merged
jwaldrip merged 1 commit into
mainfrom
feat/compare-navigation
Sep 21, 2026
Merged

jwaldrip merged 1 commit into
mainfrom
feat/compare-navigation

Conversation

@jwaldrip

Copy link
Copy Markdown
Contributor

Problem

Jason, on the comparison view:

Also it would be nice to give me the ability to jump through changes.
Also aligning the scrolls on known anchors would be great. i.e. A change may add parts to the start of the page, but change no other content, which means my synced scroll is off.
And I think for text, side by side should be the default, with the swipe one click away.
Also Can you make sure I stay in either split or side by side when I switch versions?
Dont allow duplicate versions to be uploaded in order. i.e. if version 1 and version 2 match. There is never a need for version 2.

Four of those are the comparison view. The fifth is the publish client.

What changed

Side by side is the default, and the layout is remembered. Swipe shows pixels moving but never both readings at once, so comparing prose meant remembering the left to judge the right. Swipe stays one click away for positional changes, and its slider is hidden in every layout where it does nothing. The choice survives a version switch through localStorage; before this, picking a layout and then moving between versions handed back the default that had just been rejected.

The scroll sync anchors on unchanged content. Two versions have different heights, so aligning by page fraction puts the same paragraph in two places, and the error grows with the size of the edit. Measured on the framed fixture: a version with two inserted paragraphs sat 117 pixels out. The diff now pairs identical nodes as well as changed ones, in its own id namespace, and the follower scrolls the paired node.

A change navigator, with every change addressable. Prev, next, a position that says which of how many, and a list in the sidebar whose rows jump. Each stop scrolls the pane that actually holds the change, because an addition exists in one version and a removal in the other, and asking the pane without it jumps to nothing. Before the diff has run it says it is comparing rather than claiming there are no changes.

An identical republish is refused, locally, before any request. The client keeps content_sha256 in publish state: SHA-256 over the bytes, the envelope filename and the plaintext title, so a retitle is a real version and a re-upload of the same file is not. The service cannot answer this question and is never asked, because it holds ciphertext under a fresh salt per object. It sits above the comment gate, which is the first thing in that path to touch the network, since an unchanged file is also the likeliest reason comments are still open.

Three defects the browser found, that the tests could not

  1. The frame guard accepted only one id namespace. Every message carrying an anchor was refused on the wire, so the follower sat still while the leader scrolled. Guarded now by a case that fails when either namespace is dropped.
  2. The jump lit the right node but scrolled the wrong box. It scrolled the pane element; the scroller is the prose article inside it.
  3. The light could stay on in both panes at once, because clearing was per pane rather than across the pair.

Also found while proving: the navigator claimed "No changes" before the diff had run, which reads as a finished comparison of two identical versions.

Proof

Driven in Chrome against a seeded service, on two fixtures.

Markdown pair, four changes: each of the four stops lights exactly one node, in the correct pane, at the correct scroll position, and the sidebar rows jump. Change 1 of 4 through Change 4 of 4.

Framed HTML pair, tall enough that the ends-safe fallback is not in play: 41 anchors painted on both sides, and scrolling paragraph 20 to the top of the newer version puts paragraph 20 at the same offset in the older one. Error 0 pixels, where page fractions would have been 117 out.

Layout: chosen swipe survives a version switch and a rebuild; switching back to split survives too; the slider is present only in swipe.

Duplicate refusal: describe('duplicate versions') in the MCP e2e suite, five cases, mutation-proved. Removing the gate fails four; trusting a malformed digest fails the fifth. An identical republish is refused with zero fetch calls.

Full gate: 1455 tests, 0 fail.

One gap

The duplicate refusal ships in the relic-mcp npm package, which publishes on a separate manual trigger. Merging does not put it in anybody's installed client; I will publish after merge and prove it through the published client against the live service, the same way as the version scoping.

…rsion

Side by side becomes the default for every rendered comparison and the
layout is remembered across a version switch, because a reader who picked
one and then moved between versions was handed back the default they had
just rejected. The swipe stays one click away, and its slider is hidden
wherever it would do nothing.

The scroll sync anchors on paired unchanged content instead of page
fraction. Two versions have different heights, so a fraction puts the same
paragraph in two places: measured on a framed pair, 117 pixels out. The
diff now pairs identical nodes as well as changed ones, in its own id
namespace, and the frame guard accepts both namespaces. Accepting only one
silently dropped most messages and left the follower still.

A change navigator carries prev, next, a position, and a sidebar list whose
rows jump. Each stop scrolls the pane that actually holds the change, since
an addition exists in one version and a removal in the other. Before the
diff lands it says it is comparing, never that there are no changes.

A republish whose bytes, filename and title match the live version is
refused locally, before any request. The client records a digest of that
plaintext on each publish; the service cannot answer the question, because
it holds ciphertext under a fresh salt per object. An entry written before
the digest cannot be compared, republishes once on that ground, and records
one as it lands.
@jwaldrip
jwaldrip merged commit 876b5f6 into main Sep 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant