Skip to content

Conversation

@adnaan
Copy link
Contributor

@adnaan adnaan commented Jan 18, 2026

Summary

Add a complete todo app example demonstrating progressive enhancement - apps that work both with and without JavaScript enabled.

Features Demonstrated

  • Dual-mode forms: Works via WebSocket (JS) or HTTP POST (no-JS)
  • PRG pattern: Post-Redirect-Get prevents duplicate submissions on refresh
  • Validation errors: Inline error display for both modes
  • Flash messages: Success feedback after actions
  • Auto-generated keys: No explicit data-key attribute needed

Files Added

File Description
main.go Todo app with add/toggle/delete actions
progressive-enhancement.tmpl Template with dual-mode forms
progressive_enhancement_test.go Comprehensive chromedp E2E tests
README.md Documentation for the example

Test plan

  • go test -v ./progressive-enhancement/... - all E2E tests pass
  • Manual testing with JS enabled (WebSocket updates)
  • Manual testing with JS disabled (HTTP form submissions)

Depends on: livetemplate/livetemplate#102

🤖 Generated with Claude Code

Add a complete todo app example demonstrating progressive enhancement:
- Works with JavaScript: WebSocket-based real-time updates
- Works without JavaScript: Traditional HTTP form submissions with PRG pattern

Features demonstrated:
- Form submissions via both lvt-submit (JS) and method="POST" (no-JS)
- Server-side validation with inline error display
- Flash messages after successful actions
- Auto-generated content-based keys (no explicit data-key needed)

The example includes comprehensive chromedp E2E tests covering:
- JavaScript mode WebSocket form submission
- No-JS mode HTTP form submission with redirect
- Validation error handling
- Toggle and delete actions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings January 18, 2026 19:58
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a comprehensive progressive enhancement example demonstrating how to build applications that work both with and without JavaScript enabled. The example implements a todo list application that uses WebSocket for real-time updates when JavaScript is available, and falls back to traditional HTTP form submissions with the POST-Redirect-GET pattern when JavaScript is disabled.

Changes:

  • Adds a complete todo app with dual-mode forms supporting both WebSocket and HTTP POST
  • Implements PRG pattern for non-JS clients to prevent duplicate submissions
  • Includes comprehensive E2E tests using chromedp for both JS and no-JS scenarios

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
progressive-enhancement/main.go Implements TodoController with Mount, Add, Toggle, and Delete actions
progressive-enhancement/progressive-enhancement.tmpl HTML template with dual-mode forms and progressive enhancement support
progressive-enhancement/progressive_enhancement_test.go Comprehensive E2E tests covering JS, no-JS, validation, and content negotiation
progressive-enhancement/README.md Documentation explaining progressive enhancement concepts and usage
go.mod Adds local replace directive for development
go.sum Updates checksums for dependencies

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 110 to 116
for i := range state.Items {
if state.Items[i].ID == id {
state.Items = append(state.Items[:i], state.Items[i+1:]...)
ctx.SetFlash("success", "Item deleted")
break
}
}
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The Delete action modifies the slice while iterating over it by using append with slice expressions. If the item to delete is not found, the function silently returns without any indication. Consider adding a flash message or error when the ID is not found, or at minimum ensure the flash message is only set when an item is actually deleted.

Suggested change
for i := range state.Items {
if state.Items[i].ID == id {
state.Items = append(state.Items[:i], state.Items[i+1:]...)
ctx.SetFlash("success", "Item deleted")
break
}
}
// Find index of the item to delete without modifying the slice during iteration.
deleteIndex := -1
for i, item := range state.Items {
if item.ID == id {
deleteIndex = i
break
}
}
if deleteIndex >= 0 {
state.Items = append(state.Items[:deleteIndex], state.Items[deleteIndex+1:]...)
ctx.SetFlash("success", "Item deleted")
} else {
ctx.SetFlash("error", "Item not found")
}

Copilot uses AI. Check for mistakes.
Comment on lines 97 to 103
id := ctx.GetString("id")
for i := range state.Items {
if state.Items[i].ID == id {
state.Items[i].Completed = !state.Items[i].Completed
break
}
}
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The Toggle action does not provide any feedback to the user when the toggle ID is not found. Consider adding a flash message for error cases or success confirmation, similar to the Delete action.

Suggested change
id := ctx.GetString("id")
for i := range state.Items {
if state.Items[i].ID == id {
state.Items[i].Completed = !state.Items[i].Completed
break
}
}
id := ctx.GetString("id")
found := false
for i := range state.Items {
if state.Items[i].ID == id {
state.Items[i].Completed = !state.Items[i].Completed
found = true
ctx.SetFlash("success", "Item updated")
break
}
}
if !found {
ctx.SetFlash("error", "Item not found")
}

Copilot uses AI. Check for mistakes.

- `main.go` - Controller, state, and action handlers
- `progressive-enhancement.tmpl` - Template with dual-mode forms
- `README.md` - This documentation
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The Files section lists only three files but omits the test file. Consider adding progressive_enhancement_test.go to the list since it's part of the example and demonstrates E2E testing patterns.

Suggested change
- `README.md` - This documentation
- `README.md` - This documentation
- `progressive_enhancement_test.go` - End-to-end tests for progressive enhancement behavior

Copilot uses AI. Check for mistakes.
Comment on lines 97 to 102
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()

// Set timeout
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The cancel function is being reassigned on line 101, which shadows the previous cancel function from line 97. This means the first context created on line 97 will not be properly canceled. Consider using different variable names for the timeout context and its cancel function.

Copilot uses AI. Check for mistakes.
Comment on lines 54 to 55
ctx, cancel = context.WithTimeout(ctx, 15*time.Second)
defer cancel()
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The cancel function is being reassigned on line 54, which shadows the previous cancel function from line 50. This means the first context created on line 50 will not be properly canceled. Consider using different variable names for the timeout context and its cancel function.

Suggested change
ctx, cancel = context.WithTimeout(ctx, 15*time.Second)
defer cancel()
ctx, timeoutCancel := context.WithTimeout(ctx, 15*time.Second)
defer timeoutCancel()

Copilot uses AI. Check for mistakes.
go.mod Outdated
Comment on lines 5 to 7
// Use local livetemplate for progressive enhancement development
replace github.com/livetemplate/livetemplate => ../../../livetemplate/.worktrees/progressive-enhancement

Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The replace directive points to a local worktree path that is specific to the development environment. This will break the build for anyone else trying to use this example. The replace directive should either be removed before merging (if the dependency is already published), or changed to point to a more portable location, or this file should be gitignored with a .example version checked in instead.

Suggested change
// Use local livetemplate for progressive enhancement development
replace github.com/livetemplate/livetemplate => ../../../livetemplate/.worktrees/progressive-enhancement

Copilot uses AI. Check for mistakes.
adnaan and others added 2 commits January 19, 2026 05:50
Use pseudo-version pointing to the progressive-enhancement branch commit
instead of local replace directive for CI compatibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Improvements based on Copilot review feedback:

1. **Toggle action**: Added feedback for both success and not-found cases
   with flash messages (Copilot suggestion)

2. **Delete action**: Fixed slice modification during iteration by finding
   index first, then removing. Added not-found error handling (Copilot suggestion)

3. **README**: Added test file to the Files section (Copilot suggestion)

4. **Test file**: Fixed cancel function shadowing by using distinct
   variable names (timeoutCancel) for timeout contexts (Copilot suggestion)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@adnaan adnaan merged commit fc65b0e into main Jan 19, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants