feat: migrate add modal to native dialog + command/commandfor#312
feat: migrate add modal to native dialog + command/commandfor#312
Conversation
…andfor Generated resource templates now use native <dialog> with command="show-modal"/command="close" (Tier 1) instead of <div hidden> with lvt-el:toggleAttr+data-lvt-target (Tier 2). Benefits: - Native backdrop, focus trapping, Escape key via .showModal() - Eliminates inline positioning/backdrop styles (~50 chars per modal) - form method="dialog" closes dialog AND routes action to server - Tier 1 (standard HTML) — no lvt-* attributes needed Depends on client polyfill (livetemplate/client#57) for cross-browser support (Chrome 135+ natively, polyfilled for Firefox/Safari). Edit modal remains server-managed (conditional render) — unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewNice direction — moving to native Blocking
{{if .lvt.HasError "_general"}}
<div ...>{{.lvt.Error "_general"}}</div>
{{end}}With the old pattern, the modal stayed open during the request cycle, so validation errors rendered inside a visible modal. With Two options:
This needs a decision before merging. The browser E2E tests being unchecked makes this easy to miss. Non-blocking but worth addressing before merge
The old overlay used dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
}Click-outside-to-close behavior removed The old implementation included Escaped Escape test is missing The old test summary included // Old:
t.Log(" ✓ Modal closes with Escape key")
// New: removedThe native MinorDouble-padding risk for CSS framework kits ( In the monolithic template, the Dependency on unreleased polyfill The PR description correctly flags Overall the migration approach is clean and the test updates are thorough. The |
There was a problem hiding this comment.
Pull request overview
Migrates the generated “Add” modal interaction in system kits and generator templates from the Tier 2 hidden+lvt-el:toggleAttr pattern to a native <dialog> approach driven by command/commandfor, and updates associated tests to assert dialog state via .open.
Changes:
- Updated single + multi kit resource templates and component templates to use
<dialog>andcommand/commandforfor opening/closing. - Updated generator base component templates (toolbar/form) for the same dialog/command pattern.
- Adjusted tests: kit feature parity pattern and the browser E2E modal test to validate
<dialog>behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/kits/system/single/templates/resource/template.tmpl.tmpl | Switch add modal markup to <dialog> and open/close via command/commandfor. |
| internal/kits/system/single/components/toolbar.tmpl | Update add button to command="show-modal" / commandfor="add-modal". |
| internal/kits/system/single/components/form.tmpl | Convert add modal wrapper to <dialog> and update close/cancel controls. |
| internal/kits/system/multi/templates/resource/template.tmpl.tmpl | Switch add modal markup to <dialog> and open/close via command/commandfor. |
| internal/kits/system/multi/components/toolbar.tmpl | Update add button to command="show-modal" / commandfor="add-modal". |
| internal/kits/system/multi/components/form.tmpl | Convert add modal wrapper to <dialog> and update close/cancel controls. |
| internal/kits/kits_parity_test.go | Update parity assertion to reflect commandfor usage. |
| internal/generator/templates/components/toolbar.tmpl | Update generator toolbar add button to command/commandfor. |
| internal/generator/templates/components/form.tmpl | Convert generator add modal to <dialog> and update close/cancel controls. |
| e2e/modal_test.go | Update browser E2E modal test HTML + assertions for <dialog> open state. |
| {{end}} | ||
|
|
||
| <form name="add"> | ||
| <form method="dialog" name="add"> |
There was a problem hiding this comment.
Using method="dialog" on the add form will cause the <dialog> to close automatically on submit (unless JS reliably calls preventDefault()), which can hide server-side validation errors rendered inside the dialog. Consider removing method="dialog" and instead closing the dialog only after a successful add action, so validation errors remain visible.
| <form method="dialog" name="add"> | |
| <form method="post" name="add"> |
| {{end}} | ||
|
|
||
| <form name="add"> | ||
| <form method="dialog" name="add"> |
There was a problem hiding this comment.
Using method="dialog" on the add form will cause the <dialog> to close automatically on submit (unless JS reliably calls preventDefault()), which can hide server-side validation errors rendered inside the dialog. Consider removing method="dialog" and instead closing the dialog only after a successful add action, so validation errors remain visible.
| <form method="dialog" name="add"> | |
| <form name="add"> |
| {{end}} | ||
|
|
||
| <form name="add"> | ||
| <form method="dialog" name="add"> |
There was a problem hiding this comment.
Using method="dialog" here will auto-close the dialog on submit, which can hide the validation error UI inside the dialog. Consider keeping this as a normal form (no method="dialog") and closing the dialog only on successful add, so users can correct validation errors without re-opening.
| <form method="dialog" name="add"> | |
| <form name="add"> |
| {{end}} | ||
|
|
||
| <form name="add"> | ||
| <form method="dialog" name="add"> |
There was a problem hiding this comment.
Using method="dialog" here will auto-close the dialog on submit, which can hide the validation error UI inside the dialog. Consider keeping this as a normal form (no method="dialog") and closing the dialog only on successful add, so users can correct validation errors without re-opening.
| <form method="dialog" name="add"> | |
| <form name="add"> |
| {{end}} | ||
|
|
||
| <form name="add"> | ||
| <form method="dialog" name="add"> |
There was a problem hiding this comment.
Using method="dialog" on the add form will close the dialog immediately on submit, which makes server-side validation errors (rendered inside the dialog) easy to miss. Consider removing method="dialog" and closing the dialog programmatically only after the add action succeeds.
| <form method="dialog" name="add"> | |
| <form method="post" name="add"> |
| {"update form submission", `name="update"`}, | ||
| {"add form submission", `name="add"`}, | ||
| {"add modal open button", `data-lvt-target="#add-modal"`}, | ||
| {"add modal open button", `commandfor="add-modal"`}, |
There was a problem hiding this comment.
The parity check for the "add modal open button" is now commandfor="add-modal", but that attribute also appears on the close/cancel buttons. This can let the test pass even if the open button is missing. Make the pattern more specific (e.g., include command="show-modal" along with commandfor).
| {"add modal open button", `commandfor="add-modal"`}, | |
| {"add modal open button", `command="show-modal" commandfor="add-modal"`}, |
| <body> | ||
| <div data-lvt-id="test-wrapper"> | ||
| <button id="open-btn" lvt-el:toggleAttr:on:click="hidden" data-lvt-target="#add-modal">Add Product</button> | ||
| <button id="open-btn" command="show-modal" commandfor="add-modal">Add Product</button> |
There was a problem hiding this comment.
This test now relies on the command/commandfor polyfill being present in the client JS fetched from @livetemplate/client@latest (via GetClientLibraryJS). Until that release is available, this browser test will fail even if the Go changes are correct. Consider pinning LVT_CLIENT_CDN_URL to a known-good version for CI, or switching this test to use the local client library setup approach used in other e2e tests.
Regenerated with UPDATE_GOLDEN=1 to reflect the div+hidden → dialog + command/commandfor changes in generator templates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code ReviewGood direction overall — native Critical:
|
- Remove method="dialog" from add forms: prevents dialog from closing before server validation errors can be displayed. The client's form-lifecycle-manager already closes parent <dialog> on success. - Add dialog::backdrop styling (background: rgba(0,0,0,0.5)) to match the old overlay behavior. - Make parity test pattern more specific (command="show-modal" + commandfor) to avoid false positives from close buttons. - Update golden file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supplemental Review NotesReviewing the actual diff more carefully, I want to correct two points from the earlier review and add one new observation. Correction 1:
|
Code ReviewClean migration overall — using native Observations
Inconsistent template comment in kit forms Escape key coverage dropped from test summary Accessibility: Inner wrapper redundancy in template.tmpl.tmpl Summary
The dependency on |
- Replace [data-lvt-target="#add-modal"] with [command="show-modal"][commandfor="add-modal"]
- Replace hidden attribute checks with dialog.open checks
- Replace removeAttribute('hidden') with showModal() calls
- Update diagnostic queries from [role="dialog"] to dialog element
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ReviewOverall this is a clean, well-scoped migration. The Bug:
|
Missed 5 more test files with old [data-lvt-target="#add-modal"] selectors: embedded_browser_test, pagemode_test, resource_generation_test, tutorial_test, and complete_workflow_test (add dialog close wait before edit step to prevent focus issue). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR Review: feat: migrate add modal to native dialog + command/commandforOverall this is a clean, well-scoped migration. The direction is correct — native Bugs1. Stale After clicking the correctly updated // Click the "+ Add Posts" button in toolbar to open modal
chromedp.WaitVisible(`[command="show-modal"][commandfor="add-modal"]`, chromedp.ByQuery),
chromedp.Click(`[command="show-modal"][commandfor="add-modal"]`, chromedp.ByQuery),
// Wait for modal to appear — BUG: still uses old div[role="dialog"] + hidden attribute check
waitFor(`document.querySelector('[role="dialog"]') && !document.querySelector('[role="dialog"]').hasAttribute('hidden')`, 10*time.Second),The new waitFor(`document.querySelector('dialog#add-modal')?.open === true`, 10*time.Second),All other 2. PR description says The "What changed" section shows this diff for the form: -<form name="add">
+<form method="dialog" name="add">However,
Without Code Quality3. + <style>dialog#add-modal::backdrop { background: rgba(0,0,0,0.5); }</style>
+ <dialog id="add-modal" ...>The Suggested fix: move the 4. Inner wrapper styles removed in The sizing constraints were moved from the inner wrappers to the -<article style="max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto;">
+<article>
-<div class="[[boxClass .CSSFramework]]" style="max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto; background: white;">
+<div class="[[boxClass .CSSFramework]]">For frameworks like Bulma where Dependency / Merge Readiness5. Hard dependency on The browser E2E test checkbox is unchecked for good reason — This PR should either:
Minor6. Escape key test was silently removed from the test summary log The old success log included Summary
The approach is solid and the vast majority of the changes are correct. Fixing items 1–3 and coordinating with the client#57 release would make this ready to merge. |
- Update tutorial_test.go: replace role="dialog" hidden check with dialog.open check - Fix complete_workflow_test.go edit step: use JavaScript to set input value instead of chromedp.Clear which fails with "not focusable" after a dialog was in the top layer Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code Review — Final Assessment
What's correct and well-done
Issues to address before merge1. Hard dependency on unreleased polyfill — merge blocker
2. The backdrop style lives inside the {{define \"addModal\"}}
<style>dialog#add-modal::backdrop { background: rgba(0,0,0,0.5); }</style>
<dialog id=\"add-modal\" ...>When lvt re-renders this partial (e.g., after a form validation error), the 3. CSS framework double-padding in The sizing styles were correctly moved to the -<article style=\"max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto;\">
+<article>
-<div class=\"[[boxClass .CSSFramework]]\" style=\"... background: white;\">
+<div class=\"[[boxClass .CSSFramework]]\">For Bulma ( 4. Click-outside-to-close is silently removed The old dialog.addEventListener('click', e => { if (e.target === dialog) dialog.close(); });5. Escape key test coverage dropped The test summary previously included Summary
The approach and implementation are solid. The polyfill dependency and the |
Submit the edit form via JavaScript button.click() to avoid CDP focus issues when a dialog was recently in the top layer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review: feat: migrate add modal to native dialog + command/commandforOverall this is a clean, well-scoped migration that improves accessibility and removes a lot of bespoke JS. A few things worth addressing before merge: Bug:
|
Summary
<div hidden>+lvt-el:toggleAttr+data-lvt-target) to Tier 1 (<dialog>+command/commandfor)<dialog>with.openproperty assertionscommandforpatternWhat changed
Add button (toolbar templates):
Modal container (form templates):
Form (closes dialog + routes action):
Cancel/Close buttons:
Not changed
{{if ne .EditingID ""}}conditional render) — unchangedlvt/components/modal/) — separate pattern, unchangedDependencies
Depends on livetemplate/client#57 being merged and released first (the
command/commandforpolyfill).Test plan
go test ./internal/...)<dialog>assertions@latest)🤖 Generated with Claude Code