* feat: show routines the bot sets up from chat as one quiet line
When someone mentions something recurring in passing ("every Sunday I plan the
week"), the bot now creates the routine right away and the conversation shows a
single line with the schedule, Edit and Undo, on web and mobile.
- New `event` message block for routine lines, kept in history so later turns
know what was set up or removed.
- schedule_create reuses an active repeating routine of the same name instead
of adding a duplicate, caps a bot at 50 active schedules, and falls back to
default times when none was given.
- Removing a routine (Undo, the editor, or schedule_cancel) marks its lines
removed via the new routines.noticeMessageId column.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
* test: give the routine notice test a complete job publisher
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
---------
Co-authored-by: Claude <noreply@anthropic.com>
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import type { RoutineEventBlock } from "@rakazo/contracts";
|
|
import { isOneShotRoutineCrons } from "@rakazo/core";
|
|
import { useRouter } from "expo-router";
|
|
import { useState } from "react";
|
|
import { Pressable, Text, View } from "react-native";
|
|
import { rpc } from "../lib/api";
|
|
import { useI18n } from "../lib/i18n";
|
|
import { useMobileTokens } from "../lib/native";
|
|
import { routineLineSchedule } from "../lib/routine-line";
|
|
import { NativeSymbol } from "./native-symbol";
|
|
|
|
/** A routine the bot set up from the conversation: one quiet line with Edit and Undo. */
|
|
export function RoutineEventLine({ block }: { block: RoutineEventBlock }) {
|
|
const tokens = useMobileTokens();
|
|
const { t } = useI18n();
|
|
const router = useRouter();
|
|
const [undoing, setUndoing] = useState(false);
|
|
const [failed, setFailed] = useState(false);
|
|
const oneShot = isOneShotRoutineCrons(block.crons);
|
|
const schedule = routineLineSchedule(block);
|
|
const muted = { color: tokens.mutedForeground, fontSize: 13.5 };
|
|
|
|
async function undo() {
|
|
setUndoing(true);
|
|
setFailed(false);
|
|
try {
|
|
await rpc("routines/remove", { routineId: block.routineId });
|
|
} catch {
|
|
setFailed(true);
|
|
} finally {
|
|
setUndoing(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<View
|
|
style={{
|
|
width: "100%",
|
|
paddingVertical: 4,
|
|
flexDirection: "row",
|
|
flexWrap: "wrap",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
columnGap: 6,
|
|
rowGap: 2,
|
|
}}
|
|
>
|
|
<View
|
|
accessible
|
|
accessibilityRole="image"
|
|
accessibilityLabel={oneShot ? t("Reminder") : t("Routine")}
|
|
>
|
|
<NativeSymbol
|
|
ios={oneShot ? "bell" : "repeat"}
|
|
android={oneShot ? "notifications-outline" : "repeat"}
|
|
size={14}
|
|
color={tokens.mutedForeground}
|
|
/>
|
|
</View>
|
|
<Text
|
|
style={[
|
|
muted,
|
|
block.removed
|
|
? { textDecorationLine: "line-through" }
|
|
: { color: tokens.foreground, fontWeight: "500" },
|
|
]}
|
|
>
|
|
{block.name}
|
|
</Text>
|
|
{schedule ? <Text style={muted}>· {schedule}</Text> : null}
|
|
{block.removed ? (
|
|
<Text style={muted}>· {t("Removed")}</Text>
|
|
) : (
|
|
<>
|
|
{block.event === "routine.updated" ? <Text style={muted}>· {t("Updated")}</Text> : null}
|
|
<Text style={muted}>·</Text>
|
|
<Pressable
|
|
accessibilityRole="button"
|
|
hitSlop={10}
|
|
onPress={() =>
|
|
router.push({
|
|
pathname: "/routine",
|
|
params: { botId: block.botId, routineId: block.routineId },
|
|
})
|
|
}
|
|
>
|
|
<Text style={[muted, { fontWeight: "600" }]}>{t("Edit")}</Text>
|
|
</Pressable>
|
|
<Text style={muted}>·</Text>
|
|
<Pressable
|
|
accessibilityRole="button"
|
|
accessibilityState={{ disabled: undoing }}
|
|
disabled={undoing}
|
|
hitSlop={10}
|
|
onPress={() => void undo()}
|
|
>
|
|
<Text style={[muted, { fontWeight: "600", opacity: undoing ? 0.5 : 1 }]}>
|
|
{failed ? t("Try again") : t("Undo")}
|
|
</Text>
|
|
</Pressable>
|
|
</>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|