From bc1d94fbb7fe300b0bcd3f86ca1addb64efe516e Mon Sep 17 00:00:00 2001 From: Adnaan Date: Fri, 27 Mar 2026 13:20:06 +0530 Subject: [PATCH 1/3] feat: migrate all examples to progressive complexity (Tier 1) Replace lvt-* attributes with standard HTML constructs across all 13 examples. Forms now use method="POST" with name attributes for action routing, buttons use name attributes for HTTP POST fallback, and hidden inputs replace lvt-data-* for passing data. Only lvt-scroll (chat) and lvt-upload (avatar-upload) remain as documented Tier 2 attributes where standard HTML has no equivalent. Dependencies upgraded to livetemplate v0.8.7 with latest lvt packages. README updated with progressive complexity tier tracking table. CLAUDE.md created with guidelines for future example creation. Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 69 ++++++++ README.md | 158 +++--------------- avatar-upload/avatar-upload.tmpl | 2 +- chat/chat.tmpl | 4 +- chat/chat_e2e_test.go | 24 +-- counter/counter.tmpl | 6 +- flash-messages/flash.tmpl | 13 +- flash-messages/flash_test.go | 18 +- go.mod | 16 +- go.sum | 36 ++-- graceful-shutdown/counter.tmpl | 6 +- login/login_test.go | 20 +-- login/templates/auth.html | 6 +- observability/counter.tmpl | 6 +- production/single-host/app.tmpl | 6 +- .../progressive-enhancement.tmpl | 19 +-- .../progressive_enhancement_test.go | 96 +++++++---- todos-components/todos-components.tmpl | 43 ++--- todos-components/todos_test.go | 14 +- todos/todos.tmpl | 47 +++--- todos/todos_test.go | 108 +++--------- ws-disabled/ws-disabled.tmpl | 10 +- ws-disabled/ws_disabled_test.go | 47 +++--- 23 files changed, 350 insertions(+), 424 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..64f91af --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,69 @@ +# LiveTemplate Examples + +## Progressive Complexity + +All examples follow the **progressive complexity** model introduced in livetemplate v0.8.7: + +- **Tier 1 (Standard HTML)** is the default. Use native HTML forms, buttons, and inputs. +- **Tier 2 (`lvt-*` attributes)** only when standard HTML cannot express the behavior. + +### Tier 1 Constructs + +| Construct | Pattern | Routes to | +|-----------|---------|-----------| +| Form submission | `
` | `Add()` method | +| Button action | `
diff --git a/chat/chat_e2e_test.go b/chat/chat_e2e_test.go index ffd1147..1483176 100644 --- a/chat/chat_e2e_test.go +++ b/chat/chat_e2e_test.go @@ -123,17 +123,16 @@ func TestChatE2E(t *testing.T) { err := chromedp.Run(browserCtx, // Capture initial state chromedp.Text(".stats", &initialStatsText, chromedp.ByQuery), - chromedp.Evaluate(`document.querySelector('form[lvt-submit="join"]') !== null`, &initialFormVisible), + chromedp.Evaluate(`document.querySelector('form[name="join"]') !== null`, &initialFormVisible), - // Fill and submit join form - chromedp.SetValue(`input[name="username"]`, "testuser", chromedp.ByQuery), - chromedp.Click(`button[type="submit"]`, chromedp.ByQuery), + // Join via WebSocket API (form name routing is Tier 1 HTTP fallback) + chromedp.Evaluate(`window.liveTemplateClient.send({action: 'join', data: {username: 'testuser'}})`, nil), waitFor(`document.querySelector('.messages') !== null`, 5*time.Second), // Capture after-join state chromedp.Text(".stats", &afterStatsText, chromedp.ByQuery), chromedp.Evaluate(`document.querySelector('.messages') !== null`, &afterChatVisible), - chromedp.Evaluate(`document.querySelector('form[lvt-submit="join"]') !== null`, &afterFormVisible), + chromedp.Evaluate(`document.querySelector('form[name="join"]') !== null`, &afterFormVisible), ) if err != nil { @@ -188,11 +187,9 @@ func TestChatE2E(t *testing.T) { if !isJoined { t.Log("Not yet joined, performing join...") chromedp.Run(browserCtx, - chromedp.WaitVisible(`input[name="username"]`, chromedp.ByQuery), - chromedp.SetValue(`input[name="username"]`, "testuser", chromedp.ByQuery), - chromedp.Click(`button[type="submit"]`, chromedp.ByQuery), + chromedp.Evaluate(`window.liveTemplateClient.send({action: 'join', data: {username: 'testuser'}})`, nil), waitFor(`document.querySelector('.messages') !== null`, 5*time.Second), - chromedp.WaitVisible(`.messages`, chromedp.ByQuery), // Explicitly wait for messages container + chromedp.WaitVisible(`.messages`, chromedp.ByQuery), ) t.Log("Join completed, .messages container is visible") } @@ -209,8 +206,7 @@ func TestChatE2E(t *testing.T) { t.Log("Step 2: Sending FIRST message") return nil }), - chromedp.SetValue(`input[name="message"]`, "First message", chromedp.ByQuery), - chromedp.Click(`form[lvt-submit="send"] button[type="submit"]`, chromedp.ByQuery), + chromedp.Evaluate(`window.liveTemplateClient.send({action: 'send', data: {message: 'First message'}})`, nil), waitFor(`document.querySelectorAll('.messages .message').length >= 1`, 5*time.Second), chromedp.ActionFunc(func(ctx context.Context) error { @@ -230,8 +226,7 @@ func TestChatE2E(t *testing.T) { t.Log("Step 4: Sending SECOND message") return nil }), - chromedp.SetValue(`input[name="message"]`, "Second message", chromedp.ByQuery), - chromedp.Click(`form[lvt-submit="send"] button[type="submit"]`, chromedp.ByQuery), + chromedp.Evaluate(`window.liveTemplateClient.send({action: 'send', data: {message: 'Second message'}})`, nil), waitFor(`document.querySelectorAll('.messages .message').length >= 2`, 5*time.Second), chromedp.ActionFunc(func(ctx context.Context) error { @@ -251,8 +246,7 @@ func TestChatE2E(t *testing.T) { t.Log("Step 6: Sending THIRD message") return nil }), - chromedp.SetValue(`input[name="message"]`, "Third message", chromedp.ByQuery), - chromedp.Click(`form[lvt-submit="send"] button[type="submit"]`, chromedp.ByQuery), + chromedp.Evaluate(`window.liveTemplateClient.send({action: 'send', data: {message: 'Third message'}})`, nil), waitFor(`document.querySelectorAll('.messages .message').length >= 3`, 5*time.Second), chromedp.ActionFunc(func(ctx context.Context) error { diff --git a/counter/counter.tmpl b/counter/counter.tmpl index 2241f71..db32af0 100644 --- a/counter/counter.tmpl +++ b/counter/counter.tmpl @@ -10,9 +10,9 @@

Counter: {{.Counter}}

- - - + + +