feat!: update templates for Controller+State pattern#7
Conversation
Updates all generator templates and e2e tests to use the new Controller+State pattern from livetemplate. Changes: - Split State structs into Controller (dependencies) + State (data) - Update method signatures to new pattern - Add Mount() lifecycle method - Update Handle() calls to use AsState() - Update e2e tests for new API Template changes: - resource/handler.go.tmpl: Controller holds Queries, methods return state - view/handler.go.tmpl: Empty controller, state-only pattern - app/home.go.tmpl: Controller+State for home page - auth/handler.go.tmpl: Controller holds db, queries, emailSender 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PR Review: Controller+State Pattern MigrationOverviewThis PR successfully migrates all generator templates and e2e tests to the new Controller+State pattern from livetemplate. The changes are well-structured and align with the new architectural pattern separating concerns between singleton dependencies (Controller) and per-session data (State). ✅ Strengths
🔍 Issues & ConcernsCritical: Potential State Mutation BugLocation: The func applySorting(state [[.ResourceName]]State) [[.ResourceName]]State {
switch state.SortBy {
case "name_asc":
sort.Slice(state.FilteredUsers, func(i, j int) bool { // ⚠️ MUTATES slice in place!
return strings.ToLower(state.FilteredUsers[i].Name) < strings.ToLower(state.FilteredUsers[j].Name)
})
// ...
}
return state
}Problem: Even though state is passed by value, slices in Go are reference types. Sorting mutates the underlying array, which could affect the original state if it's shared. Recommendation:
Affected files:
Medium: Missing Error Context in HandlerLocation: When finding and setting the editing resource in the custom URL handler, database errors are silently ignored: [[.ResourceNameLower]]s, err := queries.GetAll[[.ResourceNamePlural]](context.Background())
if err == nil { // ⚠️ Only processes success case, error is ignored
for _, item := range [[.ResourceNameLower]]s {
// ...
}
}Recommendation: Log errors or return HTTP error responses: [[.ResourceNameLower]]s, err := queries.GetAll[[.ResourceNamePlural]](context.Background())
if err != nil {
log.Printf("Failed to load resource for editing: %v", err)
http.Error(w, "Failed to load resource", http.StatusInternalServerError)
return
}Low: Context UsageLocation: Multiple locations using All database operations use func (c *UserController) Add(state UserState, ctx *livetemplate.Context) (UserState, error) {
dbCtx := context.Background() // ⚠️ Doesn't respect request cancellation
// ...
}Recommendation: If dbCtx := ctx.Request.Context() // or similar, if availableThis would allow database operations to be cancelled if the HTTP request is cancelled. 🎯 Code QualityGood:
Minor Suggestions:
🔒 Security✅ No major security concerns identified:
Note: The validation is present but verify that the ⚡ PerformanceGenerally good, with one consideration: The pattern of loading all resources and filtering/sorting in memory works well for small to medium datasets: [[.ResourceNameLower]]s, err := c.Queries.GetAll[[.ResourceNamePlural]](ctx)
// Then filter, sort, paginate in memoryConsideration: For large datasets (1000s+ records), consider:
This is fine for generated templates as a starting point - users can optimize as needed. 📝 Test Coverage✅ Well covered:
Suggestions:
🎓 Breaking ChangesThis is marked as ✅ Properly documented:
Migration path is clear: Users need to:
🏁 RecommendationApprove with minor fixes suggested: The PR successfully implements the Controller+State pattern migration. The main concerns are:
Once the slice mutation issue is addressed (either by fixing or documenting the intentional behavior), this is ready to merge. Great work on maintaining consistency across all templates and test coverage! 🎉 Files Reviewed:
|
There was a problem hiding this comment.
Pull request overview
This PR updates all LiveTemplate generator templates to adopt the new Controller+State architectural pattern. The change represents a fundamental shift from pointer-based state mutation to value-based immutability, aligning with the latest livetemplate framework (v0.5.1+).
Key changes:
- Controller struct: Singleton holding dependencies (DB queries, email sender, etc.)
- State struct: Pure data, passed by value, cloned per session
- Action methods: Now take state by value and return modified state:
func (c *Controller) Action(state State, ctx *livetemplate.Context) (State, error) - Lifecycle hook:
Init()replaced withMount() - Helper functions: Converted from methods to pure functions returning new state
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| testdata/golden/view_handler.go.golden | Golden test for view handler with Controller+State pattern |
| testdata/golden/resource_handler.go.golden | Golden test for resource handler with all CRUD operations updated |
| internal/kits/system/single/templates/view/handler.go.tmpl | View template for single kit updated |
| internal/kits/system/single/templates/resource/handler.go.tmpl | Resource template for single kit with complete pattern migration |
| internal/kits/system/single/templates/app/home.go.tmpl | Home page template for single kit |
| internal/kits/system/simple/templates/app/main.go.tmpl | Simple app template with counter example |
| internal/kits/system/multi/templates/view/handler.go.tmpl | View template for multi kit |
| internal/kits/system/multi/templates/resource/handler.go.tmpl | Resource template for multi kit with full CRUD |
| internal/kits/system/multi/templates/auth/handler.go.tmpl | Authentication handler with controller dependencies |
| internal/kits/system/multi/templates/app/home.go.tmpl | Home page for multi kit |
| internal/generator/templates/view/handler.go.tmpl | Core view generator template |
| internal/generator/templates/resource/handler.go.tmpl | Core resource generator template |
| internal/generator/templates/app/home.go.tmpl | Core home page generator template |
| e2e/livetemplate_core_test.go | E2E tests updated for new Handle() signature |
Addresses PR review concern about sort.Slice mutating slices in place. Added comments explaining why this is safe in the Controller+State pattern: 1. State is cloned per session via AsState (JSON creates fresh slices) 2. Slice is populated fresh from DB in each load operation 3. State is passed by value and returned, not shared across sessions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pull Request ReviewThis PR implements a significant architectural change by migrating from a mutable state pattern to the new Controller+State pattern. The changes are well-structured and consistent across all templates. Here's my comprehensive review: ✅ Code Quality and Best PracticesStrengths:
Minor Suggestions:
🐛 Potential Bugs or IssuesCritical Issues: None found Minor Observations:
⚡ Performance ConsiderationsGood:
Potential Concerns:
Recommendation: These are acceptable for typical use cases, but document expected dataset sizes and add performance notes if needed. 🔒 Security ConcernsGood:
Observations:
🧪 Test CoverageExcellent:
Suggestions:
📝 Additional ObservationsBreaking Change:
Template Generation:
Dependencies: 🎯 SummaryOverall Assessment: ✅ Approved with minor suggestions This is a well-executed architectural improvement that enhances code maintainability and follows functional programming best practices. The implementation is consistent, well-tested, and properly handles the migration from the old pattern. Recommendation:
Great work on this refactor! The Controller+State pattern is a significant improvement over the mutable state approach. 🤖 This review was generated by Claude Code |
- Move ListPatterns() call before error loop in proposer (single lock acquire) - Skip Glob for "*/"-prefixed targets, go straight to Walk for consistency - Use strings.CutPrefix instead of HasPrefix+TrimPrefix - Fix TestUpstreamProposer_NoUpstreamRepo to set MessageRe so it tests the UpstreamRepo="" path, not the MessageRe==nil guard - Add TODO comment in upstream.go for first-fix-only limitation - Simplify evolutionApply to use _ for unused args parameter - Extract defaultLookbackDays const for 30-day hardcoding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: Milestone 3 — Telemetry & Evolution (Issues #61–#67) Add self-improving evolution system that captures generation events, matches errors to known patterns, proposes fixes, and tests them in isolation. This creates a feedback loop: capture → match → propose → test. New packages: - internal/telemetry: SQLite-backed event store with noop-safe Collector/Capture API - internal/evolution/knowledge: Markdown parser for evolution/patterns.md (13 patterns) - internal/evolution: Fix proposer, tester, and upstream support Integration: - commands/gen.go, commands/auth.go: Telemetry capture around all generators - commands/evolution.go: CLI commands (status, metrics, failures, patterns, propose, apply) - main.go: Route "evolution"/"evo" command Closes #61, #62, #63, #64, #65, #66, #67 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix gofmt formatting in evolution.go and events.go Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address bot review comments on PR #130 - Change telemetry to opt-in (LVT_TELEMETRY=true) instead of opt-out - Fix double validation: refactor runPostGenValidation to return result and reuse marshalValidationResult instead of re-running validation - Parameterize LIMIT query in SQLite store - Fix scanInto to return errors on parse/unmarshal failures - Add getLvtVersion() using debug.ReadBuildInfo() - Remove unused started field from Capture - Fix tester glob to match relative paths, not just filenames - Fix proposer dedup key to include FindPattern+Replace - Add kit index to schema.sql - Fix zero-total percentage display in evolution status - Gate apply command (return not-implemented error) - Fix truncate() to use rune count instead of byte length - Sort metrics output by command name - Remove runtime.Caller(0) fallback from FindPatternsFile - Walk up directory tree from cwd to find patterns.md - Document evo alias in main.go usage - Fix TestEvolution_Patterns to use t.Skipf - Add upstream proposer unit tests - Add NewFromPatterns constructor for testing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address second round of bot review comments - Simplify NewCollector() to return *Collector (always non-nil, no error) - Remove startCapture helper and nil guards at all call sites - Remove dead filepath.Match call in tester.go Walk fallback - Mark 'apply' command as [coming soon] in help text - Make patterns.md integration tests structural (not exact counts/order) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: defer capture.Complete in auth.go to after resource-protection Move telemetry capture completion to after all post-gen work so the event correctly reflects the full outcome of the command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use deferred closure for capture.Complete in auth.go Track success with a local bool so the deferred capture.Complete correctly reflects late failures. Also relax upstream pattern count assertion to >= 1 instead of exact count. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: call capture.Complete directly at end of Auth() Replace deferred closure with direct call before return nil, matching the pattern used in gen.go. Also document the */filename glob constraint in FixTemplate.File. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: fix gofmt alignment in types.go Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Claude review #7 — proposer loop, glob/walk, upstream test - Move ListPatterns() call before error loop in proposer (single lock acquire) - Skip Glob for "*/"-prefixed targets, go straight to Walk for consistency - Use strings.CutPrefix instead of HasPrefix+TrimPrefix - Fix TestUpstreamProposer_NoUpstreamRepo to set MessageRe so it tests the UpstreamRepo="" path, not the MessageRe==nil guard - Add TODO comment in upstream.go for first-fix-only limitation - Simplify evolutionApply to use _ for unused args parameter - Extract defaultLookbackDays const for 30-day hardcoding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Claude review #8 — minor polish items - Add TODO for unwired RecordFileGenerated method - Align null-guard for inputsJSON to match errorsJSON/filesJSON - Document context regex message fallback rationale in matcher.go Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Claude review #9 — kit comment, apply test naming - Document why kit is set both in inputs map and via SetKit() - Merge Apply_NoArgs and Apply_ReturnsNotImplemented into single Apply_NotImplemented test since the stub ignores args Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Claude review #10 — surface read errors, prevent dup inserts - Return error instead of silently skipping unreadable files in applyFix - Use INSERT OR IGNORE to handle unlikely ID collisions gracefully Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: preserve file permissions in applyFix, document INSERT OR IGNORE - Use os.Stat to capture original file mode before rewriting - Add comment explaining why INSERT OR IGNORE is used Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: XDG_CONFIG_HOME isolation in tests, consistent propose behavior - Clear XDG_CONFIG_HOME in all evolution tests that set HOME for isolation - Make evolutionPropose print-and-return-nil when disabled, matching other subcommands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: mark unused args parameters with _ in evolution subcommands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Updates all generator templates and e2e tests to use the new Controller+State pattern from livetemplate.
Changes:
New Pattern:
Test plan
Depends on: livetemplate/livetemplate#69
🤖 Generated with Claude Code