Skip to content

Forms: Add rename and trash actions to single form dropdown#47589

Open
enejb wants to merge 12 commits intotrunkfrom
update/forms-single-dropdown
Open

Forms: Add rename and trash actions to single form dropdown#47589
enejb wants to merge 12 commits intotrunkfrom
update/forms-single-dropdown

Conversation

@enejb
Copy link
Member

@enejb enejb commented Mar 13, 2026

Before:

Screenshot 2026-03-13 at 2 07 59 PM

After:
Screenshot 2026-03-13 at 2 03 37 PM

Proposed changes

  • Add a "Rename" action to the single form dashboard dropdown menu, reusing the existing FormNameModal component and the same rename pattern from the forms list view.
  • Add a "Trash" action that moves the form to trash and navigates back to the forms list.
  • Reorder dropdown menu items to match the design: Preview, Copy embed, Copy shortcode, Publish/Unpublish, Rename, Duplicate, Trash.

Other information

  • Generate changelog entries for this PR (using AI).

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  • Go to the Jetpack Forms dashboard (/wp-admin/admin.php?page=jetpack-forms-responses)
  • Navigate to a single form's responses page (click on a form name)
  • Click the "more actions" (⋮) dropdown in the page header
  • Verify the menu items appear in this order: Preview, Copy embed, Copy shortcode, Publish/Unpublish, Rename, Duplicate, Trash
  • Click Rename — a modal should open with the current form name pre-filled. Enter a new name and save. Verify the breadcrumb title updates.
  • Click Duplicate — verify a new form is created (success snackbar appears).
  • Click Trash — verify the form is moved to trash (success snackbar) and you are navigated back to the forms list.
  • Test on a narrow viewport (mobile) — all actions should appear in the collapsed mobile dropdown menu as well.

@enejb enejb added the [Status] Needs Review This PR is ready for review. label Mar 13, 2026
@enejb enejb self-assigned this Mar 13, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the update/forms-single-dropdown branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/forms-single-dropdown

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions
Copy link
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@enejb
Copy link
Member Author

enejb commented Mar 13, 2026

@claude review this PR.

@claude
Copy link

claude bot commented Mar 13, 2026

Claude finished @enejb's task in 1m 19s —— View job


PR Review

  • Gather context and read changed files
  • Analyze code changes
  • Post review feedback

Overall this is a clean, well-structured addition that reuses the existing FormNameModal pattern and hooks correctly. The code is easy to follow. A few observations below.


Observations

1. Trash action fires immediately without confirmation

The Trash menu item calls trashForm directly with no confirmation dialog, while the "Empty trash" action (also in this same hook) shows a confirmation modal first. Since trashing is irreversible from the user's perspective (they have to go find the form in the trash to restore it), a confirmation step or at minimum an undo action in the snackbar would improve the UX and match WordPress conventions.

use-page-header-details.tsx:327-329

{
    title: __( 'Trash', 'jetpack-forms' ),
    onClick: () => trashForm( formItem ),
},

Consider adding an undo action to the success snackbar (similar to how updateStatus in use-form-item-actions.ts offers an undo), or a confirmation dialog. Fix this →


2. renameRetryRef is not reset after a successful rename

handleRename sets renameRetryRef.current = null on success (line 174), but closeRenameModal also resets it (line 154). That is correct. However, the FormNameModal initialValue prop reads from renameRetryRef.current?.title — if a retry had been attempted and then the user cancels (closes without saving), the ref is cleared but the FormNameModal has already been unmounted, so that path is fine. This is safe as-is. Just worth noting the logic is a bit subtle.


3. handleRename closes the modal on success, but doesn't call closeRenameModal

After a successful save (saveEntityRecord resolves), renameRetryRef.current is cleared but renameFormItem state is not reset — the modal remains open. The modal is only dismissed if the consumer calls closeRenameModal (i.e., the onClose prop). Looking at the FormNameModal, it likely calls onClose internally on successful save via onSave. Let me verify this assumption is correct — if FormNameModal only calls onClose when the user explicitly closes/cancels (and not after onSave resolves), the modal will stay open after a successful rename.

You should ensure that handleRename calls closeRenameModal() on success, or confirm that FormNameModal already does this. Fix this →


4. formItemControls includes actions that may not make sense when viewing a trashed form

When statusView === 'trash', the single form screen shows these dropdown items: Preview, Copy embed, Copy shortcode, Publish (a trashed form is neither published nor drafted, it's trashed), Rename, Duplicate, Trash.

Trashing an already-trashed form would either be a no-op or an error. You should guard against showing "Trash" (and possibly "Rename", "Publish") when formRecord?.status === 'trash'. The publish/unpublish action already reads formRecord?.status but doesn't handle 'trash' status. Fix this →


5. useDispatch( 'core' ) is used separately from useDispatch( coreDataStore )

The file already imports { store as coreDataStore } from @wordpress/core-data and uses useSelect( select => select( coreDataStore )... ) for reading. But useDispatch( 'core' ) (line 134) uses the string rather than the store object. These refer to the same store, so it works, but for consistency and type-safety prefer useDispatch( coreDataStore ). Minor nit.


6. trashForm doesn't invalidate the entity record cache

After calling deleteEntityRecord, the store will mark the record as deleted, but invalidateFormStatusCounts only updates the sidebar counts. The formRecord selector may still return stale data until the entity store resolves. In practice this doesn't matter since you navigate away immediately, but it's worth noting for completeness.


7. Changelog entry mentions "duplicate" — but duplicate was already present

projects/packages/forms/changelog/update-single-form-dropdown-actions:

Add rename, duplicate, and trash actions to the single form dashboard dropdown menu.

Duplicate was already present before this PR (it's in the existing formItemControls array prior to these changes). The changelog should more accurately say "Add rename and trash actions, and reorder dropdown menu items." Minor accuracy issue.


Summary

The implementation is solid and follows the existing patterns well. The main items worth addressing before merge are:

  1. No confirmation/undo for Trash — easy to add a snackbar undo action
  2. Verify FormNameModal auto-closes after save — or explicitly call closeRenameModal() on success
  3. Guard Trash/Rename/Publish controls for already-trashed forms

@jp-launch-control
Copy link

jp-launch-control bot commented Mar 13, 2026

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/packages/forms/src/dashboard/wp-build/hooks/use-page-header-details.tsx 127/186 (68.28%) 68.28% -54 💚

Full summary · PHP report · JS report

@enejb
Copy link
Member Author

enejb commented Mar 13, 2026

@claude review the code one more time.

@claude
Copy link

claude bot commented Mar 13, 2026

Claude finished @enejb's task in 1m 46s —— View job


PR Review (Re-review)

  • Gather context
  • Read changed files
  • Analyze code changes
  • Post review feedback

Good news: all three main issues from the previous review have been addressed. Here's a full status rundown.


Previously Raised — Now Resolved ✅

1. Trash action now has an undo snackbar action (trashForm, lines 226–286)

The undo correctly captures previousStatus before trashing, then restores via saveEntityRecord. Works well.

2. Trashed form guards are in place (formItemControls, lines 388–399)

When formRecord?.status === 'trash', the menu correctly returns only Restore + Delete permanently, skipping Rename/Trash/Publish.

3. FormNameModal auto-closes after save — confirmed safe

I read FormNameModal/index.tsx. The handleConfirm method always calls onClose() in its finally block (line 135), so the modal closes on both success and error. The concern from the previous review was unfounded.

4. Changelog entry — Now reads "Add rename and trash actions to the single form dashboard dropdown menu, and reorder menu items." — accurate.


New Observations

1. FormNameModal has a misleading internal comment

In form-name-modal/index.tsx, line 131:

} catch {
    // Error handling is left to the caller via onSave
    // Modal stays open on error  ← ❌ WRONG
}

The finally block on line 133–136 always calls onClose(), including after an error. The modal does not stay open on error — it closes, and the error snackbar's Retry action re-opens it. The comment should say "Modal closes; caller shows a retry-able error snackbar." Minor doc bug, but could mislead future devs. Fix this →

2. useDispatch( 'core' ) string vs coreDataStore reference (line 142)

Still a minor nit from the previous review — the file imports { store as coreDataStore } from @wordpress/core-data and uses it everywhere except here. No functional impact, but inconsistent and loses type narrowing.

3. Undo restore navigates back to forms list — no re-navigation to single form

After a successful Undo, invalidateFormStatusCounts() and createSuccessNotice are called, but navigate() is not called to bring the user back to the single form view. The user restores a form while on the forms list but has to manually navigate back to it. Consider whether navigating to the restored form's responses page would be better UX.

4. Test gap: handleRename doesn't assert modal dismissal

The test at line 522–556 calls onSave('New Title') and correctly asserts saveEntityRecord was called, but it doesn't assert that the modal disappears afterwards. Since FormNameModal is mocked as () => null, onClose behavior can't be meaningfully tested through the component itself — but you could assert that closeRenameModal (the onClose prop) is eventually called after onSave resolves successfully.


Summary

The implementation is solid and the critical issues are fixed. The remaining items above are all minor/non-blocking. This is ready to merge.

vianasw
vianasw previously approved these changes Mar 16, 2026
Copy link
Contributor

@vianasw vianasw left a comment

Choose a reason for hiding this comment

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

looking good, testing well

enejb and others added 11 commits March 16, 2026 12:07
Add a "Rename" option to the dropdown menu on the single form responses
page, reusing the existing FormNameModal component and the same rename
pattern from the forms list view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a "Move to trash" option to the dropdown menu on the single form
responses page. After trashing, the user is navigated back to the forms
list since the form no longer exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorder the dropdown menu items to match the design: Preview, Copy
embed, Copy shortcode, Publish/Unpublish, Rename, Duplicate, Trash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add undo action to the trash snackbar that restores the form to its
  previous status.
- Hide Rename, Duplicate, Trash, and Publish/Unpublish actions when
  viewing a trashed form.
- Fix changelog to not mention duplicate (already existed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add tests covering return shape, ariaLabel, breadcrumbs, subtitle,
formItemControls ordering, trashed form guard, badges, trash action
(success + error), and rename action.

Also fix a declaration order issue where formRecord was referenced
in trashForm's dependency array before being declared.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When viewing a trashed form's responses, the dropdown menu now shows
only "Restore" and "Delete permanently" actions instead of the normal
actions. Restore sets the form status back to publish, and Delete
permanently removes it with force: true. Both navigate to /forms after.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove navigation to /forms after restore so the user stays on the
single form responses page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…orm button

- Add an "Are you sure" ConfirmDialog before permanently deleting a
  form, matching the pattern used in the forms list view.
- Remove the Edit Form button from the single form header actions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Import and render EditFormButton on single-form screens when a sourceIdNumber exists and the form is not trashed. Refactor the useSelect call that fetches the form record into an explicit callback that guards against missing sourceIdNumber (returning undefined), improving readability and avoiding unnecessary calls. Also include formStatus in the returned dependencies so actions update correctly when the form status changes.
Remove misleading comment in FormNameModal and use coreDataStore
instead of string 'core' in useDispatch for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@enejb enejb force-pushed the update/forms-single-dropdown branch from 3a2170e to 3ae7f0c Compare March 16, 2026 19:11
…data exports

Add createReduxStore, register, and getConfig to the mock so transitive
ESM imports from the dashboard and config stores resolve correctly.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants