feat(web): replace session list refresh button with pull-to-refresh

Remove the refresh icon button from the session list toolbar and make
the list itself the refresh affordance via a touch pull gesture.

Gesture (SessionList):
- Touch listeners on the scrollable list container; pull engages only
  at scrollTop 0, with 16px feedback / 64px trigger thresholds, and
  fires on release past the trigger. Mirrors the established
  pull-to-load-older pattern in HappyThread.
- Touch-only by design: desktop has no overscroll bounce, so a wheel
  pull feels broken; desktop keeps relying on SSE live updates and
  query focus refetch.
- onRefresh widened to () => Promise<unknown> | void so the indicator
  tracks the in-flight refetch and ignores re-entrant pulls.

Feedback:
- Status pill over the list (role=status, aria-live) shows
  pull/release/refreshing states with a spinner while refreshing; it
  also covers the initial useSessions load (isLoading), which lost its
  only busy indication when the toolbar button was removed.
- The success toast is dropped (the pill is the feedback); the failure
  toast is kept. handleRefresh now returns its promise.

Empty states (review P2s):
- SessionsEmptyState and the no-results message move from the shrink-0
  header container into the scroll container, so the gesture works on
  the visible empty state (retry path after a failed initial fetch)
  and short viewports scroll instead of crushing the gesture area.
- SessionsEmptyState is gated on !isLoading so a slow initial request
  no longer flashes the final empty state with active actions.

i18n: add sessions.refresh.pull/release/refreshing (en + zh-CN),
remove now-unused button.refresh and sessions.refresh.success.*.
Desktop wheel pull was implemented and then reverted after review.
This commit is contained in:
weishu
2026-08-01 10:37:09 +08:00
parent 39da5c4b8e
commit b6897ee736
5 changed files with 157 additions and 59 deletions
+3 -3
View File
@@ -898,9 +898,9 @@ export default {
'share.preview.empty': '(empty share)',
'share.preview.files': '{n} files',
'share.noActiveSessions': 'No active sessions. Pick "New session" below.',
'button.refresh': 'Refresh',
'sessions.refresh.success.title': 'Refreshed',
'sessions.refresh.success.body': 'Session list updated.',
'sessions.refresh.pull': 'Pull to refresh',
'sessions.refresh.release': 'Release to refresh',
'sessions.refresh.refreshing': 'Refreshing…',
'sessions.refresh.failed.title': 'Refresh failed',
'codexSync.newSessionInline.title': 'Import Codex history',
'codexSync.newSessionInline.description': 'Optional: choose one local Codex session, then pick model/settings and Create to import.',
+3 -3
View File
@@ -902,9 +902,9 @@ export default {
'share.preview.empty': '(空分享)',
'share.preview.files': '{n} 个文件',
'share.noActiveSessions': '没有活跃会话。请在下方选择"新建会话"。',
'button.refresh': '刷新',
'sessions.refresh.success.title': '刷新',
'sessions.refresh.success.body': '会话列表已更新。',
'sessions.refresh.pull': '下拉即可刷新',
'sessions.refresh.release': '释放即可刷新',
'sessions.refresh.refreshing': '正在刷新…',
'sessions.refresh.failed.title': '刷新失败',
'codexSync.newSessionInline.title': '导入 Codex 历史',
'codexSync.newSessionInline.description': '可选:先单选一个本机 Codex 会话,再选择模型/思考强度,点创建时导入。',