aboutsummaryrefslogtreecommitdiffstats
path: root/tools/feed-fab-anim-test.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-11-02 16:41:36 -0700
committerAdam Soltys <[email protected]>2026-11-02 16:41:36 -0700
commitc566edbe50a8921311127efac65836bd6ff9c6d3 (patch)
treeb569d2ef83bf6dda4a7e0bb17d6297d0d95aac35 /tools/feed-fab-anim-test.js
parent06dd5cf1da7293fa397d7b3fc523b1d0aa802800 (diff)
downloadcoinosv3-master.tar.gz
coinosv3-master.zip
The feed's post button is on screen the moment the feed is, while the page is still sliding in. "I'm still not seeing the Post button right away when I open the feed. It should be there from the start" (Adam, 2026-11-02): measured on his phone over DevTools, the button was in the page 87 ms after the Feed tap — 4109 px down, below an 809 px screen — and in place at 562 ms. It is position: fixed, and it was rendered inside the page, whose entrance animation puts a transform on it; a transform makes fixed descendants position within the page, so the button rode at the foot of the feed card until the slide ended. The feed now hands the button and the new-posts pill to the shell (a pageFloaters hook, taken once per render), which paints them beside the page, outside anything that animates; the pill's CSS no longer depends on sitting inside the page, and its phone-width rule stays after the base one so it still rides above the button. tools/feed-fab-anim-test.js taps Feed from the wallet and samples the button every frame of the slide: on screen from the first. The direct-mount harnesses (feed-new-posts, feed-streaming, feed-bottom-nav) paint the floaters as the shell doesHEADmaster
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6TFgisGKUDhdStLUvs
Diffstat (limited to 'tools/feed-fab-anim-test.js')
-rw-r--r--tools/feed-fab-anim-test.js58
1 files changed, 58 insertions, 0 deletions
diff --git a/tools/feed-fab-anim-test.js b/tools/feed-fab-anim-test.js
new file mode 100644
index 0000000..bd9379c
--- /dev/null
+++ b/tools/feed-fab-anim-test.js
@@ -0,0 +1,58 @@
+// The post button is on screen the moment the feed is, while the page is
+// still sliding in. It is position: fixed, and it used to be rendered inside
+// the page — whose entrance animation puts a transform on it, which makes
+// fixed descendants position within the page: the button rode at the foot
+// of the feed card, below the screen, until the animation ended (measured
+// on a phone: 4109 px down at 87 ms, in place at 562 ms). Now the shell
+// paints it beside the page.
+// Run: bun tools/feed-fab-anim-test.js
+import assert from 'node:assert/strict';
+import puppeteer from './puppeteer.js';
+import { generateMnemonic } from '@scure/bip39';
+import { wordlist } from '@scure/bip39/wordlists/english';
+import { buildHtml } from '../build.js';
+const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
+const html = await buildHtml({ minify: true, pwa: false });
+const server = Bun.serve({ port: 0, fetch: (req) => {
+ const p = new URL(req.url).pathname;
+ if (p === '/verify-worker.js' || p.startsWith('/punks')) return new Response(Bun.file('dist' + p));
+ return new Response(html, { headers: { 'content-type': 'text/html' } });
+} });
+const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] });
+const account = { id: 'fab-test', label: 'Fab', type: 'full', mnemonic: generateMnemonic(wordlist), passphrase: '', network: 'regtest', persisted: true };
+try {
+ const page = await browser.newPage();
+ const errors = [];
+ page.on('pageerror', (e) => errors.push(e.message));
+ await page.setViewport({ width: 390, height: 844, isMobile: true, hasTouch: true });
+ await page.evaluateOnNewDocument((account) => {
+ localStorage.setItem('btc-wallet-network', 'regtest');
+ sessionStorage.setItem('btc-wallet-accounts', JSON.stringify({ accounts: [account], activeId: account.id }));
+ }, account);
+ await page.goto(server.url.origin + '/', { waitUntil: 'domcontentloaded' });
+ await page.waitForSelector('.app-bottom-nav', { timeout: 20000 });
+ await sleep(1500);
+ const tap = (which) => page.evaluate((which) => new Promise((resolve) => {
+ const b = [...document.querySelectorAll('.app-bottom-nav button')].find((x) => new RegExp(which, 'i').test(x.textContent));
+ const t0 = performance.now(); const samples = [];
+ const tick = () => {
+ const f = document.querySelector('.feed-post-fab'); const ms = Math.round(performance.now() - t0);
+ if (f) { const r = f.getBoundingClientRect(); samples.push({ ms, top: Math.round(r.top), right: Math.round(innerWidth - r.right), anim: !!document.querySelector('.anim-page'), inPage: !!f.closest('.anim-page, .feed-page') }); }
+ if (ms < 320) requestAnimationFrame(tick); else resolve(samples);
+ };
+ b.click(); requestAnimationFrame(tick);
+ }), which);
+ for (const round of [1, 2]) {
+ await tap('wallet'); await sleep(900);
+ const s = await tap('feed');
+ assert.ok(s.length >= 5, 'the button was sampled while the page slid in: ' + s.length);
+ const first = s[0];
+ assert.ok(first.anim, 'the page was still animating at the first sample (' + first.ms + ' ms)');
+ assert.ok(!first.inPage, 'the button is painted beside the page, not inside it');
+ const off = s.filter((x) => x.top > 844 - 40 || x.top < 0);
+ assert.equal(off.length, 0, `the button sat off screen in ${off.length} of ${s.length} frames: ` + JSON.stringify(s.slice(0, 4)));
+ console.log(`✓ round ${round}: the post button is on screen from the first frame (top ${first.top}px at ${first.ms} ms, ${s.length} frames sampled)`);
+ }
+ assert.deepEqual(errors, []);
+ console.log('✓ no page errors');
+} finally { await browser.close(); server.stop(true); }