feat(mobile): mobile app navigation rework and beta-feedback closeout (#2561)
Navigation model rebuilt around two full-width drawers and a minimal header (sessions / title-switcher / usage ring / workspace): - Left sessions drawer: cross-project tree with live status indicators, swipe actions on sessions (rename/archive/delete) and on group headers (project edit / two-step close, worktree delete), reorder-only edit mode with collapsible project cards and draggable worktrees, app-level footer (connected instance, settings, pending web update). - Right workspace drawer: Changes / Files / Terminal / Notes / MCP as pill tabs (inactive tabs icon-only); panes stay mounted once visited. The full desktop file editor serves the Files tab; read/skill tool taps in chat open the file there at the requested line. - Header session switcher on title tap: 10 cross-project recents with live busy/attention indicators and project · branch metadata; the usage ring opens a metadata overlay with an explicit loading state. - The overflow menu is gone on phones (its destinations moved into the drawers); iPad keeps it until its dedicated layout pass. Correctness and continuity: - /auth/session answers bearer-first, so a stale WebView cookie can no longer mask a revoked device token; cold launches classify failures fast and land on an explicit connect screen. - Authoritative session snapshots raise frozen ordering baselines and stale live ranks — recents stay truthful after the app slept. - Cold launches reopen the last active session per instance (persisted pointer, confirmed against a sessions snapshot; a user-opened draft clears it), with a logo hold instead of a draft flash. Also: collapsed pill composer gains the stop control; chat tool rows share one 36px rhythm; Task subtool rows truncate; larger bottom safe area so the composer clears big-screen corner radii; Capacitor build hides About/Update (store updates apply there); widgets link to the sessions drawer with a list icon; MobileApp split into focused modules; five mobile-surface detectors unified; translucent borders normalized to 70%; all new strings translated across the 10 locales. iPad and foldable layouts are intentionally untouched - separate next version PR.
This commit is contained in:
committed by
GitHub
parent
ea8cc5d7b0
commit
86ef96302d
@@ -19,7 +19,6 @@ import { Icon } from '@/components/icon/Icon';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ModelControls } from '../../ModelControls';
|
||||
import { MobileSessionPanelTrigger } from '../../MobileSessionStatusBar';
|
||||
import { ComposerActionButtons } from './ComposerActionButtons';
|
||||
import { ComposerAttachmentControls } from './ComposerAttachmentControls';
|
||||
import { FocusModeButton } from './FocusModeButton';
|
||||
@@ -124,10 +123,6 @@ export function ComposerFooter(props: ComposerFooterProps) {
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between gap-x-1.5">
|
||||
<div className="composer-mobile-actions flex items-center gap-x-2 pl-1">
|
||||
<MobileSessionPanelTrigger
|
||||
footerIconButtonClass={footerIconButtonClass}
|
||||
iconSizeClass={iconSizeClass}
|
||||
/>
|
||||
<ComposerAttachmentControls
|
||||
isVSCode={isVSCode}
|
||||
footerIconButtonClass={footerIconButtonClass}
|
||||
|
||||
@@ -11,15 +11,13 @@
|
||||
* the pill grow into its place.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
import { StopIcon } from '@/components/icons/StopIcon';
|
||||
import { SessionGoalRow } from '@/components/chat/SessionGoalRow';
|
||||
import { SessionSuggestionChip } from '@/components/chat/SessionSuggestionChip';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { Theme } from '@/types/theme';
|
||||
import { MobileSessionPanelTrigger } from '../../MobileSessionStatusBar';
|
||||
import { ComposerAttachmentControls } from './ComposerAttachmentControls';
|
||||
|
||||
export interface MobilePillComposerProps {
|
||||
@@ -29,8 +27,10 @@ export interface MobilePillComposerProps {
|
||||
newSessionDraftOpen: boolean;
|
||||
hasContent: boolean;
|
||||
isVSCode: boolean;
|
||||
canAbort: boolean;
|
||||
footerIconButtonClass: string;
|
||||
iconSizeClass: string;
|
||||
stopIconSizeClass: string;
|
||||
theme: Theme;
|
||||
onExpand: () => void;
|
||||
onApplySuggestion: (text: string) => void;
|
||||
@@ -40,6 +40,7 @@ export interface MobilePillComposerProps {
|
||||
onOpenPrPicker: () => void;
|
||||
onOpenAttachSheet: () => void;
|
||||
onStartDictation: () => void;
|
||||
onAbort: () => void;
|
||||
}
|
||||
|
||||
export function MobilePillComposer(props: MobilePillComposerProps) {
|
||||
@@ -51,8 +52,10 @@ export function MobilePillComposer(props: MobilePillComposerProps) {
|
||||
newSessionDraftOpen,
|
||||
hasContent,
|
||||
isVSCode,
|
||||
canAbort,
|
||||
footerIconButtonClass,
|
||||
iconSizeClass,
|
||||
stopIconSizeClass,
|
||||
theme: currentTheme,
|
||||
onExpand,
|
||||
onApplySuggestion,
|
||||
@@ -62,6 +65,7 @@ export function MobilePillComposer(props: MobilePillComposerProps) {
|
||||
onOpenPrPicker,
|
||||
onOpenAttachSheet,
|
||||
onStartDictation,
|
||||
onAbort,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -83,10 +87,6 @@ export function MobilePillComposer(props: MobilePillComposerProps) {
|
||||
className="flex h-11 min-w-0 flex-1 items-center gap-x-0.5 rounded-full border border-border/80 pl-2 pr-1 shadow-[0_4px_16px_-4px_rgb(0_0_0_/_0.12)]"
|
||||
style={{ backgroundColor: currentTheme?.colors?.surface?.subtle }}
|
||||
>
|
||||
<MobileSessionPanelTrigger
|
||||
footerIconButtonClass={footerIconButtonClass}
|
||||
iconSizeClass={iconSizeClass}
|
||||
/>
|
||||
<ComposerAttachmentControls
|
||||
isVSCode={isVSCode}
|
||||
footerIconButtonClass={footerIconButtonClass}
|
||||
@@ -125,6 +125,33 @@ export function MobilePillComposer(props: MobilePillComposerProps) {
|
||||
>
|
||||
<Icon name="mic" className={cn(iconSizeClass, 'text-current')} />
|
||||
</button>
|
||||
{/* Same visibility rule as the full composer's stop control:
|
||||
while a turn is running the stop button takes the mic's
|
||||
end slot and the mic shifts one slot left. Instant swap —
|
||||
no shape animation (WKWebView). */}
|
||||
{canAbort ? (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(footerIconButtonClass, 'text-[var(--status-error)] hover:text-[var(--status-error)]')}
|
||||
// The pill shows only while the keyboard is down — the
|
||||
// tap must abort in place, never focus/expand the
|
||||
// composer or raise the keyboard.
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onPointerDownCapture={(event) => {
|
||||
if (event.pointerType === 'touch') {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onAbort();
|
||||
}}
|
||||
title={t('chat.chatInput.actions.stopGeneratingAria')}
|
||||
aria-label={t('chat.chatInput.actions.stopGeneratingAria')}
|
||||
>
|
||||
<StopIcon className={cn(stopIconSizeClass)} />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{/* New-session button: fades/shrinks away when the draft is
|
||||
already open, letting the pill expand into its place. */}
|
||||
|
||||
Reference in New Issue
Block a user