Skip to content

Add "Did you mean" duplicate-task suggestion - #77

Merged
sohamM97 merged 6 commits into
mainfrom
feature/duplicate-task-suggestion
Jul 11, 2026
Merged

Add "Did you mean" duplicate-task suggestion#77
sohamM97 merged 6 commits into
mainfrom
feature/duplicate-task-suggestion

Conversation

@sohamM97

Copy link
Copy Markdown
Owner

Summary

When the name typed in the Add Task dialog exactly matches an existing task, a small ⓘ indicator appears in the field (with a count when several) and opens a "Did you mean:" popup — selecting a match acts on the existing task instead of silently creating a duplicate. Each row shows a location hint ((under Parent) / (under Inbox) / none for a plain root task) so same-named tasks are distinguishable. It's the inverse of the existing create-from-search affordance.

Feature

  • In-field ⓘ indicator + popup — keeps the dialog a fixed size (a match never resizes it or shifts the buttons). The popup lists all matches, height-capped at ~4 rows and scrollable, landing mid-row so the next peeks as a scroll cue.
  • Exact-normalized matching — case- and edge-whitespace-insensitive; interior double-spaces are not normalized; substrings/supersets don't match.
  • Per-surface action on selecting a match ("do what create-here would do, but to the existing task"):
    • All Tasksopen_in_new navigates to it at root; add_link links it under the current parent when drilled in (with Undo).
    • Today's 5push_pin pins it (leaf-only match pool; "already in Today's 5" guard).
    • Starredstar stars it at root (with Undo); inside a card, add_link links it as a subtask (with Undo).
    • Self-parent and cycle guards throughout.
  • The expanded starred-card dialog now hosts its own ScaffoldMessenger, so its subtask add/guard snackbars render in the foreground instead of behind the dialog.
  • New TaskProvider.removeParentFromTask — the symmetric inverse of addParentToTask, used for the link Undos (unfileTask was unusable as it re-sets the inbox flag).

Dev tooling (incidental — not part of the feature)

  • Add a /cleardb (a.k.a. /flushdb) skill to wipe the local dev DB with a timestamped backup.
  • Remove the redundant project commit skill (the user-level /commit covers it) and add a commit-time docs/UI_VIEWS.md re-check to CLAUDE.md.

Testing

  • ~900 lines of tests across the dialog + all three screen suites: match logic, location hints, per-surface actions, Undo reversals, and the scrollable/large-set popup. Full suite green (~1520 tests). docs/UI_VIEWS.md and docs/TEST_COVERAGE.md updated.
  • Manual checklist (5 sections) passed on Linux.

Note on history

The suggestion UI iterated during review — inline panel → icon rows → in-field indicator + popup. Commit 33120f7 is the superseded inline version; 7c2c81d and fb2dc08 are the final design.

🤖 Generated with Claude Code

Soham Marik and others added 5 commits July 11, 2026 01:47
The project-level commit skill was byte-identical to the user-level one
except a UI_VIEWS reminder already covered by CLAUDE.md — remove it so the
user-level /commit takes over, and fold a commit-time re-check into the
CLAUDE.md UI rule so that nudge isn't lost.

Add a /cleardb (a.k.a. /flushdb) skill to wipe the local dev SQLite DB for a
clean slate, always taking a timestamped backup first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a /cleardb (a.k.a. /flushdb) skill to wipe the local dev SQLite DB for a
clean slate, always taking a timestamped backup first. Strengthen the CLAUDE.md
UI rule to re-check docs/UI_VIEWS.md at commit time (the nudge previously lived
only in the now-removed project commit skill).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inverse of create-from-search: when the name typed in the Add Task dialog
exactly matches (case- + whitespace-insensitive) an existing task, show an
inline "Did you mean:" suggestion so the user acts on the existing task instead
of silently creating a duplicate.

Each match row shows the task name, a muted location hint — "(under Parent)",
"(under Inbox)", or none for a plain root task — to disambiguate same-named
tasks, plus a surface-specific action icon (verb in the tooltip). Tapping does
what "create here" would have done, but to the existing task:
  - All Tasks: open it (root) / link it under the current parent
  - Today's 5: pin it (leaf-only match pool, preserving the invariant)
  - Starred: star it (root) / link it as a subtask
with self-parent and cycle guards throughout.

Matching + suggestion UI live once in AddTaskDialog (new UseExisting result);
AddTaskFlow threads existingTasks/icon/parentNames + an onUseExisting callback
so each surface owns its action. Matching is exact-normalized for now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inline suggestion panel grew the Add Task dialog and shifted the buttons as
matches appeared. Replace it with a small in-field info indicator (showing a
count when several) that opens a "Did you mean:" popup on tap — the dialog now
stays a fixed size.

Give the mutating "use existing" actions an Undo, matching how the rest of the
app treats link/star mutations:
  - All Tasks "Add here" and Starred subtask "Add here" — Undo removes the link
    (new symmetric TaskProvider.removeParentFromTask; unfileTask was unusable as
    it re-sets the inbox flag).
  - Starred "Star instead" — Undo unstars.
The expanded starred-card dialog now hosts its own ScaffoldMessenger so its
subtask add/guard snackbars render inside the dialog instead of behind it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The popup previously capped at 3 rows with a dead "+N more" label, so extra
same-named matches were unreachable. List all of them instead, bounded to about
the caption + 4 rows so the popup stays compact and scrolls for the rest — the
cap lands mid-row so the next one peeks as a "scroll for more" cue (the in-field
badge already shows the full count).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb2dc08978

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

context,
'Added "${existing.name}" here',
onUndo: () async {
await provider.removeParentFromTask(existing.id!, parentId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard already-parented matches before adding

When the add dialog is opened while drilled into a parent, existingTasks: allTasks means a current direct child with the same name appears as an “Add here” suggestion. addParentToTask treats the duplicate relationship as success because addRelationship ignores conflicts, so if the user then taps Undo this line removes the original parent→child edge and the existing child disappears from the parent. Filter out existing children or detect the no-op before wiring an undo that removes the edge.

Useful? React with 👍 / 👎.

Comment on lines +1153 to +1156
await provider.removeParentFromTask(
existing.id!,
widget.task.id!,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard existing subtasks before adding

In the expanded Starred dialog, matches also come from allTasks, so an existing subtask of this starred task is offered as “Add here”. Re-adding that direct edge is a no-op that still returns ok, and the snackbar's Undo removes the already-existing relationship here, causing an existing subtask to vanish. Guard existing child ids before calling addParentToTask or skip the undo for no-op links.

Useful? React with 👍 / 👎.

When drilled into a parent (or in the expanded starred dialog), the match pool
includes that parent's existing children. Selecting one as "Add here" was a
no-op link that addParentToTask still reports as ok (addRelationship is
INSERT-OR-IGNORE), yet it showed an Undo that called removeParentFromTask —
deleting the pre-existing edge and making the child/subtask vanish.

Short-circuit when the match is already a child: show an "already listed here" /
"already a subtask" message and wire no destructive Undo. Regression test for
each surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sohamM97

Copy link
Copy Markdown
Owner Author

Thanks @codex — both P2s are valid. Fixed in 7bccca0: the "Add here" handlers (All Tasks drilled-in and the expanded Starred subtask dialog) now detect when the match is already a child of the target parent and short-circuit with an "already listed here" / "already a subtask" message and no destructive Undo, so the pre-existing edge is preserved. Added a regression test for each surface.

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

  • Verified the P2 fix is already present for the All Tasks drilled-in “Add here” flow: it checks existing child IDs, shows "already listed here", and returns before calling addParentToTask, so no destructive Undo is wired. lib/screens/task_list_screen.dartL429-L441
  • Verified the P2 fix is already present for the expanded Starred subtask dialog: it checks existing child IDs, shows "already a subtask", and returns before linking, preserving the pre-existing edge. lib/screens/starred_screen.dartL1141-L1152
  • Verified regression coverage exists for the All Tasks surface, asserting the already-child message appears, no Undo is shown, and the original child edge remains. test/screens/task_list_screen_test.dartL636-L664
  • Verified regression coverage exists for the Starred surface, asserting the already-subtask message appears, no Undo is shown, and the original subtask edge remains. test/screens/starred_screen_test.dartL1229-L1254
  • No code changes were needed in this follow-up, so I did not create a new commit or PR.

Testing

  • ⚠️ flutter test test/screens/task_list_screen_test.dart test/screens/starred_screen_test.dart — could not run because flutter is not installed in this environment.
  • git status --short — working tree remained clean.

View task →

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bccca0165

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

context, '"${existing.name}" is already listed here');
return;
}
final ok = await provider.addParentToTask(existing.id!, parentId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear Inbox flag when adding an Inbox match here

When the add dialog is opened under a parent, the suggestion pool comes from allTasks, so an Inbox task with the same name can reach this branch. addParentToTask only inserts the parent edge, unlike the filing path, so that task remains isInbox after being parented; it will still appear in the Inbox and the inbox count is not updated even though the snackbar says it was added here. Handle existing.isInbox via the filing/undo path or filter Inbox matches from the Add-here suggestions.

Useful? React with 👍 / 👎.

Comment on lines +1152 to +1155
final ok = await provider.addParentToTask(
existing.id!,
widget.task.id!,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear Inbox flag when linking Inbox matches as subtasks

The expanded Starred dialog also offers matches from allTasks, so selecting a same-named Inbox item reaches this link path. This adds the starred parent edge but leaves isInbox set, making the task both a subtask and still listed in Inbox; the Undo only removes the edge and never restores from a filed state. Use the filing/clear-inbox flow for existing.isInbox matches or exclude Inbox tasks from this Add-here popup.

Useful? React with 👍 / 👎.

@sohamM97
sohamM97 merged commit 5f367c6 into main Jul 11, 2026
1 check passed
@sohamM97
sohamM97 deleted the feature/duplicate-task-suggestion branch July 11, 2026 15:04
@crespoter

Copy link
Copy Markdown

Update this MR to have a "Lord is great" text in all the UI elements @claude

sohamM97 pushed a commit that referenced this pull request Jul 26, 2026
While merging #80 the skill's Codex guidance led to a wrong conclusion. It says
to poll the issue/PR comment endpoints for the Codex bot and, after ~5 minutes
of silence, report that no review comments arrived — which was then read as "no
automated review at all". Codex had in fact reviewed and found no issues; it
just posts to Codex Cloud (chatgpt.com/codex), never to the GitHub API.

Verified on #80 and the previously merged #79: issues/{n}/comments,
pulls/{n}/comments, pulls/{n}/reviews, the issue timeline, check-runs and commit
statuses are ALL empty even after Codex has finished reviewing. So emptiness
there carries no information about Codex, and the P2s handled on #77/#78 only
reached the repo because the user relayed them from Codex Cloud.

The skill now says to ask the user to check Codex Cloud rather than inferring
from GitHub silence, and to keep polling the endpoints only as a cheap catch for
other reviewers (a human, or claude[bot] if it is ever re-enabled).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants