Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/mobile-web/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const messages: Record<MobileLanguage, MessageTree> = {
connectionChecking: 'Checking connection...',
connectionConnected: 'Connected',
connectionUnreachable: 'Connection lost',
continueSession: 'Continue where you left off',
},
workspace: {
title: 'Workspace',
Expand Down Expand Up @@ -290,6 +291,7 @@ export const messages: Record<MobileLanguage, MessageTree> = {
connectionChecking: '检测连接中...',
connectionConnected: '已连接',
connectionUnreachable: '连接断开',
continueSession: '继续上次会话',
},
workspace: {
title: '工作区',
Expand Down Expand Up @@ -471,6 +473,7 @@ export const messages: Record<MobileLanguage, MessageTree> = {
connectionChecking: '檢測連接中...',
connectionConnected: '已連接',
connectionUnreachable: '連接斷開',
continueSession: '繼續上次會話',
},
workspace: {
title: '工作區',
Expand Down
40 changes: 39 additions & 1 deletion src/mobile-web/src/pages/SessionListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ const SessionListPage: React.FC<SessionListPageProps> = ({ sessionMgr, onSelectS
const toastTimerRef = useRef<ReturnType<typeof setTimeout>>();

const hasSearchQuery = searchQuery.trim().length > 0;
const showResumeCard = !loading && sessions.length > 0 && !hasSearchQuery;

// ── Long-press context menu ─────────────────────────────────────
const clearLongPressTimer = () => {
Expand Down Expand Up @@ -616,6 +617,43 @@ const SessionListPage: React.FC<SessionListPageProps> = ({ sessionMgr, onSelectS
</div>
)}

{/* Resume Card — quick continue for the most recent session */}
{showResumeCard && (
<button
type="button"
className="session-list__resume-card"
onClick={(e) => handleSessionClick(sessions[0], e)}
onTouchStart={(e) => handleSessionTouchStart(sessions[0], e)}
onTouchMove={handleSessionTouchMove}
onTouchEnd={handleSessionTouchEnd}
onTouchCancel={handleSessionTouchEnd}
onContextMenu={(e) => { e.preventDefault(); setMenuSession(sessions[0]); }}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onSelectSession(sessions[0].session_id, sessions[0].name);
}
}}
>
<div className={`session-list__item-icon session-list__resume-icon session-list__item-icon--${sessions[0].agent_type}`}>
<SessionTypeIcon agentType={sessions[0].agent_type} />
</div>
<div className="session-list__resume-body">
<div className="session-list__resume-label">{t('sessions.continueSession')}</div>
<div className="session-list__resume-name">{sessions[0].name || t('sessions.untitledSession')}</div>
<div className="session-list__resume-meta">
<span className={`session-list__agent-badge session-list__agent-badge--${sessions[0].agent_type}`}>
{agentLabel(sessions[0].agent_type, t)}
</span>
<span className="session-list__resume-time">{formatTime(sessions[0].updated_at, language, t)}</span>
</div>
</div>
<span className="session-list__resume-arrow">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6"/></svg>
</span>
</button>
)}

{/* Mode Toggle - Inline */}
<div className="session-list__mode-toggle">
<button
Expand Down Expand Up @@ -866,7 +904,7 @@ const SessionListPage: React.FC<SessionListPageProps> = ({ sessionMgr, onSelectS
)}

<div className="session-list__cards">
{sessions.map((s) => (
{sessions.slice(showResumeCard ? 1 : 0).map((s) => (
<div
key={s.session_id}
className={`session-list__item${menuSession?.session_id === s.session_id ? ' session-list__item--active' : ''}`}
Expand Down
79 changes: 79 additions & 0 deletions src/mobile-web/src/styles/components/sessions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,85 @@
}
}

/* Resume Card — Continue where you left off */
.session-list__resume-card {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
border: 1.5px solid var(--color-accent-200);
@include squircle(18px);
background: var(--color-accent-50);
cursor: pointer;
transition: transform var(--motion-fast) var(--easing-standard),
background var(--motion-fast) var(--easing-standard);

&:active {
transform: scale(0.985);
background: var(--color-accent-100);
}

&:focus-visible {
outline: 2px solid var(--color-accent-500);
outline-offset: 2px;
}
}

.session-list__resume-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
@include squircle(10px);
}

.session-list__resume-body {
flex: 1;
min-width: 0;
}

.session-list__resume-label {
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--color-accent-500);
margin-bottom: 2px;
}

.session-list__resume-name {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.session-list__resume-meta {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
min-width: 0;
overflow: hidden;
}

.session-list__resume-time {
font-size: 11px;
color: var(--color-text-muted);
white-space: nowrap;
flex-shrink: 0;
}

.session-list__resume-arrow {
flex-shrink: 0;
color: var(--color-text-muted);
display: inline-flex;
align-items: center;
}

/* Mode Toggle - Inline pill style */
.session-list__mode-toggle {
display: flex;
Expand Down
Loading