From 82b501e753af6737c03f2881722bb7e84b403a65 Mon Sep 17 00:00:00 2001 From: escouade-bot Date: Tue, 31 Mar 2026 00:01:14 -0400 Subject: [PATCH 1/3] fix: force widget refresh via requestWidgetUpdate after click actions (#32) The Android ListView caches its items, so visual-only changes (like toggling a subtask checkbox) were not reflected without calling requestWidgetUpdate() to invalidate the cache. Added forceWidgetRefresh() helper that calls requestWidgetUpdate() for all 3 widget sizes after TOGGLE_SUBTASK, TOGGLE_COMPLETE, and TOGGLE_EXPAND handlers. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/widgets/widgetTaskHandler.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/widgets/widgetTaskHandler.ts b/src/widgets/widgetTaskHandler.ts index 90c33f8..757de46 100644 --- a/src/widgets/widgetTaskHandler.ts +++ b/src/widgets/widgetTaskHandler.ts @@ -1,8 +1,11 @@ import type { WidgetTaskHandlerProps } from 'react-native-android-widget'; +import { requestWidgetUpdate } from 'react-native-android-widget'; import { TaskListWidget } from './TaskListWidget'; import { getWidgetState, setWidgetState, type WidgetTask } from '../services/widgetSync'; import { isValidUUID } from '../lib/validation'; +const WIDGET_NAMES = ['SimplListeSmall', 'SimplListeMedium', 'SimplListeLarge']; + const EXPAND_DEBOUNCE_MS = 2000; const lastExpandTimes = new Map(); @@ -24,6 +27,25 @@ function renderWithState( ); } +async function forceWidgetRefresh( + tasks: WidgetTask[], + isDark: boolean, + expandedTaskIds: string[], +): Promise { + for (const widgetName of WIDGET_NAMES) { + try { + await requestWidgetUpdate({ + widgetName, + renderWidget: (props) => + TaskListWidget({ ...props, widgetName, tasks, isDark, expandedTaskIds }), + widgetNotFound: () => {}, + }); + } catch { + // Widget not placed on home screen + } + } +} + export async function widgetTaskHandler( props: WidgetTaskHandlerProps ): Promise { @@ -51,6 +73,7 @@ export async function widgetTaskHandler( await setWidgetState(state); renderWithState(renderWidget, widgetInfo, state.tasks, state.isDark, state.expandedTaskIds); + await forceWidgetRefresh(state.tasks, state.isDark, state.expandedTaskIds); try { const { toggleComplete } = await import('../db/repository/tasks'); @@ -82,6 +105,7 @@ export async function widgetTaskHandler( await setWidgetState(state); renderWithState(renderWidget, widgetInfo, state.tasks, state.isDark, state.expandedTaskIds); + await forceWidgetRefresh(state.tasks, state.isDark, state.expandedTaskIds); } if (props.clickAction === 'TOGGLE_SUBTASK') { @@ -103,6 +127,7 @@ export async function widgetTaskHandler( await setWidgetState(state); renderWithState(renderWidget, widgetInfo, state.tasks, state.isDark, state.expandedTaskIds); + await forceWidgetRefresh(state.tasks, state.isDark, state.expandedTaskIds); try { const { toggleComplete } = await import('../db/repository/tasks'); -- 2.45.2 From 645f778db5b376507089f0f6a71499377214aef1 Mon Sep 17 00:00:00 2001 From: escouade-bot Date: Tue, 31 Mar 2026 02:00:49 -0400 Subject: [PATCH 2/3] fix: remove unnecessary forceWidgetRefresh from TOGGLE_COMPLETE and TOGGLE_EXPAND (#32) Only TOGGLE_SUBTASK needs forceWidgetRefresh() because ListView caches items. TOGGLE_COMPLETE and TOGGLE_EXPAND already work with renderWithState() alone since they perform structural changes (remove item / toggle children). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/widgets/widgetTaskHandler.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/widgets/widgetTaskHandler.ts b/src/widgets/widgetTaskHandler.ts index 757de46..32d01e3 100644 --- a/src/widgets/widgetTaskHandler.ts +++ b/src/widgets/widgetTaskHandler.ts @@ -73,7 +73,6 @@ export async function widgetTaskHandler( await setWidgetState(state); renderWithState(renderWidget, widgetInfo, state.tasks, state.isDark, state.expandedTaskIds); - await forceWidgetRefresh(state.tasks, state.isDark, state.expandedTaskIds); try { const { toggleComplete } = await import('../db/repository/tasks'); @@ -105,7 +104,6 @@ export async function widgetTaskHandler( await setWidgetState(state); renderWithState(renderWidget, widgetInfo, state.tasks, state.isDark, state.expandedTaskIds); - await forceWidgetRefresh(state.tasks, state.isDark, state.expandedTaskIds); } if (props.clickAction === 'TOGGLE_SUBTASK') { -- 2.45.2 From 156e45496fc655d0edaf0e569c6b0525eabaa39a Mon Sep 17 00:00:00 2001 From: escouade-bot Date: Tue, 31 Mar 2026 04:01:01 -0400 Subject: [PATCH 3/3] fix: deduplicate WIDGET_NAMES and remove double-render in TOGGLE_SUBTASK (#32) - Export WIDGET_NAMES from widgetSync.ts and import in widgetTaskHandler.ts - Remove renderWithState call before forceWidgetRefresh to avoid double-render - Use shared WIDGET_NAMES in widgetSync.ts refresh loop Co-Authored-By: Claude Opus 4.6 (1M context) --- src/services/widgetSync.ts | 3 ++- src/widgets/widgetTaskHandler.ts | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/services/widgetSync.ts b/src/services/widgetSync.ts index 5d7693e..0b6555b 100644 --- a/src/services/widgetSync.ts +++ b/src/services/widgetSync.ts @@ -8,6 +8,7 @@ import { startOfDay, endOfDay, addWeeks } from 'date-fns'; import { TaskListWidget } from '../widgets/TaskListWidget'; export const WIDGET_STATE_KEY = 'widget:state'; +export const WIDGET_NAMES = ['SimplListeSmall', 'SimplListeMedium', 'SimplListeLarge'] as const; // Legacy keys — used for migration only const LEGACY_DATA_KEY = 'widget:tasks'; @@ -214,7 +215,7 @@ export async function syncWidgetData(): Promise { await setWidgetState(state); // Request widget update for all 3 sizes - const widgetNames = ['SimplListeSmall', 'SimplListeMedium', 'SimplListeLarge']; + const widgetNames = WIDGET_NAMES; for (const widgetName of widgetNames) { try { await requestWidgetUpdate({ diff --git a/src/widgets/widgetTaskHandler.ts b/src/widgets/widgetTaskHandler.ts index 32d01e3..1e3ab43 100644 --- a/src/widgets/widgetTaskHandler.ts +++ b/src/widgets/widgetTaskHandler.ts @@ -1,11 +1,9 @@ import type { WidgetTaskHandlerProps } from 'react-native-android-widget'; import { requestWidgetUpdate } from 'react-native-android-widget'; import { TaskListWidget } from './TaskListWidget'; -import { getWidgetState, setWidgetState, type WidgetTask } from '../services/widgetSync'; +import { getWidgetState, setWidgetState, WIDGET_NAMES, type WidgetTask } from '../services/widgetSync'; import { isValidUUID } from '../lib/validation'; -const WIDGET_NAMES = ['SimplListeSmall', 'SimplListeMedium', 'SimplListeLarge']; - const EXPAND_DEBOUNCE_MS = 2000; const lastExpandTimes = new Map(); @@ -124,7 +122,6 @@ export async function widgetTaskHandler( } await setWidgetState(state); - renderWithState(renderWidget, widgetInfo, state.tasks, state.isDark, state.expandedTaskIds); await forceWidgetRefresh(state.tasks, state.isDark, state.expandedTaskIds); try { -- 2.45.2