feat: bulk-add todos via multi-line paste, retire Import buttons (#110)#117
Conversation
Multi-line paste into the main todo input or the Floating Navigator task input now opens the existing import confirm dialog (one task per line), seeded with the pasted list. Single-line paste/typing is unchanged. The empty-state "Import a list" and toolbar "Import" buttons are removed; a quiet on-voice empty-state hint replaces the button. - isMultiLinePaste reuses parsePasteToTasks so detection equals the dialog's own preview (CRLF / trailing-newline / prefix-only / blank handled identically; no dead-end dialog). - useTodoPasteImport shares the seeded-dialog + invalidate/broadcast wiring between TodoList and FloatingNavigatorContainer: the importing window invalidates its own todo/category/heatmap queries then broadcasts (it never receives its own echo). - Paste interception fires only when the input is empty/fully-selected, so mid-text edits fall through to native paste. - FloatingNavigator gains a decoupled bulkImportBanner slot so the 60s undo banner sits above the list, not clipped in the compact window. - Delete TodoImportEntry; preserve the Completed "Import past wins" flow. E2E: paste-import spec now drives a real synthetic paste ClipboardEvent (the Import button is gone) and adds AC#3/AC#6 guards. All Todo-zone specs pass locally; the 2 Completed-zone failures are a pre-existing UTC-vs-local day-detail date artifact (JST early morning), green on CI's UTC runners.
The floating route renders a desktop-only fallback in a plain browser (gated on window.floatingNavigatorAPI), so the web E2E provably can't reach the floating task input. These component tests give the genuinely-new handleTaskPaste handler deterministic CI coverage: multi-line into the empty/fully-selected input opens the bulk import dialog, a single line falls through to native paste, and a multi-line paste mid-edit is never hijacked.
$gstack-review (Standards axis) flagged the multi-line paste-interception logic as a hard DRY violation: it was copy-pasted byte-for-byte in AddTodoForm.handleTextPaste and FloatingNavigator.handleTaskPaste. The controller half was already a shared hook (useTodoPasteImport); this folds the interception half into one source too. - Add src/lib/interceptBulkPaste.ts — the single source of the "intercept a multi-line paste only when it replaces the entire field" rule (AC#1/#2/#3), reusing isMultiLinePaste so detection still matches the dialog preview. - Both onPaste handlers now call it; drop the duplicated component handlers. - Add src/lib/interceptBulkPaste.test.ts (5 DAMP cases) — the home AddTodoForm path had no unit coverage before, only the web E2E. Behavior unchanged: 671 unit + paste-import web E2E 9/9 green (TZ=UTC).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (2)
📝 WalkthroughWalkthroughImplements multi-line paste-import for the home todo input and Floating Navigator, replacing toolbar import entry points. Adds paste detection utilities, a shared paste-import controller, dialog seeding support, undo banner wiring, and updated E2E/unit coverage. ChangesBulk Paste-Import Flow (Issue
Sequence Diagram(s)sequenceDiagram
participant User
participant Input as AddTodoForm / FloatingNavigator input
participant PasteHelper as interceptBulkPaste
participant Controller as useTodoPasteImport
participant Dialog as PasteImport
participant Cache as React Query cache
participant Windows as Other browser windows
User->>Input: paste text
Input->>PasteHelper: onPaste
PasteHelper->>PasteHelper: detect multi-line + full selection
PasteHelper->>Controller: openWithPaste(text)
Controller->>Dialog: open with initialText
User->>Dialog: confirm import
Dialog->>Controller: handleImported(result)
Controller->>Cache: invalidate todo queries
Controller->>Windows: broadcast sync events
Controller->>Input: show undo banner
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #117 +/- ##
==========================================
+ Coverage 66.89% 67.04% +0.14%
==========================================
Files 137 139 +2
Lines 4335 4348 +13
Branches 1184 1188 +4
==========================================
+ Hits 2900 2915 +15
+ Misses 1218 1216 -2
Partials 217 217 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
#110 renamed the home todo input placeholder 'Enter a new todo...' -> 'Type a todo, or paste a list...' to advertise the paste-a-list affordance now that the explicit Import buttons are gone. Seven web E2E specs located the input by the OLD placeholder text, so they timed out trying to fill it once the rename landed -- failing the category, qa-fixes, sidebar-navigation, skill-tree, sound-palette, theme, and todo-app shards, while paste-import (already on the new string) passed. Updated all 21 getByPlaceholder('Enter a new todo...') call sites to the new text. Test-only; no production change.
Summary
Bulk-add todos by pasting a multi-line list, and retire the now-redundant manual Import buttons (Issue #110).
Pasting a multi-line list into a todo/task input now opens the existing paste-import confirm dialog (one task per line) instead of mangling the whole list into a single task. A single-line paste/type still adds inline. The two stand-alone "Import a list" / toolbar "Import" entry points are removed — paste is the import affordance now.
Closes #110.
What changed
src/lib/interceptBulkPaste.ts(new) — single source of the "intercept a multi-line paste only when it replaces the entire field (empty or fully-selected)" rule. Falls through to native paste for single-line, mid-edit caret, or when no bulk handler is wired.src/lib/isMultiLinePaste.ts(new) —isMultiLinePaste(text)=parsePasteToTasks(text).length >= MULTI_LINE_PASTE_MIN_LINES.src/app/(main)/home/_components/AddTodoForm.tsx— Home todo inputonPaste→ shared helper; placeholder now "Type a todo, or paste a list...".src/components/floating-navigator/FloatingNavigator.tsx— Floating task inputonPaste→ shared helper.TodoImportEntry.tsxdeleted; empty-state and toolbar Import buttons removed (AC#5/Fix Electron auth sync and remove E2E bypass #6).interceptBulkPaste.test.ts(5 DAMP unit cases — first unit coverage of the Home paste path), Floating Navigator bulk-paste component tests, and the authed web E2Epaste-import.spec.ts.The shared helper was extracted in response to the pre-landing two-axis review, which flagged the byte-for-byte duplicated
onPastehandler across the two inputs as a DRY violation.Acceptance criteria
paste-import.spec.ts(authed, real browser) + 5 unit casesTodoImportEntry.tsxdeleted; no dangling refs (typecheck + lint clean)Verification
pnpm validateGREEN — 671 unit pass, typecheck 0, build 0, lint 0.paste-import.spec.ts9/9 GREEN (CI Node 24.13.0).Out of scope
AI auto-labeling (#53 Slice2, paid — on hold) and the Completed "Import past wins" flow (
CompletedImportEntrypreserved).Summary by CodeRabbit
New Features
Bug Fixes