Skip to content

feat: preview modal actions and large-snippet UX#418

Merged
sheabunge merged 148 commits into
feat/169-snippet-cardsfrom
feat/preview-modal-actions
Jul 21, 2026
Merged

feat: preview modal actions and large-snippet UX#418
sheabunge merged 148 commits into
feat/169-snippet-cardsfrom
feat/preview-modal-actions

Conversation

@imantsk

@imantsk imantsk commented Jul 15, 2026

Copy link
Copy Markdown
Member

What changed

  • Snippet preview modal now renders an actions footer when opened for a local snippet, from both the card Preview button and the list-row Preview link:
    • Priority (left): inline number input that saves on blur/submit; disabled for locked snippets.
    • Trash: destructive link using the shared delete confirmation flow; closes the modal and refreshes the list on success; hidden for locked snippets.
    • Export: downloads the snippet export file.
    • Clone: clones the snippet, refreshes the list, and closes the modal.
    • Edit (primary, links to the snippet edit page; labelled View for locked snippets).
  • The footer only renders when a full snippet object is passed, so cloud snippet previews are unchanged.
  • Extracted the priority input from TableColumns into a shared SnippetPriorityInput common component, reused by the list table, card kebab menu, and preview modal, with an optional inputId to keep element IDs unique.
  • Improved large-snippet UX: the modal is now min(900px, 90vw) wide with a 85vh height cap; the header and footer stay pinned and the CodeMirror editor is the only scroll region, scrolling long code vertically and horizontally without wrapping. CodeMirror's default viewport rendering is retained so very long snippets stay fast.

How verified

  • npm run build compiles successfully.
  • npm run lint:js and npm run lint:styles pass with no errors or warnings.
  • PHPUnit: 116 tests, 257 assertions; only the pre-existing intermittent REST_API_Cloud per-page failure, matching the baseline on the parent branch.

imantsk added 30 commits July 15, 2026 17:52
@imantsk

imantsk commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

Assertion corrected to the snippets list; the post-save setup and dirty-state checks are unchanged.

@code-snippets-bot

Copy link
Copy Markdown
Contributor

Follow-up adversarial re-audit found two unresolved issues:

  1. The preview remains inaccessible to keyboard users. SnippetPreviewModal.tsx:233 still initializes CodeMirror with readOnly: 'nocursor'. In CodeMirror 5, that mode prevents the editor from receiving focus, so keyboard users cannot enter the preview to select or copy code. Please use readOnly: true and add a keyboard-focus/selection assertion.

  2. The corrected Back test still asserts an unreachable destination. The first save from Add New uses pushState() in SnippetForm.tsx:98-99; the replaceState() path in useSubmitSnippet.tsx:143-146 is guarded by request.id, which is false for a create request. History remains [snippets, add-snippet, edit-snippet]. Cancelling the first Back pushes Edit again, so accepting the next Back reaches Add New, not the snippets list asserted in code-snippets-edit.spec.ts:109. Please either replace the Add New entry when transitioning to Edit or update the intended navigation contract and test accordingly.

@imantsk

imantsk commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

Resolved. Preview CodeMirror uses readOnly true with keyboard focus and selection coverage. The Add New-to-Edit transition now replaces the Add New history entry, while dirty navigation restores Edit after cancellation and prompts once before returning to the snippets list. The relevant E2E coverage passes.

@code-snippets-bot
code-snippets-bot marked this pull request as ready for review July 18, 2026 10:12
@code-snippets-bot code-snippets-bot added the run-tests Trigger automated tests label Jul 19, 2026
Maisy and others added 2 commits July 19, 2026 17:28
Propagation also duplicated the keyboard-selection test; the older inline
copy (same title) blocked Playwright discovery, so it is removed in favor
of the refactored helper-based version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The keyboard-selection test located textarea[aria-label] from the whole
modal, matching both the readonly fallback and the CodeMirror input.
The input element also differs by CodeMirror inputStyle (contenteditable
on current WordPress, textarea on older builds), so inputValue/toHaveValue
cannot target it portably. Read the document and selection through the
already-scoped .CodeMirror editor instance instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@code-snippets-bot code-snippets-bot added the build Adding this label will trigger the zip build action label Jul 19, 2026
@code-snippets-bot

code-snippets-bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Download and install

📦 code-snippets.4.0.0-beta.2.zip

Base automatically changed from feat/whats-new-typography to feat/169-snippet-cards July 21, 2026 04:07
use Code_Snippets\Model\Snippet;
use function Code_Snippets\code_snippets;

defined( 'ABSPATH' ) || exit;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unnecessary for PHP class files.

*
* @return array<string, int> Map of type name to snippet count, including 'all'.
*/
public function count(): array {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it make sense to have this as its own class? Would it make more sense as a function in snippet-ops.php?

public function __construct( Manage_Menu_Screen_Options $screen_options ) {
public function __construct(
Manage_Menu_Screen_Options $screen_options,
Snippet_Type_Counter $type_counter

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's no reason why this should be passed through from Plugin – this class can initialise its own local version.

</form>
)
}
export const PriorityColumn: React.FC<ColumnProps> = ({ snippet }) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Inline this in the column definition.

(type?: SnippetType) => countedSnippets
? type ? typeCounts?.get(type) ?? 0 : countedSnippets.length
: localized?.[type ?? 'all'],
[countedSnippets, typeCounts, localized]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't know why countedSnippets and typeCounts are memorised separately when they just feed into this?

@sheabunge
sheabunge merged commit 7296a39 into feat/169-snippet-cards Jul 21, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Adding this label will trigger the zip build action run-tests Trigger automated tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants