refine swipe action reveal timing

This commit is contained in:
dobby-d-elf
2026-05-28 12:47:55 -06:00
parent 354fec90bd
commit b253d345cf
5 changed files with 95 additions and 16 deletions

View File

@@ -113,12 +113,28 @@ def _run_git(args, cwd, timeout=10):
def _dirty_suffix(path: Path, timeout=1) -> str:
"""Return a best-effort ``-dirty`` suffix without blocking version display."""
out, ok = _run_git(['diff-index', '--quiet', 'HEAD', '--'], path, timeout=timeout)
if ok:
try:
r = subprocess.run(
['git', 'diff-index', '--quiet', 'HEAD', '--'],
cwd=str(path),
capture_output=True,
text=True,
timeout=timeout,
)
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
return ""
if r.returncode == 0:
return ""
# diff-index exits 1 with no output for a dirty tree. Timeouts and real git
# failures include a diagnostic; skip the suffix so the base version remains.
return "-dirty" if not out else ""
diagnostic = (r.stderr or r.stdout or "").strip()
if r.returncode != 1 or diagnostic:
return ""
diff, ok = _run_git(['diff', '--binary', 'HEAD', '--'], path, timeout=timeout)
if not (ok and diff):
return "-dirty"
digest = hashlib.sha1(diff.encode('utf-8', errors='replace')).hexdigest()[:8]
return f"-dirty-{digest}"
def _describe_git_version(path: Path, *, timeout=5, dirty_timeout=1) -> str | None:

View File

@@ -3866,20 +3866,24 @@ function renderSessionListFromCache(){
const offset=Math.sign(rawOffset)*(Math.abs(revealedOffset)+Math.sqrt(overshoot)*5);
const progress=Math.min(1,Math.abs(revealedOffset)/72);
const reveal=Math.abs(offset);
const iconScale=Math.min(1,Math.max(.01,progress*1.7));
const actionRevealScale=1.15;
const iconScale=Math.min(1,Math.max(.01,progress*actionRevealScale));
const badgeSize=34*iconScale;
const iconSize=18*iconScale;
const labelScale=Math.min(1,Math.max(.01,progress*1.7));
const actionInset=0;
const labelScale=Math.min(1,Math.max(.01,progress*actionRevealScale));
const actionOpacity=Math.min(1,Math.max(.01,progress*actionRevealScale));
const actionInset=6;
const tileGap=6;
const badgeStretch=Math.min(Math.max(0,reveal-34),overshoot*1.15,Math.max(0,reveal-badgeSize-actionInset-tileGap));
const stretchStart=72/actionRevealScale;
const stretchProgress=Math.max(0,reveal-stretchStart);
const badgeStretch=Math.min(Math.max(0,reveal-34),stretchProgress*1.15,Math.max(0,reveal-badgeSize-actionInset-tileGap));
el.style.setProperty('--session-swipe-offset',offset+'px');
el.style.setProperty('--session-swipe-reveal',reveal+'px');
el.style.setProperty('--session-swipe-badge-size',badgeSize+'px');
el.style.setProperty('--session-swipe-icon-size',iconSize+'px');
el.style.setProperty('--session-swipe-label-scale',labelScale);
el.style.setProperty('--session-swipe-badge-stretch',badgeStretch+'px');
el.style.setProperty('--session-swipe-progress',Math.pow(progress,1.5));
el.style.setProperty('--session-swipe-progress',actionOpacity);
el.classList.toggle('swiping-right',offset>0);
el.classList.toggle('swiping-left',offset<0);
};

View File

@@ -807,8 +807,8 @@
transform:scale(var(--session-swipe-label-scale,1));
transform-origin:top center;
}
.session-swipe-affordance-right .session-swipe-action-stack{margin-left:0;}
.session-swipe-affordance-left .session-swipe-action-stack{margin-right:0;}
.session-swipe-affordance-right .session-swipe-action-stack{transform:translateX(6px);}
.session-swipe-affordance-left .session-swipe-action-stack{transform:translateX(-6px);}
.session-item.dragging{transition:background .15s,color .15s,box-shadow .15s ease;will-change:transform;}
.session-item.session-reflowing{transition:background .15s,color .15s,transform .36s cubic-bezier(.2,.8,.2,1),box-shadow .15s ease;will-change:transform;}
.session-item.swipe-committed,

View File

@@ -123,16 +123,22 @@ def test_session_swipe_paint_uses_transform_only_exit():
assert "--session-swipe-progress" in paint
assert "--session-swipe-badge-stretch" in paint
assert "const reveal=Math.abs(offset);" in paint
assert "const iconScale=Math.min(1,Math.max(.01,progress*1.7));" in paint
assert "const actionRevealScale=1.15;" in paint
assert "const iconScale=Math.min(1,Math.max(.01,progress*actionRevealScale));" in paint
assert "const badgeSize=34*iconScale;" in paint
assert "const iconSize=18*iconScale;" in paint
assert "const labelScale=Math.min(1,Math.max(.01,progress*1.7));" in paint
assert "const actionInset=0;" in paint
assert "const labelScale=Math.min(1,Math.max(.01,progress*actionRevealScale));" in paint
assert "const actionOpacity=Math.min(1,Math.max(.01,progress*actionRevealScale));" in paint
assert "const actionInset=6;" in paint
assert "const tileGap=6;" in paint
assert "const badgeStretch=Math.min(Math.max(0,reveal-34),overshoot*1.15,Math.max(0,reveal-badgeSize-actionInset-tileGap));" in paint
assert "const stretchStart=72/actionRevealScale;" in paint
assert "const stretchProgress=Math.max(0,reveal-stretchStart);" in paint
assert "const badgeStretch=Math.min(Math.max(0,reveal-34),stretchProgress*1.15,Math.max(0,reveal-badgeSize-actionInset-tileGap));" in paint
assert "el.style.setProperty('--session-swipe-badge-size',badgeSize+'px');" in paint
assert "el.style.setProperty('--session-swipe-icon-size',iconSize+'px');" in paint
assert "el.style.setProperty('--session-swipe-label-scale',labelScale);" in paint
assert "el.style.setProperty('--session-swipe-progress',actionOpacity);" in paint
assert "Math.pow(progress,1.5)" not in paint
assert "window.innerWidth+'px'" in complete
assert "el.style.height=rect.height+'px'" in complete
assert "requestAnimationFrame(()=>el.classList.add('swipe-removing'))" in complete
@@ -189,8 +195,8 @@ def test_session_swipe_actions_use_circular_icon_badges():
assert "font-size:10px" in label
assert "transform:scale(var(--session-swipe-label-scale,1))" in label
assert "transform-origin:top center" in label
assert ".session-swipe-affordance-right .session-swipe-action-stack{margin-left:0;}" in STYLE_CSS
assert ".session-swipe-affordance-left .session-swipe-action-stack{margin-right:0;}" in STYLE_CSS
assert ".session-swipe-affordance-right .session-swipe-action-stack{transform:translateX(6px);}" in STYLE_CSS
assert ".session-swipe-affordance-left .session-swipe-action-stack{transform:translateX(-6px);}" in STYLE_CSS
def test_session_removal_reflows_surviving_rows_smoothly():

View File

@@ -34,6 +34,59 @@ def _make_run_git_side_effect(*sequence):
_MODULE = 'api.updates'
# ---------------------------------------------------------------------------
# Tests for version dirty suffix
# ---------------------------------------------------------------------------
class TestVersionDirtySuffix:
def test_dirty_suffix_detects_diff_index_dirty_exit(self, tmp_path):
"""git diff-index exits 1 with no output for normal dirty checkouts."""
from api import updates
diff_index = subprocess.CompletedProcess(
['git', 'diff-index', '--quiet', 'HEAD', '--'],
1,
stdout='',
stderr='',
)
with patch(f'{_MODULE}.subprocess.run', return_value=diff_index), \
patch(f'{_MODULE}._run_git', return_value=('changed file content', True)):
assert updates._dirty_suffix(tmp_path).startswith('-dirty-')
def test_dirty_suffix_changes_with_diff_content(self, tmp_path):
"""Dirty asset tokens should change when local tracked edits change."""
from api import updates
diff_index = subprocess.CompletedProcess(
['git', 'diff-index', '--quiet', 'HEAD', '--'],
1,
stdout='',
stderr='',
)
with patch(f'{_MODULE}.subprocess.run', return_value=diff_index), \
patch(f'{_MODULE}._run_git', return_value=('first diff', True)):
first = updates._dirty_suffix(tmp_path)
with patch(f'{_MODULE}.subprocess.run', return_value=diff_index), \
patch(f'{_MODULE}._run_git', return_value=('second diff', True)):
second = updates._dirty_suffix(tmp_path)
assert first.startswith('-dirty-')
assert second.startswith('-dirty-')
assert first != second
def test_dirty_suffix_ignores_git_errors(self, tmp_path):
"""Real git diagnostics should not mark unknown/broken repos dirty."""
from api import updates
completed = subprocess.CompletedProcess(
['git', 'diff-index', '--quiet', 'HEAD', '--'],
128,
stdout='',
stderr='fatal: not a git repository',
)
with patch(f'{_MODULE}.subprocess.run', return_value=completed):
assert updates._dirty_suffix(tmp_path) == ''
# ---------------------------------------------------------------------------
# Tests for _apply_update_inner() diagnostic paths
# ---------------------------------------------------------------------------