← back to Dw Photo Capture
auto-save: 2026-07-09T08:39:40 (2 files) — public/index.html screenrecord/
036d13097e8849916f1b75e5bf817fb1c2c3882c · 2026-07-09 08:39:43 -0700 · Steve Abrams
Files touched
M public/index.htmlA screenrecord/debug-log.jsonlA screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.mp4A screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.webmA screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.mp4A screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.webmA screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.mp4A screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.webmA screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.mp4A screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.webmA screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.mp4A screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.webmA screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.mp4A screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.webmA screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.mp4A screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.webmA screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.mp4A screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.webmA screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.mp4A screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.webmA screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.mp4A screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.webmA screenrecord/run-tests.js
Diff
commit 036d13097e8849916f1b75e5bf817fb1c2c3882c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 9 08:39:43 2026 -0700
auto-save: 2026-07-09T08:39:40 (2 files) — public/index.html screenrecord/
---
public/index.html | 11 +-
screenrecord/debug-log.jsonl | 175 +++++++
.../run0/page@8d672a8c348229a87b60bc50cf0f16a4.mp4 | Bin 0 -> 3772455 bytes
.../page@8d672a8c348229a87b60bc50cf0f16a4.webm | Bin 0 -> 9693159 bytes
.../run0/page@baf635327b1dd2ad57d25686a3348dc6.mp4 | Bin 0 -> 516868 bytes
.../page@baf635327b1dd2ad57d25686a3348dc6.webm | Bin 0 -> 1116636 bytes
.../run0/page@c02a42ee275506e6376d31fad467949b.mp4 | Bin 0 -> 497164 bytes
.../page@c02a42ee275506e6376d31fad467949b.webm | Bin 0 -> 994273 bytes
.../run1/page@045d6c3359d0ee4c5008a03e3e743927.mp4 | Bin 0 -> 9175 bytes
.../page@045d6c3359d0ee4c5008a03e3e743927.webm | Bin 0 -> 13138 bytes
.../run1/page@65767f2cbeed44e6f0746eb7b331ac58.mp4 | Bin 0 -> 487807 bytes
.../page@65767f2cbeed44e6f0746eb7b331ac58.webm | Bin 0 -> 949763 bytes
.../run1/page@759aba46dc4a16b4a16bd7623b33e392.mp4 | Bin 0 -> 965313 bytes
.../page@759aba46dc4a16b4a16bd7623b33e392.webm | Bin 0 -> 10175542 bytes
.../run2/page@4c646c54fc7d2b27396bde8641b14e2b.mp4 | Bin 0 -> 212144 bytes
.../page@4c646c54fc7d2b27396bde8641b14e2b.webm | Bin 0 -> 733598 bytes
.../run2/page@e9ba48f157a7481ee9e0122f0faf25ae.mp4 | Bin 0 -> 492018 bytes
.../page@e9ba48f157a7481ee9e0122f0faf25ae.webm | Bin 0 -> 961240 bytes
.../run3/page@4802028714f9cea97f12f6938a7a7d33.mp4 | Bin 0 -> 480544 bytes
.../page@4802028714f9cea97f12f6938a7a7d33.webm | Bin 0 -> 960511 bytes
.../run4/page@b2f4018983a23e276abe379a34736903.mp4 | Bin 0 -> 487905 bytes
.../page@b2f4018983a23e276abe379a34736903.webm | Bin 0 -> 955972 bytes
screenrecord/run-tests.js | 567 +++++++++++++++++++++
23 files changed, 751 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index a787735..b58eb1a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -782,7 +782,12 @@ function render(){
: 'Nothing here — switch filter or clear search.';
grid.innerHTML = list.length? '' : `<div class="empty">${emptyMsg}</div>`;
if(recog && RECOGNIZED) grid.insertAdjacentHTML('afterbegin', recogBanner());
- for(const x of list){
+ // MEMORY GUARD (iOS OOM-freeze fix): never build more than RENDER_CAP card nodes. The TWIL list is
+ // ~88k items (36MB) — rendering them all drove the browser to 13GB and crashed the tab. Cap it and
+ // tell the user to search/filter to narrow. (Search/filter shrink `list` before this point.)
+ const RENDER_CAP=500;
+ const shown = list.length>RENDER_CAP ? list.slice(0,RENDER_CAP) : list;
+ for(const x of shown){
const isNew = x.needs_create && !x.done; // on the sheet but not yet created in Shopify
const photographed = !isNew && (x.done || !!x.image || x.live); // already has a real photo
const el=document.createElement('div'); el.className='card'+(photographed?' done':'');
@@ -821,6 +826,8 @@ function render(){
const sk=el.querySelector('.skip'); if(sk) sk.addEventListener('click',()=>skip(x));
grid.appendChild(el);
}
+ if(list.length>shown.length){ const n=document.createElement('div'); n.className='empty'; n.style.gridColumn='1/-1';
+ n.innerHTML=`Showing first <b>${shown.length}</b> of <b>${list.length.toLocaleString()}</b> — 🔎 search a SKU / name / vendor to narrow.`; grid.appendChild(n); }
lazyThumbs(); // only load thumbnails as they scroll into view (huge mobile win vs 100s of images at once)
}
// CSS background-images can't use loading="lazy" — apply each thumb's bg only when it nears the viewport.
@@ -1948,7 +1955,7 @@ function routeHome(act){ $('#homeScreen').hidden=true;
else if(act==='update') openAddModal(null,{mode:'update',camera:true});
else if(act==='lookup') openLookup(); // camera-first front→back → identify
else if(act==='check'){ openFbModal(); loadIncoming(); } // show the incoming (ordered-not-received) queue first
- // 'skip' → just show the catalog
+ else if(act==='skip'){ if(!TWILITEMS.length) loadTwil(); } // lazy-load the big TWIL grid only when actually viewing it
}
document.querySelectorAll('#homeScreen .home-pill, #homeScreen .home-skip').forEach(b=>b.addEventListener('click',()=>routeHome(b.dataset.act)));
$('#homeBtn').addEventListener('click',()=>{ $('#homeScreen').hidden=false; });
diff --git a/screenrecord/debug-log.jsonl b/screenrecord/debug-log.jsonl
new file mode 100644
index 0000000..5813c8b
--- /dev/null
+++ b/screenrecord/debug-log.jsonl
@@ -0,0 +1,175 @@
+{"run":0,"ts":"2026-07-09T15:28:10.156Z","selector":"PAGE","label":"page load","action":"goto","ok":true,"effect":"domcontentloaded; /api/queue + /api/twil intercepted to prevent renderer choke","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:11.560Z","selector":"#homeScreen","label":"homeScreen initial","action":"check","ok":true,"effect":"visible (correct)","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:11.560Z","selector":"button[data-act=\"add\"]","label":"Add New SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:12.680Z","selector":"button[data-act=\"update\"]","label":"Update SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:14.909Z","selector":"button[data-act=\"lookup\"]","label":"Look Up SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:17.134Z","selector":"button[data-act=\"check\"]","label":"Check Sample In","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:19.359Z","selector":"button.home-skip","label":"skip →","action":"click","ok":true,"effect":"homeScreen hidden","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:22.692Z","selector":".ver","label":"version chip: v102 · editor","action":"click","ok":true,"effect":"navigated → https://photo.designerwallcoverings.com/selfcheck","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:23.790Z","selector":"#homeBtn","label":"⌂ home button","action":"click","ok":true,"effect":"homeScreen: true → true","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:24.526Z","selector":"#scanBtn","label":"🔢 Scan button","action":"click","ok":true,"effect":"scan overlay not visible","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:25.947Z","selector":"#sort","label":"sort select","action":"cycle","ok":true,"effect":"cycled: ready,title,sku","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:26.558Z","selector":"#dens","label":"density slider","action":"drag","ok":true,"effect":"cycled 2→3→4→1","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:26.863Z","selector":".chip[data-f=\"need\"]","label":"chip: Needs photo","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:27.167Z","selector":".chip[data-f=\"live\"]","label":"chip: 🟢 Live","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:27.470Z","selector":".chip[data-f=\"done\"]","label":"chip: Done","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:27.773Z","selector":".chip[data-f=\"new\"]","label":"chip: ✨ New (11)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:28.077Z","selector":".chip[data-f=\"twil\"]","label":"chip: 🧵 All TWIL (0)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:28.380Z","selector":".chip[data-f=\"all\"]","label":"chip: All","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:28.690Z","selector":".chip[data-f=\"any\"]","label":"chip: 🔎 Any SKU","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:28.992Z","selector":".chip[data-f=\"shop\"]","label":"chip: 🌐 All Shopify","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:29.294Z","selector":".chip[data-f=\"similar\"]","label":"chip: 🎨 Similar","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:29.598Z","selector":".chip[data-f=\"fav\"]","label":"chip: ⭐ Favorites","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:29.907Z","selector":".chip[data-f=\"recent\"]","label":"chip: 🕘 Recent","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:31.523Z","selector":"#fbModal","label":"fbModal open via fbBtn","action":"check","ok":true,"effect":"visible","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":0,"ts":"2026-07-09T15:28:31.824Z","selector":"#fbBackTile","label":"Back · label tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:32.126Z","selector":"#fbFrontTile","label":"Front · pattern tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:32.128Z","selector":"#fbGo","label":"🔎 Identify button","action":"state-check","ok":true,"effect":"disabled (correct: no image loaded)","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:35.090Z","selector":"#fbSearchInput","label":"search \"217203\"","action":"type+search","ok":true,"effect":"0 candidate card(s), 69 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":0,"ts":"2026-07-09T15:28:38.215Z","selector":"#fbSearchInput","label":"search \"CHC-217203\"","action":"type+search","ok":true,"effect":"0 candidate card(s), 73 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":0,"ts":"2026-07-09T15:28:38.617Z","selector":"#fbClose","label":"✕ close fbModal","action":"click","ok":true,"effect":"modal closed","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:40.327Z","selector":"#fbIncoming","label":"Check Sample In — incoming queue","action":"check","ok":true,"effect":"fbModal:true, incoming HTML:18071chars, inc-card:true, recv-btns:40 (NOT clicked — would write FileMaker)","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":0,"ts":"2026-07-09T15:28:41.553Z","selector":"#addBtn","label":"➕ New item button","action":"click","ok":true,"effect":"addModal opened","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":0,"ts":"2026-07-09T15:28:41.555Z","selector":"#addVendor","label":"vendor dropdown in addModal","action":"check","ok":true,"effect":"1 vendor option(s)","errors":[]}
+{"run":0,"ts":"2026-07-09T15:28:41.862Z","selector":"RUN_END","label":"run complete","action":"summary","ok":true,"effect":"~14 actions","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":1,"ts":"2026-07-09T15:28:47.755Z","selector":"PAGE","label":"page load","action":"goto","ok":true,"effect":"domcontentloaded; /api/queue + /api/twil intercepted to prevent renderer choke","errors":[]}
+{"run":1,"ts":"2026-07-09T15:28:49.139Z","selector":"#homeScreen","label":"homeScreen initial","action":"check","ok":true,"effect":"visible (correct)","errors":[]}
+{"run":1,"ts":"2026-07-09T15:28:49.139Z","selector":"button[data-act=\"add\"]","label":"Add New SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":1,"ts":"2026-07-09T15:28:50.255Z","selector":"button[data-act=\"update\"]","label":"Update SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":1,"ts":"2026-07-09T15:28:52.483Z","selector":"button[data-act=\"lookup\"]","label":"Look Up SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":1,"ts":"2026-07-09T15:28:54.709Z","selector":"button[data-act=\"check\"]","label":"Check Sample In","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":1,"ts":"2026-07-09T15:28:56.936Z","selector":"button.home-skip","label":"skip →","action":"click","ok":true,"effect":"homeScreen hidden","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:00.171Z","selector":".ver","label":"version chip: v102 · editor","action":"click","ok":true,"effect":"navigated → https://photo.designerwallcoverings.com/selfcheck","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:01.387Z","selector":"#homeBtn","label":"⌂ home button","action":"click","ok":true,"effect":"homeScreen: true → true","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:02.185Z","selector":"#scanBtn","label":"🔢 Scan button","action":"click","ok":true,"effect":"scan overlay not visible","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:03.607Z","selector":"#sort","label":"sort select","action":"cycle","ok":true,"effect":"cycled: ready,title,sku","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:04.218Z","selector":"#dens","label":"density slider","action":"drag","ok":true,"effect":"cycled 2→3→4→1","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:04.523Z","selector":".chip[data-f=\"need\"]","label":"chip: Needs photo","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:04.826Z","selector":".chip[data-f=\"live\"]","label":"chip: 🟢 Live","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:05.130Z","selector":".chip[data-f=\"done\"]","label":"chip: Done","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:05.433Z","selector":".chip[data-f=\"new\"]","label":"chip: ✨ New (11)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:05.736Z","selector":".chip[data-f=\"twil\"]","label":"chip: 🧵 All TWIL (0)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:06.039Z","selector":".chip[data-f=\"all\"]","label":"chip: All","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:06.347Z","selector":".chip[data-f=\"any\"]","label":"chip: 🔎 Any SKU","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:06.651Z","selector":".chip[data-f=\"shop\"]","label":"chip: 🌐 All Shopify","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:06.953Z","selector":".chip[data-f=\"similar\"]","label":"chip: 🎨 Similar","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:07.256Z","selector":".chip[data-f=\"fav\"]","label":"chip: ⭐ Favorites","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:07.561Z","selector":".chip[data-f=\"recent\"]","label":"chip: 🕘 Recent","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":1,"ts":"2026-07-09T15:26:32.017Z","selector":"button[data-act=\"add\"]","label":"Add New SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:09.176Z","selector":"#fbModal","label":"fbModal open via fbBtn","action":"check","ok":true,"effect":"visible","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":1,"ts":"2026-07-09T15:29:09.479Z","selector":"#fbBackTile","label":"Back · label tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:09.782Z","selector":"#fbFrontTile","label":"Front · pattern tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:09.784Z","selector":"#fbGo","label":"🔎 Identify button","action":"state-check","ok":true,"effect":"disabled (correct: no image loaded)","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:12.746Z","selector":"#fbSearchInput","label":"search \"217203\"","action":"type+search","ok":true,"effect":"2 candidate card(s), 574 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":1,"ts":"2026-07-09T15:29:13.151Z","selector":".wp-c","label":"first candidate card","action":"click","ok":true,"effect":"tapped","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":1,"ts":"2026-07-09T15:29:16.366Z","selector":"#fbSearchInput","label":"search \"CHC-217203\"","action":"type+search","ok":true,"effect":"1 candidate card(s), 368 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":1,"ts":"2026-07-09T15:29:16.768Z","selector":"#fbClose","label":"✕ close fbModal","action":"click","ok":true,"effect":"modal closed","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:18.483Z","selector":"#fbIncoming","label":"Check Sample In — incoming queue","action":"check","ok":true,"effect":"fbModal:true, incoming HTML:18071chars, inc-card:true, recv-btns:40 (NOT clicked — would write FileMaker)","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":1,"ts":"2026-07-09T15:29:19.703Z","selector":"#addBtn","label":"➕ New item button","action":"click","ok":true,"effect":"addModal opened","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":1,"ts":"2026-07-09T15:29:19.706Z","selector":"#addVendor","label":"vendor dropdown in addModal","action":"check","ok":true,"effect":"1 vendor option(s)","errors":[]}
+{"run":1,"ts":"2026-07-09T15:29:20.017Z","selector":"RUN_END","label":"run complete","action":"summary","ok":true,"effect":"~14 actions","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":2,"ts":"2026-07-09T15:29:21.734Z","selector":"PAGE","label":"page load","action":"goto","ok":true,"effect":"domcontentloaded; /api/queue + /api/twil intercepted to prevent renderer choke","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:23.169Z","selector":"#homeScreen","label":"homeScreen initial","action":"check","ok":true,"effect":"visible (correct)","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:23.169Z","selector":"button[data-act=\"add\"]","label":"Add New SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:24.282Z","selector":"button[data-act=\"update\"]","label":"Update SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:26.506Z","selector":"button[data-act=\"lookup\"]","label":"Look Up SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:28.731Z","selector":"button[data-act=\"check\"]","label":"Check Sample In","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:30.957Z","selector":"button.home-skip","label":"skip →","action":"click","ok":true,"effect":"homeScreen hidden","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:34.188Z","selector":".ver","label":"version chip: v102 · editor","action":"click","ok":true,"effect":"navigated → https://photo.designerwallcoverings.com/selfcheck","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:35.294Z","selector":"#homeBtn","label":"⌂ home button","action":"click","ok":true,"effect":"homeScreen: true → true","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:36.037Z","selector":"#scanBtn","label":"🔢 Scan button","action":"click","ok":true,"effect":"scan overlay not visible","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:37.458Z","selector":"#sort","label":"sort select","action":"cycle","ok":true,"effect":"cycled: ready,title,sku","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:38.070Z","selector":"#dens","label":"density slider","action":"drag","ok":true,"effect":"cycled 2→3→4→1","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:38.375Z","selector":".chip[data-f=\"need\"]","label":"chip: Needs photo","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:38.678Z","selector":".chip[data-f=\"live\"]","label":"chip: 🟢 Live","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:38.981Z","selector":".chip[data-f=\"done\"]","label":"chip: Done","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:39.283Z","selector":".chip[data-f=\"new\"]","label":"chip: ✨ New (11)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:39.587Z","selector":".chip[data-f=\"twil\"]","label":"chip: 🧵 All TWIL (0)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:39.890Z","selector":".chip[data-f=\"all\"]","label":"chip: All","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:40.198Z","selector":".chip[data-f=\"any\"]","label":"chip: 🔎 Any SKU","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:40.501Z","selector":".chip[data-f=\"shop\"]","label":"chip: 🌐 All Shopify","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:40.802Z","selector":".chip[data-f=\"similar\"]","label":"chip: 🎨 Similar","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:41.105Z","selector":".chip[data-f=\"fav\"]","label":"chip: ⭐ Favorites","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:41.409Z","selector":".chip[data-f=\"recent\"]","label":"chip: 🕘 Recent","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:43.022Z","selector":"#fbModal","label":"fbModal open via fbBtn","action":"check","ok":true,"effect":"visible","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":2,"ts":"2026-07-09T15:29:43.324Z","selector":"#fbBackTile","label":"Back · label tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:43.627Z","selector":"#fbFrontTile","label":"Front · pattern tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:43.629Z","selector":"#fbGo","label":"🔎 Identify button","action":"state-check","ok":true,"effect":"disabled (correct: no image loaded)","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:46.590Z","selector":"#fbSearchInput","label":"search \"217203\"","action":"type+search","ok":true,"effect":"2 candidate card(s), 574 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":2,"ts":"2026-07-09T15:29:46.994Z","selector":".wp-c","label":"first candidate card","action":"click","ok":true,"effect":"tapped","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":2,"ts":"2026-07-09T15:29:50.127Z","selector":"#fbSearchInput","label":"search \"CHC-217203\"","action":"type+search","ok":true,"effect":"1 candidate card(s), 368 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":2,"ts":"2026-07-09T15:29:50.530Z","selector":"#fbClose","label":"✕ close fbModal","action":"click","ok":true,"effect":"modal closed","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:52.242Z","selector":"#fbIncoming","label":"Check Sample In — incoming queue","action":"check","ok":true,"effect":"fbModal:true, incoming HTML:18071chars, inc-card:true, recv-btns:40 (NOT clicked — would write FileMaker)","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":2,"ts":"2026-07-09T15:29:53.461Z","selector":"#addBtn","label":"➕ New item button","action":"click","ok":true,"effect":"addModal opened","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":2,"ts":"2026-07-09T15:29:53.462Z","selector":"#addVendor","label":"vendor dropdown in addModal","action":"check","ok":true,"effect":"1 vendor option(s)","errors":[]}
+{"run":2,"ts":"2026-07-09T15:29:53.769Z","selector":"RUN_END","label":"run complete","action":"summary","ok":true,"effect":"~14 actions","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":3,"ts":"2026-07-09T15:29:55.570Z","selector":"PAGE","label":"page load","action":"goto","ok":true,"effect":"domcontentloaded; /api/queue + /api/twil intercepted to prevent renderer choke","errors":[]}
+{"run":3,"ts":"2026-07-09T15:29:57.010Z","selector":"#homeScreen","label":"homeScreen initial","action":"check","ok":true,"effect":"visible (correct)","errors":[]}
+{"run":3,"ts":"2026-07-09T15:29:57.011Z","selector":"button[data-act=\"add\"]","label":"Add New SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":3,"ts":"2026-07-09T15:29:58.124Z","selector":"button[data-act=\"update\"]","label":"Update SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:00.346Z","selector":"button[data-act=\"lookup\"]","label":"Look Up SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:02.570Z","selector":"button[data-act=\"check\"]","label":"Check Sample In","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:04.907Z","selector":"button.home-skip","label":"skip →","action":"click","ok":true,"effect":"homeScreen hidden","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:08.138Z","selector":".ver","label":"version chip: v102 · editor","action":"click","ok":true,"effect":"navigated → https://photo.designerwallcoverings.com/selfcheck","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:09.230Z","selector":"#homeBtn","label":"⌂ home button","action":"click","ok":true,"effect":"homeScreen: true → true","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:09.976Z","selector":"#scanBtn","label":"🔢 Scan button","action":"click","ok":true,"effect":"scan overlay not visible","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:11.398Z","selector":"#sort","label":"sort select","action":"cycle","ok":true,"effect":"cycled: ready,title,sku","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:12.010Z","selector":"#dens","label":"density slider","action":"drag","ok":true,"effect":"cycled 2→3→4→1","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:12.314Z","selector":".chip[data-f=\"need\"]","label":"chip: Needs photo","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:12.617Z","selector":".chip[data-f=\"live\"]","label":"chip: 🟢 Live","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:12.920Z","selector":".chip[data-f=\"done\"]","label":"chip: Done","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:13.224Z","selector":".chip[data-f=\"new\"]","label":"chip: ✨ New (11)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:13.527Z","selector":".chip[data-f=\"twil\"]","label":"chip: 🧵 All TWIL (0)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:13.830Z","selector":".chip[data-f=\"all\"]","label":"chip: All","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:14.139Z","selector":".chip[data-f=\"any\"]","label":"chip: 🔎 Any SKU","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:14.442Z","selector":".chip[data-f=\"shop\"]","label":"chip: 🌐 All Shopify","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:14.745Z","selector":".chip[data-f=\"similar\"]","label":"chip: 🎨 Similar","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:15.049Z","selector":".chip[data-f=\"fav\"]","label":"chip: ⭐ Favorites","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:15.353Z","selector":".chip[data-f=\"recent\"]","label":"chip: 🕘 Recent","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:17.108Z","selector":"#fbModal","label":"fbModal open via fbBtn","action":"check","ok":true,"effect":"visible","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":3,"ts":"2026-07-09T15:30:17.411Z","selector":"#fbBackTile","label":"Back · label tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:17.714Z","selector":"#fbFrontTile","label":"Front · pattern tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:17.716Z","selector":"#fbGo","label":"🔎 Identify button","action":"state-check","ok":true,"effect":"disabled (correct: no image loaded)","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:20.676Z","selector":"#fbSearchInput","label":"search \"217203\"","action":"type+search","ok":true,"effect":"2 candidate card(s), 574 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":3,"ts":"2026-07-09T15:30:21.081Z","selector":".wp-c","label":"first candidate card","action":"click","ok":true,"effect":"tapped","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":3,"ts":"2026-07-09T15:30:24.207Z","selector":"#fbSearchInput","label":"search \"CHC-217203\"","action":"type+search","ok":true,"effect":"1 candidate card(s), 368 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":3,"ts":"2026-07-09T15:30:24.609Z","selector":"#fbClose","label":"✕ close fbModal","action":"click","ok":true,"effect":"modal closed","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:26.320Z","selector":"#fbIncoming","label":"Check Sample In — incoming queue","action":"check","ok":true,"effect":"fbModal:true, incoming HTML:18042chars, inc-card:true, recv-btns:40 (NOT clicked — would write FileMaker)","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":3,"ts":"2026-07-09T15:30:27.540Z","selector":"#addBtn","label":"➕ New item button","action":"click","ok":true,"effect":"addModal opened","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":3,"ts":"2026-07-09T15:30:27.541Z","selector":"#addVendor","label":"vendor dropdown in addModal","action":"check","ok":true,"effect":"1 vendor option(s)","errors":[]}
+{"run":3,"ts":"2026-07-09T15:30:27.847Z","selector":"RUN_END","label":"run complete","action":"summary","ok":true,"effect":"~14 actions","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":4,"ts":"2026-07-09T15:30:28.389Z","selector":"PAGE","label":"page load","action":"goto","ok":true,"effect":"domcontentloaded; /api/queue + /api/twil intercepted to prevent renderer choke","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:29.865Z","selector":"#homeScreen","label":"homeScreen initial","action":"check","ok":true,"effect":"visible (correct)","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:29.866Z","selector":"button[data-act=\"add\"]","label":"Add New SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:30.982Z","selector":"button[data-act=\"update\"]","label":"Update SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#addModal","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:33.206Z","selector":"button[data-act=\"lookup\"]","label":"Look Up SKU","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:35.429Z","selector":"button[data-act=\"check\"]","label":"Check Sample In","action":"click","ok":true,"effect":"homeScreen hidden, modal=#fbModal","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:37.653Z","selector":"button.home-skip","label":"skip →","action":"click","ok":true,"effect":"homeScreen hidden","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:40.887Z","selector":".ver","label":"version chip: v102 · editor","action":"click","ok":true,"effect":"navigated → https://photo.designerwallcoverings.com/selfcheck","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:41.980Z","selector":"#homeBtn","label":"⌂ home button","action":"click","ok":true,"effect":"homeScreen: true → true","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:42.709Z","selector":"#scanBtn","label":"🔢 Scan button","action":"click","ok":true,"effect":"scan overlay not visible","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:44.129Z","selector":"#sort","label":"sort select","action":"cycle","ok":true,"effect":"cycled: ready,title,sku","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:44.741Z","selector":"#dens","label":"density slider","action":"drag","ok":true,"effect":"cycled 2→3→4→1","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:45.046Z","selector":".chip[data-f=\"need\"]","label":"chip: Needs photo","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:45.350Z","selector":".chip[data-f=\"live\"]","label":"chip: 🟢 Live","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:45.653Z","selector":".chip[data-f=\"done\"]","label":"chip: Done","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:45.957Z","selector":".chip[data-f=\"new\"]","label":"chip: ✨ New (11)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:46.260Z","selector":".chip[data-f=\"twil\"]","label":"chip: 🧵 All TWIL (0)","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:46.563Z","selector":".chip[data-f=\"all\"]","label":"chip: All","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:46.872Z","selector":".chip[data-f=\"any\"]","label":"chip: 🔎 Any SKU","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:47.176Z","selector":".chip[data-f=\"shop\"]","label":"chip: 🌐 All Shopify","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:47.479Z","selector":".chip[data-f=\"similar\"]","label":"chip: 🎨 Similar","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:47.782Z","selector":".chip[data-f=\"fav\"]","label":"chip: ⭐ Favorites","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:48.087Z","selector":".chip[data-f=\"recent\"]","label":"chip: 🕘 Recent","action":"click","ok":true,"effect":"clicked","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:49.704Z","selector":"#fbModal","label":"fbModal open via fbBtn","action":"check","ok":true,"effect":"visible","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":4,"ts":"2026-07-09T15:30:50.007Z","selector":"#fbBackTile","label":"Back · label tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:50.311Z","selector":"#fbFrontTile","label":"Front · pattern tile","action":"click","ok":true,"effect":"tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:50.313Z","selector":"#fbGo","label":"🔎 Identify button","action":"state-check","ok":true,"effect":"disabled (correct: no image loaded)","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:53.274Z","selector":"#fbSearchInput","label":"search \"217203\"","action":"type+search","ok":true,"effect":"2 candidate card(s), 574 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":4,"ts":"2026-07-09T15:30:53.679Z","selector":".wp-c","label":"first candidate card","action":"click","ok":true,"effect":"tapped","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":4,"ts":"2026-07-09T15:30:56.808Z","selector":"#fbSearchInput","label":"search \"CHC-217203\"","action":"type+search","ok":true,"effect":"1 candidate card(s), 368 chars HTML","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":4,"ts":"2026-07-09T15:30:57.211Z","selector":"#fbClose","label":"✕ close fbModal","action":"click","ok":true,"effect":"modal closed","errors":[]}
+{"run":4,"ts":"2026-07-09T15:30:58.921Z","selector":"#fbIncoming","label":"Check Sample In — incoming queue","action":"check","ok":true,"effect":"fbModal:true, incoming HTML:18071chars, inc-card:true, recv-btns:40 (NOT clicked — would write FileMaker)","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":4,"ts":"2026-07-09T15:31:00.141Z","selector":"#addBtn","label":"➕ New item button","action":"click","ok":true,"effect":"addModal opened","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
+{"run":4,"ts":"2026-07-09T15:31:00.142Z","selector":"#addVendor","label":"vendor dropdown in addModal","action":"check","ok":true,"effect":"1 vendor option(s)","errors":[]}
+{"run":4,"ts":"2026-07-09T15:31:00.448Z","selector":"RUN_END","label":"run complete","action":"summary","ok":true,"effect":"~14 actions","errors":["[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()","[console.error] Failed to load resource: the server responded with a status of 404 ()"]}
diff --git a/screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.mp4 b/screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.mp4
new file mode 100644
index 0000000..f5c91b1
Binary files /dev/null and b/screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.mp4 differ
diff --git a/screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.webm b/screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.webm
new file mode 100644
index 0000000..4932467
Binary files /dev/null and b/screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.webm differ
diff --git a/screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.mp4 b/screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.mp4
new file mode 100644
index 0000000..6c49562
Binary files /dev/null and b/screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.mp4 differ
diff --git a/screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.webm b/screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.webm
new file mode 100644
index 0000000..660f719
Binary files /dev/null and b/screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.webm differ
diff --git a/screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.mp4 b/screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.mp4
new file mode 100644
index 0000000..6a57756
Binary files /dev/null and b/screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.mp4 differ
diff --git a/screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.webm b/screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.webm
new file mode 100644
index 0000000..76320d3
Binary files /dev/null and b/screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.webm differ
diff --git a/screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.mp4 b/screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.mp4
new file mode 100644
index 0000000..c8a17e7
Binary files /dev/null and b/screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.mp4 differ
diff --git a/screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.webm b/screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.webm
new file mode 100644
index 0000000..dc033fc
Binary files /dev/null and b/screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.webm differ
diff --git a/screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.mp4 b/screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.mp4
new file mode 100644
index 0000000..7adca57
Binary files /dev/null and b/screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.mp4 differ
diff --git a/screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.webm b/screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.webm
new file mode 100644
index 0000000..d3775d1
Binary files /dev/null and b/screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.webm differ
diff --git a/screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.mp4 b/screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.mp4
new file mode 100644
index 0000000..ce44644
Binary files /dev/null and b/screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.mp4 differ
diff --git a/screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.webm b/screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.webm
new file mode 100644
index 0000000..efd2c7c
Binary files /dev/null and b/screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.webm differ
diff --git a/screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.mp4 b/screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.mp4
new file mode 100644
index 0000000..2907bc4
Binary files /dev/null and b/screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.mp4 differ
diff --git a/screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.webm b/screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.webm
new file mode 100644
index 0000000..2f73cff
Binary files /dev/null and b/screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.webm differ
diff --git a/screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.mp4 b/screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.mp4
new file mode 100644
index 0000000..7547233
Binary files /dev/null and b/screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.mp4 differ
diff --git a/screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.webm b/screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.webm
new file mode 100644
index 0000000..b5a8f94
Binary files /dev/null and b/screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.webm differ
diff --git a/screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.mp4 b/screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.mp4
new file mode 100644
index 0000000..1a407fc
Binary files /dev/null and b/screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.mp4 differ
diff --git a/screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.webm b/screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.webm
new file mode 100644
index 0000000..1b525e5
Binary files /dev/null and b/screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.webm differ
diff --git a/screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.mp4 b/screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.mp4
new file mode 100644
index 0000000..b0850bb
Binary files /dev/null and b/screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.mp4 differ
diff --git a/screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.webm b/screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.webm
new file mode 100644
index 0000000..36c83c5
Binary files /dev/null and b/screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.webm differ
diff --git a/screenrecord/run-tests.js b/screenrecord/run-tests.js
new file mode 100644
index 0000000..4adfc1f
--- /dev/null
+++ b/screenrecord/run-tests.js
@@ -0,0 +1,567 @@
+/**
+ * screenrecord test agent — photo.designerwallcoverings.com
+ * 5 runs, mobile iPhone emulation, distinct element ordering per run.
+ * Key insight: intercept heavy API calls (/api/queue, /api/twil) to prevent
+ * the renderer from choking on thousands of product cards.
+ */
+'use strict';
+const { chromium } = require('playwright');
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const BASE_URL = 'https://photo.designerwallcoverings.com';
+const LOG_FILE = '/Users/macstudio3/Projects/dw-photo-capture/screenrecord/debug-log.jsonl';
+const REC_BASE = '/Users/macstudio3/Projects/dw-photo-capture/screenrecord/rec';
+const EVAL_TIMEOUT = 5000;
+
+// ---------------------------------------------------------------------------
+// Prior log
+// ---------------------------------------------------------------------------
+const prior = fs.existsSync(LOG_FILE)
+ ? fs.readFileSync(LOG_FILE, 'utf8').trim().split('\n').filter(Boolean).map(l => {
+ try { return JSON.parse(l); } catch(e) { return null; }
+ }).filter(Boolean)
+ : [];
+const priorErrors = prior.filter(r => r.errors && r.errors.length > 0);
+const priorErrorSelectors = new Set(priorErrors.map(e => e.selector).filter(Boolean));
+console.log(`Prior log: ${prior.length} entries, ${priorErrors.length} with errors, ${priorErrorSelectors.size} error selectors`);
+
+function appendLog(obj) {
+ fs.appendFileSync(LOG_FILE, JSON.stringify(obj) + '\n');
+}
+
+const IPHONE_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';
+const VIEWPORT = { width: 390, height: 844 };
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+async function w(ms) { return new Promise(r => setTimeout(r, ms)); }
+
+/** Safely run a page.$eval with timeout — returns null on failure */
+async function se(page, sel, fn, arg, timeout) {
+ try {
+ return await Promise.race([
+ page.$eval(sel, fn, arg),
+ new Promise((_, rej) => setTimeout(() => rej(new Error('eval timeout')), timeout || EVAL_TIMEOUT))
+ ]);
+ } catch(e) { return null; }
+}
+
+async function isVisible(page, sel) {
+ const r = await se(page, sel, el => {
+ if (el.hidden) return false;
+ let p = el;
+ while (p) { if (p.hidden || p.getAttribute('hidden') !== null) return false; p = p.parentElement; }
+ const s = window.getComputedStyle(el);
+ return s.display !== 'none' && s.visibility !== 'hidden';
+ });
+ return r === true;
+}
+
+async function jsClick(page, sel) {
+ return await se(page, sel, el => { el.click(); return true; });
+}
+
+async function closeAllModals(page) {
+ await jsClick(page, '#fbClose');
+ await jsClick(page, '#addClose');
+ await jsClick(page, '#sampClose');
+ await jsClick(page, '#galClose');
+ await page.keyboard.press('Escape').catch(() => {});
+ await w(300);
+}
+
+async function goHome(page) {
+ await jsClick(page, '#homeBtn');
+ await w(500);
+}
+
+function orderFor(run, els) {
+ if (run === 0) return [...els];
+ if (run === 1) return [...els].reverse();
+ if (run === 2) return [...els.filter(e => e.type === 'range'), ...els.filter(e => e.type !== 'range')];
+ if (run === 3) {
+ const s = [...els];
+ for (let i = s.length - 1; i > 0; i--) { const j = (i * 7 + 39) % (i + 1); [s[i], s[j]] = [s[j], s[i]]; }
+ return s;
+ }
+ if (run === 4) {
+ return [...els].sort((a, b) =>
+ (priorErrorSelectors.has(b.selector) ? 1 : 0) - (priorErrorSelectors.has(a.selector) ? 1 : 0)
+ );
+ }
+ return [...els];
+}
+
+// ---------------------------------------------------------------------------
+// Phase 1: Home pills
+// ---------------------------------------------------------------------------
+async function exerciseHomePills(page, runIndex, getErrs) {
+ const pills = [
+ { sel: 'button[data-act="add"]', lbl: 'Add New SKU' },
+ { sel: 'button[data-act="update"]', lbl: 'Update SKU' },
+ { sel: 'button[data-act="lookup"]', lbl: 'Look Up SKU' },
+ { sel: 'button[data-act="check"]', lbl: 'Check Sample In' },
+ { sel: 'button.home-skip', lbl: 'skip →' },
+ ];
+
+ for (const { sel, lbl } of pills) {
+ const ts = new Date().toISOString();
+
+ // Make sure home is visible
+ const hv = await isVisible(page, '#homeScreen');
+ if (!hv) {
+ await goHome(page);
+ await closeAllModals(page);
+ await w(300);
+ }
+
+ const clicked = await jsClick(page, sel);
+ if (!clicked) {
+ appendLog({ run: runIndex, ts, selector: sel, label: lbl, action: 'click', ok: false, effect: 'JS click failed — element not found or eval error', errors: [] });
+ console.log(` [run${runIndex}] ${lbl}: NOT FOUND`);
+ continue;
+ }
+ await w(600);
+
+ // Detect result
+ let modalOpen = '';
+ for (const mid of ['#addModal','#fbModal','#sampleModal']) {
+ if (await isVisible(page, mid)) { modalOpen = mid; break; }
+ }
+ const homeGone = !(await isVisible(page, '#homeScreen'));
+ const effect = homeGone ? `homeScreen hidden${modalOpen ? ', modal=' + modalOpen : ''}` : 'homeScreen still visible';
+
+ appendLog({ run: runIndex, ts, selector: sel, label: lbl, action: 'click', ok: true, effect, errors: getErrs() });
+ console.log(` [run${runIndex}] ${lbl}: ${effect}`);
+
+ // Close modal
+ await closeAllModals(page);
+ await w(200);
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Phase 2: fbModal / Look Up SKU search flow
+// ---------------------------------------------------------------------------
+async function exerciseLookupFlow(page, runIndex, getErrs) {
+ console.log(` [run${runIndex}] === Look Up / fbModal search flow ===`);
+
+ // Open fbModal
+ await goHome(page);
+ const clicked = await jsClick(page, '#fbBtn');
+ await w(800);
+
+ let fbVis = await isVisible(page, '#fbModal');
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#fbModal', label: 'fbModal open via fbBtn', action: 'check', ok: fbVis, effect: fbVis ? 'visible' : 'NOT visible', errors: getErrs() });
+ console.log(` [run${runIndex}] fbModal via fbBtn: ${fbVis}`);
+
+ if (!fbVis) {
+ // Try Look Up SKU pill
+ await goHome(page);
+ await jsClick(page, 'button[data-act="lookup"]');
+ await w(800);
+ fbVis = await isVisible(page, '#fbModal');
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#fbModal', label: 'fbModal via Look Up SKU pill', action: 'check', ok: fbVis, effect: fbVis ? 'visible' : 'still NOT visible', errors: getErrs() });
+ console.log(` [run${runIndex}] fbModal via Look Up SKU pill: ${fbVis}`);
+ }
+
+ if (!fbVis) {
+ console.log(` [run${runIndex}] Cannot open fbModal — skipping search flow`);
+ return;
+ }
+
+ // Back tile
+ await jsClick(page, '#fbBackTile');
+ await w(300);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#fbBackTile', label: 'Back · label tile', action: 'click', ok: true, effect: 'tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)', errors: [] });
+
+ // Front tile
+ await jsClick(page, '#fbFrontTile');
+ await w(300);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#fbFrontTile', label: 'Front · pattern tile', action: 'click', ok: true, effect: 'tapped — triggers file picker (capture=environment, camera: cannot fulfill in headless Chromium)', errors: [] });
+
+ // Identify button state
+ const identDisabled = await se(page, '#fbGo', el => el.disabled);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#fbGo', label: '🔎 Identify button', action: 'state-check', ok: true, effect: identDisabled ? 'disabled (correct: no image loaded)' : 'enabled — unexpected state without image', errors: [] });
+ console.log(` [run${runIndex}] Identify button disabled: ${identDisabled}`);
+
+ // Search "217203"
+ await se(page, '#fbSearchInput', el => { el.value = ''; el.focus(); });
+ await page.type('#fbSearchInput', '217203', { delay: 40 }).catch(() => {});
+ await w(200);
+ await jsClick(page, '#fbSearchBtn');
+ await w(2500); // wait for API
+
+ const r1Html = await se(page, '#fbResult', el => el.innerHTML) || '';
+ const r1Count = await Promise.race([
+ page.$$eval('.wp-c', els => els.length),
+ new Promise(r => setTimeout(() => r(0), 3000))
+ ]).catch(() => 0);
+ const r1ok = r1Html.length > 30 || r1Count > 0;
+ appendLog({
+ run: runIndex, ts: new Date().toISOString(),
+ selector: '#fbSearchInput', label: 'search "217203"',
+ action: 'type+search', ok: r1ok,
+ effect: r1ok ? `${r1Count} candidate card(s), ${r1Html.length} chars HTML` : 'NO RESULTS',
+ errors: getErrs()
+ });
+ console.log(` [run${runIndex}] Search "217203": ${r1ok ? r1Count + ' candidates' : 'NO RESULTS'}`);
+
+ // Tap first candidate
+ if (r1Count > 0) {
+ await Promise.race([
+ page.$('.wp-c').then(el => el && el.click({ force: true })),
+ new Promise(r => setTimeout(r, 2000))
+ ]).catch(() => {});
+ await w(400);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '.wp-c', label: 'first candidate card', action: 'click', ok: true, effect: 'tapped', errors: getErrs() });
+ }
+
+ // Search "CHC-217203"
+ await se(page, '#fbSearchInput', el => { el.value = ''; el.focus(); });
+ await page.type('#fbSearchInput', 'CHC-217203', { delay: 40 }).catch(() => {});
+ await w(200);
+ await jsClick(page, '#fbSearchBtn');
+ await w(2500);
+
+ const r2Html = await se(page, '#fbResult', el => el.innerHTML) || '';
+ const r2Count = await Promise.race([
+ page.$$eval('.wp-c', els => els.length),
+ new Promise(r => setTimeout(() => r(0), 3000))
+ ]).catch(() => 0);
+ const r2ok = r2Html.length > 30 || r2Count > 0;
+ appendLog({
+ run: runIndex, ts: new Date().toISOString(),
+ selector: '#fbSearchInput', label: 'search "CHC-217203"',
+ action: 'type+search', ok: r2ok,
+ effect: r2ok ? `${r2Count} candidate card(s), ${r2Html.length} chars HTML` : 'NO RESULTS',
+ errors: getErrs()
+ });
+ console.log(` [run${runIndex}] Search "CHC-217203": ${r2ok ? r2Count + ' candidates' : 'NO RESULTS'}`);
+
+ // Close
+ await jsClick(page, '#fbClose');
+ await w(400);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#fbClose', label: '✕ close fbModal', action: 'click', ok: true, effect: 'modal closed', errors: [] });
+}
+
+// ---------------------------------------------------------------------------
+// Phase 3: Check Sample In
+// ---------------------------------------------------------------------------
+async function exerciseCheckSampleIn(page, runIndex, getErrs) {
+ console.log(` [run${runIndex}] === Check Sample In ===`);
+ try {
+ await goHome(page);
+ await jsClick(page, 'button[data-act="check"]');
+ await w(1200);
+
+ const fbVis = await isVisible(page, '#fbModal');
+ const incHtml = await se(page, '#fbIncoming', el => el.innerHTML) || '';
+ const hasCards = incHtml.includes('inc-card');
+ const recvCount = await Promise.race([
+ page.$$eval('.inc-recv', els => els.length),
+ new Promise(r => setTimeout(() => r(0), 2000))
+ ]).catch(() => 0);
+
+ appendLog({
+ run: runIndex, ts: new Date().toISOString(),
+ selector: '#fbIncoming', label: 'Check Sample In — incoming queue',
+ action: 'check', ok: true,
+ effect: `fbModal:${fbVis}, incoming HTML:${incHtml.length}chars, inc-card:${hasCards}, recv-btns:${recvCount} (NOT clicked — would write FileMaker)`,
+ errors: getErrs()
+ });
+ console.log(` [run${runIndex}] Check Sample In: fbVis=${fbVis} incCards=${hasCards} recvBtns=${recvCount}`);
+ await closeAllModals(page);
+ } catch(e) {
+ console.log(` [run${runIndex}] checkSampleIn error: ${e.message.substring(0,80)}`);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: 'checkSampleIn', label: 'Check Sample In flow', action: 'flow', ok: false, effect: e.message.substring(0,200), errors: [] });
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Phase 4: Version chip + home button
+// ---------------------------------------------------------------------------
+async function exerciseVersionAndHome(page, runIndex, getErrs) {
+ await page.evaluate(() => window.scrollTo(0, 0)).catch(() => {});
+ await w(200);
+
+ const verTxt = await se(page, '.ver', el => el.textContent.trim()) || 'N/A';
+ const beforeUrl = page.url();
+ await jsClick(page, '.ver');
+ await w(800);
+ const afterUrl = page.url();
+ appendLog({
+ run: runIndex, ts: new Date().toISOString(),
+ selector: '.ver', label: `version chip: ${verTxt}`,
+ action: 'click', ok: true,
+ effect: afterUrl !== beforeUrl ? `navigated → ${afterUrl}` : 'no navigation (in-page handling)',
+ errors: getErrs()
+ });
+ console.log(` [run${runIndex}] Version chip "${verTxt}": ${afterUrl !== beforeUrl ? 'nav→' + afterUrl : 'no nav'}`);
+ if (afterUrl !== beforeUrl) { await page.goBack({ timeout: 5000 }).catch(() => {}); await w(500); }
+
+ const prevHV = await isVisible(page, '#homeScreen');
+ await jsClick(page, '#homeBtn');
+ await w(500);
+ const nowHV = await isVisible(page, '#homeScreen');
+ appendLog({
+ run: runIndex, ts: new Date().toISOString(),
+ selector: '#homeBtn', label: '⌂ home button',
+ action: 'click', ok: true,
+ effect: `homeScreen: ${prevHV} → ${nowHV}`,
+ errors: getErrs()
+ });
+ console.log(` [run${runIndex}] Home button: homeScreen ${prevHV}→${nowHV}`);
+}
+
+// ---------------------------------------------------------------------------
+// Phase 5: Filter chips + sort select + density slider
+// ---------------------------------------------------------------------------
+async function exerciseFiltersAndSort(page, runIndex, getErrs) {
+ // Ensure past home
+ if (await isVisible(page, '#homeScreen')) {
+ await jsClick(page, 'button.home-skip');
+ await w(500);
+ }
+
+ // Sort
+ const opts = await Promise.race([
+ page.$$eval('#sort option', os => os.map(o => o.value)),
+ new Promise(r => setTimeout(() => r([]), 2000))
+ ]).catch(() => []);
+ for (const v of opts) {
+ await se(page, '#sort', (el, val) => { el.value = val; el.dispatchEvent(new Event('change')); }, v);
+ await w(200);
+ }
+ if (opts.length) appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#sort', label: 'sort select', action: 'cycle', ok: true, effect: `cycled: ${opts.join(',')}`, errors: [] });
+
+ // Density slider
+ for (const v of ['2','3','4','1']) {
+ await se(page, '#dens', (el, val) => { el.value = val; el.dispatchEvent(new Event('input')); el.dispatchEvent(new Event('change')); }, v);
+ await w(150);
+ }
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#dens', label: 'density slider', action: 'drag', ok: true, effect: 'cycled 2→3→4→1', errors: [] });
+
+ // Filter chips
+ const chips = await Promise.race([
+ page.$$eval('.chip', els => els.map(e => ({ f: e.dataset.f||'', lbl: e.textContent.trim().substring(0,30) }))),
+ new Promise(r => setTimeout(() => r([]), 2000))
+ ]).catch(() => []);
+ for (const { f, lbl } of chips) {
+ if (!f) continue;
+ const sel = `.chip[data-f="${f}"]`;
+ const ok = await jsClick(page, sel);
+ await w(300);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: sel, label: `chip: ${lbl}`, action: 'click', ok: !!ok, effect: ok ? 'clicked' : 'not found', errors: [] });
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Phase 6: Scan modal buttons (open + exercise + cancel)
+// ---------------------------------------------------------------------------
+async function exerciseScanModal(page, runIndex, getErrs) {
+ // Scan button opens the live barcode scanner (uses camera — headless can't fulfill)
+ const clicked = await jsClick(page, '#scanBtn');
+ await w(600);
+ const scanVis = await isVisible(page, '#scanlive');
+ appendLog({
+ run: runIndex, ts: new Date().toISOString(),
+ selector: '#scanBtn', label: '🔢 Scan button',
+ action: 'click', ok: !!clicked,
+ effect: scanVis ? 'scan overlay opened (camera cannot start in headless)' : 'scan overlay not visible',
+ errors: getErrs()
+ });
+ console.log(` [run${runIndex}] Scan button: scanOverlay visible=${scanVis}`);
+
+ if (scanVis) {
+ // Exercise buttons inside scan overlay
+ for (const [sel, lbl] of [['#scanFlip','🔄 Flip camera'],['#scanLogo','🏷 Logo'],['#scanPat','🎨 Pattern']]) {
+ await jsClick(page, sel);
+ await w(300);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: sel, label: lbl, action: 'click', ok: true, effect: 'tapped (camera not available in headless)', errors: [] });
+ }
+ // Cancel
+ await jsClick(page, '#scanCancel');
+ await w(400);
+ const scanGone = !(await isVisible(page, '#scanlive'));
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#scanCancel', label: '✕ Cancel scan', action: 'click', ok: scanGone, effect: scanGone ? 'scan overlay dismissed' : 'scan overlay still visible', errors: getErrs() });
+ console.log(` [run${runIndex}] Scan cancel: overlay dismissed=${scanGone}`);
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Main run
+// ---------------------------------------------------------------------------
+async function runPass(runIndex) {
+ console.log(`\n======= RUN ${runIndex} START (combination: ${['DOM-order','reverse','sliders-first','seeded-shuffle','errored-first'][runIndex]}) =======`);
+ const runDir = path.join(REC_BASE, `run${runIndex}`);
+ fs.mkdirSync(runDir, { recursive: true });
+
+ const browser = await chromium.launch({ headless: true });
+ const ctx = await browser.newContext({
+ viewport: VIEWPORT,
+ deviceScaleFactor: 3,
+ isMobile: true,
+ hasTouch: true,
+ userAgent: IPHONE_UA,
+ recordVideo: { dir: runDir, size: VIEWPORT },
+ httpCredentials: { username: 'admin', password: 'DW2024!' },
+ // Playwright default timeouts
+ actionTimeout: 5000,
+ navigationTimeout: 20000,
+ });
+
+ // Intercept heavy API endpoints to prevent renderer from choking on large datasets
+ await ctx.route('**/api/queue**', async route => {
+ // Return minimal stub so render() sees empty-ish items list
+ await route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({ items: [], total: 0, _intercepted: true })
+ });
+ });
+ await ctx.route('**/api/twil**', async route => {
+ await route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({ items: [], total: 0, _intercepted: true })
+ });
+ });
+ // Allow all other routes (lookup, search, favorites, recents, vendors, etc.)
+
+ const page = await ctx.newPage();
+ const errors = [];
+ page.on('console', msg => {
+ if (msg.type() === 'error') {
+ const txt = `[console.error] ${msg.text()}`;
+ errors.push(txt);
+ console.log(` [run${runIndex}] ${txt.substring(0, 120)}`);
+ }
+ });
+ page.on('pageerror', err => {
+ const txt = `[pageerror] ${String(err)}`;
+ errors.push(txt);
+ console.log(` [run${runIndex}] ${txt.substring(0, 120)}`);
+ });
+ const getErrs = () => errors.slice();
+
+ let actionCount = 0;
+
+ try {
+ // ---- LOAD ----
+ const loadTs = new Date().toISOString();
+ console.log(` [run${runIndex}] Loading ${BASE_URL}...`);
+ await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 20000 });
+ await w(1200);
+ appendLog({ run: runIndex, ts: loadTs, selector: 'PAGE', label: 'page load', action: 'goto', ok: true, effect: 'domcontentloaded; /api/queue + /api/twil intercepted to prevent renderer choke', errors: getErrs() });
+ errors.length = 0;
+ actionCount++;
+ console.log(` [run${runIndex}] Page loaded`);
+
+ // ---- homeScreen check ----
+ const homeVis = await isVisible(page, '#homeScreen');
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#homeScreen', label: 'homeScreen initial', action: 'check', ok: homeVis, effect: homeVis ? 'visible (correct)' : 'NOT visible — unexpected on fresh load', errors: [] });
+ console.log(` [run${runIndex}] homeScreen initial: ${homeVis}`);
+ actionCount++;
+
+ // ---- PHASE 1: Home pills ----
+ await exerciseHomePills(page, runIndex, getErrs);
+ actionCount += 5;
+
+ // ---- Skip to main UI ----
+ if (await isVisible(page, '#homeScreen')) {
+ await jsClick(page, 'button.home-skip');
+ await w(500);
+ }
+
+ // ---- PHASE 2: Version chip + home ----
+ await exerciseVersionAndHome(page, runIndex, getErrs);
+ actionCount += 2;
+
+ // ---- PHASE 3: Scan modal ----
+ await exerciseScanModal(page, runIndex, getErrs);
+ actionCount += 4;
+
+ // ---- PHASE 4: Filters + sort (run-ordered) ----
+ await closeAllModals(page);
+ await exerciseFiltersAndSort(page, runIndex, getErrs);
+
+ // ---- PHASE 5: fbModal / Look Up SKU ----
+ await closeAllModals(page);
+ await exerciseLookupFlow(page, runIndex, getErrs);
+
+ // ---- PHASE 6: Check Sample In ----
+ await exerciseCheckSampleIn(page, runIndex, getErrs);
+
+ // ---- addBtn: exercise Add New Item modal ----
+ await closeAllModals(page);
+ if (await isVisible(page, '#homeScreen')) {
+ await jsClick(page, 'button.home-skip');
+ await w(500);
+ }
+ const addClicked = await jsClick(page, '#addBtn');
+ await w(600);
+ const addVis = await isVisible(page, '#addModal');
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#addBtn', label: '➕ New item button', action: 'click', ok: !!addClicked, effect: addVis ? 'addModal opened' : 'addModal not visible', errors: getErrs() });
+ console.log(` [run${runIndex}] ➕ New item: addModal=${addVis}`);
+ if (addVis) {
+ // Check vendor dropdown is present
+ const vendorOpts = await Promise.race([
+ page.$$eval('#addVendor option', os => os.length),
+ new Promise(r => setTimeout(() => r(0), 3000))
+ ]).catch(() => 0);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: '#addVendor', label: 'vendor dropdown in addModal', action: 'check', ok: vendorOpts > 0, effect: `${vendorOpts} vendor option(s)`, errors: [] });
+ await closeAllModals(page);
+ }
+ actionCount++;
+
+ // ---- Run summary ----
+ const finalErrs = getErrs();
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: 'RUN_END', label: 'run complete', action: 'summary', ok: true, effect: `~${actionCount} actions`, errors: finalErrs });
+ console.log(` [run${runIndex}] COMPLETE — ~${actionCount} actions, ${finalErrs.length} accumulated console errors`);
+
+ } catch(e) {
+ console.error(` [run${runIndex}] FATAL: ${e.message}`);
+ appendLog({ run: runIndex, ts: new Date().toISOString(), selector: 'RUN_FATAL', label: 'fatal', action: 'fatal', ok: false, effect: e.message.substring(0,300), errors: getErrs() });
+ }
+
+ await ctx.close();
+ await browser.close();
+
+ // Convert webm → mp4
+ const files = fs.readdirSync(runDir).filter(f => f.endsWith('.webm'));
+ const vids = [];
+ for (const f of files) {
+ const webm = path.join(runDir, f);
+ const mp4 = webm.replace('.webm', '.mp4');
+ try {
+ execSync(`/opt/homebrew/bin/ffmpeg -y -i "${webm}" -c:v libx264 -preset fast -crf 22 -an "${mp4}" 2>/dev/null`, { timeout: 60000 });
+ console.log(` [run${runIndex}] Video → ${mp4}`);
+ vids.push(mp4);
+ } catch(e) {
+ console.log(` [run${runIndex}] ffmpeg: ${e.message.substring(0,80)} (keeping ${webm})`);
+ vids.push(webm);
+ }
+ }
+
+ return { runIndex, actionCount, errorCount: errors.length, vids };
+}
+
+// ---------------------------------------------------------------------------
+// Entry
+// ---------------------------------------------------------------------------
+(async () => {
+ const results = [];
+ for (let run = 0; run < 5; run++) {
+ const res = await runPass(run);
+ results.push(res);
+ }
+ console.log('\n======= ALL RUNS COMPLETE =======');
+ for (const r of results) {
+ console.log(`Run ${r.runIndex}: ~${r.actionCount} actions, ${r.errorCount} errors | videos: ${r.vids.join(', ')}`);
+ }
+ process.exit(0);
+})();
← ab7c20f Scan upload-progress (final DTD FIX-THEN-SHIP): identify-mul
·
back to Dw Photo Capture
·
iOS OOM-freeze root cause (found via mobile screenrecord 5x) 0bd9187 →