rowboatlabs / rowboatlabs/rowboat
Home suggestion tray re-shows the same (declined/dismissed) suggestion
- Dominant language
- TypeScript
- Stars
- 17.6k
- Forks
- 1.7k
- Avg merge
- 11h 37m
- Merged PRs (30d)
- 173
Description
## Summary
On the Home to-do surface, a suggestion that was already declined/dismissed (or already accepted) can reappear in the suggestion tray on a later planner run instead of staying permanently filtered out.
## Where this lives in code (found on `origin/feat/todo-homepage-v2` / `origin/fix/todo-home-polish` — not on `main` yet)
- `apps/x/apps/renderer/src/components/todo-view.tsx:1123` — stores `suggestions` state; `:1209-1214` loads them via `todo:get`; `:1334-1352` accept/decline handlers; `:1915-1948` renders the suggestion tray.
- `apps/x/apps/main/src/ipc.ts:2656-2686` — exposes `todo:get`, `todo:acceptSuggestion`, `todo:declineSuggestion`; `:2796-2805` records dismissal of accepted proposed items.
- `apps/x/packages/core/src/runtime/tools/domains/todo.ts:47-78` — implements the `todo-propose` tool used by the planner.
- `apps/x/packages/core/src/todo/planner-memory.ts:71-96` — records planner feedback and builds "sticky dismissed" keys; `:128-143` dedupes against the current `todo/suggestions.md`.
- `apps/x/packages/core/src/todo/fileops.ts:35-37` — normalizes suggestion keys (lowercase + whitespace collapse only); `:305-314` accepts a suggestion into `todo.md`.
## Root cause hypothesis
1. Decline/dismiss signals are recorded fire-and-forget in the IPC layer (`void recordPlannerSignal(...)` at `ipc.ts:2684` and `:2802`). If a planner run happens immediately after a decline, or the app is closed/crashes right after, the write can be missed and the same suggestion gets proposed again.
2. `planner_feedback.json` caps stored signals at 200 — older dismissed keys can age out and silently lose their "sticky dismissed" protection.
3. `normalizeKey` (fileops.ts:35-37) only lowercases and collapses whitespace. Any trivial rewording or punctuation change from the planner produces a different key, bypassing dedupe even though it's semantically the same suggestion the user already declined.
## Repro steps
1. On the Home to-do surface, trigger a suggestion (click "Suggest" / let the planner run).
2. Decline one of the suggestions.
3. Immediately trigger another planner run (click "Suggest" again, or restart the app shortly after declining).
4. Observe that the same suggestion — or a trivially reworded version of it — reappears in the tray instead of staying filtered out.
## Suggested fix directions
- Await `recordPlannerSignal` (or otherwise make the dismissal write synchronous/durable) before the IPC call resolves, so a dismiss is guaranteed to be persisted before the next planner run can start.
- Don't cap dismissed/sticky keys at a fixed count that discards old entries, or otherwise separate "sticky dismissed" storage from the general feedback signal log so it doesn't age out.
- Make `normalizeKey` more robust to punctuation/light rewording (e.g. strip punctuation, or use a fuzzy/substring match) so a re-generated suggestion with the same intent still matches a previously dismissed one.
_Note: the affected code appears to live on branches `feat/todo-homepage-v2` / `fix/todo-home-polish`, not yet on `main`._
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by tracing the decline flow in `apps/x/apps/renderer/src/components/todo-view.tsx` and `apps/x/apps/main/src/ipc.ts`, then follow dismissal persistence and deduplication through `apps/x/packages/core/src/todo/planner-memory.ts` and `fileops.ts`. Check how the planner feedback cap and `normalizeKey` relate to the repro before choosing among the suggested fix directions. Done means a declined or dismissed suggestion stays filtered out on subsequent planner runs, including after restart and for the described trivial rewording.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- full-stack
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100