Compare commits
1 Commits
main
...
claude/pro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af70e30160 |
@ -1,3 +1,4 @@
|
|||||||
|
import type { AutopilotRecent } from "@rakazo/contracts";
|
||||||
import {
|
import {
|
||||||
normalizeCreateBotProfile,
|
normalizeCreateBotProfile,
|
||||||
type RunActivityRow,
|
type RunActivityRow,
|
||||||
@ -46,6 +47,7 @@ import {
|
|||||||
selectSpace,
|
selectSpace,
|
||||||
} from "../lib/api";
|
} from "../lib/api";
|
||||||
import { mobileTokens, resolveMobileAppearance } from "../lib/appearance";
|
import { mobileTokens, resolveMobileAppearance } from "../lib/appearance";
|
||||||
|
import { autopilotSummary } from "../lib/autopilot";
|
||||||
import { allowFocusPrompt, scheduleFocusPrompt } from "../lib/focus-prompt";
|
import { allowFocusPrompt, scheduleFocusPrompt } from "../lib/focus-prompt";
|
||||||
import { t, useI18n } from "../lib/i18n";
|
import { t, useI18n } from "../lib/i18n";
|
||||||
import { botTag, filterBots, formatThreadTime, userInitials } from "../lib/inbox";
|
import { botTag, filterBots, formatThreadTime, userInitials } from "../lib/inbox";
|
||||||
@ -87,6 +89,7 @@ export default function Home() {
|
|||||||
const [botSections, setBotSections] = useState<MobileBotSection[]>([]);
|
const [botSections, setBotSections] = useState<MobileBotSection[]>([]);
|
||||||
const [spaces, setSpaces] = useState<MobileSpace[]>([]);
|
const [spaces, setSpaces] = useState<MobileSpace[]>([]);
|
||||||
const [me, setMe] = useState<MobileMe | null>(null);
|
const [me, setMe] = useState<MobileMe | null>(null);
|
||||||
|
const [autopilots, setAutopilots] = useState<AutopilotRecent[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
const [hasSession, setHasSession] = useState(false);
|
const [hasSession, setHasSession] = useState(false);
|
||||||
@ -131,9 +134,10 @@ export default function Home() {
|
|||||||
const requestId = ++inboxRequestId.current;
|
const requestId = ++inboxRequestId.current;
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const [navigation, nextMe] = await Promise.all([
|
const [navigation, nextMe, recentAutopilots] = await Promise.all([
|
||||||
rpc<MobileSpaceNavigation>("spaces/list"),
|
rpc<MobileSpaceNavigation>("spaces/list"),
|
||||||
rpc<MobileMe>("me"),
|
rpc<MobileMe>("me"),
|
||||||
|
rpc<{ items: AutopilotRecent[] }>("autopilot/recent").catch(() => null),
|
||||||
]);
|
]);
|
||||||
if (requestId !== inboxRequestId.current) return;
|
if (requestId !== inboxRequestId.current) return;
|
||||||
if (!(await selectInitialSpace(nextMe.spaceId))) {
|
if (!(await selectInitialSpace(nextMe.spaceId))) {
|
||||||
@ -145,6 +149,7 @@ export default function Home() {
|
|||||||
setGroups(navigation.current.groups);
|
setGroups(navigation.current.groups);
|
||||||
setSpaces(navigation.spaces);
|
setSpaces(navigation.spaces);
|
||||||
setMe(nextMe);
|
setMe(nextMe);
|
||||||
|
if (recentAutopilots) setAutopilots(recentAutopilots.items);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (requestId !== inboxRequestId.current) return;
|
if (requestId !== inboxRequestId.current) return;
|
||||||
setError(err instanceof Error ? err.message : t("Could not load bots"));
|
setError(err instanceof Error ? err.message : t("Could not load bots"));
|
||||||
@ -543,11 +548,15 @@ export default function Home() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
activityMode &&
|
!searching && !query.trim() ? (
|
||||||
!searching &&
|
<>
|
||||||
!query.trim() &&
|
{autopilots.length > 0 ? (
|
||||||
(activity.active.length > 0 || activity.recent.length > 0) ? (
|
<AutopilotSection autopilots={autopilots} bots={bots} groups={groups} />
|
||||||
|
) : null}
|
||||||
|
{activityMode && (activity.active.length > 0 || activity.recent.length > 0) ? (
|
||||||
<ActivitySection activity={activity} bots={bots} />
|
<ActivitySection activity={activity} bots={bots} />
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
@ -744,6 +753,67 @@ function ActivitySection({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The night's work, like Today on web: each autopilot that runs, waits, or ended within a day. */
|
||||||
|
function AutopilotSection({
|
||||||
|
autopilots,
|
||||||
|
bots,
|
||||||
|
groups,
|
||||||
|
}: {
|
||||||
|
autopilots: AutopilotRecent[];
|
||||||
|
bots: MobileBot[];
|
||||||
|
groups: MobileGroup[];
|
||||||
|
}) {
|
||||||
|
const styles = useThemedStyles(createHomeStyles);
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const botsById = useMemo(() => new Map(bots.map((bot) => [bot.id, bot])), [bots]);
|
||||||
|
const open = (autopilot: AutopilotRecent) =>
|
||||||
|
void openMobileSpace(autopilot.spaceId, () =>
|
||||||
|
router.push(
|
||||||
|
autopilot.groupId
|
||||||
|
? {
|
||||||
|
pathname: "/group-thread",
|
||||||
|
params: {
|
||||||
|
groupId: autopilot.groupId,
|
||||||
|
name: groups.find((group) => group.id === autopilot.groupId)?.name ?? t("Group"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
pathname: "/thread",
|
||||||
|
params: {
|
||||||
|
botId: autopilot.botId,
|
||||||
|
name: botsById.get(autopilot.botId)?.name ?? "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.activitySection} testID="home-autopilot">
|
||||||
|
<Text style={styles.sectionHeading}>{t("Autopilot")}</Text>
|
||||||
|
{autopilots.map((autopilot) => {
|
||||||
|
const { detail, trail } = autopilotSummary(autopilot);
|
||||||
|
return (
|
||||||
|
<ConversationRow
|
||||||
|
key={autopilot.id}
|
||||||
|
title={autopilot.goal}
|
||||||
|
preview={detail}
|
||||||
|
time={trail}
|
||||||
|
accessibilityLabel={`${autopilot.goal}, ${trail}`}
|
||||||
|
onPress={() => open(autopilot)}
|
||||||
|
avatar={
|
||||||
|
<BotAvatar
|
||||||
|
identity={autopilot.botId}
|
||||||
|
color={botsById.get(autopilot.botId)?.color ?? FALLBACK_COLOR}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ActivityRow({
|
function ActivityRow({
|
||||||
run,
|
run,
|
||||||
bot,
|
bot,
|
||||||
|
|||||||
@ -69,6 +69,7 @@ import { AppConnectCard } from "../components/AppConnectCard";
|
|||||||
import { AskActions } from "../components/AskActions";
|
import { AskActions } from "../components/AskActions";
|
||||||
import type { AutopilotAction } from "../components/autopilot-line";
|
import type { AutopilotAction } from "../components/autopilot-line";
|
||||||
import { AutopilotLine } from "../components/autopilot-line";
|
import { AutopilotLine } from "../components/autopilot-line";
|
||||||
|
import { AutopilotTimePicker } from "../components/autopilot-time-picker";
|
||||||
import { BotAvatar } from "../components/bot-avatar";
|
import { BotAvatar } from "../components/bot-avatar";
|
||||||
import {
|
import {
|
||||||
MarkdownArtifactPreview,
|
MarkdownArtifactPreview,
|
||||||
@ -245,6 +246,7 @@ function Thread() {
|
|||||||
const colorScheme = useResolvedAppearance();
|
const colorScheme = useResolvedAppearance();
|
||||||
const tokens = mobileTokens();
|
const tokens = mobileTokens();
|
||||||
const [botActionsOpen, setBotActionsOpen] = useState(false);
|
const [botActionsOpen, setBotActionsOpen] = useState(false);
|
||||||
|
const [autopilotTimeFor, setAutopilotTimeFor] = useState<((time: string) => void) | null>(null);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -1282,16 +1284,18 @@ function Thread() {
|
|||||||
function showAttachMenu() {
|
function showAttachMenu() {
|
||||||
// The autopilot takes the typed order, so it is offered once there is one.
|
// The autopilot takes the typed order, so it is offered once there is one.
|
||||||
const order = draft.trim();
|
const order = draft.trim();
|
||||||
Alert.alert(t("Attach"), undefined, [
|
presentMessageActionSheet({
|
||||||
{
|
title: t("Attach"),
|
||||||
text: t("Photo library"),
|
actions: [
|
||||||
onPress: () => void addAttachments(pickFromLibrary),
|
{ text: t("Photo library"), onPress: () => void addAttachments(pickFromLibrary) },
|
||||||
},
|
|
||||||
{ text: t("Camera"), onPress: () => void addAttachments(takePhoto) },
|
{ text: t("Camera"), onPress: () => void addAttachments(takePhoto) },
|
||||||
{ text: t("File"), onPress: () => void addAttachments(pickDocuments) },
|
{ text: t("File"), onPress: () => void addAttachments(pickDocuments) },
|
||||||
...(order ? [{ text: t("Autopilot"), onPress: () => askAutopilotUntil(order) }] : []),
|
...(order ? [{ text: t("Autopilot"), onPress: () => askAutopilotUntil(order) }] : []),
|
||||||
{ text: t("Cancel"), style: "cancel" as const },
|
],
|
||||||
]);
|
colorScheme,
|
||||||
|
cancel: t("Cancel"),
|
||||||
|
more: t("More"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function askAutopilotUntil(order: string) {
|
function askAutopilotUntil(order: string) {
|
||||||
@ -1314,11 +1318,20 @@ function Thread() {
|
|||||||
Alert.alert(t("Autopilot"), err instanceof Error ? err.message : t("Try again.")),
|
Alert.alert(t("Autopilot"), err instanceof Error ? err.message : t("Try again.")),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Alert.alert(t("Until when?"), order, [
|
presentMessageActionSheet({
|
||||||
|
title: t("Until when?"),
|
||||||
|
actions: [
|
||||||
{ text: t("Until done"), onPress: () => start({ kind: "done" }) },
|
{ text: t("Until done"), onPress: () => start({ kind: "done" }) },
|
||||||
{ text: t("Until tomorrow morning"), onPress: () => start({ kind: "morning" }) },
|
{ text: t("Until tomorrow morning"), onPress: () => start({ kind: "morning" }) },
|
||||||
{ text: t("Cancel"), style: "cancel" },
|
{
|
||||||
]);
|
text: t("Until a time"),
|
||||||
|
onPress: () => setAutopilotTimeFor(() => (time: string) => start({ kind: "time", time })),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
colorScheme,
|
||||||
|
cancel: t("Cancel"),
|
||||||
|
more: t("More"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addAttachments(
|
async function addAttachments(
|
||||||
@ -2200,6 +2213,15 @@ function Thread() {
|
|||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
{autopilotTimeFor ? (
|
||||||
|
<AutopilotTimePicker
|
||||||
|
onPick={(time) => {
|
||||||
|
setAutopilotTimeFor(null);
|
||||||
|
autopilotTimeFor(time);
|
||||||
|
}}
|
||||||
|
onClose={() => setAutopilotTimeFor(null)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<Modal
|
<Modal
|
||||||
visible={botActionsOpen}
|
visible={botActionsOpen}
|
||||||
transparent
|
transparent
|
||||||
|
|||||||
71
apps/mobile/components/autopilot-time-picker.tsx
Normal file
71
apps/mobile/components/autopilot-time-picker.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { DateTimePicker } from "@expo/ui/community/datetime-picker";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Modal, Platform, Pressable, Text, View } from "react-native";
|
||||||
|
import { autopilotTimeOfDay } from "../lib/autopilot";
|
||||||
|
import { useI18n } from "../lib/i18n";
|
||||||
|
import { useMobileTokens, useResolvedAppearance } from "../lib/native";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The native time picker for "Until a time". Android opens its clock dialog; iOS shows the wheel in
|
||||||
|
* a sheet with Start. Either way the caller gets "HH:MM" and unmounts the picker.
|
||||||
|
*/
|
||||||
|
export function AutopilotTimePicker({
|
||||||
|
onPick,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
onPick: (time: string) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
const tokens = useMobileTokens();
|
||||||
|
const colorScheme = useResolvedAppearance();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const [value, setValue] = useState(() => {
|
||||||
|
const morning = new Date();
|
||||||
|
morning.setHours(8, 0, 0, 0);
|
||||||
|
return morning;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Platform.OS === "android") {
|
||||||
|
return (
|
||||||
|
<DateTimePicker
|
||||||
|
mode="time"
|
||||||
|
value={value}
|
||||||
|
onValueChange={(_event, date) => onPick(autopilotTimeOfDay(date))}
|
||||||
|
onDismiss={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal visible animationType="slide" presentationStyle="pageSheet" onRequestClose={onClose}>
|
||||||
|
<View style={{ flex: 1, backgroundColor: tokens.background, padding: 20, gap: 12 }}>
|
||||||
|
<View
|
||||||
|
style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}
|
||||||
|
>
|
||||||
|
<Pressable accessibilityRole="button" onPress={onClose} hitSlop={12}>
|
||||||
|
<Text style={{ color: tokens.mutedForeground, fontSize: 17 }}>{t("Cancel")}</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Text style={{ color: tokens.foreground, fontSize: 17, fontWeight: "600" }}>
|
||||||
|
{t("Until when?")}
|
||||||
|
</Text>
|
||||||
|
<Pressable
|
||||||
|
accessibilityRole="button"
|
||||||
|
onPress={() => onPick(autopilotTimeOfDay(value))}
|
||||||
|
hitSlop={12}
|
||||||
|
>
|
||||||
|
<Text style={{ color: tokens.foreground, fontSize: 17, fontWeight: "600" }}>
|
||||||
|
{t("Start")}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
<DateTimePicker
|
||||||
|
mode="time"
|
||||||
|
display="spinner"
|
||||||
|
value={value}
|
||||||
|
themeVariant={colorScheme}
|
||||||
|
onValueChange={(_event, date) => setValue(date)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
apps/mobile/lib/autopilot.test.ts
Normal file
50
apps/mobile/lib/autopilot.test.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import type { AutopilotRecent } from "@rakazo/contracts";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { autopilotSummary, autopilotTimeOfDay } from "./autopilot";
|
||||||
|
|
||||||
|
vi.mock("./i18n", () => ({
|
||||||
|
t: (message: string, values?: Record<string, string | number>) =>
|
||||||
|
message.replace(/\{(\w+)\}/g, (_, key: string) => String(values?.[key] ?? "")),
|
||||||
|
}));
|
||||||
|
|
||||||
|
function autopilot(patch: Partial<AutopilotRecent> = {}): AutopilotRecent {
|
||||||
|
return {
|
||||||
|
id: "autopilot-1",
|
||||||
|
botId: "bot-1",
|
||||||
|
threadId: "thread-1",
|
||||||
|
spaceId: "space-1",
|
||||||
|
groupId: null,
|
||||||
|
goal: "Tidy the tickets",
|
||||||
|
focus: null,
|
||||||
|
until: null,
|
||||||
|
status: "active",
|
||||||
|
pauseReason: null,
|
||||||
|
lastCheckpoint: null,
|
||||||
|
nextStep: null,
|
||||||
|
result: null,
|
||||||
|
stepCount: 0,
|
||||||
|
startedAt: "2026-10-05T00:00:00.000Z",
|
||||||
|
endedAt: null,
|
||||||
|
...patch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("autopilot on mobile", () => {
|
||||||
|
it("hands the picked time to autopilotUntil as HH:MM", () => {
|
||||||
|
expect(autopilotTimeOfDay(new Date(2026, 9, 5, 6, 5))).toBe("06:05");
|
||||||
|
expect(autopilotTimeOfDay(new Date(2026, 9, 5, 23, 30))).toBe("23:30");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sums up the night like Today on web", () => {
|
||||||
|
expect(
|
||||||
|
autopilotSummary(autopilot({ stepCount: 2, lastCheckpoint: "Ticket 2 tidied" })),
|
||||||
|
).toEqual({ detail: "last: Ticket 2 tidied", trail: "2 steps" });
|
||||||
|
expect(
|
||||||
|
autopilotSummary(autopilot({ status: "finished", stepCount: 1, result: "All tidy." })),
|
||||||
|
).toEqual({ detail: "All tidy.", trail: "1 step" });
|
||||||
|
expect(
|
||||||
|
autopilotSummary(autopilot({ status: "paused", stepCount: 3, pauseReason: "Need a key." })),
|
||||||
|
).toEqual({ detail: "Need a key.", trail: "Paused" });
|
||||||
|
expect(autopilotSummary(autopilot())).toEqual({ detail: "", trail: "0 steps" });
|
||||||
|
});
|
||||||
|
});
|
||||||
30
apps/mobile/lib/autopilot.ts
Normal file
30
apps/mobile/lib/autopilot.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import type { AutopilotRecent } from "@rakazo/contracts";
|
||||||
|
import { t } from "./i18n";
|
||||||
|
|
||||||
|
/** "HH:MM" for autopilotUntil's time choice. */
|
||||||
|
export function autopilotTimeOfDay(date: Date): string {
|
||||||
|
const pad = (value: number) => String(value).padStart(2, "0");
|
||||||
|
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One autopilot in the home list, like Today on web: how it ended or where it stands, and its step
|
||||||
|
* count, or Paused when it waits on the user.
|
||||||
|
*/
|
||||||
|
export function autopilotSummary(autopilot: AutopilotRecent): { detail: string; trail: string } {
|
||||||
|
const detail =
|
||||||
|
autopilot.status === "finished"
|
||||||
|
? autopilot.result
|
||||||
|
: autopilot.status === "paused"
|
||||||
|
? autopilot.pauseReason
|
||||||
|
: autopilot.lastCheckpoint
|
||||||
|
? t("last: {latest}", { latest: autopilot.lastCheckpoint })
|
||||||
|
: null;
|
||||||
|
const trail =
|
||||||
|
autopilot.status === "paused"
|
||||||
|
? t("Paused")
|
||||||
|
: autopilot.stepCount === 1
|
||||||
|
? t("1 step")
|
||||||
|
: t("{count} steps", { count: autopilot.stepCount });
|
||||||
|
return { detail: detail ?? "", trail };
|
||||||
|
}
|
||||||
@ -694,6 +694,8 @@ export const RU_MESSAGES: Record<string, string> = {
|
|||||||
"Until when?": "До какого времени?",
|
"Until when?": "До какого времени?",
|
||||||
"Until done": "До готовности",
|
"Until done": "До готовности",
|
||||||
"Until tomorrow morning": "До завтрашнего утра",
|
"Until tomorrow morning": "До завтрашнего утра",
|
||||||
|
"Until a time": "До определённого времени",
|
||||||
|
Start: "Запустить",
|
||||||
"1 step": "1 шаг",
|
"1 step": "1 шаг",
|
||||||
"{count} steps": "Шагов: {count}",
|
"{count} steps": "Шагов: {count}",
|
||||||
"last: {latest}": "последний: {latest}",
|
"last: {latest}": "последний: {latest}",
|
||||||
|
|||||||
@ -678,6 +678,8 @@ export const ZH_MESSAGES: Record<string, string> = {
|
|||||||
"Until when?": "持续到什么时候?",
|
"Until when?": "持续到什么时候?",
|
||||||
"Until done": "直到完成",
|
"Until done": "直到完成",
|
||||||
"Until tomorrow morning": "直到明天早上",
|
"Until tomorrow morning": "直到明天早上",
|
||||||
|
"Until a time": "直到指定时间",
|
||||||
|
Start: "开始",
|
||||||
"1 step": "1 步",
|
"1 step": "1 步",
|
||||||
"{count} steps": "{count} 步",
|
"{count} steps": "{count} 步",
|
||||||
"last: {latest}": "最近:{latest}",
|
"last: {latest}": "最近:{latest}",
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/metro-runtime": "57.0.16",
|
"@expo/metro-runtime": "57.0.16",
|
||||||
|
"@expo/ui": "57.0.21",
|
||||||
"@rakazo/chat-ui": "workspace:*",
|
"@rakazo/chat-ui": "workspace:*",
|
||||||
"@rakazo/contracts": "workspace:*",
|
"@rakazo/contracts": "workspace:*",
|
||||||
"@rakazo/core": "workspace:*",
|
"@rakazo/core": "workspace:*",
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -125,6 +125,9 @@ importers:
|
|||||||
'@expo/metro-runtime':
|
'@expo/metro-runtime':
|
||||||
specifier: 57.0.16
|
specifier: 57.0.16
|
||||||
version: 57.0.16(@expo/log-box@57.0.4)(expo@57.0.26)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
version: 57.0.16(@expo/log-box@57.0.4)(expo@57.0.26)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
||||||
|
'@expo/ui':
|
||||||
|
specifier: 57.0.21
|
||||||
|
version: 57.0.21(@babel/core@7.29.7)(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(expo@57.0.26)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3))(react-native@0.86.3(@babel/core@7.29.7)(@react-native/metro-config@0.86.3(@babel/core@7.29.7))(@types/react@19.2.18)(react@19.2.3))(react@19.2.3)
|
||||||
'@rakazo/chat-ui':
|
'@rakazo/chat-ui':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/chat-ui
|
version: link:../../packages/chat-ui
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user