From e5f37b003d93a17c2a9958697269b2d3228d8830 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 4 Mar 2026 23:06:35 -0800 Subject: tuning --- perf-test.mjs | 83 ++++++++++++ perf-test.ts | 146 +++++++++++++++++++++ src/app.css | 8 +- src/app.html | 1 - src/components/Account.svelte | 75 ++++++----- src/components/AppHeader.svelte | 12 +- src/components/InvoiceActions.svelte | 73 ++++++----- src/components/InvoiceData.svelte | 11 +- src/components/WalletPass.svelte | 100 -------------- src/hooks.server.ts | 21 +++ src/lib/parse.ts | 3 +- src/lib/passwordCache.ts | 62 +-------- src/lib/utils.ts | 5 +- src/locales/ar.json | 7 - src/locales/bn.json | 7 - src/locales/de.json | 7 - src/locales/el.json | 7 - src/locales/en.json | 7 - src/locales/es.json | 7 - src/locales/fa.json | 7 - src/locales/fr.json | 7 - src/locales/hi.json | 7 - src/locales/it.json | 7 - src/locales/ja.json | 7 - src/locales/ko.json | 7 - src/locales/nl.json | 7 - src/locales/pl.json | 7 - src/locales/pt.json | 7 - src/locales/ru.json | 7 - src/locales/th.json | 7 - src/locales/tr.json | 7 - src/locales/zh.json | 7 - src/routes/(app)/+layout.server.ts | 51 +++---- src/routes/(app)/+layout.svelte | 8 +- src/routes/(app)/[username]/(profile)/+page.svelte | 2 +- src/routes/(app)/account/new/+page.svelte | 16 ++- src/routes/(app)/invoice/+page.server.ts | 2 +- src/routes/(app)/invoice/[id]/+layout.server.ts | 61 --------- src/routes/(app)/invoice/[id]/+layout.ts | 66 ++++++++++ src/routes/(app)/invoice/[id]/+page.svelte | 4 +- src/routes/(app)/invoice/[id]/tip/+page.server.ts | 5 +- .../(app)/send/ark/[address]/+layout.server.ts | 15 ++- .../(app)/send/ark/[address]/+page.server.ts | 16 ++- src/routes/(app)/send/ark/[address]/+page.svelte | 4 +- .../[amount]/[...feeRate]/+page.server.ts | 14 +- .../[address]/[amount]/[...feeRate]/+page.svelte | 4 +- src/routes/(static)/login/+page.svelte | 13 ++ src/routes/+layout.server.ts | 9 +- src/routes/+layout.svelte | 13 +- static/fonts/Public_Sans/OFL.txt | 93 ------------- .../PublicSans-Italic-VariableFont_wght.ttf | Bin 105976 -> 0 bytes .../Public_Sans/PublicSans-VariableFont_wght.ttf | Bin 101592 -> 0 bytes static/fonts/Public_Sans/README.txt | 81 ------------ .../fonts/Public_Sans/static/PublicSans-Black.ttf | Bin 60364 -> 0 bytes .../Public_Sans/static/PublicSans-BlackItalic.ttf | Bin 62768 -> 0 bytes .../fonts/Public_Sans/static/PublicSans-Bold.ttf | Bin 60264 -> 0 bytes .../Public_Sans/static/PublicSans-BoldItalic.ttf | Bin 62600 -> 0 bytes .../Public_Sans/static/PublicSans-ExtraBold.ttf | Bin 60428 -> 0 bytes .../static/PublicSans-ExtraBoldItalic.ttf | Bin 62812 -> 0 bytes .../Public_Sans/static/PublicSans-ExtraLight.ttf | Bin 60124 -> 0 bytes .../static/PublicSans-ExtraLightItalic.ttf | Bin 62432 -> 0 bytes .../fonts/Public_Sans/static/PublicSans-Italic.ttf | Bin 62228 -> 0 bytes .../fonts/Public_Sans/static/PublicSans-Light.ttf | Bin 60124 -> 0 bytes .../Public_Sans/static/PublicSans-LightItalic.ttf | Bin 62372 -> 0 bytes .../fonts/Public_Sans/static/PublicSans-Medium.ttf | Bin 60080 -> 0 bytes .../Public_Sans/static/PublicSans-MediumItalic.ttf | Bin 62336 -> 0 bytes .../Public_Sans/static/PublicSans-Regular.ttf | Bin 60084 -> 0 bytes .../Public_Sans/static/PublicSans-Regular.woff2 | Bin 24300 -> 0 bytes .../Public_Sans/static/PublicSans-SemiBold.ttf | Bin 60152 -> 0 bytes .../static/PublicSans-SemiBoldItalic.ttf | Bin 62464 -> 0 bytes .../fonts/Public_Sans/static/PublicSans-Thin.ttf | Bin 59964 -> 0 bytes .../Public_Sans/static/PublicSans-ThinItalic.ttf | Bin 62212 -> 0 bytes 72 files changed, 523 insertions(+), 687 deletions(-) create mode 100644 perf-test.mjs create mode 100644 perf-test.ts delete mode 100644 src/components/WalletPass.svelte delete mode 100644 src/routes/(app)/invoice/[id]/+layout.server.ts create mode 100644 src/routes/(app)/invoice/[id]/+layout.ts delete mode 100644 static/fonts/Public_Sans/OFL.txt delete mode 100644 static/fonts/Public_Sans/PublicSans-Italic-VariableFont_wght.ttf delete mode 100644 static/fonts/Public_Sans/PublicSans-VariableFont_wght.ttf delete mode 100644 static/fonts/Public_Sans/README.txt delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Black.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-BlackItalic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Bold.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-BoldItalic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-ExtraBold.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-ExtraBoldItalic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-ExtraLight.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-ExtraLightItalic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Italic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Light.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-LightItalic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Medium.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-MediumItalic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Regular.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Regular.woff2 delete mode 100644 static/fonts/Public_Sans/static/PublicSans-SemiBold.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-SemiBoldItalic.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-Thin.ttf delete mode 100644 static/fonts/Public_Sans/static/PublicSans-ThinItalic.ttf diff --git a/perf-test.mjs b/perf-test.mjs new file mode 100644 index 00000000..2279585e --- /dev/null +++ b/perf-test.mjs @@ -0,0 +1,83 @@ +import { chromium } from "playwright"; + +const BASE = "http://localhost:5173"; + +const browser = await chromium.launch({ headless: true }); +const ctx = await browser.newContext(); +const page = await ctx.newPage(); + +const reqs = []; +page.on("request", (r) => { r._t = Date.now(); }); +page.on("response", (r) => { + const req = r.request(); + if (req._t) reqs.push({ url: req.url().replace(BASE, ""), ms: Date.now() - req._t }); +}); + +// Login +console.log("Logging in..."); +await page.goto(BASE + "/login"); +await page.fill('input[name="username"]', "bob"); +await page.fill('input[name="password"]', "pw"); +await page.click('button[type="submit"]'); +await page.waitForURL("**/bob", { timeout: 15000 }); +console.log("Logged in at", page.url()); +await page.waitForTimeout(2000); + +// First nav +reqs.length = 0; +console.log("\n=== Nav 1: dashboard -> /invoice/lnurl ==="); +const t1 = Date.now(); +const btn = page.locator('[data-testid="account-receive"]').first(); +const href = await btn.locator("..").getAttribute("href"); +console.log("Receive href:", href); +await btn.click(); +await page.waitForURL("**/invoice/**", { timeout: 10000 }); +console.log("URL ready:", Date.now() - t1, "ms"); + +try { + await page.waitForFunction(() => { + const img = document.querySelector(".invoice img"); + return img && img.naturalWidth > 0; + }, { timeout: 5000 }); + console.log("QR loaded:", Date.now() - t1, "ms"); +} catch { console.log("QR not loaded in 5s"); } + +await page.screenshot({ path: "/tmp/nav1.png" }); +console.log("Network requests:"); +reqs.sort((a, b) => b.ms - a.ms).forEach((r) => console.log(" ", r.ms + "ms", r.url)); + +const domState = await page.evaluate(() => { + const main = document.querySelector("main"); + const mainStyle = main ? getComputedStyle(main) : null; + const qr = document.querySelector(".invoice img"); + return { + mainVisibility: mainStyle?.visibility, + invoiceExists: !!document.querySelector(".invoice"), + qrSrc: qr?.getAttribute("src")?.substring(0, 100), + qrWidth: qr?.naturalWidth, + }; +}); +console.log("DOM state:", JSON.stringify(domState, null, 2)); + +// Second nav (cached) +reqs.length = 0; +console.log("\n=== Nav 2: back -> /invoice/lnurl (cached) ==="); +await page.goBack(); +await page.waitForTimeout(1000); +const t2 = Date.now(); +await btn.click(); +await page.waitForURL("**/invoice/**", { timeout: 10000 }); +console.log("URL ready:", Date.now() - t2, "ms"); +try { + await page.waitForFunction(() => { + const img = document.querySelector(".invoice img"); + return img && img.naturalWidth > 0; + }, { timeout: 5000 }); + console.log("QR loaded:", Date.now() - t2, "ms"); +} catch { console.log("QR not loaded in 5s"); } + +await page.screenshot({ path: "/tmp/nav2.png" }); +console.log("Network requests:"); +reqs.sort((a, b) => b.ms - a.ms).forEach((r) => console.log(" ", r.ms + "ms", r.url)); + +await browser.close(); diff --git a/perf-test.ts b/perf-test.ts new file mode 100644 index 00000000..082d9113 --- /dev/null +++ b/perf-test.ts @@ -0,0 +1,146 @@ +import { chromium } from "@playwright/test"; + +const BASE = "http://localhost:5173"; + +(async () => { + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext(); + const page = await context.newPage(); + + // Collect all network requests during navigation + const requests: { url: string; duration: number; size: number }[] = []; + + // Login as bob + console.log("=== Logging in as bob ==="); + await page.goto(`${BASE}/login`); + await page.fill('input[name="username"]', "bob"); + await page.fill('input[name="password"]', "pw"); + await page.click('button[type="submit"]'); + await page.waitForURL("**/bob", { timeout: 10000 }); + console.log("Logged in, at:", page.url()); + + // Wait for page to fully settle + await page.waitForTimeout(2000); + + // Now measure navigation to /invoice/lnurl + console.log("\n=== Navigating to /invoice/lnurl ==="); + + // Enable request interception to measure timings + const navRequests: { url: string; method: string; duration: number }[] = []; + + page.on("request", (req) => { + (req as any)._startTime = Date.now(); + }); + + page.on("response", (res) => { + const req = res.request(); + const start = (req as any)._startTime; + if (start) { + navRequests.push({ + url: req.url().replace(BASE, ""), + method: req.method(), + duration: Date.now() - start, + }); + } + }); + + // Take screenshot before navigation + await page.screenshot({ path: "/tmp/perf-before.png" }); + + const navStart = Date.now(); + + // Click the Receive button on the first account (custodial/spending) + const receiveBtn = page.locator('[data-testid="account-receive"]').first(); + const receiveHref = await receiveBtn.locator("..").getAttribute("href"); + console.log("Receive link href:", receiveHref); + + await receiveBtn.click(); + + // Wait for the invoice page to load + await page.waitForURL("**/invoice/**", { timeout: 10000 }); + const urlReady = Date.now() - navStart; + console.log(`URL changed in ${urlReady}ms`); + + // Wait for QR to appear + try { + await page.waitForSelector('.invoice img[src]', { timeout: 5000 }); + const qrReady = Date.now() - navStart; + console.log(`QR img element appeared in ${qrReady}ms`); + } catch { + console.log("QR img not found within 5s"); + } + + // Check if QR image has actually loaded (naturalWidth > 0) + try { + await page.waitForFunction( + () => { + const img = document.querySelector(".invoice img") as HTMLImageElement; + return img && img.naturalWidth > 0; + }, + { timeout: 5000 } + ); + const qrLoaded = Date.now() - navStart; + console.log(`QR image fully loaded in ${qrLoaded}ms`); + } catch { + console.log("QR image did not fully load within 5s"); + } + + const totalTime = Date.now() - navStart; + + await page.screenshot({ path: "/tmp/perf-after.png" }); + + console.log(`\nTotal navigation time: ${totalTime}ms`); + console.log("\n=== Network requests during navigation ==="); + for (const r of navRequests.sort((a, b) => b.duration - a.duration)) { + console.log(` ${r.method} ${r.url} - ${r.duration}ms`); + } + + // Now measure a second navigation (back and forth) to see cached behavior + console.log("\n=== Going back to profile ==="); + navRequests.length = 0; + await page.goBack(); + await page.waitForTimeout(1000); + + console.log("\n=== Second navigation to /invoice/lnurl ==="); + navRequests.length = 0; + const nav2Start = Date.now(); + await receiveBtn.click(); + await page.waitForURL("**/invoice/**", { timeout: 10000 }); + const url2Ready = Date.now() - nav2Start; + console.log(`URL changed in ${url2Ready}ms`); + + try { + await page.waitForFunction( + () => { + const img = document.querySelector(".invoice img") as HTMLImageElement; + return img && img.naturalWidth > 0; + }, + { timeout: 5000 } + ); + const qr2Loaded = Date.now() - nav2Start; + console.log(`QR image fully loaded in ${qr2Loaded}ms`); + } catch { + console.log("QR image did not fully load within 5s"); + } + + await page.screenshot({ path: "/tmp/perf-after2.png" }); + + console.log(`\nTotal 2nd navigation time: ${Date.now() - nav2Start}ms`); + console.log("\n=== Network requests during 2nd navigation ==="); + for (const r of navRequests.sort((a, b) => b.duration - a.duration)) { + console.log(` ${r.method} ${r.url} - ${r.duration}ms`); + } + + // Also capture performance entries + const perfEntries = await page.evaluate(() => { + return performance.getEntriesByType("navigation").map((e: any) => ({ + name: e.name, + duration: e.duration, + domContentLoaded: e.domContentLoadedEventEnd - e.startTime, + loadEvent: e.loadEventEnd - e.startTime, + })); + }); + console.log("\n=== Performance entries ===", perfEntries); + + await browser.close(); +})(); diff --git a/src/app.css b/src/app.css index 3c12f531..0c11c09c 100644 --- a/src/app.css +++ b/src/app.css @@ -36,12 +36,6 @@ w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl; } -@font-face { - font-family: "PublicSans"; - src: url("/fonts/Public_Sans/static/PublicSans-Regular.woff2") format("woff2"); - font-display: optional; -} - :root { --toastContainerTop: auto; --toastContainerRight: auto; @@ -51,7 +45,7 @@ } html { - font-family: PublicSans, sans-serif; + font-family: system-ui, sans-serif; } ::selection { diff --git a/src/app.html b/src/app.html index 42d00a9e..9ed29d0c 100644 --- a/src/app.html +++ b/src/app.html @@ -10,7 +10,6 @@ - %sveltekit.head% @@ -145,7 +158,7 @@
- setAccount(e, receiveUrl)}> + e.stopPropagation()}> - setAccount(e, "/send")}> + setAccount(e, "/send")}> {/each} @@ -90,15 +89,14 @@ {/if} - {#if subject} + {#if subject || user}
- +
{/if} -{/if}