Skip to content
Merged
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
cdb2648
test(perf): establish staged Tauri experiment fixtures
rianjs Sep 9, 2026
8868b59
test(perf): record Library baseline and seed playlist playback
rianjs Sep 9, 2026
c504db2
perf(playback): isolate native elapsed presentation updates
rianjs Sep 9, 2026
b86a02e
docs(perf): record elapsed presentation comparison
rianjs Sep 9, 2026
22342c7
test(perf): measure playlist interaction costs
rianjs Sep 9, 2026
04ad86a
perf(playlist): window rows and retain stable ordered queues
rianjs Sep 9, 2026
69ba9f8
docs(perf): record playlist windowing comparison
rianjs Sep 9, 2026
71467f3
test(perf): add native fixture and hidden presentation probe
rianjs Sep 9, 2026
ca9a4ee
perf(playback): pause hidden elapsed and marquee presentation
rianjs Sep 9, 2026
0ed594b
docs(perf): record hidden presentation probe results
rianjs Sep 9, 2026
70c4a94
test(perf): measure the startup settings load boundary
rianjs Sep 9, 2026
eb7fae6
perf(startup): preserve existing settings without a rewrite
rianjs Sep 9, 2026
4e8a828
docs(perf): record startup settings load comparison
rianjs Sep 9, 2026
67f09f6
test(perf): measure production window entry loading
rianjs Sep 9, 2026
280e840
perf(startup): load the selected native window entry
rianjs Sep 9, 2026
48456a6
fix(startup): retain per-window chunk preload dependencies
rianjs Sep 9, 2026
311bbf9
test(startup): verify window routing and built resource isolation
rianjs Sep 9, 2026
4d8f29f
docs(perf): record entry size reduction and unchanged startup latency
rianjs Sep 9, 2026
78c76f9
Measure startup file loading before background experiment
rianjs Sep 9, 2026
78ad327
Load startup files in background before publishing command state
rianjs Sep 9, 2026
0a7bca6
Record background startup scheduling measurements and limits
rianjs Sep 9, 2026
0c09cf8
Add repeatable importer refresh burst baseline
rianjs Sep 9, 2026
fea5335
Stop superseded importer queue loads between pages
rianjs Sep 9, 2026
948e1b4
Record importer burst traffic reduction and unchanged latency
rianjs Sep 9, 2026
ebaf788
Preserve experiment validation and outstanding native checks
rianjs Sep 9, 2026
ae410bc
test(perf): measure native WebKit experiments and record adoption ass…
rianjs Sep 10, 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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ the behavior you are changing:
| Completed SOLID audit evidence | [SOLID audit record](docs/SOLID.md) |
| Completed Rust audit evidence | [Rust audit record](docs/rust.md) |
| Current responsiveness work | [Responsiveness plan](docs/plans/responsiveness-burndown.md) |
| Staged Tauri performance experiments | [Experiment measurements](docs/performance/tauri-experiments/README.md) |
| Responsiveness audit evidence | [Rust performance audit](docs/rust-slop-performance-audit.md), [Last.fm interaction audit](docs/lastfm-interaction-audit.md), [type-ahead audit](docs/typeahead-performance-audit.md) |

## Invariants
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
performance-dist
*.local

# Editor directories and files
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint --deny-warnings",
"test": "node --test --experimental-strip-types test/ui.test.ts test/gateways.test.ts && vitest run test/interactions.test.tsx",
"test": "node --test --experimental-strip-types test/ui.test.ts test/gateways.test.ts && vitest run test/interactions.test.tsx test/mainEntry.test.tsx test/importQueue.test.ts",
"preview": "vite preview"
},
"dependencies": {
Expand Down
15 changes: 15 additions & 0 deletions apps/desktop/performance/check-startup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Check browser resource observations, including Vite's conditional-preload regression.
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
const report = JSON.parse(await readFile(process.argv[2], 'utf8'))
assert.equal(report.commit, process.argv[3], 'Use observations from the candidate being reviewed')
for (const label of ['main', 'lastfm-importer']) {
const samples = report.samples.filter(sample => sample.window === label)
assert(samples.length >= 3)
for (const sample of samples) {
const unwanted = label === 'main' ? 'LastFmImporter-' : 'App-'
assert(!sample.resources.some(resource => resource.name.startsWith(unwanted)), `${label} loaded the other window`)
if (label === 'lastfm-importer') assert(sample.resources.some(resource => resource.name.startsWith('LastFmImporter-') && resource.name.endsWith('.css')), 'Importer CSS was not loaded')
}
}
console.log('Both window entries load only their own view and required importer CSS')
1 change: 1 addition & 0 deletions apps/desktop/performance/importer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Importer queue benchmark</title></head><body><div id="root"></div><script type="module" src="./importer.tsx"></script></body></html>
27 changes: 27 additions & 0 deletions apps/desktop/performance/importer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Profiler } from 'react'
import { createRoot } from 'react-dom/client'
import LastFmImporter from '../src/LastFmImporter.tsx'
import { invalidate, queueWire } from './native.ts'
import '../src/index.css'
import '../src/App.css'
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
let measuring = false, renderMs = 0, commits = 0
const results: unknown[] = []
const panel = document.createElement('aside')
panel.style.cssText = 'position:fixed;bottom:0;right:0;z-index:10000;background:white;color:black;max-height:160px;overflow:auto;font:12px monospace'
const button = document.createElement('button'); button.textContent = 'Measure importer refresh burst'
const output = document.createElement('pre'); output.id = 'importer-results'
panel.append(button, output); document.body.append(panel)
button.onclick = async () => {
button.disabled = true
const calls = queueWire.calls, bytes = queueWire.bytes
renderMs = 0; commits = 0; measuring = true
const start = performance.now()
for (let i = 0; i < 6; i++) { invalidate('lastfm-import-changed'); await wait(1) }
while (queueWire.inFlight) await wait(1)
await new Promise(requestAnimationFrame); await new Promise(requestAnimationFrame)
measuring = false
results.push({ durationMs: performance.now() - start, queueCalls: queueWire.calls - calls, queueBytes: queueWire.bytes - bytes, renderMs, commits, queueRows: document.querySelectorAll('[data-import-nav="queue"]').length, selectionLabel: document.querySelector('.import-queue-bulk-actions')?.textContent, viewport: [innerWidth,innerHeight] })
output.textContent = JSON.stringify(results, null, 2); button.disabled = false
}
createRoot(document.getElementById('root')!).render(<Profiler id="importer" onRender={(_,__,duration) => { if(measuring) { renderMs += duration; commits++ } }}><LastFmImporter /></Profiler>)
1 change: 1 addition & 0 deletions apps/desktop/performance/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html><head><meta charset="utf-8"><title>Retune performance experiments</title></head><body><div id="root"></div><script type="module" src="/performance/main.tsx"></script></body></html>
94 changes: 94 additions & 0 deletions apps/desktop/performance/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Profiler } from 'react'
import { createRoot } from 'react-dom/client'
import App from '../src/App.tsx'
import '../src/index.css'
import { calls, emit, player } from './native.ts'

const results: unknown[] = []
let measuring = false
let renders: number[] = []
let frames: number[] = []
let longTasks: number[] = []
let previousFrame = 0
function frame(now: number) {
if (measuring && previousFrame) frames.push(now - previousFrame)
previousFrame = now
requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
if (PerformanceObserver.supportedEntryTypes.includes('longtask')) {
new PerformanceObserver(list => { if (measuring) longTasks.push(...list.getEntries().map(entry => entry.duration)) }).observe({ type: 'longtask' })
}
const panel = document.createElement('aside')
panel.style.cssText = 'position:fixed;right:12px;bottom:26px;z-index:10000;background:white;color:black;border:1px solid #888;padding:8px;font:12px monospace;max-width:520px;max-height:180px;overflow:auto'
const button = document.createElement('button')
button.textContent = 'Measure 12 playback ticks'
const output = document.createElement('pre'); output.id = 'performance-results'
panel.append(button, output); document.body.append(panel)
// A controlled document-visibility seam; this does not model native occlusion/CPU.
let simulatedVisibility: DocumentVisibilityState = 'visible'
Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => simulatedVisibility })
const hiddenLabel = document.createElement('label')
const hiddenInput = document.createElement('input'); hiddenInput.type = 'checkbox'
hiddenLabel.append(hiddenInput, 'Simulate hidden document'); panel.append(hiddenLabel)
hiddenInput.onchange = () => {
simulatedVisibility = hiddenInput.checked ? 'hidden' : 'visible'
document.dispatchEvent(new Event('visibilitychange'))
}
const wait = (milliseconds: number) => new Promise(resolve => setTimeout(resolve, milliseconds))
const painted = async () => { await new Promise(requestAnimationFrame); await new Promise(requestAnimationFrame) }
const interactionButton = document.createElement('button')
interactionButton.textContent = 'Measure playlist interactions'
const interactionOutput = document.createElement('pre'); interactionOutput.id = 'performance-interactions'
panel.append(interactionButton, interactionOutput)
const interactionResults: unknown[] = []
interactionButton.onclick = async () => {
interactionButton.disabled = true; button.disabled = true
document.querySelector<HTMLButtonElement>('.source-row')!.click()
await wait(500)
const actions: unknown[] = []
const measure = async (name: string, action: () => void) => {
renders = []; frames = []; longTasks = []; measuring = true
const started = performance.now()
action(); await painted()
measuring = false
actions.push({ name, durationMs: performance.now() - started, renderMs: renders.reduce((sum, value) => sum + value, 0),
commits: renders.length, rows: document.querySelectorAll('.playlist-track-row').length,
focusedRow: document.activeElement?.getAttribute('aria-label'),
longTasksMs: longTasks, maxFrameGapMs: Math.max(0, ...frames) })
}
await measure('open', () => document.querySelector<HTMLButtonElement>('.playlist-row')!.click())
await measure('select', () => document.querySelector<HTMLElement>('.playlist-track-row')!.click())
await measure('end', () => document.querySelector<HTMLElement>('.playlist-track-row')!.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true })))
await measure('shift-home', () => document.activeElement!.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', shiftKey: true, bubbles: true })))
await measure('sort', () => document.querySelector<HTMLButtonElement>('.playlist-track-header [data-column="name"]')!.click())
await measure('scroll-middle', () => { const scroll = document.querySelector<HTMLElement>('.playlist-track-scroll')!; scroll.scrollTop = scroll.scrollHeight / 2 })
interactionResults.push({ viewport: [innerWidth, innerHeight], actions })
interactionOutput.textContent = JSON.stringify(interactionResults, null, 2)
interactionButton.disabled = false; button.disabled = false
}
button.onclick = async () => {
button.disabled = true
const first = document.querySelector<HTMLElement>('[data-track-id], .playlist-track-row')
first?.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }))
emit({ type: 'playerState', payload: player(0) })
await wait(1500)
renders = []; frames = []; longTasks = []
const callsBefore = calls.length
measuring = true
const started = performance.now()
for (let elapsed = 1; elapsed <= 12; elapsed++) { emit({ type: 'playerState', payload: player(elapsed) }); await wait(1000) }
measuring = false
const sample = {
scenario: document.querySelector('.playlist-view') ? 'playlist-playback' : 'library-playback',
durationMs: performance.now() - started, commits: renders.length,
totalRenderMs: renders.reduce((sum, duration) => sum + duration, 0), maxRenderMs: Math.max(0, ...renders),
maxFrameGapMs: Math.max(0, ...frames), longTasksMs: longTasks,
ipcDuringTicks: calls.length - callsBefore, rows: document.querySelectorAll('[data-track-id], .playlist-track-row').length,
elapsed: document.querySelector<HTMLInputElement>('[aria-label="Playback position"]')?.value,
viewport: [innerWidth, innerHeight], samples: renders,
simulatedVisibility, animationStates: document.querySelector('.marquee')?.getAnimations().map(animation => animation.playState),
}
results.push(sample); output.textContent = JSON.stringify(results, null, 2); button.disabled = false
}
createRoot(document.getElementById('root')!).render(<Profiler id="app" onRender={(_, __, duration) => { if (measuring) renders.push(duration) }}><App /></Profiler>)
74 changes: 74 additions & 0 deletions apps/desktop/performance/native-probe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Opt-in bundled harness: actual App, IPC and WebKit; no production entry changes.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
const painted = async () => { await new Promise(requestAnimationFrame); await new Promise(requestAnimationFrame) }
const until = async predicate => {
const start = performance.now()
while (!predicate()) {
if (performance.now() - start > 15000) throw new Error('Native fixture timed out')
await wait(10)
}
}
const results = { navigationTimeOrigin: performance.timeOrigin, readyMs: null, interactions: [], visibility: [], playback: [] }
const panel = document.createElement('aside')
panel.style.cssText = 'position:fixed;right:12px;bottom:26px;z-index:10000;background:white;color:black;border:1px solid #888;padding:6px;font:12px monospace'
const output = document.createElement('textarea')
output.readOnly = true
output.setAttribute('aria-label', 'Native performance results')
output.style.cssText = 'display:block;width:380px;height:90px;font:10px monospace'
const show = () => { output.value = JSON.stringify(results) }
const snapshot = () => ({ atMs: performance.now(), state: document.visibilityState,
elapsed: document.querySelector('[aria-label="Playback position"]')?.value,
animationStates: [...document.querySelectorAll('.marquee')].flatMap(node => node.getAnimations().map(animation => animation.playState)) })
document.addEventListener('visibilitychange', () => {
results.visibility.push(snapshot()); show()
setTimeout(() => { results.visibility.push(snapshot()); show() }, 100)
})
const button = (label, action) => {
const control = document.createElement('button')
control.textContent = label
control.onclick = async () => {
control.disabled = true
try { await action() } catch (error) { results.error = String(error) }
finally { control.disabled = false; show() }
}
panel.append(control)
}
button('Measure native playlist', async () => {
for (let sample = 0; sample < 3; sample++) {
document.querySelector('.source-row').click()
await until(() => document.querySelector('[data-track-id]'))
await wait(300)
const actions = []
const measure = async (name, action, ready = () => true) => {
const start = performance.now()
action(); await until(ready); await painted()
actions.push({ name, durationMs: performance.now() - start,
mountedRows: document.querySelectorAll('.playlist-track-row').length,
selectedRows: document.querySelectorAll('.playlist-track-row.selected').length,
focusedRow: document.activeElement?.getAttribute('aria-label') })
}
await measure('open', () => document.querySelector('.playlist-row').click(), () => document.querySelector('.playlist-track-row'))
await measure('select', () => document.querySelector('.playlist-track-row').click())
await measure('end', () => document.querySelector('.playlist-track-row').dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true })))
await measure('shift-home', () => document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', shiftKey: true, bubbles: true })))
await measure('sort', () => document.querySelector('.playlist-track-header [data-column="name"]').click())
await measure('scroll-middle', () => { const scroll = document.querySelector('.playlist-track-scroll'); scroll.scrollTop = scroll.scrollHeight / 2 })
results.interactions.push({ viewport: [innerWidth, innerHeight], actions }); show()
}
})
button('Five-second silent playback', async () => {
try {
document.querySelector('.source-row').click()
await until(() => document.querySelector('[data-track-id]'))
document.querySelector('[data-track-id]').dispatchEvent(new MouseEvent('dblclick', { bubbles: true }))
await until(() => document.querySelector('[aria-label="Pause"]'))
results.playback.push(snapshot())
await wait(5000)
results.playback.push(snapshot())
} finally { document.querySelector('[aria-label="Pause"]')?.click() }
})
panel.append(output)
document.body.append(panel)
until(() => document.querySelector('[data-track-id]')).then(painted).then(() => {
results.readyMs = performance.now(); show()
}).catch(error => { results.error = String(error); show() })
26 changes: 26 additions & 0 deletions apps/desktop/performance/native-process.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Read-only macOS process counters; compile with clang native-process.c -o /tmp/native-process.
#include <libproc.h>
#include <mach/mach_time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/proc_info.h>
#include <sys/resource.h>

int main(int argc, char **argv) {
if (argc != 2) return 2;
char *end;
long value = strtol(argv[1], &end, 10);
if (*end || value <= 0 || value > 2147483647) return 2;
int pid = (int)value;
struct proc_bsdinfo info;
struct rusage_info_v2 usage;
mach_timebase_info_data_t timebase;
if (mach_timebase_info(&timebase) != KERN_SUCCESS) return 1;
if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, sizeof(info)) != sizeof(info)
|| proc_pid_rusage(pid, RUSAGE_INFO_V2, (rusage_info_t *)&usage)) return 1;
printf("{\"pid\":%d,\"startEpochMs\":%.3f,\"cpuNs\":%llu,\"physicalFootprintBytes\":%llu}\n",
pid, info.pbi_start_tvsec * 1000.0 + info.pbi_start_tvusec / 1000.0,
(unsigned long long)((usage.ri_user_time + usage.ri_system_time) * timebase.numer / timebase.denom),
(unsigned long long)usage.ri_phys_footprint);
return 0;
}
33 changes: 33 additions & 0 deletions apps/desktop/performance/native-sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Read three 5-second CPU/footprint samples for explicitly attributed macOS PIDs.

Usage: python3 native-sample.py /tmp/native-process output.json PID [PID ...]
CPU 100% means one core. Audio is controlled separately by the native fixture.
"""
import json
import os
from pathlib import Path
import subprocess
import sys
import time

probe, output, *pids = sys.argv[1:]
assert pids and all(pid.isdecimal() and int(pid) > 0 for pid in pids)
def read(pid):
return json.loads(subprocess.check_output([probe, str(pid)], text=True))

# Small runnable checks for the probe's input boundary and counter units.
assert subprocess.run([probe, '1x'], capture_output=True).returncode == 2
own = read(os.getpid())
assert own['physicalFootprintBytes'] > 0 and own['cpuNs'] > 0
samples = []
for _ in range(3):
before = [read(pid) for pid in pids]
start = time.monotonic_ns()
time.sleep(5)
after = [read(pid) for pid in pids]
elapsed = time.monotonic_ns() - start
samples.append(dict(durationMs=elapsed / 1e6, before=before, after=after,
cpuPercent=sum(b['cpuNs'] - a['cpuNs'] for a, b in zip(before, after)) / elapsed * 100,
summedPhysicalFootprintBytes=sum(p['physicalFootprintBytes'] for p in after)))
Path(output).write_text(json.dumps(samples, indent=2) + '\n')
print(json.dumps([dict(cpuPercent=s['cpuPercent'], footprintMiB=s['summedPhysicalFootprintBytes'] / 1048576) for s in samples]))
8 changes: 8 additions & 0 deletions apps/desktop/performance/native.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"productName": "Retune Performance",
"identifier": "com.rianjs.retune.performance",
"build": { "beforeBuildCommand": "npm exec vite -- build --config performance/native.vite.config.mjs" },
"app": {
"windows": [{ "label": "main", "title": "Retune Performance", "width": 1120, "height": 720, "resizable": true }]
}
}
Loading
Loading