mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 20:03:13 +08:00
4fb4784b81
Replace the drawer-based mobile layout with a single navigation stack. Home is the session list grouped by status (waiting for you, working, then by day) with the new-task composer docked under it; a session, Settings and subagent or team-member pages push on top, and the system back gesture moves up one level. Touch tablets keep the list beside the page. - Composer: one row (+, model, send); permission mode, context usage and the capability menu move into a + bottom sheet. - Approvals: a waiting permission, question or plan takes the composer's place; the transcript keeps a one-line marker. Denying can carry a reason. - Messages: press and hold for copy, select text, edit and branch instead of an always-visible icon row. - Tool runs fold into one card per run and open the full timeline in a sheet; the turn change card opens a file's diff full screen. - Activity: a pill in the session bar opens the activity sheet instead of auto-opening over the chat. - Settings: one grouped list with the connection, provider, phone-editable preferences and the computer-only settings shown read-only. - Server: GET /api/sessions/live-status reports every running or waiting session so the list can sort sessions the phone never opened.
80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { Tab } from '../../stores/tabStore'
|
|
import { deriveMobileSessionStatus } from './mobileSessionStatus'
|
|
|
|
type ChatSource = Parameters<typeof deriveMobileSessionStatus>[1][string]
|
|
|
|
function chat(overrides: Partial<NonNullable<ChatSource>> = {}): NonNullable<ChatSource> {
|
|
return {
|
|
chatState: 'idle',
|
|
connectionState: 'connected',
|
|
pendingPermission: null,
|
|
pendingPermissions: undefined,
|
|
backgroundAgentTasks: undefined,
|
|
...overrides,
|
|
} as NonNullable<ChatSource>
|
|
}
|
|
|
|
const pending = {
|
|
requestId: 'permission-1',
|
|
toolName: 'Bash',
|
|
input: { command: 'bun run build' },
|
|
} as never
|
|
|
|
describe('deriveMobileSessionStatus', () => {
|
|
it('reads sessions nobody here connected to from the server answer', () => {
|
|
const status = deriveMobileSessionStatus(
|
|
[],
|
|
{},
|
|
new Map([['remote-waiting', 'waiting'], ['remote-running', 'running']]),
|
|
)
|
|
|
|
expect([...status.attentionIds]).toEqual(['remote-waiting'])
|
|
expect([...status.runningIds]).toEqual(['remote-running'])
|
|
})
|
|
|
|
it('trusts a live socket over a poll that has not caught up yet', () => {
|
|
// Answered on the phone a moment ago: the store has cleared the request,
|
|
// the last poll still says waiting. The row must not keep asking.
|
|
const status = deriveMobileSessionStatus(
|
|
[],
|
|
{ answered: chat({ chatState: 'idle' }) },
|
|
new Map([['answered', 'waiting']]),
|
|
)
|
|
|
|
expect(status.attentionIds.has('answered')).toBe(false)
|
|
expect(status.runningIds.has('answered')).toBe(false)
|
|
})
|
|
|
|
it('still uses the poll for a session whose socket has dropped', () => {
|
|
const status = deriveMobileSessionStatus(
|
|
[],
|
|
{ dropped: chat({ connectionState: 'reconnecting' }) },
|
|
new Map([['dropped', 'waiting']]),
|
|
)
|
|
|
|
expect(status.attentionIds.has('dropped')).toBe(true)
|
|
})
|
|
|
|
it('counts a connected session parked on a card as waiting, not working', () => {
|
|
const status = deriveMobileSessionStatus(
|
|
[{ sessionId: 'card', title: 'Card', type: 'session', status: 'running' } as Tab],
|
|
{ card: chat({ chatState: 'tool_executing', pendingPermission: pending }) },
|
|
new Map(),
|
|
)
|
|
|
|
expect(status.attentionIds.has('card')).toBe(true)
|
|
expect(status.runningIds.has('card')).toBe(false)
|
|
})
|
|
|
|
it('marks a tab the desktop reported running as working', () => {
|
|
const status = deriveMobileSessionStatus(
|
|
[{ sessionId: 'tab', title: 'Tab', type: 'session', status: 'running' } as Tab],
|
|
{},
|
|
new Map(),
|
|
)
|
|
|
|
expect(status.runningIds.has('tab')).toBe(true)
|
|
})
|
|
})
|