feat: add live-preview example for Change() capability#35
Conversation
Example showing the Change() method convention for Tier 1 auto-inferred input bindings. The controller's Change() method triggers the server to include "change" in the capabilities metadata of the initial render. Includes WebSocket tests verifying: - capabilities=["change"] in initial render response - Change() method dispatches correctly via WebSocket Depends on: livetemplate/livetemplate#253 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new live-preview example to the LiveTemplate examples repo to demonstrate the Change() method convention (Tier 1 auto-inferred input bindings) using a plain HTML form (no lvt-* attributes), with accompanying WebSocket and browser E2E tests.
Changes:
- Added
live-previewexample server (PreviewControllerwithChange()andSubmit()) and template using a standard POST form. - Added WebSocket tests for initial capabilities metadata and
changeaction dispatch, plus a chromedp browser E2E test. - Registered the new example in
test-all.shand ignored its built binary in.gitignore.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
test-all.sh |
Includes live-preview in the working examples test matrix. |
live-preview/preview.tmpl |
New template showing a standard HTML form with DevMode/CDN client loading. |
live-preview/main.go |
New example server wiring Change() + Submit() into a LiveTemplate handler. |
live-preview/live_preview_test.go |
Adds WebSocket and browser E2E coverage for the example behavior. |
.gitignore |
Ignores the live-preview compiled binary. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for i := 0; i < 50; i++ { | ||
| if resp, err := http.Get(serverURL); err == nil { | ||
| resp.Body.Close() | ||
| break | ||
| } | ||
| time.Sleep(100 * time.Millisecond) | ||
| } | ||
|
|
There was a problem hiding this comment.
startServer() polls http://localhost: but never fails the test if the server never becomes reachable (it breaks on success, otherwise returns after ~5s regardless). This can lead to confusing downstream failures in WebSocket dialing. Track whether a successful HTTP response was observed and t.Fatalf with the captured server logs if the server didn't start in time (optionally using an http.Client with a timeout per request).
| for i := 0; i < 50; i++ { | |
| if resp, err := http.Get(serverURL); err == nil { | |
| resp.Body.Close() | |
| break | |
| } | |
| time.Sleep(100 * time.Millisecond) | |
| } | |
| // Use an HTTP client with a short timeout to avoid hanging on each attempt. | |
| client := &http.Client{ | |
| Timeout: 200 * time.Millisecond, | |
| } | |
| serverReady := false | |
| for i := 0; i < 50; i++ { | |
| resp, err := client.Get(serverURL) | |
| if err == nil { | |
| resp.Body.Close() | |
| serverReady = true | |
| break | |
| } | |
| if resp != nil && resp.Body != nil { | |
| resp.Body.Close() | |
| } | |
| time.Sleep(100 * time.Millisecond) | |
| } | |
| if !serverReady { | |
| t.Fatalf("server did not become reachable at %s within timeout; logs:\n%s", serverURL, logs.String()) | |
| } |
live-preview/preview.tmpl
Outdated
| <body> | ||
| <h1>Live Preview</h1> | ||
| <form method="POST"> | ||
| <input name="Name" value="{{.Name}}" placeholder="Type your name..."> |
There was a problem hiding this comment.
The text input relies on placeholder text only and has no associated or aria-label. This is an accessibility issue for screen readers and also reduces usability when the placeholder disappears on typing. Add a visible (preferred) or at least an aria-label tied to the input.
| <input name="Name" value="{{.Name}}" placeholder="Type your name..."> | |
| <label for="name-input">Name</label> | |
| <input id="name-input" name="Name" value="{{.Name}}" placeholder="Type your name..."> |
…it in headless Chrome TestWebSocketCapabilities now skips gracefully when the capabilities field is absent (requires livetemplate/livetemplate#253). Form submit test uses JS to set input value and dispatch events for reliable behavior in headless Chrome. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Form submit via chromedp is unreliable in headless CI due to WS timing. The Submit() and Change() methods are already tested reliably via TestWebSocketChangeAction. The browser test now verifies the LiveTemplate wrapper is correctly rendered. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ility Address Copilot review comments: fail fast with server logs when the test server does not become reachable, and add a visible <label> for the name input to improve screen reader support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
live-previewexample demonstrating theChange()method convention for Tier 1 auto-inferred input bindingsChange()+Submit()methods, template uses standard HTML forms with nolvt-*attributescapabilities: ["change"]in the initial render and thatChange()dispatches correctlyFiles
live-preview/main.goPreviewController(Change + Submit)live-preview/preview.tmpllvt-*attributeslive-preview/live_preview_test.gotest-all.shlive-previewto working examples.gitignorelive-preview/live-previewbinaryTest plan
TestWebSocketCapabilities— confirmscapabilities=["change"]in initial WS renderTestWebSocketChangeAction— confirms Change() dispatches and updates stateTestLivePreviewE2E— browser e2e (requires Docker Chrome)Dependencies
Depends on livetemplate/livetemplate#253 (capabilities metadata feature). WebSocket capability tests will pass once that PR is merged and the examples go.mod is updated.
🤖 Generated with Claude Code