feat(mobile): pick a time for the autopilot and see the night on home (#147)
The attach sheet's autopilot now offers "Until a time" with the native time picker from Expo UI: Android's clock dialog, an iOS wheel in a sheet with Start. The home list opens with the autopilots that run, wait or ended within a day, as Today does on web, and each row opens its thread. The attach and until menus move to the shared action sheet, so Android pages them instead of dropping buttons past three. Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1 Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
54658ee689
commit
ca57ebda85
@ -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} />
|
||||||
<ActivitySection activity={activity} bots={bots} />
|
) : null}
|
||||||
|
{activityMode && (activity.active.length > 0 || activity.recent.length > 0) ? (
|
||||||
|
<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({
|
||||||
{ text: t("Until done"), onPress: () => start({ kind: "done" }) },
|
title: t("Until when?"),
|
||||||
{ text: t("Until tomorrow morning"), onPress: () => start({ kind: "morning" }) },
|
actions: [
|
||||||
{ text: t("Cancel"), style: "cancel" },
|
{ text: t("Until done"), onPress: () => start({ kind: "done" }) },
|
||||||
]);
|
{ text: t("Until tomorrow morning"), onPress: () => start({ kind: "morning" }) },
|
||||||
|
{
|
||||||
|
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