Improve example

This commit is contained in:
Andras Schmelczer 2025-06-22 21:57:45 +01:00
parent 779579d38f
commit a0cfef3238
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
5 changed files with 167 additions and 100 deletions

View file

@ -1,10 +1,7 @@
{
"jest.jestCommandLine": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" npx jest",
"jest.rootPath": "plugin",
"files.exclude": {
"**/dist": true,
"**/node_modules": true,
"**/.sqlx": true,
"**/snapshots": true,
}
}
}

View file

@ -23,39 +23,110 @@
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header>
<h1>3-Way Text Merge</h1>
<p>Use this tool to merge three versions of a text.</p>
</header>
<div class="page-wrapper">
<header>
<h1>3-Way Text Merge</h1>
<p>
The
<a
href="https://github.com/schmelczer/reconcile"
target="_blank"
>reconcile</a
>
solves a fundamental challenge in collaborative editing:
what happens when multiple people edit the same text
simultaneously?
<code
>reconcile(parent: str, left: str, right: str) ->
str</code
>
takes conflicting concurrent edits and intelligently merges
them into a unified result. Beyond basic conflict
resolution, it offers sophisticated merging heuristics,
flexible tokenization options, and cursor position tracking.
</p>
<p>
The algorithm begins with your chosen tokenizer, then
applies Myers' diff algorithm to compare the original text
with both conflicting versions. These diffs undergo
transformation to preserve meaningful change sequences,
before a final merge strategy—inspired by Operational
Transformation (OT)—reconciles all conflicting modifications
without losing any edits.
</p>
<p>
For more details, see the
<a
href="https://github.com/schmelczer/reconcile"
target="_blank"
>README</a
>.
</p>
</header>
<main>
<div class="text-area-card diamond-parent">
<label for="original">Original</label>
<textarea id="original" name="original"></textarea>
</div>
<main>
<div class="text-area-card diamond-parent">
<label for="original">Original</label>
<textarea id="original" name="original"></textarea>
</div>
<div class="text-area-card diamond-left">
<label for="left">
First concurrent edit
<div class="box Left"></div>
</label>
<textarea id="left" name="left"></textarea>
</div>
<div class="text-area-card diamond-left">
<label for="left">
First concurrent edit
<div
class="box Left"
title="Colour-coded tokens mark the origin of each token, including ones that got deleted."
></div>
</label>
<textarea id="left" name="left"></textarea>
</div>
<div class="text-area-card diamond-right">
<label for="right"
>Second concurrent edit
<div
class="box Right"
title="Indicates changes from the second concurrent edit"
></div>
</label>
<textarea id="right" name="right"></textarea>
</div>
<div class="text-area-card diamond-right">
<label for="right"
>Second concurrent edit
<div
class="box Right"
title="Colour-coded tokens mark the origin of each token, including ones that got deleted."
></div>
</label>
<textarea id="right" name="right"></textarea>
</div>
<div class="text-area-card diamond-result">
<label
><svg
<div class="text-area-card diamond-result">
<label>
Deconflicted result
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
title="Read-only. Change the above text boxes to change the content of this box."
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10 10l-6 6v4h4l6 -6m1.99 -1.99l2.504 -2.504a2.828 2.828 0 1 0 -4 -4l-2.5 2.5"
/>
<path d="M13.5 6.5l4 4" />
<path d="M3 3l18 18" />
</svg>
</label>
<div id="merged"></div>
</div>
</main>
<footer>
<p>2025 Andras Schmelczer</p>
<a
href="https://github.com/schmelczer/reconcile"
class="github-link"
aria-label="GitHub repository"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
@ -68,42 +139,12 @@
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M10 10l-6 6v4h4l6 -6m1.99 -1.99l2.504 -2.504a2.828 2.828 0 1 0 -4 -4l-2.5 2.5"
d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5"
/>
<path d="M13.5 6.5l4 4" />
<path d="M3 3l18 18" />
</svg>
Deconflicted result (readonly)</label
>
<div id="merged"></div>
</div>
</main>
<footer>
<p>2025 Andras Schmelczer</p>
<a
href="https://github.com/schmelczer/reconcile"
class="github-link"
aria-label="GitHub repository"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5"
/>
</svg>
</a>
</footer>
</a>
</footer>
</div>
<script type="module" src="script.js"></script>
</body>

View file

@ -5,13 +5,7 @@ const leftTextArea = document.getElementById("left");
const rightTextArea = document.getElementById("right");
const mergedTextArea = document.getElementById("merged");
const sampleTexts = [
"The quick brown fox jumps over the lazy dog.",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.",
"A journey of a thousand miles begins with a single step.",
"To be, or not to be, that is the question.",
];
const sampleText = `The \`reconcile\` Rust library is embedded on this page a WASM module and it powers these text boxes. Experiment with the "Original", "First concurrent edit", and "Second concurrent edit" text boxes to watch competing changes merge in real-time within the "Deconflicted result" box. Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result highly depends on the tokenization strategy, for example, deciding how casing or white-spacing is taken into account.`;
async function run() {
await init();
@ -19,6 +13,7 @@ async function run() {
originalTextArea.addEventListener("input", updateMergedText);
leftTextArea.addEventListener("input", updateMergedText);
rightTextArea.addEventListener("input", updateMergedText);
window.addEventListener("resize", resizeTextAreas);
loadSample();
updateMergedText();
@ -29,7 +24,15 @@ async function run() {
leftTextArea.selectionEnd = leftTextArea.value.length;
}
function resizeTextAreas() {
autoResize(originalTextArea);
autoResize(leftTextArea);
autoResize(rightTextArea);
}
function updateMergedText() {
resizeTextAreas();
const original = originalTextArea.value;
const left = leftTextArea.value;
const right = rightTextArea.value;
@ -48,11 +51,18 @@ function updateMergedText() {
}
function loadSample() {
const randomIndex = Math.floor(Math.random() * sampleTexts.length);
const text = sampleTexts[randomIndex];
originalTextArea.value = text;
leftTextArea.value = text;
rightTextArea.value = text;
originalTextArea.value = sampleText;
leftTextArea.value =
sampleText.replace("color", "colour") +
" Check out what's the most complex conflict you can come up with!";
rightTextArea.value = sampleText
.replace(", for example,", " such as")
.replace("WASM", "WebAssembly");
}
function autoResize(textarea) {
textarea.style.height = "auto";
textarea.style.height = textarea.scrollHeight + "px";
}
run();

View file

@ -11,24 +11,33 @@ body {
body {
font-family: "Segoe UI", Arial, sans-serif;
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ef 100%);
color: #23272f;
overflow-y: auto;
}
.page-wrapper {
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 100%;
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ef 100%);
}
header {
padding: 32px 20px 0 20px;
text-align: center;
padding: 32px 32px 0 32px;
}
header > h1 {
font-size: 2.5rem;
font-weight: 700;
color: #2451a6;
margin-bottom: 8px;
margin-bottom: 24px;
text-align: center;
}
p,
p * {
user-select: text;
}
header > p {
@ -37,16 +46,18 @@ header > p {
margin-bottom: 0;
}
header > p:not(:first-of-type) {
margin-top: 16px;
}
main {
flex: 1;
display: grid;
grid-template-rows: auto auto auto;
grid-template-rows: min-content;
grid-template-columns: 1fr 1fr;
gap: 20px;
justify-items: center;
align-items: center;
padding: 32px 12vw 32px 12vw;
min-height: 540px;
}
.diamond-parent {
@ -70,6 +81,15 @@ main {
align-items: center;
}
.diamond-result label {
display: flex;
align-items: center;
}
.diamond-result svg {
margin-left: 6px;
}
.text-area-card {
display: flex;
flex-direction: column;
@ -108,12 +128,10 @@ textarea {
resize: none;
outline: none;
margin-bottom: 0;
height: 100%;
}
#merged {
width: 100%;
text-align: left;
user-select: text;
}
@ -143,39 +161,42 @@ textarea {
@media (max-width: 768px) {
main {
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto auto;
}
main > * {
grid-column: 1;
grid-template-rows: auto auto auto auto;
}
.diamond-parent {
grid-column: 1;
grid-row: 1;
}
.diamond-left {
grid-column: 1;
grid-row: 2;
}
.diamond-right {
grid-column: 1;
grid-row: 3;
}
.diamond-result {
grid-row: 5;
grid-column: 1;
grid-row: 4;
}
}
footer {
padding: 16px;
width: 100%;
position: relative;
margin-top: 32px;
padding: 28px 0 18px 0;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
color: #5a6272;
font-size: 1rem;
box-shadow: 0 -4px 12px 0 rgba(28, 28, 87, 0.1),
0 -1px 2px 0 rgba(1, 1, 3, 0.1);
background-color: #fff;
}
.github-link > svg {

View file

@ -28,8 +28,6 @@ cargo set-version --bump $1
wasm-pack build --target web --features wasm
# Commit and tag
git add .
git commit -m "Bump versions to $TAG"