feat(bulk-duplicate): add "Create more" toggle to Deep Duplicate modal - #50
Conversation
- delete unused modal-factory + use-subscription-ref hooks and the unused progress-state / empty-state / section-header UI primitives (~560 LOC) - add targeted eslint-disable + reason on legit external-sync effects (fetch / timer / reset-on-prop) flagged by react-hooks/set-state-in-effect - dedupe the 3x iteration+endDate mapping in sprint-handlers via a local withEndDate helper
…siblings Split bulk-edit-flyout.tsx (717 -> 440 LOC) by moving the presentational sub-components into bulk-edit-value-picker.tsx and bulk-edit-field-row.tsx, and the DOM helper firstRepoNameFromDom into bulk-edit-flyout-helpers.ts. No behavior change; public component + props unchanged.
Move SelectSectionsStep and ReviewStep out of bulk-duplicate-modal.tsx (1490 -> 1199 LOC) into bulk-duplicate-steps.tsx. No behavior change; BulkDuplicateModal public API unchanged.
Remove the Primer components, icons, and helpers that are no longer referenced in bulk-edit-flyout.tsx / bulk-duplicate-modal.tsx after their sub-components moved to sibling modules.
There was a problem hiding this comment.
Cursor auto review
No actionable issues found on changed lines.
The Create more feature is well-structured, but rapid Duplicate clicks can bypass the 3-concurrent guard: queueStore.getActiveCount() is checked before the background worker registers the job, so extra requests are silently dropped after flyToTracker already ran.
Generated automatically when this PR was submitted using Cursor CLI with --model auto.
There was a problem hiding this comment.
All reported issues were addressed across 27 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Mirror GitHub's new-issue "Create more": queue a copy without leaving the modal. Checked keeps the modal open and resets edits to source defaults so the user can queue another copy of the same item; unchecked closes and hands off to the queue tracker. Also drop the blocking await in handleDuplicate so the modal no longer stays frozen until every duplication finishes — the background service worker runs the copy to completion regardless, and errors surface via the queue tracker. Extract applyPreviewDefaults() as the single source-defaults reset path, shared by the initial preview load and the Create-more re-arm.
95c28aa to
c0deeb9
Compare
queueStore.getActiveCount() only reflects a fired duplicate once the background SW's queueStateUpdate broadcast lands, one round-trip after the fire. The fire-and-forget "Create more" flow could send several duplicateItem messages ahead of that broadcast, letting rapid clicks slip past the max-3-concurrent guard. Track just-fired-but-not-yet-reflected duplicates in a local pending tally and drain it as the real queue count catches up.
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Let the modal roll back optimistic concurrent tally when the background gate is full, instead of fire-and-forget with a silent reject.
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
handleDuplicate closed the modal synchronously right after firing the fire-and-forget duplicateItem message, before the accept/reject verdict came back. If the background concurrency gate rejected the request, the warning Flash rendered on an already-closed modal and the duplicate was silently dropped. Move the close/re-arm decision inside the promise resolution so it only fires on an accepted verdict, and surface concurrentError on rejection and on send failure. Addresses #50 (comment)
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
With Create more unchecked, repeated clicks before the accept/reject reply could queue multiple copies even though that mode is meant to submit exactly one and close. Add a submitting lock that disables the Duplicate button and guards re-entry, clearing it on rejection or transport error.


Summary
awaitso the modal no longer stays frozen until every duplication finishes — the background worker runs each copy to completion regardlessChanges
Modal (
src/features/bulk-duplicate-modal.tsx)applyPreviewDefaults()as the single source-defaults reset path, shared by the initial preview load and the Create-more re-armcreateMorestate (unchecked, no persistence — resets each open)handleDuplicateto fire-and-forget theduplicateItemmessage; keep the 3-concurrent cap guard; checked re-arms the form, unchecked closesReview step (
src/features/bulk-duplicate-steps.tsx)ReviewStepgainscreateMore/onToggleCreateMorepropsCheckbox+ label in the footer beside the Duplicate button (Primer +sxonly, inside the existing Shadow DOM /ShadowThemeProvidertree)Docs (
CONTEXT.md)Test Plan
<title> (copy)defaults, modal stays open, copy flies to the queue tracker; repeat to queue severalpnpm typecheck && pnpm lint && pnpm test— 0 type errors, 0 lint errors, 415 tests passSummary by cubic
Add a Create more toggle to the Deep Duplicate modal so you can queue multiple copies without closing the modal. Duplication now runs in the background with a 3‑concurrent cap across tabs, and the modal only closes after the background accepts the request.
New Features
{ accepted }verdict fromduplicateItem. On reject or send failure, roll back the optimistic tally, keep the modal open, and show the cap warning. When Create more is off, lock the Duplicate button until the verdict returns to prevent double‑submits.Refactors
SelectSectionsStepandReviewSteptosrc/features/bulk-duplicate-steps.tsx. AddapplyPreviewDefaults()as the single reset path.bulk-edit-value-picker.tsxandbulk-edit-field-row.tsx; movefirstRepoNameFromDomto helpers.modal-factory,use-subscription-ref, and UI primitives (and tests). Add targetedreact-hooks/set-state-in-effectdisables and dedupe sprintendDatemapping via a localwithEndDatehelper.Written for commit fe25516. Summary will update on new commits.