fix(recommender): remove Todoist fallback on orchestrator failure; add snooze exclusion

When fetchOrchestratorTip returned null (LiteLLM timeout, bad JSON, etc.)
the recommender silently fell back to randomPolicy, serving a raw Todoist
task with no rationale — explaining both reported symptoms.

- Remove randomPolicy/signalToCandidate; return 204 when orchestrator fails
  so the UI shows "All clear" instead of a confusing Todoist task
- Pass recent_tip through the stack (frontend → POST /recommend →
  fetchOrchestratorTip → ml/serving RecommendRequest → build_orchestrator_messages)
  so after snooze the LLM is instructed not to repeat the snoozed content

Fixes #122

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-12 13:28:32 +00:00
parent d4b40e2590
commit 59c493323f
5 changed files with 29 additions and 41 deletions

View File

@@ -40,11 +40,11 @@ export default function TipPage() {
}
}, [state]);
const loadTip = useCallback(async () => {
const loadTip = useCallback(async (recentTip?: string) => {
setVisible(false);
setState('loading');
try {
const rec = await getRecommendation();
const rec = await getRecommendation(recentTip);
if (!rec) {
setState('empty');
return;
@@ -62,10 +62,11 @@ export default function TipPage() {
const react = async (action: 'done' | 'dismiss' | 'snooze') => {
if (!tip) return;
const snoozedContent = action === 'snooze' ? tip.content : undefined;
setVisible(false);
setState('done');
await sendFeedback(tip.id, { action });
setTimeout(() => loadTip(), 700);
setTimeout(() => loadTip(snoozedContent), 700);
};
const onPointerDown = () => {

View File

@@ -23,9 +23,12 @@ export async function getSession() {
return apiFetch<{ user: { id: string; email: string; name?: string; image?: string } | null }>('/auth/session');
}
export async function getRecommendation(): Promise<RecommendResponse | null> {
export async function getRecommendation(recentTip?: string): Promise<RecommendResponse | null> {
try {
return await apiFetch<RecommendResponse>('/recommend', { method: 'POST' });
return await apiFetch<RecommendResponse>('/recommend', {
method: 'POST',
body: JSON.stringify(recentTip ? { recent_tip: recentTip } : {}),
});
} catch (e: any) {
if (e.status === 204 || e.status === 422) return null;
throw e;