Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f21a8a4
Run the summarize plugin in the browser engine
claude Oct 8, 2026
1cb6ed6
Add search, onboarding, agent tools and lenses to diffr web
claude Oct 8, 2026
2b70b05
Offer diffr web's agent tools through document.modelContext and regis…
claude Oct 8, 2026
32451ac
Keep diffr web's results in IndexedDB across reloads
claude Oct 8, 2026
775b170
Make the summarizer's pseudocode readable
claude Oct 8, 2026
c027943
Name diffr web's cache database diffr-results
claude Oct 8, 2026
3edec38
List only a lens's files while it is shown
milanb17 Oct 8, 2026
0481217
Make diffr usable on mobile with native scrolling and a file tree
milanb17 Oct 9, 2026
36d2180
Fix mobile horizontal swipes and keep background updates from disrupt…
milanb17 Oct 9, 2026
8ec26fb
Apply repository lint conventions to review fixes
milanb17 Oct 9, 2026
59283fd
Use native popover toggles and preserve exit animations
milanb17 Oct 9, 2026
273af89
fix(diffr-web): smooth mobile scrolling and sheet dismissal
milanb17 Oct 9, 2026
99d66f9
fix(diffr-web): keep touch fold controls inside the gutter
milanb17 Oct 9, 2026
bdd7b54
fix(diffr-web): avoid rebuilding idle menu triggers on close
milanb17 Oct 9, 2026
a3a434a
refactor(diffr-web): remove redundant mobile checks and comments
milanb17 Oct 9, 2026
85fba6b
test(diffr-web): tighten mobile interaction assertions
milanb17 Oct 9, 2026
800e38e
deploy(diffr-web): serve diffs.fast and diffr.fast
milanb17 Oct 9, 2026
d13f537
deploy(diffr-web): retain diffs.fast redirect
milanb17 Oct 9, 2026
19772d1
fix(diffr-web): preserve upward scrolling across file boundaries
milanb17 Oct 9, 2026
321c0cc
fix(diffr-web): clarify optional onboarding and credential setup
milanb17 Oct 9, 2026
d804041
fix(diffr-web): keep fold headers anchored across file boundaries
milanb17 Oct 9, 2026
8137f49
Use diffr.fast as the canonical app domain
milanb17 Oct 9, 2026
6c43454
Apply worker spacing conventions
milanb17 Oct 9, 2026
5697eb8
Explain GitHub rate limits and token privacy in onboarding
milanb17 Oct 9, 2026
5df4eae
Apply onboarding hint spacing convention
milanb17 Oct 9, 2026
4a007f4
Ask for plugin SDK 0.3.0 in summarize's native build too
milanb17 Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/diffr-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
},
"dependencies": {
"@dev.fast/diffr": "workspace:*",
"@dev.fast/review-protocol": "workspace:*"
"@dev.fast/review-protocol": "workspace:*",
"zod": "catalog:"
},
"devDependencies": {
"@types/trusted-types": "2.0.7",
Expand All @@ -23,6 +24,7 @@
"@vscode/diff": "0.0.2-7",
"@vscode/tree-sitter-wasm": "0.3.1",
"@webgpu/types": "0.1.66",
"playwright": "catalog:",
"typescript": "6.0.3",
"vite": "8.0.16"
},
Expand Down
120 changes: 120 additions & 0 deletions apps/diffr-web/scripts/fold-scroll-anchor.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/** Against Vite: node apps/diffr-web/scripts/fold-scroll-anchor.mjs [chromium|webkit]. */
import assert from "node:assert/strict";

import { chromium, devices, webkit } from "playwright";

import { fixture } from "./mobile-fixture.mjs";

const engine = process.argv[2] ?? "chromium";

const browser = await { chromium, webkit }[engine].launch();

try {
const page = await browser.newPage({
...devices["iPhone 13"],
deviceScaleFactor: 1,
});

await page.route("**/__ui-fixture.json", (r) =>
r.fulfill({ json: fixture() }),
);
await page.goto("http://127.0.0.1:4181/fixture/mobile/pull/1?ui-fixture");
await page
.getByRole("button", { name: "Continue without token", exact: true })
.click();
await page
.getByRole("button", { name: "Start reviewing", exact: true })
.click();
await page
.locator(".modified .view-line")
.first()
.waitFor({ timeout: 60000 });
await page
.getByRole("button", { name: "Open file tree", exact: true })
.click();
await page
.getByRole("button", { name: "src/group-0/file-1.ts", exact: true })
.click();
await page.waitForTimeout(800);
await page.locator(".app-native-scroll").evaluate((n) => {
n.dispatchEvent(new TouchEvent("touchstart", { bubbles: true }));
n.scrollTop -= 650;
});
await page.waitForTimeout(600);

const scope = page.locator(".multiDiffEntry").filter({
has: page.locator('[aria-label="Mark viewed: src/group-0/file-0.ts"]'),
});

const fold = scope.locator(
'.modified .app-touch-fold[aria-label="Fold scope at line 155"]',
);

const before = await fold.boundingBox();

const top = await page
.locator(".app-native-scroll")
.evaluate((n) => n.scrollTop);

console.log("before", before, top);

await fold.tap();
await page.waitForTimeout(500);

const after = await scope
.locator('.modified .app-touch-fold[aria-label="Expand scope at line 155"]')
.boundingBox();

console.log(
"after",
after,
await page.locator(".app-native-scroll").evaluate((n) => n.scrollTop),
);

assert.ok(
Math.abs(after.y - before.y) < 2,
`fold moved ${after.y - before.y}px`,
);

const next = page.locator(
'[aria-label="Mark viewed: src/group-0/file-1.ts"]',
);

assert.ok(
(await next.boundingBox()).y < 844,
"Collapsing should bring the following file into view",
);

for (let i = 0; i < 12; i++) {
await scope
.locator(
'.modified .app-touch-fold[aria-label="Expand scope at line 155"]',
)
.tap();
await page.waitForTimeout(100);
assert.ok(
Math.abs((await fold.boundingBox()).y - before.y) < 2,
"Expanding moved the fold header",
);
await fold.tap();
await page.waitForTimeout(100);
assert.ok(
Math.abs(
(
await scope
.locator(
'.modified .app-touch-fold[aria-label="Expand scope at line 155"]',
)
.boundingBox()
).y - before.y,
) < 2,
"Repeated folding moved the header",
);
}

console.log(
`${engine}: collapse reveals next file; 12 expand/collapse cycles preserve position`,
);
} finally {
await browser.close();
}
116 changes: 116 additions & 0 deletions apps/diffr-web/scripts/menu-close.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/** Verify hovered menu triggers do not flash during dismissal. Run against Vite on port 4181. */
import assert from "node:assert/strict";

import { chromium, devices, webkit } from "playwright";

import { fixture } from "./mobile-fixture.mjs";

const base = process.env.DIFFR_TEST_URL ?? "http://127.0.0.1:4181";

for (const [name, engine] of [
["chromium", chromium],
["webkit", webkit],
]) {
const b = await engine.launch();

try {
const p = await b.newPage({
...devices["iPhone 13"],
deviceScaleFactor: 1,
});

const data = fixture();
data.change.files = data.change.files.slice(0, 1);
await p.route("**/__ui-fixture.json", (r) => r.fulfill({ json: data }));
await p.goto(`${base}/fixture/mobile/pull/1?ui-fixture`);
await p
.getByRole("button", { name: "Continue without token", exact: true })
.click();
await p
.getByRole("button", { name: "Start reviewing", exact: true })
.click();
await p.locator(".modified .view-line").first().waitFor({ timeout: 60000 });
await p.waitForTimeout(1500);

for (const label of ["Display settings", "Theme settings"]) {
const button = p.getByRole("button", { name: label, exact: true });
await button.click();
assert.equal(await p.locator(":popover-open").count(), 1);
await button.hover();
await p.waitForTimeout(250);

const color = await button.evaluate(
(n) => getComputedStyle(n).backgroundColor,
);

await p.evaluate((label) => {
window.colors = [];
const start = performance.now();

function sample() {
const n = [...document.querySelectorAll(".app-header button")].find(
(n) => n.getAttribute("aria-label") === label,
);

window.colors.push(getComputedStyle(n).backgroundColor);

if (performance.now() - start < 550) requestAnimationFrame(sample);
}

requestAnimationFrame(sample);
}, label);
await button.click();
await p.waitForTimeout(600);
assert.equal(await p.locator(":popover-open").count(), 0);
const colors = await p.evaluate(() => [...new Set(window.colors)]);
console.log(name, label, { color, colors });
assert.deepEqual(
colors,
[color],
"hovered trigger must not flash while closing",
);
}

await p.close();
const loading = await b.newPage({ ...devices["iPhone 13"] });
let release;

const ready = new Promise((resolve) => {
release = resolve;
});

await loading.route("**/__ui-fixture.json", async (route) => {
await ready;
await route.fulfill({ json: data });
});
await loading.goto(`${base}/fixture/mobile/pull/1?ui-fixture`);
await loading
.getByRole("button", { name: "Continue without token", exact: true })
.click();
await loading
.getByRole("button", { name: "Start reviewing", exact: true })
.click();

const trigger = loading.getByRole("button", {
name: "Display settings",
exact: true,
});

await trigger.click();
release();
await loading
.locator(".modified .view-line")
.first()
.waitFor({ timeout: 60000 });
assert.equal(await loading.locator(":popover-open").count(), 1);
await trigger.click();
await loading.waitForFunction(
(title) => document.querySelector(".app-title").textContent === title,
data.change.title,
);
assert.equal(await loading.locator(":popover-open").count(), 0);
console.log(name, "deferred header update: PASS");
} finally {
await b.close();
}
}
47 changes: 47 additions & 0 deletions apps/diffr-web/scripts/mobile-fixture.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export const summary =
"// pseudocode\nCollect every matching entry, normalize its path and metadata, then render the complete result while preserving the original ordering and error details for the caller. Keep source locations available so each result can be traced back to its input.\nReturn the processed entries.";

export const plugin = {
name: "mobile-stress-summary",
description: "Deterministic summary for UI tests",
enabled: true,
code: `(file)=>{for(const side of [file.lhs,file.rhs]){if(!side)continue;const visit=(r,depth)=>{if(r.kind!=='fold')return;if(depth===1)r.visibility={collapsed:true,label:${JSON.stringify(summary)}};else r.children.forEach(c=>visit(c,depth+1));};visit(side.root,0);}}`,
};

export function fixture() {
const files = [],
texts = { base: {}, head: {} };

for (let f = 0; f < 24; f++) {
const path = `src/group-${Math.floor(f / 6)}/file-${f}.ts`;

const source = (version) =>
Array.from(
{ length: 12 },
(_, i) =>
`export function process${i}(value: number) {\n${Array.from({ length: 10 }, (_, j) => ` const result${j} = value + ${j + version}; // ${"long code content ".repeat(12)}`).join("\n")}\n return result9;\n}`,
).join("\n\n");

files.push({
path,
status: "modified",
additions: 120,
deletions: 120,
sha: `blob-${f}`,
});
texts.base[path] = source(0);
texts.head[path] = source(1);
}

return {
change: {
target: { kind: "pull", owner: "fixture", repo: "mobile", number: 1 },
title: "Mobile interaction stress fixture",
url: "https://github.com/fixture/mobile/pull/1",
base: "base",
head: "head",
files,
},
texts,
};
}
Loading