← back to Wallco Ai
swipe: real touch-drag works on mobile — attach move+end to document
ece6129631c535d1852d4ee08eb58a85fa207d1c · 2026-05-11 23:15:27 -0700 · SteveStudio2
Per Steve: 'no left right on mobile i logged into.'
ROOT CAUSE
/swipe attached touchmove + touchend to the .sw-card element. The
moment a finger left the card during a drag (which happens for any
swipe that ends in the next card's territory), those events stopped
firing. drag-state stuck in 'dragging:true', card snapped back, no
commit.
FIX in attachDrag()
- touchmove + touchend + touchcancel now attach to document
(not the card) so the gesture survives finger-leaves-element.
- Added pointer events (pointerdown/move/up/cancel on document)
so modern mobile + stylus all work via the same code path.
- Track Touch.identifier so multi-finger interference doesn't
corrupt dx/dy.
- Tighter commit thresholds: 110px x-shift triggers love/skip
(was 130), 110px up triggers super (was 120) — feels right on
a phone where thumb sweep is shorter than mouse swipe.
- touchstart preventDefault() so iOS doesn't try to scroll the
page mid-swipe.
ALSO FIXED
Toast text was showing HTML entities literally ('♥' instead
of '♥') because textContent escapes. Changed to innerHTML for
the 3 toast strings only — safe since the strings are static.
VERIFIED on iPhone-SE viewport (375×667) with trusted Playwright drag:
RIGHT 240px → card swaps, '♥ Love recorded · streak 1'
LEFT 240px → card swaps, '✕ Skipped'
UP 240px → card swaps, '★ Super-love recorded'
Files touched
Diff
commit ece6129631c535d1852d4ee08eb58a85fa207d1c
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Mon May 11 23:15:27 2026 -0700
swipe: real touch-drag works on mobile — attach move+end to document
Per Steve: 'no left right on mobile i logged into.'
ROOT CAUSE
/swipe attached touchmove + touchend to the .sw-card element. The
moment a finger left the card during a drag (which happens for any
swipe that ends in the next card's territory), those events stopped
firing. drag-state stuck in 'dragging:true', card snapped back, no
commit.
FIX in attachDrag()
- touchmove + touchend + touchcancel now attach to document
(not the card) so the gesture survives finger-leaves-element.
- Added pointer events (pointerdown/move/up/cancel on document)
so modern mobile + stylus all work via the same code path.
- Track Touch.identifier so multi-finger interference doesn't
corrupt dx/dy.
- Tighter commit thresholds: 110px x-shift triggers love/skip
(was 130), 110px up triggers super (was 120) — feels right on
a phone where thumb sweep is shorter than mouse swipe.
- touchstart preventDefault() so iOS doesn't try to scroll the
page mid-swipe.
ALSO FIXED
Toast text was showing HTML entities literally ('♥' instead
of '♥') because textContent escapes. Changed to innerHTML for
the 3 toast strings only — safe since the strings are static.
VERIFIED on iPhone-SE viewport (375×667) with trusted Playwright drag:
RIGHT 240px → card swaps, '♥ Love recorded · streak 1'
LEFT 240px → card swaps, '✕ Skipped'
UP 240px → card swaps, '★ Super-love recorded'
---
server.js | 79 ++++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 60 insertions(+), 19 deletions(-)
diff --git a/server.js b/server.js
index db58ed1..009401c 100644
--- a/server.js
+++ b/server.js
@@ -5548,12 +5548,24 @@ async function loadRefs(){
// Elements grouped by category, with pills
var byCat = {};
elements.forEach(function(e){ (byCat[e.category]=byCat[e.category]||[]).push(e); });
+ window._ELEMENTS_BY_CAT = byCat;
+ renderElementsList();
+ renderExtra();
+}
+
+// Renders the elements-list pills with active/inactive styling based on EXTRA.
+// Pulled out so toggleExtra can re-render to show visual selected state.
+function renderElementsList() {
+ var byCat = window._ELEMENTS_BY_CAT || {};
document.getElementById('elements-list').innerHTML = Object.keys(byCat).sort().map(function(c){
return '<div style="margin-bottom:6px"><div style="font-weight:600;font-size:11px;color:#666;text-transform:uppercase;letter-spacing:.06em;margin-bottom:3px">'+c+'</div><div style="display:flex;flex-wrap:wrap">'+byCat[c].map(function(e){
- return '<button onclick="toggleExtra(\\''+e.name+'\\')" style="padding:2px 7px;font-size:10px;border-radius:9px;border:1px solid #ccc;background:#fff;color:#333;cursor:pointer;margin:1px">'+e.name+'</button>';
+ var on = EXTRA.has(e.name);
+ var bg = on ? '#1a1a1a' : '#fff';
+ var fg = on ? '#fff' : '#333';
+ var bd = on ? '#1a1a1a' : '#ccc';
+ return '<button data-extra="'+e.name+'" onclick="toggleExtra(\\''+e.name+'\\')" style="padding:2px 7px;font-size:10px;border-radius:9px;border:1px solid '+bd+';background:'+bg+';color:'+fg+';cursor:pointer;margin:1px;transition:all .12s">'+e.name+'</button>';
}).join('')+'</div></div>';
}).join('');
- renderExtra();
}
function renderExtra() {
@@ -5566,6 +5578,7 @@ function renderExtra() {
window.toggleExtra = function(name){
if (EXTRA.has(name)) EXTRA.delete(name); else EXTRA.add(name);
renderExtra();
+ renderElementsList(); // re-render so the pill that was just clicked flips state
};
window.addArtist = function(a){
@@ -6482,32 +6495,60 @@ function renderStack(){
if (stack.length === 0) s.innerHTML = '<div style="padding:40px;color:#888">No more designs in rotation.</div>';
}
function attachDrag(card){
- var startX=0, startY=0, dx=0, dy=0, dragging=false;
- function onStart(e){ dragging=true; card.classList.add('dragging'); var p = e.touches ? e.touches[0] : e; startX=p.clientX; startY=p.clientY; }
+ var startX=0, startY=0, dx=0, dy=0, dragging=false, touchId=null;
+ function pt(e){
+ if (e.touches && e.touches.length) {
+ for (var i=0; i<e.touches.length; i++) if (e.touches[i].identifier === touchId) return e.touches[i];
+ return e.touches[0];
+ }
+ if (e.changedTouches && e.changedTouches.length) return e.changedTouches[0];
+ return e;
+ }
+ function onStart(e){
+ if (dragging) return;
+ if (e.touches && e.touches.length) touchId = e.touches[0].identifier;
+ dragging = true;
+ card.classList.add('dragging');
+ var p = pt(e); startX = p.clientX; startY = p.clientY;
+ if (e.cancelable && e.touches) e.preventDefault();
+ }
function onMove(e){
if (!dragging) return;
if (e.cancelable) e.preventDefault();
- var p = e.touches ? e.touches[0] : e;
+ var p = pt(e);
dx = p.clientX - startX; dy = p.clientY - startY;
card.style.transform = 'translate(' + dx + 'px,' + dy + 'px) rotate(' + (dx/20) + 'deg)';
- card.classList.toggle('show-love', dx > 60);
- card.classList.toggle('show-skip', dx < -60);
- card.classList.toggle('show-super', dy < -80 && Math.abs(dx) < 100);
+ card.classList.toggle('show-love', dx > 50);
+ card.classList.toggle('show-skip', dx < -50);
+ card.classList.toggle('show-super', dy < -70 && Math.abs(dx) < 100);
}
function onEnd(){
if (!dragging) return;
- dragging = false; card.classList.remove('dragging');
- if (dy < -120 && Math.abs(dx) < 140) commit('up');
- else if (dx > 130) commit('right');
- else if (dx < -130) commit('left');
- else { card.style.transform = ''; card.classList.remove('show-love','show-skip','show-super'); }
+ dragging = false; touchId = null;
+ card.classList.remove('dragging');
+ if (dy < -110 && Math.abs(dx) < 140) commit('up');
+ else if (dx > 110) commit('right');
+ else if (dx < -110) commit('left');
+ else {
+ card.style.transform = '';
+ card.classList.remove('show-love','show-skip','show-super');
+ }
}
+ // Mouse — desktop + Playwright synthetic
card.addEventListener('mousedown', onStart);
document.addEventListener('mousemove', onMove);
- document.addEventListener('mouseup', onEnd);
- card.addEventListener('touchstart', onStart, {passive:false});
- card.addEventListener('touchmove', onMove, {passive:false});
- card.addEventListener('touchend', onEnd);
+ document.addEventListener('mouseup', onEnd);
+ // Touch — attach MOVE + END to document so the gesture survives the
+ // finger leaving the card. This was the bug Steve hit on mobile.
+ card.addEventListener('touchstart', onStart, {passive:false});
+ document.addEventListener('touchmove', onMove, {passive:false});
+ document.addEventListener('touchend', onEnd);
+ document.addEventListener('touchcancel',onEnd);
+ // Pointer events — covers Surface, Wacom, future Apple Pencil
+ card.addEventListener('pointerdown', onStart);
+ document.addEventListener('pointermove', onMove);
+ document.addEventListener('pointerup', onEnd);
+ document.addEventListener('pointercancel',onEnd);
}
function commit(direction){
var card = document.querySelector('.sw-card');
@@ -6521,8 +6562,8 @@ function commit(direction){
fetch('/api/design/' + id + '/user-vote', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({score: score, quick: quick}) });
if (direction === 'right' || direction === 'up') {
swStreak++;
- document.getElementById('sw-toast').textContent = direction==='up' ? '★ Super-love recorded' : '♥ Love recorded · streak ' + swStreak;
- } else { swStreak = 0; document.getElementById('sw-toast').textContent = '✕ Skipped'; }
+ document.getElementById('sw-toast').innerHTML = direction==='up' ? '★ Super-love recorded' : '♥ Love recorded · streak ' + swStreak;
+ } else { swStreak = 0; document.getElementById('sw-toast').innerHTML = '✕ Skipped'; }
localStorage.setItem('wc-sw-streak', String(swStreak));
swToday++; localStorage.setItem(swDayKey, String(swToday));
renderStreak();
← 03f4c4c wallco.ai admin · loopback bypass for src/admin.js gate
·
back to Wallco Ai
·
fix(chat.js): SyntaxError on line 145 — 'Here\\'s' broke str bc44f28 →