Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"expo": "~57.0.15",
"expo-apple-authentication": "~57.0.1",
"expo-application": "~57.0.2",
"expo-battery": "~57.0.2",
"expo-blur": "~57.0.2",
"expo-build-properties": "~57.0.13",
"expo-clipboard": "~57.0.1",
Expand Down
11 changes: 7 additions & 4 deletions apps/mobile/src/app/(app)/(tabs)/(1_kiloclaw)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ export default function KiloClawTab() {
// billing/access issue is still surfaced as a card annotation when the
// list is non-empty — see `personalAccessIssue` below.
const onboardingQuery = useKiloClawMobileOnboardingState();
const personalAccessIssue = onboardingQuery.data
? resolveAccessRequiredSubcase(onboardingQuery.data)
// Read the nullable state once into a local binding: `tsgo` does not narrow
// repeated `onboardingQuery.data` property accesses, only local variables.
const onboardingState = onboardingQuery.data;
const personalAccessIssue = onboardingState
? resolveAccessRequiredSubcase(onboardingState)
: null;
useForegroundInvalidateKiloclawState();

Expand Down Expand Up @@ -107,7 +110,7 @@ export default function KiloClawTab() {
className="px-[22px]"
/>
<Animated.View layout={LinearTransition} className="flex-1 px-4">
{showInstanceSkeleton || onboardingQuery.data === undefined ? (
{showInstanceSkeleton || onboardingState === undefined ? (
<Animated.View exiting={FadeOut.duration(150)} className="w-full gap-3 pt-5">
<Skeleton className="h-[72px] w-full rounded-2xl" />
<Skeleton className="h-[72px] w-full rounded-2xl" />
Expand All @@ -117,7 +120,7 @@ export default function KiloClawTab() {
<Animated.View entering={FadeIn.duration(200)} className="flex-1">
<EmptyStateContent
foregroundColor={colors.foreground}
state={onboardingQuery.data}
state={onboardingState}
onCreate={() => {
router.push('/(app)/onboarding' as Href);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
import SessionDetailScreen from './[session-id]';

const useLocalSearchParamsMock = vi.hoisted(() => vi.fn());
vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('@/components/ui/refresh-control', () => ({ RefreshControl: 'RefreshControl' }));
const useRouterMock = vi.hoisted(() => vi.fn());
const useQueryMock = vi.hoisted(() => vi.fn());
const queryOptionsMock = vi.hoisted(() => vi.fn());
Expand Down
4 changes: 4 additions & 0 deletions apps/mobile/src/app/(app)/kiloclaw/[instance-id]/billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { useTRPC } from '@/lib/trpc';
import { cn } from '@/lib/utils';

const SUBSCRIPTION_PLAN_KEYS = {
// i18n-dup-ok: prReview.overview.commit_* is a numeral count unit ('1 commit');
// this key is the billing plan name — be/pl/uk translate the two apart.
commit: 'kiloclaw.billing.commit',
standard: 'kiloclaw.billing.planName.standard',
} satisfies Record<string, string>;
Expand Down Expand Up @@ -107,6 +109,8 @@ function FinalCommitTermDetails({

return (
<View>
{/* i18n-dup-ok: prReview.overview.commit_* is a numeral count unit ('1 commit');
this key is the billing plan name — be/pl/uk translate the two apart. */}
<DetailRow label={t('kiloclaw.billing.plan')} value={t('kiloclaw.billing.commit')} />
<View className="h-px bg-border" />
<DetailRow label={t('kiloclaw.billing.finalTermEnds')} value={finalDate} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { type Href, useLocalSearchParams, useRouter } from 'expo-router';
import { CreditCard, Newspaper, Pencil } from '@/components/ui/icons';
import { useCallback, useState } from 'react';
import { Alert, Linking, Platform, Pressable, RefreshControl, View } from 'react-native';
import { Alert, Linking, Platform, Pressable, View } from 'react-native';
import { RefreshControl } from '@/components/ui/refresh-control';
import { useTranslation } from 'react-i18next';
import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ShieldCheck, Zap } from '@/components/ui/icons';
import { ActivityIndicator, Pressable, View } from 'react-native';
import { Pressable, View } from 'react-native';
import { ActivityIndicator } from '@/components/ui/activity-indicator';
import { useTranslation } from 'react-i18next';
import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';
import { useLocalSearchParams } from 'expo-router';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { useQuery } from '@tanstack/react-query';
import { Check, Eye, Search } from '@/components/ui/icons';
import { useCallback, useMemo, useRef, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Pressable,
TextInput,
View,
type ViewStyle,
} from 'react-native';
import { FlatList, Pressable, TextInput, View, type ViewStyle } from 'react-native';
import { ActivityIndicator } from '@/components/ui/activity-indicator';
import { useTranslation } from 'react-i18next';
import { useLocalSearchParams, useRouter } from 'expo-router';

Expand Down
29 changes: 16 additions & 13 deletions apps/mobile/src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { PrivacyCoverOverlay } from '@/components/privacy-cover-overlay';
import { QueryError } from '@/components/query-error';
import { splashContentScale } from '@/components/splash-reveal';
import { announceForA11y, moveA11yFocus } from '@/lib/a11y/announce';
import { MotionProvider } from '@/lib/a11y/motion';
import { useAuth } from '@/lib/auth/auth-context';
import { resolveBootstrapDecision } from '@/lib/bootstrap-decision';
import { consentModeForSearchParam } from '@/components/consent/consent-mode';
Expand Down Expand Up @@ -1048,19 +1049,21 @@ function RootLayout() {
}, []);

return (
<ShareIntentProvider options={SHARE_INTENT_OPTIONS}>
<ThemeProvider value={navigationTheme}>
<AppRootProviders languageReady={languageReady}>
<StatusBar style="auto" />
<AppContentReveal>
<StateSurface className="flex-1">
<RootLayoutNav languageReady={languageReady} setLanguageReady={setLanguageReady} />
</StateSurface>
</AppContentReveal>
<AnimatedSplashOverlay />
</AppRootProviders>
</ThemeProvider>
</ShareIntentProvider>
<MotionProvider>
<ShareIntentProvider options={SHARE_INTENT_OPTIONS}>
<ThemeProvider value={navigationTheme}>
<AppRootProviders languageReady={languageReady}>
<StatusBar style="auto" />
<AppContentReveal>
<StateSurface className="flex-1">
<RootLayoutNav languageReady={languageReady} setLanguageReady={setLanguageReady} />
</StateSurface>
</AppContentReveal>
<AnimatedSplashOverlay />
</AppRootProviders>
</ThemeProvider>
</ShareIntentProvider>
</MotionProvider>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ vi.mock('@/lib/auth/account-metadata-write', () => ({
setAccountMetadata: vi.fn().mockResolvedValue(undefined),
}));
vi.mock('sonner-native', () => ({ toast: { error: vi.fn() } }));
vi.mock('@/components/ui/refresh-control', () => ({ RefreshControl: 'RefreshControl' }));
vi.mock('react-native', () => ({
Platform: { OS: 'ios' },
AppState: { addEventListener: () => ({ remove: () => undefined }) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const a11yMock = vi.hoisted(() => ({

vi.mock('@/components/centered-state', () => ({ CenteredState: 'CenteredState' }));
vi.mock('@/components/centered-state-surface', () => ({ StateSurface: 'View' }));
vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('react-native', () => ({
ActivityIndicator: 'ActivityIndicator',
Modal: 'Modal',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import {
type AccessibilityActionEvent,
type AccessibilityActionInfo,
ActivityIndicator,
type LayoutChangeEvent,
Platform,
Pressable,
ScrollView,
View,
} from 'react-native';
import { ActivityIndicator } from '@/components/ui/activity-indicator';
import {
Gesture,
GestureDetector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ vi.mock('react-native-reanimated', () => ({
default: { View: 'Animated.View' },
FadeIn: { duration: vi.fn(() => ({})) },
FadeOut: { duration: vi.fn(() => ({})) },
useReducedMotion: () => false,
}));
vi.mock('@/lib/a11y/motion', () => ({
selectReducedMotionEntrance: <T>(_reduced: boolean, entrance: T) => entrance,
useMotionPolicy: () => ({ reducedMotion: false, scrollAnimated: true }),
}));

vi.mock('react-native-safe-area-context', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ vi.mock('react-native-reanimated', () => ({
default: { View: 'Animated.View' },
FadeIn: { duration: vi.fn(() => ({})) },
FadeOut: { duration: vi.fn(() => ({})) },
useReducedMotion: () => false,
}));
vi.mock('@/lib/a11y/motion', () => ({
useMotionPolicy: () => ({ reducedMotion: false, scrollAnimated: true }),
}));
vi.mock('@/components/ui/activity-indicator', () => ({
ActivityIndicator: 'ActivityIndicator',
}));
vi.mock('@/components/ui/icons', () => ({
ArrowUp: 'ArrowUp',
Expand Down
7 changes: 4 additions & 3 deletions apps/mobile/src/components/agents/chat-composer-input-row.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { ArrowUp, CornerDownLeft, Paperclip, Square } from '@/components/ui/icons';
import { CLOUD_AGENT_PROMPT_MAX_LENGTH } from '@kilocode/cloud-agent-sdk/limits';
import { type RefObject } from 'react';
import Animated, { FadeIn, FadeOut, useReducedMotion } from 'react-native-reanimated';
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
import { useTranslation } from 'react-i18next';
import {
ActivityIndicator,
type LayoutChangeEvent,
Platform,
Pressable,
Expand All @@ -15,7 +14,9 @@ import {
} from 'react-native';

import { shouldEnableComposerInputScroll } from '@/components/agents/chat-composer-input-height';
import { ActivityIndicator } from '@/components/ui/activity-indicator';
import { VoiceInputButton } from '@/components/voice-input-control';
import { useMotionPolicy } from '@/lib/a11y/motion';
import { useThemeColors } from '@/lib/hooks/use-theme-colors';
import { cn } from '@/lib/utils';
import { type VoiceInputStatus } from '@/lib/voice-input/voice-input-state';
Expand Down Expand Up @@ -96,7 +97,7 @@ export function ChatComposerInputRow({
}: Readonly<ChatComposerInputRowProps>) {
const colors = useThemeColors();
const { t } = useTranslation();
const reducedMotion = useReducedMotion();
const { reducedMotion } = useMotionPolicy();
const inputScrollable = shouldEnableComposerInputScroll(measureHeight, maxInputHeight);

return (
Expand Down
9 changes: 8 additions & 1 deletion apps/mobile/src/components/agents/chat-composer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,14 @@ vi.mock('react-native-reanimated', () => ({
default: { View: 'Animated.View' },
FadeIn: { duration: vi.fn(() => ({})) },
FadeOut: { duration: vi.fn(() => ({})) },
useReducedMotion: () => reducedMotionOn.value,
}));
vi.mock('@/lib/a11y/motion', () => ({
selectReducedMotionEntrance: <T>(reduced: boolean, entrance: T) =>
reduced ? undefined : entrance,
useMotionPolicy: () => ({
reducedMotion: reducedMotionOn.value,
scrollAnimated: !reducedMotionOn.value,
}),
}));

vi.mock('expo-haptics', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import { i18n } from '@/i18n';

vi.mock('@/components/centered-state', () => ({ CenteredState: 'CenteredState' }));
vi.mock('@/components/centered-state-surface', () => ({ StateSurface: 'View' }));
vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('@/components/ui/refresh-control', () => ({ RefreshControl: 'RefreshControl' }));

async function mountRecovery(messages = [makeAssistantMessage()]) {
const fetchPage = vi
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable typescript-eslint/no-deprecated -- react-test-renderer is the DOM-free renderer used to mount React/RN trees under vitest. */
/* eslint-disable max-lines, typescript-eslint/no-deprecated -- the child-session suites share this mounted native fixture. */
import { type ComponentProps, createElement, type ReactNode } from 'react';
import { type FlashListProps } from '@shopify/flash-list';
import TestRenderer, { act } from 'react-test-renderer';
Expand Down Expand Up @@ -30,6 +30,10 @@ const safeAreaMock = vi.hoisted(() => ({
}));
export { reactNativeMock, safeAreaMock };

vi.mock('@/lib/a11y/motion', () => ({
useMotionPolicy: () => ({ reducedMotion: false, scrollAnimated: true }),
}));
vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('react-native', () => ({
Modal: 'Modal',
View: 'View',
Expand All @@ -43,7 +47,6 @@ vi.mock('react-native-reanimated', () => ({
LinearTransition: { duration: () => ({}) },
FadeIn: { duration: () => ({}) },
FadeOut: { duration: () => ({}) },
useReducedMotion: () => false,
}));
vi.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: safeAreaMock.useSafeAreaInsets,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { ChildSessionModelLabel } from './child-session-model-label';

vi.mock('@/components/centered-state', () => ({ CenteredState: 'CenteredState' }));
vi.mock('@/components/centered-state-surface', () => ({ StateSurface: 'View' }));
vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('@/components/ui/refresh-control', () => ({ RefreshControl: 'RefreshControl' }));

describe('ChildSessionSheet title layout', () => {
it.each([
Expand Down
2 changes: 2 additions & 0 deletions apps/mobile/src/components/agents/file-part-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export function getFilePartKind(input: { mime: string; filename?: string }): Fil
}

function resolveName(filename: string | undefined): string {
// i18n-dup-ok: prReview.overview.file_* is a numeral count unit ('1 file'), which
// languages inflect by number; this key is the standalone noun label.
return filename && filename.trim() !== '' ? filename : i18n.t('common.file');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const safeAreaMock = vi.hoisted(() => ({
useSafeAreaInsets: vi.fn(() => ({ top: 0, bottom: 0 })),
}));

vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('react-native', () => ({
ActivityIndicator: 'ActivityIndicator',
Modal: 'Modal',
Expand Down
9 changes: 8 additions & 1 deletion apps/mobile/src/components/agents/file-part-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useActionSheet } from '@expo/react-native-action-sheet';
import { type FilePart } from '@kilocode/cloud-agent-sdk';
import { Directory, File, Paths } from 'expo-file-system';
import { useEffect, useRef, useState } from 'react';
import { ActivityIndicator, Pressable, ScrollView, View } from 'react-native';
import { Pressable, ScrollView, View } from 'react-native';
import { ActivityIndicator } from '@/components/ui/activity-indicator';
import { useTranslation } from 'react-i18next';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { toast } from 'sonner-native';
Expand Down Expand Up @@ -304,6 +305,8 @@ export function FilePartRenderer({ part, onLongPress }: Readonly<FilePartRendere
<ImageViewerModal
visible={viewerVisible}
uri={url}
// i18n-dup-ok: prReview.overview.file_* is a numeral count unit ('1 file'),
// which languages inflect by number; this key is the standalone noun label.
filename={part.filename ?? t('common.file')}
onShare={() => {
void handleShare();
Expand Down Expand Up @@ -370,6 +373,8 @@ export function FilePartRenderer({ part, onLongPress }: Readonly<FilePartRendere
<FileIcon size={14} color={colors.mutedForeground} />
)}
<Text className="text-sm text-muted-foreground" numberOfLines={1}>
{/* i18n-dup-ok: prReview.overview.file_* is a numeral count unit ('1 file'),
which languages inflect by number; this key is the standalone noun label. */}
{part.filename ?? t('common.file')}
</Text>
</Pressable>
Expand Down Expand Up @@ -501,6 +506,8 @@ function FilePreviewModal({
return (
<SessionPageSheet visible onClose={onClose}>
<SheetHeader
// i18n-dup-ok: prReview.overview.file_* is a numeral count unit ('1 file'),
// which languages inflect by number; this key is the standalone noun label.
title={part.filename ?? t('common.file')}
titleEllipsis="middle"
onDone={onClose}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { describe, expect, it, vi } from 'vitest';

import { FixedPartRow } from './fixed-part-row';

vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('react-native', () => ({
ActivityIndicator: 'ActivityIndicator',
I18nManager: { isRTL: false },
Expand Down
3 changes: 2 additions & 1 deletion apps/mobile/src/components/agents/fixed-part-row.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type LucideIcon, XCircle } from '@/components/ui/icons';
import { DirectionalChevronRight } from '@/components/ui/directional-icons';
import { ActivityIndicator, Pressable, View } from 'react-native';
import { Pressable, View } from 'react-native';
import { ActivityIndicator } from '@/components/ui/activity-indicator';
import { useTranslation } from 'react-i18next';

import { Eyebrow } from '@/components/ui/eyebrow';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type ListProps<T> = {
};

vi.mock('@/components/centered-state', () => ({ CenteredState: 'CenteredState' }));
vi.mock('@/components/ui/activity-indicator', () => ({ ActivityIndicator: 'ActivityIndicator' }));
vi.mock('react-native', () => ({
FlatList: <T,>(props: ListProps<T>) =>
createElement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ export function LiveSessionListEmptyState({
<Button
variant="outline"
className="max-w-full"
accessibilityLabel={t('home.newCodingTask')}
accessibilityLabel={t('common.newSession')}
onPress={() => {
router.push(getNewAgentSessionPath(organizationId) as Href);
}}
>
<Plus size={16} color={colors.foreground} />
<Text className="shrink text-center">{t('home.newCodingTask')}</Text>
<Text className="shrink text-center">{t('common.newSession')}</Text>
</Button>
}
/>
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/components/agents/markdown-table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ vi.mock('react-native-marked', () => ({
Renderer: class Renderer {},
}));

vi.mock('@/components/ui/activity-indicator', () => ({
ActivityIndicator: 'ActivityIndicator',
}));
vi.mock('react-native', () => ({
ActivityIndicator: 'ActivityIndicator',
I18nManager: { isRTL: false },
Expand Down
Loading