Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7f2535b
perf(transfers): freeze history pages with snapshot cursors
woahwhattheheck Sep 24, 2026
6672617
Fix snapshot cursor binding and preserve missing rows
woahwhattheheck Sep 26, 2026
13c1cd2
Cover missing rows and out-of-scope cursor recovery
woahwhattheheck Sep 26, 2026
d02dc97
fix(transfers): bind page cursor to preceding row
woahwhattheheck Sep 26, 2026
914a3e3
fix(transfers): refresh fields across frozen snapshot filters
woahwhattheheck Oct 3, 2026
4e02ada
fix(transfers): keep empty and selection states scoped to snapshot
woahwhattheheck Oct 4, 2026
b8c2226
fix(transfers): isolate same-tick snapshot cursor generations [skip ci]
github-actions[bot] Oct 4, 2026
aa146d6
fix(transfers): clear selections when snapshot membership recovers
woahwhattheheck Oct 4, 2026
157ff7b
fix(ci): restore existing transfer-flow tests and formatting
woahwhattheheck Oct 4, 2026
409bc61
docs(ci): publish complete PR297 gate evidence
woahwhattheheck Oct 4, 2026
d86d94b
docs: remove publication byline from validation report [skip ci]
woahwhattheheck Oct 4, 2026
db690af
docs(validation): remove optional report byline [skip ci]
woahwhattheheck Oct 4, 2026
2ae9335
perf(frontend): prepare snapshot sort keys once per row
woahwhattheheck Oct 4, 2026
5ef95f7
perf(frontend): integrate measured snapshot sort improvement
woahwhattheheck Oct 4, 2026
45b6090
perf(frontend): bound snapshot page lookups to visible IDs
woahwhattheheck Oct 4, 2026
d81632a
docs(perf): retain snapshot paging measurement program [skip ci]
woahwhattheheck Oct 4, 2026
3bb8747
docs(perf): record bounded page lookup results and limits [skip ci]
woahwhattheheck Oct 4, 2026
afa3b0e
perf(history): stop snapshot lookup after resolving visible rows
woahwhattheheck Oct 4, 2026
6ed4086
fix(transfers): retain other pages when deselecting an all-page selec…
woahwhattheheck Oct 4, 2026
521ef15
docs: record focused across-page selection regression results
woahwhattheheck Oct 4, 2026
9f1debe
perf(transfers): avoid clock allocation when the date filter is inactive
woahwhattheheck Oct 4, 2026
cc7d859
docs(perf): retain native date-filter benchmark and compatibility checks
woahwhattheheck Oct 4, 2026
1ee074a
docs(perf): record inactive-date-filter timing and source-bound valid…
woahwhattheheck Oct 4, 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
58 changes: 58 additions & 0 deletions docs/performance/date-range-clock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Date-filter clock allocation

The `All time` filter previously constructed a default `Date` for each transfer before returning `true`. Production commit `9f1debe8fb5ba5f79fc6fe83349d4264faa7b13b` defers that clock until a nonzero date range needs it. Explicit caller clocks, inclusive cutoffs, invalid-date handling, and active-range behavior are preserved. The selection repair and peer pagination optimization remain intact.

## Native measurement — 4 October 2026

Node **22.16.0**, Linux x64, 100,000 records. Three warmups per implementation, then nine alternating paired samples. Times measure `Array.filter` calling the complete production helper, not rendering, API calls, storage, or full-page latency. Native clocks are used during timing; fixed-clock compatibility checks and allocation instrumentation are separate and untimed. Output membership, order and object references matched.

All time median: **12.115259 → 4.508355 ms**, **62.79% less time**. Untimed default-clock allocations fall **100,000 → 0**.

The active 30-day control measured **62.660949 → 60.724011 ms** with **100,000 default-clock allocations in both versions**. That small difference is not treated as a material active-filter improvement; raw samples vary.

Raw paired milliseconds (baseline, candidate), in execution order:

```text
All time:
11.827676 4.604149
12.521932 4.508355
12.354159 4.730149
12.115259 4.209475
11.933276 4.286680
12.185305 4.649849
12.091634 4.467593
12.650215 4.578040
12.020006 4.353351

30d:
57.947415 61.450144
67.611370 59.298888
62.732698 61.345878
59.746007 60.724011
62.942054 58.739440
62.660949 61.517115
57.514723 58.042810
63.476268 61.369984
59.706392 59.051290
```

Baseline helper blob `ec341ab656c3091f5f2d37037e7e2e4c6214bf08`, SHA-256 `091b841c006388822c59e0282aa8a5be3124870a678907218581aaf1cab6b660`. Executed/published candidate blob `697f74780073bc37775051341776eb54efc99b65`, SHA-256 `289356fe227fe6dd540326ae8637d584358a17355267c4613ae84d27b2f29006`.

## Reproduce

From the repository root with Node.js installed:

```sh
tmp=$(mktemp -d)
git show 521ef150b7da2a0d93bebfd9537a5ce1c9d2897f:src/utils/dateRange.js > "$tmp/baseline.mjs"
git show 9f1debe8fb5ba5f79fc6fe83349d4264faa7b13b:src/utils/dateRange.js > "$tmp/candidate.mjs"
node docs/performance/date-range-clock.mjs "$tmp/baseline.mjs" "$tmp/candidate.mjs"
```

The driver checks omitted and supplied clocks, empty/invalid/oversized range tokens, invalid timestamps and the inclusive boundary. Its control fixture uses fixed historical timestamps; active-range result counts change with the real run date. The recorded run retained 100,000 All time records and 30,000 30-day records. Timings are environment-specific observations, not a universal multiplier.

## Existing application checks

[Native run 37206173166](https://github.com/woahwhattheheck/RemitFlow-Frontend/actions/runs/37206173166) is successful on pinned source `9f1debe8fb5ba5f79fc6fe83349d4264faa7b13b`. All **eight unchanged** `test/integration/transfers-filter.test.jsx` cases pass, including no-filter display, date preset selection and URL restoration. The original package lock is installed with `npm ci --no-audit --no-fund`; scoped source formatting and the clean-worktree check pass. No new test file or dependency was added for this continuation.

The separate five-case selection result remains bound to production commit `6ed4086fa9520c36639b5419a42145951a16cd9a` in `docs/validation/across-page-deselection.md`; it is not added to a current-head suite total. No full build, complete suite, deployed throughput, acceptance or payout is asserted here.
72 changes: 72 additions & 0 deletions docs/performance/date-range-clock.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { performance } from 'node:perf_hooks';
import { pathToFileURL } from 'node:url';
import crypto from 'node:crypto';

const [baselinePath, candidatePath] = process.argv.slice(2);
if (!baselinePath || !candidatePath) throw new Error('Usage: node date-range-bench.mjs BASELINE CANDIDATE');
const baseline = (await import(pathToFileURL(baselinePath))).isWithinDateRange;
const candidate = (await import(pathToFileURL(candidatePath))).isWithinDateRange;
const realDate = Date;
const fixedNow = '2026-10-04T12:00:00.000Z';
let clockAllocations = 0;
class FixedDate extends realDate {
constructor(...args) {
if (!args.length) { clockAllocations++; super(fixedNow); }
else super(...args);
}
}
const timestamps = ['2026-09-27T11:59:59.999Z', '2026-09-27T12:00:00.000Z', '2026-10-04T12:00:00.000Z', '2026-10-05T12:00:00.000Z', 'invalid', null, 0];
const ranges = ['', undefined, null, '0d', 'bad', '7d', '30d', '90d', '9'.repeat(320) + 'd'];
const clocks = [undefined, new Date(fixedNow), new Date('invalid')];
globalThis.Date = FixedDate;
let comparisons = 0;
try {
for (const created of timestamps) for (const range of ranges) for (const now of clocks) {
assert.equal(candidate(created, range, now), baseline(created, range, now));
comparisons++;
}
for (const fn of [baseline, candidate]) {
assert.equal(fn('2026-09-27T11:59:59.999Z', '7d'), false);
assert.equal(fn('2026-09-27T12:00:00.000Z', '7d'), true);
assert.throws(() => fn(fixedNow, '7d', null), TypeError);
}
} finally { globalThis.Date = realDate; }

const rows = Array.from({ length: 100_000 }, (_, i) => ({ id: i, createdAt: new Date(Date.parse(fixedNow) - (i % 100) * 86400000).toISOString() }));
const allocationCounts = [];
for (const range of ['', '30d']) {
const counts = [];
globalThis.Date = FixedDate;
try {
for (const fn of [baseline, candidate]) {
clockAllocations = 0;
rows.filter(row => fn(row.createdAt, range));
counts.push(clockAllocations);
}
} finally { globalThis.Date = realDate; }
allocationCounts.push({ range, baseline: counts[0], candidate: counts[1] });
}
const workloads = [];
const median = xs => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)];
for (const range of ['', '30d']) {
// Native clock is used during measurement. Fixtures are hours from any edge.
const before = rows.filter(row => baseline(row.createdAt, range));
const after = rows.filter(row => candidate(row.createdAt, range));
assert.equal(before.length, after.length);
before.forEach((row, i) => assert.equal(row, after[i]));
const run = fn => { const start = performance.now(); const result = rows.filter(row => fn(row.createdAt, range)); return { ms: performance.now() - start, count: result.length }; };
for (let i = 0; i < 3; i++) { run(baseline); run(candidate); }
const samples = [];
for (let i = 0; i < 9; i++) {
let a, b;
if (i % 2) { b = run(candidate); a = run(baseline); }
else { a = run(baseline); b = run(candidate); }
assert.equal(a.count, b.count);
samples.push({ baseline_ms: a.ms, candidate_ms: b.ms });
}
workloads.push({ range, count: before.length, baseline_median_ms: median(samples.map(s => s.baseline_ms)), candidate_median_ms: median(samples.map(s => s.candidate_ms)), samples });
}
const hash = path => crypto.createHash('sha256').update(fs.readFileSync(path)).digest('hex');
console.log(JSON.stringify({ node: process.version, platform: process.platform, arch: process.arch, baseline_sha256: hash(baselinePath), candidate_sha256: hash(candidatePath), comparisons, allocationCounts, workloads }, null, 2));
70 changes: 70 additions & 0 deletions docs/performance/snapshot-early-stop-20261004/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Snapshot page lookup: early termination

This continuation of PR297 changes only live-record lookup in
`src/utils/transferSnapshot.js`. The preceding bounded lookup remains; no
full-history map or persistent cache is introduced.

For ordinary arrays, scanning backward makes the first match the last duplicate
in input order. Removing resolved IDs from the page-sized set permits an early
stop once every visible ID is found. Empty pages read no records. Missing IDs
still require a full scan and retain their snapshot fallback. Sets, generators,
and arrays with custom iterators keep the forward-only duplicate policy.

## Executed source and results

Baseline commit: `3bb8747d62c1e9690f9a1559c7f6d73f792bd9d1`.
Baseline source blob: `74893db1e8b99de594c40305b1428f83ae3c7926`.
Candidate source blob: `f12b9a0163b8d47b8d0e903764245ae18a1ea2a2`.

Node v22.16.0, Linux x64, AMD EPYC 9V74. Complete unmodified modules were
imported, not extracted functions. Nine alternating pairs, twenty calls per
sample, three warmup samples. Medians are milliseconds per `pageFromSnapshot`
call; source loading, fixture/snapshot creation and assertions are untimed.

| 100,000-row workload, five-row page | Before | After |
| --- | ---: | ---: |
| Ascending input, first/newest page | 2.793942 | 0.007302 |
| Descending input, first/newest page | 2.754172 | 2.726743 |
| One visible ID missing (99,999 live rows) | 2.621030 | 2.336080 |
| Empty filtered snapshot | 1.590464 | 0.002271 |
| Last duplicate appended (100,001 live rows) | 2.790019 | 0.005145 |
| Ascending input, last/oldest page | 2.798958 | 2.804947 |

All six complete output objects and returned record references agreed.
Three focused assertion groups passed: duplicate/fallback/cursor/iterable
compatibility; empty-page work elimination; early termination and missing-ID
full-scan behavior. Untimed access counts distinguished actual work: an empty
page read 25 to 0 IDs; a five-row tail page over 30 rows read 35 to 10 IDs
(including the second ID read used to insert a match). Frozen inputs and the
snapshot stayed unchanged. Scope rejection, expiry and page-two cursor behavior
were included in the compatibility group.

These are input-position-dependent improvements, not a general 100,000-row
constant-time claim. The last-page full-scan median was essentially unchanged,
slightly higher in this run. Full scans remain O(N), with O(page size) auxiliary
storage. No browser, rendering, storage, provider, quota or whole-application
speedup is asserted. Prior benchmark ratios must not be multiplied by these
results.

## Reproduce

From a checkout containing this continuation:

```sh
work=$(mktemp -d)
git show 3bb8747d62c1e9690f9a1559c7f6d73f792bd9d1:src/utils/transferSnapshot.js > "$work/base.mjs"
cp src/utils/transferSnapshot.js "$work/candidate.mjs"
node docs/performance/snapshot-early-stop-20261004/check-and-bench.mjs \
"$work/base.mjs" "$work/candidate.mjs"
```

`results.json` retains every timed sample and source identity. The script uses
only Node built-ins and exits nonzero on an assertion failure. The first local
invocation stopped before imports because its argument mapper passed unwanted
Array.map parameters to path.resolve; that script-only error was corrected before
the retained execution. No product change was made in response to that error.

No dependency installation, Vitest suite, production build, new workflow or
external provider call was performed for this continuation. Existing maintained
tests were not changed or weakened; their historical results are not represented
as a new run at this source. Sponsor review and acceptance remain separate.
159 changes: 159 additions & 0 deletions docs/performance/snapshot-early-stop-20261004/check-and-bench.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Usage: node check-and-bench.mjs <baseline-module> <candidate-module>
// Complete source modules are imported, not extracted or rewritten functions.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { pathToFileURL } from 'node:url';
import { resolve } from 'node:path';
import { performance } from 'node:perf_hooks';
import os from 'node:os';

const [basePath, nextPath] = process.argv.slice(2).map((path) => resolve(path));
if (!basePath || !nextPath) throw new Error('Provide baseline and candidate module paths');
const base = await import(pathToFileURL(basePath).href);
const next = await import(pathToFileURL(nextPath).href);
const now = Date.parse('2026-10-04T12:00:00Z');
const options = { now };
const row = (id, i, extra = {}) => ({
id,
createdAt: new Date(now - 1_000_000 + i).toISOString(),
recipient: `recipient-${id}`,
status: 'pending',
...extra,
});
const snapshotOf = (rows, name, pageSize = 5) =>
base.createTransferSnapshot(rows, { now, pageSize, snapshotId: name });
const equivalent = (snapshot, live, opts = options) => {
const before = base.pageFromSnapshot(snapshot, live, opts);
const after = next.pageFromSnapshot(snapshot, live, opts);
assert.deepEqual(after, before);
after.items.forEach((item, i) => assert.equal(item, before.items[i]));
return after;
};
const regressions = [];

// 1. Last duplicate wins, missing rows fall back, pagination and stale scopes
// stay identical. Forward-only and custom array iterators keep their ordering.
{
const original = Array.from({ length: 7 }, (_, i) => row(`r${i}`, i));
const snapshot = snapshotOf(original, 'compat');
const duplicate = { ...original[6], status: 'completed' };
const live = Object.freeze([...original.filter((r) => r.id !== 'r5'), duplicate]);
const captured = JSON.stringify(snapshot);
const first = equivalent(snapshot, live);
assert.equal(first.items[0], duplicate);
assert.equal(first.items[1], snapshot.items[1].record);
equivalent(snapshot, live, { now, cursor: first.nextCursor });
equivalent(snapshot, live, { now, cursor: 'bad' });
equivalent(snapshot, live, { now, filters: { status: 'failed' }, cursor: first.cursor });
equivalent(snapshot, live, { now: now + base.SNAPSHOT_TTL_MS });
equivalent(snapshot, new Set(live));
const custom = [...live];
custom[Symbol.iterator] = function* () { yield* live.slice().reverse(); };
equivalent(snapshot, custom);
const generator = () => (function* () { yield* live; })();
const expected = base.pageFromSnapshot(snapshot, generator(), options);
const actual = next.pageFromSnapshot(snapshot, generator(), options);
assert.deepEqual(actual, expected);
actual.items.forEach((value, i) => assert.equal(value, expected.items[i]));
equivalent(snapshot, null);
assert.equal(JSON.stringify(snapshot), captured);
regressions.push('duplicate/fallback/cursor/iterable compatibility');
}

// 2. Empty snapshot skips all live rows; count accesses outside timed work.
{
const snapshot = snapshotOf([], 'empty');
let reads = 0;
const live = Array.from({ length: 25 }, (_, i) => ({ get id() { reads++; return `r${i}`; } }));
const expected = base.pageFromSnapshot(snapshot, live, options);
assert.equal(reads, 25);
reads = 0;
assert.deepEqual(next.pageFromSnapshot(snapshot, live, options), expected);
assert.equal(reads, 0);
regressions.push('empty snapshot: 25 to 0 live-ID reads');
}

// 3. Complete tail match stops after its five records, even with older
// duplicates earlier in the array. A missing ID still inspects all rows.
{
const original = Array.from({ length: 30 }, (_, i) => row(`r${i}`, i));
const snapshot = snapshotOf(original, 'tail');
let reads = 0;
const observe = (records) => records.map((r) => ({
...r, get id() { reads++; return r.id; },
}));
const live = observe(original);
equivalent(snapshot, live);
reads = 0;
next.pageFromSnapshot(snapshot, live, options);
// The matched id is read for delete and Map insertion, as in the baseline.
assert.equal(reads, 10);
reads = 0;
base.pageFromSnapshot(snapshot, live, options);
assert.equal(reads, 35);
const missing = observe(original.filter((r) => r.id !== 'r29'));
reads = 0;
next.pageFromSnapshot(snapshot, missing, options);
assert.equal(reads, 29 + 4);
regressions.push('tail early-stop and missing-ID full-scan');
}

const rows = Object.freeze(Array.from({ length: 100_000 }, (_, i) => Object.freeze(row(`t${i}`, i))));
const full = snapshotOf(rows, 'large');
const empty = snapshotOf([], 'empty-large');
const reversed = Object.freeze(rows.slice().reverse());
const missing = Object.freeze(rows.slice(0, -1));
const duplicateTail = Object.freeze([...rows, { ...rows.at(-1), status: 'completed' }]);
const workloads = [
['ascending-first-page', full, rows, options],
['descending-first-page', full, reversed, options],
['missing-one-visible-id', full, missing, options],
['empty-filtered-snapshot', empty, rows, options],
['duplicate-tail', full, duplicateTail, options],
['ascending-last-page', full, rows, { now, cursor: base.encodeCursor({
snapshotId: full.id, scope: full.scope, page: 20_000, after: full.items[99_994],
}) }],
];
let sink = 0;
const measure = (mod, snapshot, live, opts) => {
const start = performance.now();
for (let i = 0; i < 20; i++) sink += mod.pageFromSnapshot(snapshot, live, opts).items.length;
return (performance.now() - start) / 20;
};
const median = (values) => [...values].sort((a, b) => a - b)[Math.floor(values.length / 2)];
const measurements = [];
for (const [name, snapshot, live, opts] of workloads) {
equivalent(snapshot, live, opts);
for (let warm = 0; warm < 3; warm++) {
measure(base, snapshot, live, opts);
measure(next, snapshot, live, opts);
}
const before = [], after = [];
for (let i = 0; i < 9; i++) {
if (i % 2) {
after.push(measure(next, snapshot, live, opts));
before.push(measure(base, snapshot, live, opts));
} else {
before.push(measure(base, snapshot, live, opts));
after.push(measure(next, snapshot, live, opts));
}
}
measurements.push({ name, rows: live.length, page_size: snapshot.pageSize,
baseline_ms: median(before), candidate_ms: median(after),
baseline_samples_ms: before, candidate_samples_ms: after,
});
}
const blob = (path) => {
const bytes = readFileSync(path);
return createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex');
};
console.log(JSON.stringify({
recorded_at: new Date().toISOString(), node: process.version,
platform: `${process.platform}/${process.arch}`, cpu: os.cpus()[0]?.model,
baseline_blob: blob(basePath), candidate_blob: blob(nextPath),
regression_groups_passed: regressions, workload_outputs_and_references_equal: true,
pairs: 9, calls_per_sample: 20, warmup_samples: 3,
scope: 'complete pure-module paging; excludes snapshot construction, filtering, UI, storage and network',
measurements, sink,
}, null, 2));
Loading