Skip to content

feat(web): swipe session rows to archive or delete on touch - #3154

Closed
btli wants to merge 2 commits into
omnigent-ai:mainfrom
btli:feature/mobile-slide-actions
Closed

feat(web): swipe session rows to archive or delete on touch#3154
btli wants to merge 2 commits into
omnigent-ai:mainfrom
btli:feature/mobile-slide-actions

Conversation

@btli

@btli btli commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Related issue

N/A

Summary

Session rows in the left panel had no touch-friendly quick actions — on
mobile you had to open the kebab to archive or delete. This adds a
configurable horizontal swipe gesture on each row:

  • Swipe a row left or right to run an action. Each direction independently
    maps to Archive, Delete, or None, persisted per-device.
  • Configure it in Settings → Appearance → Swipe actions (two selects,
    one per direction). Defaults: swipe-left → Archive, swipe-right → None.
  • Swipe→archive reuses the kebab's existing stop-then-archive handler;
    swipe→delete opens the same delete confirm dialog the kebab uses (no
    immediate delete, no separate undo path). "None" is inert.
  • Coexists with the existing dnd-kit drag-to-project: the swipe locks by
    axis (horizontal past a threshold) and yields to the long-press drag, so
    drag-to-file-into-project is unaffected. Gated to touch/mobile so desktop
    hover controls don't regress.

Test Plan

Run in web/ (Node 20):

  • npm run type-check — clean
  • npm run lint — no new issues in changed files (pre-existing warnings/errors only)
  • npm run test — 4099 passed | 3 expected fail | 2 skipped
  • npm run format:check — all files match Prettier style

New/updated tests:

  • web/src/lib/swipeActionPreferences.test.ts — read/write/validate/defaults round-trip.
  • web/src/shell/Sidebar.rowActions.test.tsx — swipe→archive drives the
    stop→archive handler; swipe→delete opens the confirm dialog (deletes only
    after confirm); "none" is inert; both-directions-same-action; sub-threshold
    swipe is a no-op; desktop ignores the gesture. Driven with
    fireEvent.pointerDown/Move/Up since jsdom has no real touch.

Demo

Recorded on a mobile viewport (touch emulation). The clip shows: opening
Settings → Appearance → Swipe actions and setting Swipe left → Archive
and Swipe right → Delete, then back on the session list a swipe-left
archives a row (it slides over the archive hint and leaves the list), and a
swipe-right on another row opens the Delete confirmation dialog — delete
stays gated behind confirm, never immediate.

Swipe a session row to archive or delete on touch

Download MP4

Type of change

  • Bug fix
  • Feature
  • UI / frontend change
  • Refactor / chore
  • Docs
  • Test / CI
  • Breaking change

Test coverage

  • Unit tests added / updated
  • Integration tests added / updated
  • E2E tests added / updated
  • Manual verification completed
  • Existing tests cover this change
  • Not applicable

Coverage notes

Behavior is covered by unit tests (preference round-trip + swipe gesture via
synthetic pointer events). The live touch gesture / visual affordance is shown in the Demo section above.

Changelog

Swipe left or right on a session row (touch devices) to archive or delete it, configurable in Settings → Appearance.

This pull request and its description were written by Isaac.

@github-actions github-actions Bot added the size/XL Pull request size: XL label Jul 23, 2026
@github-actions
github-actions Bot requested a review from PattaraS July 23, 2026 17:06
@github-actions

Copy link
Copy Markdown
Contributor

@btli This PR is a Bug fix, Feature, or UI / frontend change but the Demo section is missing or only contains a placeholder.

These change types require a screenshot or screen recording so reviewers can see the new behaviour without checking out the branch. Please update the Demo section with:

  • A screenshot or screen recording of the change, or
  • A link to a hosted video or GIF showing the new behaviour.

Use N/A only when the change has no user-visible effect whatsoever (e.g. a pure refactor or test-only change). If that's the case, uncheck the relevant type box and check Refactor / chore or Test / CI instead.

@github-actions github-actions Bot added the needs-demo PR needs a demo screenshot or recording label Jul 23, 2026
@btli
btli force-pushed the feature/mobile-slide-actions branch 3 times, most recently from f5a2567 to 371798c Compare July 23, 2026 21:07
Add a configurable horizontal swipe gesture on left-panel session rows
for touch devices. Each direction (left/right) maps to an action —
archive, delete, or none — persisted per-device and set from Settings →
Appearance.

Swipe→archive reuses the kebab's stop-then-archive handler; swipe→delete
opens the same confirm dialog (no immediate delete). The gesture
disambiguates from the existing dnd-kit drag by axis and yields once a
long-press drag begins, so drag-to-project is unaffected.

Co-authored-by: Isaac
Signed-off-by: Bryan Li <bryan.li@gmail.com>
@btli
btli force-pushed the feature/mobile-slide-actions branch 4 times, most recently from 61d8e1c to d037cea Compare July 24, 2026 22:39
Address review findings on the touch swipe-to-archive/delete gesture:

- Only wrap the row in the translating moving-surface div when the swipe
  is actually available (touch + mobile + owner, not selection mode).
  Selection/desktop rows keep the prior `<li> > link` DOM so the
  selection-mode checkbox stays a sibling of the link's parent (fixes the
  standing sidebar bulk-actions e2e selection-mode assertion).
- Add `touch-action: pan-y` to the swiped row so vertical scroll stays
  native and the horizontal gesture isn't handed off to native scroll
  (which would pointercancel during the pre-activation window).
- Suppress the trailing synthesized click after a committed swipe so the
  row's <Link> can't navigate into the session being archived/deleted.
  Scope the capture-phase guard to the moving surface so it never
  swallows a click in the delete-confirm dialog.
- Replace the stale-closure eslint-disable on the swipe handler with a
  latest-handler ref, so archive/delete always see the live row state.
- Release pointer capture when a dnd-kit drag preempts a locked swipe.
- Cache the swipe-action preference snapshot so getSnapshot stops
  re-parsing localStorage on every render; refresh only on real changes
  (writes, cross-tab storage events, and subscribe).

Add a mobile e2e test that drives a real touch swipe (CDP touch) on a
session row: swipe-left archives (server flag flips), swipe-right opens
the delete-confirm dialog and cancels cleanly.

Signed-off-by: Bryan Li <bryan.li@gmail.com>
@btli
btli force-pushed the feature/mobile-slide-actions branch from d037cea to 5a9676a Compare July 24, 2026 23:20
@fanzeyi

fanzeyi commented Jul 26, 2026

Copy link
Copy Markdown
Member

Hi, thanks for working on this. There are a few issues with this PR:

  • There are now background color for each row in the session:
image
  • The icons location are incorrect in small screen (included in the screenshot above, the unread indicator).
  • When swiping, the icon is now overlapping with the session content itself:
image

@fanzeyi fanzeyi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment above

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Closing this PR because it has been labeled waiting-on-author for 7 days without an author reply or new commit.

The label was last applied on 2026-07-26T23:17:55Z. If you are ready to continue, please reopen this PR or open a new one.

@btli

btli commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Continued in #3985 — I don't have permission to reopen this one, so the work landed in a fresh PR against current main.

@fanzeyi all three points from your review are fixed there:

  • Row background colors — the translating wrapper had an unconditional bg-sidebar, which plated every row at rest (desktop included). It now paints only while a swipe is in progress: at rest backgroundColor reads rgba(0, 0, 0, 0).
  • Icon positions on small screens — the session-state badge and the pin/kebab are absolutely positioned and were sitting outside the translating wrapper. All row content now shares one layout-neutral surface; measured in a real browser, the wrapper rect is identical to the <li> (left 9, right 400.5, top 292.5, height 36), so nothing moves at rest.
  • Icon overlapping the session content mid-swipe — that was the same root cause: the icons did not translate, so the text slid under them. They now travel with the row (kebab 369 → 273, exactly the −96px swipe), and the reveal hint sits in the gap the swipe opens — hint icon at 362–385 vs the row edge at 304.5, so no overlap.

#3985 has the demo recorded against the running app, plus the geometry readings behind those numbers. A second review pass also turned up a real gesture bug — a fast flick could commit off a stale offset and either miss the archive or fire when you had pulled back — fixed there with a regression test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-demo PR needs a demo screenshot or recording size/XL Pull request size: XL waiting-on-author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants